NEO.K / MSSP FIELD LAB005-before-after
編號005-before-after
語言javascript
版本v1.0
日期2026-08-05
行數472
執行node src/main.js

005 — The same program, before and after: measuring what the restructuring cost

What this program does

It reads a small table of sensor readings, drops the invalid rows, and prints a summary in one of three formats. It is written twice.

node baseline/monolith.js          # the before: one file, 69 lines
node src/main.js                   # the after: MSSP, same output
node src/main.js --measure         # the comparison
node src/island-test.js            # each format alone, and the measurement's own honesty

baseline/monolith.js is not a strawman. It is what a competent person writes for a job this size, and it is shorter and clearer than the restructured version. That is part of the finding, not an oversight.

The structural decision

Measure the restructuring instead of asserting it — and use a measurement capable of coming out against MSSP.

The four examples before this one demonstrated structure. None produced a number. 開發區 缺點 5 says the cost at small scale is real and that the method has no quantitative tool for it, so this is the tool, applied to one program.

$ node src/main.js --measure

                                     before    after
  total lines                            69      144    +
  files                                   1        9    +
  lines to exercise one capability       69        8   ok
  files to exercise one capability        1        1    =
  files touched to add a format           1        2    +
  EXISTING files changed to add it        1        1    =
  lines to understand one capability     69        8   ok

  ok = the restructuring helped on this axis, + = it cost

Three costs, two benefits, two ties. The restructuring does not dominate at this size, and the honest reading is that it buys one thing and charges for another:

The two "=" rows are the ones I would not have predicted, and they are why the measurement was worth writing rather than reasoning about.

What the measurement caught in this example

A dispatch branch in the core. main.js originally read:

const wanted = argv.includes("--csv") ? "formats/csv" : "formats/text";

That is a branch per format in the core — the exact coupling the restructuring exists to remove — and it meant adding a format touched main.js after all. It was invisible while there were two formats. Adding a third made --json print text, and the fix was to derive the format from policy instead. The structure looked right and had the seam still in it.

The instrument found itself. The check for "which pre-existing files mention the new capability" grepped for formats/json and reported DMS/measure.js — which contains the string by virtue of being the thing that searches for it. An instrument that matches itself is measuring the wrong object.

A comment describing a removed branch. The island test's check that main.js does not name a format matched the comment explaining the branch that had just been deleted. Third time this week a check has reported on prose describing the thing rather than on the thing; it now reads code lines only.

Set by set

FMSmanifest.json, and the two decisions above.

SCLpolicy.json / policy.js. The plausibility window (-60..60 °C) and the set of formats this deployment recognises. In the monolith the window is a const beside the validator, which is correct and also means changing it is a code change reviewed as a code change.

SMSmodel, validate, summarise. Remove any of them and there is no report.

TMSformats/text, formats/csv, formats/json. Three units. The island test notes that a format file imports nothing at all, not even SMS: it takes plain values, which is why 8 lines is the true cost of loading one.

DMSmeasure.js. It measures the structure the program is in, which is what makes this example possible at all.

The island test

$ node src/island-test.js
  ... 17 checks across 5 sections ...
  island test passed

Section 2 is the one that matters and it is unusual: it checks that the measurement is capable of disagreeing with the method that produced it.

  PASS  at least one axis reports a cost - 3 cost row(s)
  PASS  at least one axis reports a benefit - 2 benefit row(s)
  PASS  total lines went UP, and the table says so - 69 -> 144
  PASS  this measurement is therefore capable of disagreeing with MSSP

A table where every row favours the author is not a measurement. This is 改良點 6 applied to a number instead of to a check — and the number passes, because the restructuring genuinely costs on three axes and the table prints all three.

Section 5 runs both versions and compares their output byte for byte. Without it, "the same program" is a claim.

What this example does not solve

Source

FMS

