Refactoring a 1,000‑Line Untested Core File with AI: My Proven Workflow

The author refactors a 1,021‑line, zero‑test IM conversion module that is referenced in five places by building a characterization‑test net with esbuild stubs, generating 130 exhaustive samples, recording a golden baseline, applying three systematic code transformations, and verifying behavior and a 2.77× speedup, all without touching business logic.

大转转FE
大转转FE
大转转FE
Refactoring a 1,000‑Line Untested Core File with AI: My Proven Workflow

What the file is

The target is convertNormalMsg.js, a 1,021‑line JavaScript converter that turns raw server messages into renderable structures for an IM product. It handles roughly 30 message types, is referenced in five different parts of the codebase, and has zero test coverage, making any change risky.

File lines: 1,021 (exceeds team’s 750‑line warning and 1,000‑line red line)

Message types: ~30 (text, image, order card, work order, logistics, hand‑off, etc.)

References: 5 places (connection layer, history, notes, …)

Test coverage: 0

Step 1 – Build a characterization‑test net

Because there are no tests, the author first creates a “characterization test” (golden‑master) that records the current output for a given input. For example, feeding an image message that previously returned {msgType:2, picUrl:'…', isRead:1} stores that object as the expected answer. The net also records thrown exceptions.

To run the old code in Node, the author bundles it with esbuild and writes an onResolve plugin that stubs out dependencies that cannot be loaded (e.g., window, document, sentry) while leaving real values for modules that affect output.

const stub = {
  setup(build) {
    // replace sentry with no‑op
    build.onResolve({filter:/sentry-capture$/},()=>({path:STUB_SENTRY}))
    // keep only the four pure utils functions used
    build.onResolve({filter:/libs\/utils$/},()=>({path:STUB_UTILS}))
    // keep only the stub for common helpers
    build.onResolve({filter:/libs\/common$/},()=>({path:STUB_COMMON}))
    // let ./constants resolve normally to keep real msgType values
  }
}

Step 2 – Generate exhaustive samples

The author creates 130 input samples that cover every combination of three dimensions:

Message type : all 30 msgType values.

Sender side : user‑side and agent‑side variations.

Real‑time flag : isOnMsg true/false.

Edge cases such as missing richText, empty data, unknown msgType, and other boundary conditions are also added.

Step 3 – Record the baseline

Running the bundled old version against the 130 samples produces a JSON file golden.json that contains the normalized (canonical‑sorted) output for each case, including any captured exceptions.

const canon = (v)=>Array.isArray(v)?v.map(canon):
  (v && typeof v==='object')?
    Object.fromEntries(Object.keys(v).sort().map(k=>[k,canon(v[k])])):v;

Step 4 – Refactor in three passes

Pass 1 – Inject a context object

All local variables used inside callbacks ( content, fromSelf, avatar, …) are collected into a single ctx object produced by buildContext(msgObj, isOnMsg). Handlers become pure functions (ctx)=>{…} instead of closures that capture outer scope.

Pass 2 – Lift the strategy map

The multipleActionMap([...]) is moved to module scope and instantiated once as HANDLER_MAP. The exported convertNormalMsg now simply builds the context, looks up the handler, and returns its result.

const HANDLER_MAP = multipleActionMap(MSG_HANDLERS);
export default function convertNormalMsg(msgObj, isOnMsg=false){
  const ctx = buildContext(msgObj, isOnMsg);
  const handler = HANDLER_MAP.get(ctx.msgType) || HANDLER_MAP.get('default');
  return handler(ctx);
}

This changes the hot path from “create a new Map and 30+ closures on every call” to “reuse a single pre‑built map”.

Pass 3 – Extract shared fields

The nine base fields that were copied in every handler are factored into baseMsg(ctx, extra). The extra argument carries type‑specific differences, reducing an image‑handler from dozens of lines to a single call.

Two legacy message types (coupon and sell‑card) lack the isRead field; the author leaves them unchanged and adds a comment explaining the omission, because the characterization net would otherwise flag a false‑positive change.

Step 5 – Acceptance

The new version is run against the same 130 samples. The comparison reports:

All 130 cases pass (identical output).

Single‑message latency improves from 1.49 µs to 0.54 µs (≈2.77× faster).

File size shrinks from 1,021 lines to 729 lines (‑28.6%).

Object creation per message drops from “1 Map + 30+ closures” to a single ctx object.

Public API remains unchanged.

A benchmark on 500 k messages shows the old version taking 745 ms versus 269 ms for the new version, confirming the speedup.

Step 6 – Make the pipeline reusable

The entire “characterization‑net → sample generation → baseline → refactor → verification” script is stored under src/dal/__characterization__/. To apply it to another legacy file, only three questions need answering: which file to target, which dependencies to stub, and how to generate appropriate samples.

The stubbing rule is simple: replace pure side‑effects (logging, reporting) with no‑ops; keep real values for anything that influences output.

A concise prompt for AI‑assisted refactoring is also provided, describing the four steps above and a final self‑check that deliberately mutates an output to ensure the net turns red before proceeding.

Overall, the key insight is that the risky part is not the refactor itself but the lack of a reliable “did I break anything?” oracle; building a characterization net supplies that oracle, allowing confident AI‑driven changes.

convertNormalMsg 重构前的热路径
convertNormalMsg 重构前的热路径
织网流水线:旧版打包打桩 → 130 样本 → 归一 → 录成底片 golden.json
织网流水线:旧版打包打桩 → 130 样本 → 归一 → 录成底片 golden.json
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

performanceJavaScriptAIRefactoringesbuildCharacterization Test
大转转FE
Written by

大转转FE

Regularly sharing the team's thoughts and insights on frontend development

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.