Engineering Is the Checkable Fraction of Your Practice
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. 手艺(Craft)不外化任何东西,只能通过学徒制传承。而工程学将支配性的关系记录下来,以便他人能指出其中的错误。
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. 第三个从句有着值得称道的渊源。类型推断已经“拒绝”了五十年。Hindley-Milner 统一算法在无法找到合理的替代方案时会失败,而不是进行猜测:当两种类型无法调和时,答案是错误,而不是猜测。核心 HM 不需要任何注解——它能推断出主类型,这正是其核心所在。有趣的是,当后来的扩展打破这一保证时发生了什么。类型类(Type classes)允许存在类型可推断但实例不可推断的程序;GADT 和多态递归则直接破坏了主类型性。在这些情况下,编译器本可以自由选择一个合理的候选者,但它们却要求必须提供注解。
Build systems joined later: Bazel refuses an undeclared dependency rather than resolving it from ambient state (this rule is missing dependency declarations), and Nix in pure evaluation mode will not fetch what its inputs do not pin (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.
构建系统后来也加入了这一行列:Bazel 会拒绝未声明的依赖,而不是从环境状态中解析它(规则是:缺少依赖声明即报错);Nix 在纯评估模式下不会获取输入未锁定的内容(在纯评估模式下,‘fetchTree’ 不会获取未锁定的输入),并以 --impure 作为刻意的退出机制。在每种情况下,“拒绝”都是一项特性。正是它让成功的输出有了意义。
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. 据我所知,分解方法从未具备这一点。分层架构、六边形架构、C4、arc42、领域建模流派——给它们任何一个定义不足的系统,你都会得到一张图表。我不知道有哪种方法会对任何输入返回“这无法确定边界”。因此,这一举措是移植,而非发明:采用推断引擎和构建系统已证明有效的准则,并将其应用于结构。这比追求创新更谦逊,也更有用,因为它意味着最困难的部分已经有了五十年的证据支持。
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. 这种模式在无人协调的情况下不断重现,是有合理原因的。这种“拒绝”的美学在类型化函数式编程中无处不在——解析而非验证、全函数优于偏函数、非法状态不可表示、结果优于异常。接下来的内容可能只是将这一传统的核心举措,从值(values)提升到了结构(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.
-
按相似性或类型分组。将所有 *Service 类放在一个包中,所有 DTO 放在另一个包中。相似性并非变更的归因。
-
“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.
-
没有任何历史记录可以反驳的分解。如果没有任何可观察的事实能反驳你的模块边界,那么它们就什么也没断言。如果一个声称应用了此原则的方法没有禁止上述五点中的任何一点,那么它就不是真正的应用。
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 down. 什么使它可验证:“强制变更”之所以能占据一席之地,是因为它有一个可观察的代理指标。版本控制记录了哪些文件一起变更,无论是否有人关注,因此可以将分解方案与历史记录进行对比,从而发现不一致之处。这种度量既不新鲜,也不是我的发明:自 1998 年 Gall 和 2004 年 Zimmermann 的软件仓库挖掘文献以来,逻辑耦合(logical coupling)就一直被研究,后来 Adam Tornhill 将其产品化为 CodeScene 中的变更耦合(change coupling)。因此,这一部分在今天是可以度量的,有着二十年的文献支持和现成的计算工具。这带来的是可用性,而非证明——该指标在预测维护成本方面的准确性仍然是一个活跃的研究课题。对比必须设置正确才有意义,其原因本身就很有趣:共同变更(co-change)在一定程度上是内生的。文件一起变更,部分是因为当前的结构强制它们这样做,所以原始的共同变更数据……