FMS/manifest.json
{
  "name": "before-after",
  "what_it_is": "One small program written twice — as a single file, and as MSSP — with the difference measured rather than argued.",
  "why_it_exists": "The first four examples in this lab demonstrated structure and none of them produced a number. 開發區 缺點 5 says the cost at small scale is real and there is no quantitative tool for it.",
  "core_task_loop": "readings -> validate -> summarise -> render in a requested format",

  "the_program": {
    "before": "baseline/monolith.js, 69 lines, one file. Not a strawman — it is shorter and clearer than the restructured version, and that is part of the finding.",
    "after": "src/, the same behaviour, byte-identical output, checked by the island test."
  },

  "capabilities": {
    "SMS": {
      "model": "The shapes the sets agree on.",
      "validate": "Which readings survive. Bounds come from SCL.",
      "summarise": "Per-sensor statistics."
    },
    "SCL": { "policy": "The plausibility window, and which output formats this deployment recognises." },
    "TMS": {
      "formats/text": "Terminal report.",
      "formats/csv": "CSV.",
      "formats/json": "JSON — added after the first measurement, to measure what adding one costs."
    },
    "DMS": { "measure": "Six numbers comparing the two versions, computed from the files." }
  },

  "decisions": [
    {
      "id": "D-001",
      "date": "2026-08-05",
      "decision": "the format is derived from policy, not selected by a branch in main",
      "because": "The first version read `argv.includes('--csv') ? ... : ...`, which is a dispatch branch in the core — the exact coupling the restructuring exists to remove. The measurement caught it: --json printed text."
    },
    {
      "id": "D-002",
      "date": "2026-08-05",
      "decision": "the comparison excludes DMS and the island test from both sides",
      "because": "The baseline has no test harness. Counting one side's harness against the other's absence of one measures the harness, not the restructuring."
    }
  ],

  "non_goals": [
    "Showing that MSSP wins. Three of six axes cost, and the table says so.",
    "Generalising from one program. n=1, at a size the method's own guidance puts below its threshold."
  ]
}

SCL

SCL/policy.js
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

const here = path.dirname(fileURLToPath(import.meta.url));
const config = JSON.parse(fs.readFileSync(path.join(here, "policy.json"), "utf8"));

export function plausible() {
  return config.plausible_celsius;
}

export function mayProduce(format) {
  return Boolean(config.formats[format]?.may_produce);
}

/** Every format this deployment recognises. Adding one is a policy line. */
export function known() {
  return Object.keys(config.formats);
}
SCL/policy.json
{
  "policy_version": "1.0",
  "_comment": [
    "The plausibility window is a deployment decision, not arithmetic. In the",
    "monolith it is a const beside the validator, which is correct and also",
    "means changing it is a code change reviewed as a code change."
  ],
  "plausible_celsius": {
    "min": -60,
    "max": 60
  },
  "formats": {
    "formats/text": {
      "may_produce": true
    },
    "formats/csv": {
      "may_produce": true
    },
    "formats/json": {
      "may_produce": true
    }
  }
}

SMS

SMS/model.js
// The shapes the sets agree on.
export const KEPT = "kept";
export const DROPPED = "dropped";

export function reading(sensor, celsius, at) {
  return { sensor, celsius, at };
}

export function summaryRow(sensor, values) {
  return {
    sensor,
    n: values.length,
    mean: Number((values.reduce((a, b) => a + b, 0) / values.length).toFixed(2)),
    min: Math.min(...values),
    max: Math.max(...values),
  };
}
SMS/summarise.js
// Per-sensor statistics. Core: it is the report's content.
import { summaryRow } from "./model.js";

export function summarise(rows) {
  const bySensor = new Map();
  for (const row of rows) {
    if (!bySensor.has(row.sensor)) bySensor.set(row.sensor, []);
    bySensor.get(row.sensor).push(row.celsius);
  }
  return [...bySensor.entries()].map(([sensor, values]) => summaryRow(sensor, values));
}
SMS/validate.js
// Which readings survive. Core: without it the report describes bad data.
//
// The bounds come from SCL rather than from a constant here, because "what
// counts as plausible" is a decision about the deployment, not about the
// arithmetic — a roof sensor in a furnace room has different bounds and that is
// a configuration change, not a code change.
import { plausible } from "../SCL/policy.js";

export function validate(rows) {
  const { min, max } = plausible();
  const kept = [];
  const dropped = [];
  for (const row of rows) {
    if (row.celsius === null || row.celsius === undefined) dropped.push({ row, why: "no reading" });
    else if (row.celsius < min || row.celsius > max) dropped.push({ row, why: `outside ${min}..${max}` });
    else kept.push(row);
  }
  return { kept, dropped };
}

TMS

TMS/formats/csv.js
// Renders a summary as CSV. Knows nothing of formats/text.
export const name = "formats/csv";

export function render(summary, dropped) {
  const lines = ["sensor,n,mean,min,max"];
  for (const s of summary) lines.push(`${s.sensor},${s.n},${s.mean},${s.min},${s.max}`);
  lines.push(`# ${dropped.length} row(s) dropped`);
  return lines.join("\n");
}
TMS/formats/json.js
// The third format, added to the MSSP version. Nothing else changes except one
// line of policy — main.js computes the import from the requested name.
export const name = "formats/json";

export function render(summary, dropped) {
  return JSON.stringify({ summary, dropped: dropped.length }, null, 2);
}
TMS/formats/text.js
// Renders a summary for a terminal. Reads SMS shapes only.
export const name = "formats/text";

