DEV Community

Engineering Is the Checkable Fraction of Your Practice

Craft externalizes nothing and transfers by apprenticeship. Engineering writes the governing relation down where someone else can find it wrong. Four times now I have written the same three sentences in different notations, for four problems that looked unrelated: a design method, a coding technology, an architecture-derivation procedure, and a contract-modelling tool. I noticed the repetition only after the fourth. Here it is, stated as precisely as I can manage. Structure is derived from the attribution of forced change. The attribution is kept as an explicit, checkable artifact. The derivation refuses rather than guesses when its inputs underdetermine the answer. Three clauses, each carrying weight. Drop one and look at what remains. Drop derived and you have a documentation exercise: the structure was chosen first and the attribution written to match. This is the normal case, and it makes no prediction, so nothing can disagree with it. Drop explicit artifact and you have taste. Real, valuable, and transferable only by apprenticeship. Drop refusal and you have a generator that answers every question. Its answers carry no information, because it was always going to produce one. The third clause has a lineage worth claiming Type inference has refused for fifty years. Hindley-Milner unification fails rather than picking a plausible substitution: when two types cannot be reconciled, the answer is an error, not a guess. Core HM needs no annotations at all -- it infers principal types, and that is the point. The interesting part is what happened when later extensions broke that guarantee. Type classes admit programs whose type is inferable while the instance to use is not; GADTs and polymorphic recursion break principality outright. In each case the compilers were free to pick a plausible candidate, and they demand an annotation instead. Build systems joined later: Bazel refuses an undeclared dependency rather than resolving it from ambient state (this rule is missing dependency ), and Nix in pure evaluation mode will not fetch what its inputs do not pin declarations (in pure evaluation mode, 'fetchTree' will not fetch unlocked input ), with --impure as the deliberate opt-out. In each case the refusal is the feature. It is what makes the successful output mean something. Decomposition methods have never had this, as far as I know. Layered, hexagonal, C4, arc42, the domain modelling schools -- feed any of them an underspecified system and you get a diagram. I know of none that returns this does not determine a boundary for any input. So the move is a transplant, not an invention: take a discipline that inference engines and build systems have proven out, and apply it to structure. That is a smaller claim than novelty and a more useful one, because it means the hard part already has fifty years of evidence behind it. There is a plausible reason the shape keeps recurring without anyone coordinating it. The refusal aesthetic is ambient in typed functional programming -- parse don't validate, total functions over partial ones, illegal states unrepresentable, results over exceptions. What follows may simply be that tradition's core move, lifted from values to structure. What it forbids A principle that forbids nothing is decoration. This one rules out five things concretely enough to check on a Monday morning: - Grouping by similarity or type. All the *Service classes in one package, all the DTOs in another. Similarity is not an attribution of change. - "We needed coverage" as a test's reason for existing. A test names the property it pins, or it does not get written. - Emitting a value the inputs do not determine. No defaults dressed as derivations, no "approximately this" where the honest output is a refusal. - A structure whose reason is recorded nowhere. If the attribution lives only in the head of whoever drew the boundary, no one else can check it, and it leaves when they do. - A decomposition no history could contradict. If nothing observable could disagree with your module boundaries, they assert nothing. If a claimed application of this forbids none of the five, it is not an application of it. Where the shape showed up Each of these stands on its own argument, worked out in full at the links. What they have in common is the third clause -- each one has an output that is not an answer. - Process-First Design decomposes by change driver, and will not draw a boundary when the drivers are unknown. - Java Backend Coding Technology shapes test suites by the code property under test, and will not write a test whose only justification is coverage. - Architecture Synthesis returns underdetermined instead of a recommendation when the inputs do not support one. - A contract-modelling tool, built recently by someone working from the question "as a contract artifact, what could be better?" and almost none of the above, reports every fidelity loss rather than approximating over it. The question steered the axis; the refusal-shaped decisions were unprompted. What makes it checkable "Forced change" earns its place because it has an observable proxy. Version control records which files change together whether or not anyone was paying attention, so a decomposition can be compared against history and found to disagree. The measurement is neither new nor mine: logical coupling has been studied in the mining-software-repositories literature since Gall in 1998 and Zimmermann in 2004, and Adam Tornhill later productized it as change coupling in CodeScene. So this half is measurable today, with two decades of literature behind it and a tool that computes it off the shelf. What that buys is availability, not proof -- how well the metric predicts maintenance cost is still a live research question. The comparison has to be set up correctly to mean anything, and the reason is interesting in its own right: co-change is partly endogenous. Files change together partly because the current structure forces them to, so raw co-change is downstream of the decomposition it is being used to check. Two adjustments make the instrument sharp: - Measure cross-boundary co-change. Coupling that crosses a boundary indicates misattribution regardless of whether it was forced or induced. - Use restructuring events as natural experiments. Co-change that survives a known restructure is real. Co-change that disappears was an artifact of the old structure. Why this is the engineering question Vincenti's What Engineers Know and How They Know It divides engineering knowledge into the explicit -- descriptive and prescriptive -- and the tacit. He is arguing something else with that taxonomy, that engineering knowledge is its own thing rather than applied science, so the reading that follows is mine rather than his. Rigor is not what separates engineering from craft. Master craftsmen are rigorous. What separates them is whether the knowledge governing a decision is externalized into an artifact that someone other than the maker can check. A stress calculation can be audited by an engineer who was not present. A mason's judgment transfers only by apprenticeship. That is the second clause almost verbatim, and the first matches too: engineering derives dimensions from load cases, craft selects proportions from precedent and eye. It also explains something otherwise puzzling. Fifty years of calling this work "software engineering" did not make it engineering, and the usual diagnoses -- insufficient process, measurement, or professionalism -- do not fit, because all three have been tried at length. The explanation that does fit is that the governing relations stayed tacit. Nothing was externalized, so nothing could be audited, and nothing could be wrong. Which gives a definition worth using, stated as a ratio rather than a binary: Engineering is the checkable fraction of your practice. Craft is the limit at zero. Engineering-as-aspiration is the limit at one. Every real practice sits in between, and the same repository can be at both ends at once -- one of mine audited as engineering on its import path and craft on its export path, simultaneously. The daily form Three clauses at project scale need a version that survives a Tuesday. Three questions at the moment of a decision: - What relation governs this? What would force this to change? What property is this test pinning? What does this boundary protect? - Is that relation written down where it could contradict me later? A commit message, a decision line, a test name. The artifact clause at whatever grain the moment affords. - Do my inputs actually determine the answer? Question 3 has a daily form distinct from its project form. You usually cannot refuse -- deadlines exist, most decisions are underdetermined, and they get made anyway. So the working version is never dressing a guess as a derivation: record it as a guess, with what would settle it. Two conditions keep this from becoming ceremony, which is how good disciplines die. Apply it only where a checkable relation exists; forced onto naming debates and aesthetic choices it degenerates into ritual. And "this is taste, and I am marking it as taste" is a valid answer to question 3. The smallest instance Everything above is project-scale. Here is the same thing at the scale of one working session, which is where the day actually goes. I keep a CLAUDE.md -- a file the coding agent reads before every session. That file is a governing relation, externalized into an artifact, constraining something whose output can then be checked against it. Which makes it an instance of the second clause rather than an illustration of one. Two of its lines carry the formulation directly, quoted as they stand: - Record what governs a decision where it can later contradict you. The driver behind a boundary, the property a test pins, the reason a default was chosen - at whatever grain the moment affords: a commit message, a test name, one line in the response. Never a new document un

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.