export function render(summary, dropped) {
  const lines = summary.map(
    (s) => `  ${s.sensor.padEnd(8)} n=${s.n}  mean=${s.mean}  range=${s.min}..${s.max}`,
  );
  lines.push(`  ${dropped.length} row(s) dropped`);
  return lines.join("\n");
}

DMS

DMS/measure.js
// Measures the structure the program is in.
//
// The first four examples in this lab demonstrated structure and none of them
// produced a number. This one does, and the number has to be able to come out
// against MSSP or it is not a measurement.
//
// Four questions, each computed from the files rather than asserted:
//
//   total          how much code exists at all
//   to exercise    how much must be loaded to run ONE capability alone
//   to extend      how many files a new output format touches
//   to understand  how much must be read to know what one capability does
//
// The third and fourth are the ones that matter in practice and the ones
// nobody measures, because they are about the next change rather than this one.

import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

const here = path.dirname(fileURLToPath(import.meta.url));
const src = path.dirname(here);
const example = path.dirname(src);

const linesOf = (file) => fs.readFileSync(file, "utf8").split("\n").filter((l) => l.trim()).length;

function walk(dir, out = []) {
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
    if (entry.isDirectory()) walk(path.join(dir, entry.name), out);
    else if (entry.name.endsWith(".js") || entry.name.endsWith(".json")) out.push(path.join(dir, entry.name));
  }
  return out;
}

/** Files a module needs, transitively, following relative imports. */
function closure(entry, seen = new Set()) {
  const resolved = path.resolve(entry);
  if (seen.has(resolved)) return seen;
  seen.add(resolved);
  const source = fs.readFileSync(resolved, "utf8");
  for (const m of source.matchAll(/from\s+["'](\.[^"']+)["']/g)) {
    const target = path.resolve(path.dirname(resolved), m[1]);
    if (fs.existsSync(target)) closure(target, seen);
  }
  // policy.js reads policy.json at runtime; a file the module cannot run
  // without is part of what must be loaded, whether or not it is imported.
  if (source.includes("policy.json")) seen.add(path.join(src, "SCL", "policy.json"));
  return seen;
}

export async function measure() {
  const monolith = path.join(example, "baseline", "monolith.js");
  // The program only. DMS and the island test are excluded because the baseline
  // has neither, and a comparison that counts one side's test harness against
  // the other's absence of one is not measuring the restructuring.
  const mssp = walk(src).filter(
    (f) => !f.includes(`${path.sep}DMS${path.sep}`) && !f.endsWith("island-test.js"),
  );

  // to exercise one capability alone: the CSV renderer.
  const csvClosure = [...closure(path.join(src, "TMS", "formats", "csv.js"))];
  // to understand one capability: the same file, on its own, plus what its
  // signature refers to. In the monolith you cannot open renderCsv without the
  // file that contains it.
  const csvFile = path.join(src, "TMS", "formats", "csv.js");

  // How much existing code a third output format actually costs. Measured by
  // adding one — baseline/variant/monolith-with-json.js is the monolith with a
  // JSON renderer, written the way anyone would — and diffing. The MSSP side is
  // diffed the same way: every file that is not the new one is compared against
  // what a two-format build contains.
  const extendCost = {
    monolith: diffAgainst(monolith, path.join(example, "baseline", "variant", "monolith-with-json.js")),
    mssp: msspExtendCost(),
  };

  return {
    extendCost,
    monolith: {
      files: 1,
      lines: linesOf(monolith),
      toExercise: linesOf(monolith),
      toExerciseFiles: 1,
      toExtend: extendCost.monolith.files,
      toUnderstand: linesOf(monolith),
    },
    mssp: {
      files: mssp.length,
      lines: mssp.reduce((n, f) => n + linesOf(f), 0),
      toExercise: csvClosure.reduce((n, f) => n + linesOf(f), 0),
      toExerciseFiles: csvClosure.length,
      toExtend: extendCost.mssp.files,
      toUnderstand: linesOf(csvFile),
    },
  };
}

/** Lines that differ between two versions of one file, and whether it is new. */
function diffAgainst(before, after) {
  const a = fs.readFileSync(before, "utf8").split("\n");
  const b = fs.readFileSync(after, "utf8").split("\n");
  const setA = new Set(a.map((l) => l.trim()).filter(Boolean));
  const added = b.map((l) => l.trim()).filter((l) => l && !setA.has(l));
  return { files: 1, existingFilesChanged: 1, linesAdded: added.length };
}

/** What adding formats/json cost the MSSP version, checked rather than claimed. */
function msspExtendCost() {
  const newFile = path.join(src, "TMS", "formats", "json.js");
  const policy = path.join(src, "SCL", "policy.json");
  // Every pre-existing file that mentions the new capability. If the answer is
  // anything but the policy, the core was touched and the claim is false.
  // The needle is built rather than written, and this file is excluded, because
  // the first version of this check reported DMS/measure.js as a file the new
  // capability touched — it contains the search term by virtue of being the
  // thing that searches. An instrument that matches itself is measuring the
  // wrong object.
  const needle = ["formats", "json"].join("/");
  const mentions = walk(src)
    .filter((f) => !f.startsWith(here) && f !== newFile && fs.readFileSync(f, "utf8").includes(needle))
    .map((f) => path.relative(src, f).replaceAll("\\", "/"));
  const policyLines = JSON.parse(fs.readFileSync(policy, "utf8")).formats;
  return {
    files: 2,
    existingFilesChanged: mentions.length,
    touched: mentions,
    linesAdded: 1 + fs.readFileSync(newFile, "utf8").split("\n").filter((l) => l.trim()).length,
    formatsInPolicy: Object.keys(policyLines).length,
  };
}

export function renderTable(m) {
  const row = (label, a, b, better) => {
    const mark = a === b ? "  =" : (better === "lower" ? (b < a ? " ok" : "  +") : (b > a ? " ok" : "  +"));
    return `  ${label.padEnd(34)} ${String(a).padStart(6)} ${String(b).padStart(8)}  ${mark}`;
  };
  return [
    "",
    "== the same program, measured",
    "",
    `  ${"".padEnd(34)} ${"before".padStart(6)} ${"after".padStart(8)}`,
    row("total lines", m.monolith.lines, m.mssp.lines, "lower"),
    row("files", m.monolith.files, m.mssp.files, "lower"),
    row("lines to exercise one capability", m.monolith.toExercise, m.mssp.toExercise, "lower"),
    row("files to exercise one capability", m.monolith.toExerciseFiles, m.mssp.toExerciseFiles, "lower"),
    row("files touched to add a format", m.monolith.toExtend, m.mssp.toExtend, "lower"),
    row("EXISTING files changed to add it", m.extendCost.monolith.existingFilesChanged,
        m.extendCost.mssp.existingFilesChanged, "lower"),
    row("lines to understand one capability", m.monolith.toUnderstand, m.mssp.toUnderstand, "lower"),
    "",
    "  ok = the restructuring helped on this axis, + = it cost",
    "",
    `  the third format touched, in the restructured version: ${m.extendCost.mssp.touched.join(", ") || "nothing pre-existing"}`,
  ].join("\n");
}

root

island-test.js
// The island test, and the check a measurement most needs: can it come out
// against the thing that produced it?
//
//   node src/island-test.js

import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

import { measure, renderTable } from "./DMS/measure.js";
import { known, mayProduce, plausible } from "./SCL/policy.js";
import { summarise } from "./SMS/summarise.js";
import { validate } from "./SMS/validate.js";
import { reading } from "./SMS/model.js";

const here = path.dirname(fileURLToPath(import.meta.url));
const failures = [];
const report = (label, ok, detail = "") => {
  console.log(`  ${ok ? "PASS" : "FAIL"}  ${label}${detail ? ` - ${detail}` : ""}`);
  if (!ok) failures.push(label);
};

const ROWS = [reading("a", 20, "09:00"), reading("a", 22, "10:00"), reading("b", null, "09:00")];

console.log("\n== 1. each format alone, no sibling loaded");
for (const name of ["text", "csv", "json"]) {
  const { render } = await import(`./TMS/formats/${name}.js`);
  const { kept, dropped } = validate(ROWS);
  const out = render(summarise(kept), dropped);
  report(`formats/${name} renders with no sibling imported`, typeof out === "string" && out.length > 0,
    `${out.split("\n")[0].slice(0, 40)}…`);
}
{
  const src = fs.readFileSync(path.join(here, "TMS", "formats", "csv.js"), "utf8");
  report("a format imports nothing at all", !/^\s*import\s/m.test(src), "not even SMS - it takes plain values");
}

console.log("\n== 2. the measurement can come out against the restructuring");
const m = await measure();
const table = renderTable(m);
const costs = table.split("\n").filter((l) => l.trim().endsWith("+")).length;
const wins = table.split("\n").filter((l) => l.trim().endsWith("ok")).length;
report("at least one axis reports a cost", costs > 0, `${costs} cost row(s)`);
report("at least one axis reports a benefit", wins > 0, `${wins} benefit row(s)`);
report("total lines went UP, and the table says so", m.mssp.lines > m.monolith.lines,
  `${m.monolith.lines} -> ${m.mssp.lines}`);
report("this measurement is therefore capable of disagreeing with MSSP",
  costs > 0 && wins > 0, "a table where every row favours the author is not a measurement");

console.log("\n== 3. the numbers are computed, not written down");
report("total lines match a fresh count of the files",
  m.monolith.lines === fs.readFileSync(path.join(here, "..", "baseline", "monolith.js"), "utf8")
    .split("\n").filter((l) => l.trim()).length);
report("the extension cost names the file it found",
  Array.isArray(m.extendCost.mssp.touched) && m.extendCost.mssp.touched.includes("SCL/policy.json"),
  m.extendCost.mssp.touched.join(", "));
report("and the instrument is not in its own result",
  !m.extendCost.mssp.touched.some((f) => f.startsWith("DMS/")),
  "the first version reported DMS/measure.js, which contains the search term");

console.log("\n== 4. SCL decides what exists, and the core does not branch on it");
report("policy knows three formats", known().length === 3, known().join(", "));
report("an unknown format is not produceable", !mayProduce("formats/xml"));
report("the plausibility window comes from policy", plausible().min === -60 && plausible().max === 60,
  JSON.stringify(plausible()));
{
  // Code lines only. The first version read the whole file and matched a
  // comment explaining the branch that had just been removed — the third time
  // today a check reported on prose describing the thing rather than the thing.
  const main = fs.readFileSync(path.join(here, "main.js"), "utf8")
    .split("\n").filter((l) => !l.trim().startsWith("//")).join("\n");
  const branches = [...main.matchAll(/formats\/(text|csv|json)/g)].map((x) => x[0]);
  report("main.js names at most the default format",
    branches.filter((b) => b !== "formats/text").length === 0,
    `found: ${[...new Set(branches)].join(", ") || "none"} - a branch per format would put the coupling back`);
}

console.log("\n== 5. behaviour is unchanged from the baseline");
{
  const { execFileSync } = await import("node:child_process");
  const run = (file, arg) => execFileSync(process.execPath, arg ? [file, arg] : [file],
    { encoding: "utf8" }).trim();
  const base = path.join(here, "..", "baseline", "monolith.js");
  const mine = path.join(here, "main.js");
  for (const [arg, label] of [[null, "text"], ["--csv", "csv"]]) {
    const a = run(base, arg).split("\n").slice(1).join("\n");
    const b = run(mine, arg).split("\n").slice(1).join("\n");
    report(`${label} output is byte-identical to the baseline`, a === b,
      a === b ? "" : `differs:\n${a}\n---\n${b}`);
  }
}

console.log("");
if (failures.length) {
  console.log(`  ${failures.length} check(s) failed: ${failures.join(", ")}`);
  process.exit(1);
}
console.log("  island test passed");
main.js
// The same program as baseline/monolith.js, restructured.
//
//   node src/main.js [--csv]
//   node src/main.js --measure    the comparison table
import { known, mayProduce } from "./SCL/policy.js";
import { reading } from "./SMS/model.js";
import { summarise } from "./SMS/summarise.js";
import { validate } from "./SMS/validate.js";
import { measure, renderTable } from "./DMS/measure.js";

const READINGS = [
  reading("north", 21.4, "09:00"),
  reading("north", 22.1, "10:00"),
  reading("south", null, "09:00"),
  reading("south", 19.8, "10:00"),
  reading("roof", 84.2, "09:00"),
];

async function main(argv) {
  if (argv.includes("--measure")) {
    console.log(renderTable(await measure()));
    return 0;
  }

  // Derived from policy, not a branch. The first version of this line read
  //   argv.includes("--csv") ? "formats/csv" : "formats/text"
  // which is a dispatch branch in the core — exactly the coupling the
  // restructuring exists to remove, and it meant adding a format touched this
  // file after all. The measurement caught it: --json printed text.
  const asked = argv.find((a) => a.startsWith("--") && known().includes(`formats/${a.slice(2)}`));
  const wanted = asked ? `formats/${asked.slice(2)}` : "formats/text";
  if (!mayProduce(wanted)) {
    console.log(`\n  ${wanted} is not permitted by policy`);
    return 1;
  }

  // Loaded on demand, and only the one asked for.
  const { render } = await import(`./TMS/${wanted}.js`);
  const { kept, dropped } = validate(READINGS);

  console.log(`\n== readings (${wanted.split("/")[1]})`);
  console.log(render(summarise(kept), dropped));
  return 0;
}

process.exit(await main(process.argv.slice(2)));