Glean 拾遗
日刊 /2026-09-16 / 好代码容易删掉,而不是容易扩展

好代码容易删掉,而不是容易扩展

原文 programmingisterrible.com 收录 2026-09-16 06:00 阅读 20 min
AI 解读

作者主张把代码行数视为“花掉的行数”而非“产出的行数”:每行代码都要持续支付维护成本,而为提高复用率构建的抽象会把调用方绑死在实现的显式与隐式行为上,让后续变更代价更高。因此目标应是可删除(disposable),而非可复用、可扩展。文中给出递进策略:能不写就不写;先复制粘贴几次再提取函数;把无状态、与应用无关的代码放进 util 并一工具一文件;接受 boilerplate 换来的灵活性;像 requests 包住 urllib3 那样把 policy 与 protocol 分层;允许业务逻辑先做成一坨泥巴;按“不与谁共享”而非功能拆分模块;用统一接口、HTTP 缓存与 CDN、feature flag 制造可替换点;错误处理放在端到端的最外层,如同 Erlang 监督树用 fail-fast 重启替代就地恢复。适合维护长期演进代码库的工程师与设计者。

原文 20 分钟
原文 programmingisterrible.com ↗
§ 1

“Every line of code is written without reason, maintained out of weakness, and deleted by chance” Jean-Paul Sartre’s Programming in ANSI C.

Every line of code written comes at a price: maintenance. To avoid paying for a lot of code, we build reusable software. The problem with code re-use is that it gets in the way of changing your mind later on.

The more consumers of an API you have, the more code you must rewrite to introduce changes. Similarly, the more you rely on an third-party api, the more you suffer when it changes. Managing how the code fits together, or which parts depend on others, is a significant problem in large scale systems, and it gets harder as your project grows older.

“每一行代码都毫无理由地写就,因软弱而维护,又偶然被删除。”——让-保罗·萨特《ANSI C 编程》。

写的每一行代码都有代价:维护。为了避免为大量代码付出代价,我们构建可复用的软件。代码复用的问题在于,它会妨碍你日后改变主意。

API 的消费者越多,引入变更时需要重写的代码就越多。同样,你越依赖第三方 API,当它变化时你就越痛苦。管理代码如何组合,或者哪些部分依赖其他部分,是大型系统中的重要问题,并且随着项目变老而愈发困难。

§ 2

My point today is that, if we wish to count lines of code, we should not regard them as “lines produced” but as “lines spent” EWD 1036

If we see ‘lines of code’ as ‘lines spent’, then when we delete lines of code, we are lowering the cost of maintenance. Instead of building re-usable software, we should try to build disposable software.

I don’t need to tell you that deleting code is more fun than writing it.

我今天的观点是,如果我们想计算代码行数,就不应把它们看作“产出的行数”,而应看作“花费的行数” EWD 1036

如果我们把“代码行数”视为“花费的行数”,那么当我们删除代码行时,就是在降低维护成本。与其构建可复用的软件,不如尝试构建一次性的软件。

不用我说你也知道,删除代码比写代码更有趣。

§ 3

To write code that’s easy to delete: repeat yourself to avoid creating dependencies, but don’t repeat yourself to manage them. Layer your code too: build simple-to-use APIs out of simpler-to-implement but clumsy-to-use parts. Split your code: isolate the hard-to-write and the likely-to-change parts from the rest of the code, and each other. Don’t hard code every choice, and maybe allow changing a few at runtime. Don’t try to do all of these things at the same time, and maybe don’t write so much code in the first place.

要写出容易删除的代码:重复自己以避免创建依赖,但不要为了管理依赖而重复自己。还要分层:用更易实现但难用的部分构建出易用的 API。拆分代码:把难写的、可能变化的部分与其余代码隔离,也彼此隔离。不要硬编码每一个选择,也许允许在运行时改变其中一些。不要试图同时做所有这些事情,也许首先就不要写这么多代码。

§ 4

Step 0: Don’t write code

The number of lines of code doesn’t tell us much on its own, but the magnitude does 50, 500 5,000, 10,000, 25,000, etc. A million line monolith is going to be more annoying than a ten thousand line one and significantly more time, money, and effort to replace.

Although the more code you have the harder it is to get rid of, saving one line of code saves absolutely nothing on its own.

Even so, the easiest code to delete is the code you avoided writing in the first place.

第0步:不要写代码

代码行数本身说明不了什么,但数量级可以:50、500、5,000、10,000、25,000 等等。一百万行的单体应用会比一万行的更烦人,替换起来也要多花大量时间、金钱和精力。

尽管代码越多越难摆脱,但单独节省一行代码本身绝对省不下什么。

即便如此,最容易删除的代码,就是你一开始就避免写的代码。

§ 5

Step 1: Copy-paste code

Building reusable code is something that’s easier to do in hindsight with a couple of examples of use in the code base, than foresight of ones you might want later. On the plus side, you’re probably re-using a lot of code already by just using the file-system, why worry that much? A little redundancy is healthy.

It’s good to copy-paste code a couple of times, rather than making a library function, just to get a handle on how it will be used. Once you make something a shared API, you make it harder to change.

The code that calls your function will rely on both the intentional and the unintentional behaviours of the implementation behind it. The programmers using your function will not rely on what you document, but what they observe.

It’s simpler to delete the code inside a function than it is to delete a function.

第1步:复制粘贴代码

构建可复用代码,在事后有了代码库中的几个使用示例时去做,比预想日后可能需要的用法要容易。好的一面是,你可能仅仅通过使用文件系统就已经在复用大量代码了,何必那么担心?一点冗余是健康的。

复制粘贴代码几次,而不是做一个库函数,这有助于了解它将被如何使用。一旦你把某个东西变成共享 API,你就让它更难改变。

调用你函数的代码会依赖其背后实现的有意和无意行为。使用你函数的程序员不会依赖你文档中写的东西,而是依赖他们观察到的行为。

删除函数内部的代码,比删除一个函数更简单。

§ 6

Step 2: Don’t copy paste code

When you’ve copy and pasted something enough times, maybe it’s time to pull it up to a function. This is the “save me from my standard library” stuff: the “open a config file and give me a hash table”, “delete this directory”. This includes functions without any state, or functions with a little bit of global knowledge like environment variables. The stuff that ends up in a file called “util”.

Aside: Make a util directory and keep different utilities in different files. A single util file will always grow until it is too big and yet too hard to split apart. Using a single util file is unhygienic.

The less specific the code is to your application or project, the easier they are to re-use and the less likely to change or be deleted. Library code like logging, or third party APIs, file handles, or processes. Other good examples of code you’re not going to delete are lists, hash tables, and other collections. Not because they often have very simple interfaces, but because they don’t grow in scope over time.

Instead of making code easy-to-delete, we are trying to keep the hard-to-delete parts as far away as possible from the easy-to-delete parts.

第2步:不要复制粘贴代码

当你复制粘贴某个东西足够多次后,也许是时候把它提取成函数了。这是“把我从标准库里拯救出来”的东西:“打开配置文件并给我一个哈希表”、“删除这个目录”。这包括没有任何状态的函数,或者带有一点全局知识(如环境变量)的函数。这些东西最终会进入一个叫“util”的文件。

旁注:建一个 util 目录,把不同的工具放在不同的文件里。单个 util 文件总会不断增长,直到它太大却又难以拆分。使用单个 util 文件是不卫生的。

代码越不特定于你的应用或项目,就越容易复用,也越不可能改变或被删除。像日志、第三方 API、文件句柄或进程这样的库代码。其他不会被删除的好例子是列表、哈希表和其他集合。不是因为它们通常有非常简单的接口,而是因为它们不会随时间扩大范围。

我们不是在让代码容易删除,而是试图让难以删除的部分尽可能远离容易删除的部分。

§ 7

Step 3: Write more boilerplate

Despite writing libraries to avoid copy pasting, we often end up writing a lot more code through copy paste to use them, but we give it a different name: boilerplate. Boiler plate is a lot like copy-pasting, but you change some of the code in a different place each time, rather than the same bit over and over.

Like with copy paste, we are duplicating parts of code to avoid introducing dependencies, gain flexibility, and pay for it in verbosity.

Libraries that require boilerplate are often stuff like network protocols, wire formats, or parsing kits, stuff where it’s hard to interweave policy (what a program should do), and protocol (what a program can do) together without limiting the options. This code is hard to delete: it’s often a requirement for talking to another computer or handling different files, and the last thing we want to do is litter it with business logic.

This is not an exercise in code reuse: we’re trying keep the parts that change frequently, away from the parts that are relatively static. Minimising the dependencies or responsibilities of library code, even if we have to write boilerplate to use it.

You are writing more lines of code, but you are writing those lines of code in the easy-to-delete parts.

第3步:写更多样板代码

尽管我们写库来避免复制粘贴,但最终常常通过复制粘贴写了更多的代码来使用它们,只是换了个名字:样板代码。样板代码很像复制粘贴,但每次你在不同地方修改一些代码,而不是一遍又一遍地改同一处。

就像复制粘贴一样,我们复制部分代码以避免引入依赖、获得灵活性,并为此付出冗长的代价。

需要样板代码的库通常是网络协议、线格式或解析工具包之类的东西,这些东西很难在不限制选项的情况下将策略(程序应该做什么)和协议(程序能做什么)交织在一起。这种代码很难删除:它通常是与其他计算机对话或处理不同文件的需求,而我们最不想做的就是让它到处散落业务逻辑。

这不是代码复用的练习:我们试图让频繁变化的部分远离相对静态的部分。最小化库代码的依赖或职责,即使我们必须写样板代码来使用它。

你在写更多的代码行,但你把这些代码行写在容易删除的部分。

§ 8

Step 4: Don’t write boilerplate

Boilerplate works best when libraries are expected to cater to all tastes, but sometimes there is just too much duplication. It’s time to wrap your flexible library with one that has opinions on policy, workflow, and state. Building simple-to-use APIs is about turning your boilerplate into a library.

This isn’t as uncommon as you might think: One of the most popular and beloved python http clients, requests, is a successful example of providing a simpler interface, powered by a more verbose-to-use library urllib3 underneath. requests caters to common workflows when using http, and hides many practical details from the user. Meanwhile, urllib3 does the pipelining, connection management, and does not hide anything from the user.

It is not so much that we are hiding detail when we wrap one library in another, but we are separating concerns: requests is about popular http adventures, urllib3 is about giving you the tools to choose your own adventure.

第4步:不要写样板代码

当库被期望满足所有口味时,样板代码最有效,但有时重复实在太多。是时候用一个对策略、工作流和状态有主见的库来包装你灵活的库了。构建易用的 API 就是把你的样板代码变成库。

这并不像你想象的那么罕见:最受欢迎和喜爱的 Python HTTP 客户端之一 requests,就是一个成功提供更简单接口的例子,底层由更冗长难用的库 urllib3 驱动。requests 迎合使用 HTTP 时的常见工作流,并向用户隐藏许多实际细节。同时,urllib3 负责流水线、连接管理,并且不向用户隐藏任何东西。

当我们把一个库包装在另一个库中时,与其说我们在隐藏细节,不如说我们在分离关注点:requests 关注流行的 HTTP 冒险,urllib3 关注给你工具去选择自己的冒险。

§ 9

I’m not advocating you go out and create a /protocol/ and a /policy/ directory, but you do want to try and keep your util directory free of business logic, and build simpler-to-use libraries on top of simpler-to-implement ones. You don’t have to finish writing one library to start writing another atop.

It’s often good to wrap third party libraries too, even if they aren’t protocol-esque. You can build a library that suits your code, rather than lock in your choice across the project. Building a pleasant to use API and building an extensible API are often at odds with each other.

This split of concerns allows us to make some users happy without making things impossible for other users. Layering is easiest when you start with a good API, but writing a good API on top of a bad one is unpleasantly hard. Good APIs are designed with empathy for the programmers who will use it, and layering is realising we can’t please everyone at once.

Layering is less about writing code we can delete later, but making the hard to delete code pleasant to use (without contaminating it with business logic).

我不是主张你去建一个 /protocol/ 和一个 /policy/ 目录,但你确实应该努力让 util 目录不包含业务逻辑,并在更易实现的基础上构建更易使用的库。你不必写完一个库再开始在上面写另一个。

包装第三方库通常也是好的,即使它们不是协议风格的。你可以构建适合自己代码的库,而不是在整个项目中锁定你的选择。构建好用的 API 和构建可扩展的 API 常常互相冲突。

这种关注点的分离让我们可以让一些用户满意,同时不会让其他用户无路可走。分层最容易的时候是你从一个好的 API 开始,但在糟糕的 API 之上写一个好的 API 会难过得要命。好的 API 设计时对使用它的程序员有同理心,而分层就是意识到我们无法一次取悦所有人。

分层与其说是写我们以后可以删除的代码,不如说是让难以删除的代码用起来愉快(不沾染业务逻辑)。

§ 10

Step 5: Write a big lump of code

You’ve copy-pasted, you’ve refactored, you’ve layered, you’ve composed, but the code still has to do something at the end of the day. Sometimes it’s best just to give up and write a substantial amount of trashy code to hold the rest together.

Business logic is code characterised by a never ending series of edge cases and quick and dirty hacks. This is fine. I am ok with this. Other styles like ‘game code’, or ‘founder code’ are the same thing: cutting corners to save a considerable amount of time.

The reason? Sometimes it’s easier to delete one big mistake than try to delete 18 smaller interleaved mistakes. A lot of programming is exploratory, and it’s quicker to get it wrong a few times and iterate than think to get it right first time.

This is especially true of more fun or creative endeavours. If you’re writing your first game: don’t write an engine. Similarly, don’t write a web framework before writing an application. Go and write a mess the first time. Unless you’re psychic you won’t know how to split it up.

Monorepos are a similar tradeoff: You won’t know how to split up your code in advance, and frankly one large mistake is easier to deploy than 20 tightly coupled ones.

When you know what code is going to be abandoned soon, deleted, or easily replaced, you can cut a lot more corners. Especially if you make one-off client sites, event web pages. Anything where you have a template and stamp out copies, or where you fill in the gaps left by a framework.

第5步:写一大坨代码

你复制粘贴过,重构过,分层过,组合过,但代码归根结底还是得做点什么。有时候最好放弃,写大量垃圾代码把其余部分粘合起来。

业务逻辑的特点是没完没了的边缘情况和快速肮脏的 hack。这没关系。我能接受。其他风格如“游戏代码”或“创始人代码”也是一回事:为了节省大量时间而抄近路。

原因?有时候删除一个大错误比试图删除 18 个相互交错的小错误更容易。很多编程是探索性的,快速犯错几次并迭代,比一开始就想着一次做对要快。

对于更有趣或更有创造性的努力尤其如此。如果你在写你的第一个游戏:不要写引擎。同样,不要在写应用之前写 Web 框架。第一次就去写一团乱麻。除非你能通灵,否则你不会知道如何拆分它。

单体仓库也是类似的权衡:你不会提前知道如何拆分代码,坦率地说,一个大错误比 20 个紧密耦合的错误更容易部署。

当你知道哪些代码很快会被废弃、删除或容易替换时,你可以抄更多近路。尤其是如果你做一次性的客户网站、活动网页。任何你有模板并复制粘贴,或者填补框架留下的空白的地方。

§ 11

I’m not suggesting you write the same ball of mud ten times over, perfecting your mistakes. To quote Perlis: “Everything should be built top-down, except the first time”. You should be trying to make new mistakes each time, take new risks, and slowly build up through iteration.

Becoming a professional software developer is accumulating a back-catalogue of regrets and mistakes. You learn nothing from success. It is not that you know what good code looks like, but the scars of bad code are fresh in your mind.

Projects either fail or become legacy code eventually anyway. Failure happens more than success. It’s quicker to write ten big balls of mud and see where it gets you than try to polish a single turd.

It’s easier to delete all of the code than to delete it piecewise.

我不是建议你把同一个泥球写十遍,完善你的错误。引用 Perlis 的话:“一切都应该自顶向下构建,除了第一次”。你应该每次都尝试犯新的错误,冒新的风险,并通过迭代慢慢构建。

成为一名专业的软件开发人员,就是积累一堆后悔和错误。你从成功中学不到任何东西。不是你知道好代码长什么样,而是坏代码的伤疤在你脑海中记忆犹新。

项目最终要么失败,要么变成遗留代码。失败比成功更常发生。写十个大泥球看看它把你带到哪里,比试图打磨一坨屎要快。

删除所有代码比逐块删除更容易。

§ 12

Step 6: Break your code into pieces

Big balls of mud are the easiest to build but the most expensive to maintain. What feels like a simple change ends up touching almost every part of the code base in an ad-hoc fashion. What was easy to delete as a whole is now impossible to delete piecewise.

In the same we have layered our code to separate responsibilities, from platform specific to domain specific, we need to find a means to tease apart the logic atop.

[Start] with a list of difficult design decisions or design decisions which are likely to change. Each module is then designed to hide such a decision from the others. D. Parnas

Instead of breaking code into parts with common functionality, we break code apart by what it does not share with the rest. We isolate the most frustrating parts to write, maintain, or delete away from each other.

We are not building modules around being able to re-use them, but being able to change them.

Unfortunately, some problems are more intertwined and hard to separate than others. Although the single responsibility principle suggests that ‘each module should only handle one hard problem’, it is more important that ‘each hard problem is only handled by one module’

When a module does two things, it is usually because changing one part requires changing the other. It is often easier to have one awful component with a simple interface, than two components requiring a careful co-ordination between them.

第6步:把代码拆成小块

大泥球最容易构建,但维护成本最高。感觉上简单的改动,最终却以临时的方式触及代码库的几乎每个部分。整体上容易删除的东西,现在却无法逐块删除。

正如我们将代码分层以分离职责——从平台特定到领域特定——我们需要找到一种方法来拆开上层的逻辑。

[开始]列出困难的设计决策或可能变化的设计决策。然后每个模块的设计都是为了向其他模块隐藏这样的决策。D. Parnas

我们不是按共同功能把代码分成部分,而是按它与其余部分不共享的东西来拆分代码。我们把最难写、最难维护或最难删除的部分彼此隔离开来。

我们构建模块不是为了能够复用它们,而是为了能够改变它们。

不幸的是,有些问题比其他问题更交织、更难分离。虽然单一职责原则建议“每个模块只处理一个难题”,但更重要的是“每个难题只由一个模块处理”

当一个模块做两件事时,通常是因为改变一部分需要改变另一部分。通常,有一个糟糕的组件配上简单的接口,比有两个需要小心协调的组件更容易。

§ 13

I shall not today attempt further to define the kinds of material I understand to be embraced within that shorthand description [”loose coupling”], and perhaps I could never succeed in intelligibly doing so. But I know it when I see it, and the code base involved in this case is not that. SCOTUS Justice Stewart

A system where you can delete parts without rewriting others is often called loosely coupled, but it’s a lot easier to explain what one looks like rather than how to build it in the first place.

Even hardcoding a variable once can be loose coupling, or using a command line flag over a variable. Loose coupling is about being able to change your mind without changing too much code.

For example, Microsoft Windows has internal and external APIs for this very purpose. The external APIs are tied to the lifecycle of desktop programs, and the internal API is tied to the underlying kernel. Hiding these APIs away gives Microsoft flexibility without breaking too much software in the process.

HTTP has examples of loose coupling too: Putting a cache in front of your HTTP server. Moving your images to a CDN and just changing the links to them. Neither breaks the browser.

HTTP’s error codes are another example of loose coupling: common problems across web servers have unique codes. When you get a 400 error, doing it again will get the same result. A 500 may change. As a result, HTTP clients can handle many errors on the programmers behalf.

我今天不打算进一步定义我所理解的“[松耦合]”这个简略描述所包含的材料种类,也许我永远也无法以可理解的方式成功定义。但当我看到它时我就知道,而本案涉及的代码库并不是那样。美国最高法院大法官斯图尔特

一个可以删除部分而不重写其他部分的系统,通常被称为松耦合,但解释它看起来是什么样,比一开始就解释如何构建它要容易得多。

甚至硬编码一个变量一次也可以是松耦合,或者使用命令行标志而不是变量。松耦合是关于能够改变主意而不改变太多代码。

例如,微软 Windows 就有内部和外部 API 正是为此目的。外部 API 与桌面程序的生命周期绑定,内部 API 与底层内核绑定。把这些 API 隐藏起来让微软获得灵活性,而不会在这个过程中破坏太多软件。

HTTP 也有松耦合的例子:在你的 HTTP 服务器前面放一个缓存。把你的图片移到 CDN 并只是更改它们的链接。两者都不会破坏浏览器。

HTTP 的错误码是松耦合的另一个例子:跨 Web 服务器的常见问题有唯一的代码。当你得到 400 错误时,再做一次会得到相同的结果。500 可能会改变。因此,HTTP 客户端可以代表程序员处理许多错误。

§ 14

How your software handles failure must be taken into account when decomposing it into smaller pieces. Doing so is easier said than done.

I have decided, reluctantly to use LaTeX. Making reliable distributed systems in the presence of software errors. Armstrong, 2003

Erlang/OTP is relatively unique in how it chooses to handle failure: supervision trees. Roughly, each process in an Erlang system is started by and watched by a supervisor. When a process encounters a problem, it exits. When a process exits, it is restarted by the supervisor.

(These supervisors are started by a bootstrap process, and when a supervisor encounters a fault, it is restarted by the bootstrap process)

The key idea is that it is quicker to fail-fast and restart than it is to handle errors. Error handling like this may seem counter-intuitive, gaining reliability by giving up when errors happen, but turning things off-and-on again has a knack for suppressing transient faults.

Error handling, and recovery are best done at the outer layers of your code base. This is known as the end-to-end principle. The end-to-end principle argues that it is easier to handle failure at the far ends of a connection than anywhere in the middle. If you have any handling inside, you still have to do the final top level check. If every layer atop must handle errors, so why bother handling them on the inside?

在把软件分解成更小的部分时,必须考虑它如何处理失败。说起来容易做起来难。

我决定,勉强使用 LaTeX。在软件错误存在的情况下构建可靠的分布式系统。Armstrong, 2003

Erlang/OTP 在处理失败的方式上相对独特:监督树。大致上,Erlang 系统中的每个进程由一个监督者启动和监视。当进程遇到问题时,它会退出。当进程退出时,它会被监督者重启。

(这些监督者由引导进程启动,当监督者遇到故障时,它会被引导进程重启)

关键思想是,快速失败并重启比处理错误更快。这样的错误处理可能看起来反直觉——通过放弃来获得可靠性——但反复开关有抑制瞬时故障的诀窍。

错误处理和恢复最好在代码库的外层进行。这被称为端到端原则。端到端原则认为,在连接的远端处理失败比在中间任何地方都容易。如果你在内部有任何处理,你仍然必须做最后的顶层检查。如果上层的每一层都必须处理错误,那为什么还要在内部处理呢?

§ 15

Error handling is one of the many ways in which a system can be tightly bound together. There are many other examples of tight coupling, but it is a little unfair to single one out as being badly designed. Except for IMAP.

In IMAP almost every each operation is a snowflake, with unique options and handling. Error handling is painful: errors can come halfway through the result of another operation.

Instead of UUIDs, IMAP generates unique tokens to identify each message. These can change halfway through the result of an operation too. Many operations are not atomic. It took more than 25 years to get a way to move email from one folder to another that reliably works. There is a special UTF-7 encoding, and a unique base64 encoding too.

I am not making any of this up.

错误处理是系统可以紧密绑定在一起的众多方式之一。还有很多其他紧耦合的例子,但单独挑一个说设计糟糕有点不公平。除了 IMAP。

在 IMAP 中,几乎每个操作都是雪花,有独特的选项和处理方式。错误处理很痛苦:错误可能出现在另一个操作结果的中途。

IMAP 不是用 UUID,而是生成唯一的令牌来标识每条消息。这些也可以在操作结果中途改变。许多操作不是原子的。花了超过 25 年才找到一种可靠地将电子邮件从一个文件夹移动到另一个文件夹的方法。还有一种特殊的 UTF-7 编码,以及一种独特的 base64 编码。

我没有编造任何这些。

§ 16

By comparison, both file systems and databases make much better examples of remote storage. With a file system, you have a fixed set of operations, but a multitude of objects you can operate on.

Although SQL may seem like a much broader interface than a filesystem, it follows the same pattern. A number of operations on sets, and a multitude of rows to operate on. Although you can’t always swap out one database for another, it is easier to find something that works with SQL over any homebrew query language.

相比之下,文件系统和数据库都是远程存储的更好例子。对于文件系统,你有一组固定的操作,但可以操作大量对象。

尽管 SQL 看起来比文件系统广泛得多,但它遵循同样的模式。对一些集合进行若干操作,并操作大量行。虽然你不能总是把一个数据库换成另一个,但找到能使用 SQL 的东西比任何自制的查询语言更容易。

§ 17

Other examples of loose coupling are other systems with middleware, or filters and pipelines. For example, Twitter’s Finagle uses a common API for services, and this allows generic timeout handling, retry mechanisms, and authentication checks to be added effortlessly to client and server code.

(I’m sure if I didn’t mention the UNIX pipeline here someone would complain at me)

First we layered our code, but now some of those layers share an interface: a common set of behaviours and operations with a variety of implementations. Good examples of loose coupling are often examples of uniform interfaces.

A healthy code base doesn’t have to be perfectly modular. The modular bit makes it way more fun to write code, in the same way that Lego bricks are fun because they all fit together. A healthy code base has some verbosity, some redundancy, and just enough distance between the moving parts so you won’t trap your hands inside.

Code that is loosely coupled isn’t necessarily easy-to-delete, but it is much easier to replace, and much easier to change too.

松耦合的其他例子是带有中间件、过滤器或管道的系统。例如,Twitter 的 Finagle 为服务使用通用 API,这使得通用超时处理、重试机制和认证检查可以轻松添加到客户端和服务器代码中。

(我敢肯定,如果我在这里不提 UNIX 管道,有人会抱怨我)

首先我们把代码分层,但现在其中一些层共享一个接口:一组通用的行为和操作,有多种实现。松耦合的好例子通常是统一接口的例子。

健康的代码库不必完美模块化。模块化的部分让写代码更有趣,就像乐高积木之所以有趣,是因为它们都能拼在一起。健康的代码库有一些冗长、一些冗余,以及运动部件之间刚刚好的距离,这样你就不会把手夹进去。

松耦合的代码不一定容易删除,但它更容易替换,也更容易改变。

§ 18

Step 7: Keep writing code

Being able to write new code without dealing with old code makes it far easier to experiment with new ideas. It isn’t so much that you should write microservices and not monoliths, but your system should be capable of supporting one or two experiments atop while you work out what you’re doing.

Feature flags are one way to change your mind later. Although feature flags are seen as ways to experiment with features, they allow you to deploy changes without re-deploying your software.

Google Chrome is a spectacular example of the benefits they bring. They found that the hardest part of keeping a regular release cycle, was the time it took to merge long lived feature branches in.

By being able to turn the new code on-and-off without recompiling, larger changes could be broken down into smaller merges without impacting existing code. With new features appearing earlier in the same code base, it made it more obvious when long running feature developement would impact other parts of the code.

A feature flag isn’t just a command line switch, it’s a way of decoupling feature releases from merging branches, and decoupling feature releases from deploying code. Being able to change your mind at runtime becomes increasingly important when it can take hours, days, or weeks to roll out new software. Ask any SRE: Any system that can wake you up at night is one worth being able to control at runtime.

第7步:持续写代码

能够在不处理旧代码的情况下写新代码,使得尝试新想法容易得多。并不是说你应该写微服务而不是单体,而是你的系统应该能够在你摸索要做什么的时候,在上面支持一两个实验。

功能标志是日后改变主意的一种方式。虽然功能标志被视为试验功能的方式,但它们允许你部署变更而无需重新部署软件。

Google Chrome 是它们带来的好处的一个壮观例子。他们发现,保持规律发布周期最难的部分,是合并长期存在的功能分支所需的时间。

通过能够在不重新编译的情况下打开和关闭新代码,更大的变更可以被分解成更小的合并,而不影响现有代码。由于新功能更早地出现在同一个代码库中,长期功能开发何时会影响代码的其他部分就变得更明显。

功能标志不仅仅是一个命令行开关,它是一种将功能发布与合并分支解耦的方式,也是将功能发布与部署代码解耦的方式。当推出新软件可能需要数小时、数天或数周时,能够在运行时改变主意变得越来越重要。问问任何 SRE:任何能在夜里把你吵醒的系统,都值得在运行时能够控制。

§ 19

It isn’t so much that you’re iterating, but you have a feedback loop. It is not so much you are building modules to re-use, but isolating components for change. Handling change is not just developing new features but getting rid of old ones too. Writing extensible code is hoping that in three months time, you got everything right. Writing code you can delete is working on the opposite assumption.

The strategies i’ve talked about — layering, isolation, common interfaces, composition — are not about writing good software, but how to build software that can change over time.

The management question, therefore, is not whether to build a pilot system and throw it away. You will do that. […] Hence plan to throw one away; you will, anyhow. Fred Brooks

You don’t need to throw it all away but you will need to delete some of it. Good code isn’t about getting it right the first time. Good code is just legacy code that doesn’t get in the way.

Good code is easy to delete.

与其说你在迭代,不如说你有一个反馈循环。与其说你构建模块是为了复用,不如说是为了变化而隔离组件。处理变化不仅仅是开发新功能,也包括摆脱旧功能。写可扩展的代码是希望三个月后,你一切做对了。写可以删除的代码则基于相反的假设。

我谈到的策略——分层、隔离、通用接口、组合——不是为了写出好软件,而是如何构建能随时间变化的软件。

因此,管理问题不是是否要构建一个试验系统然后扔掉。你会那样做的。[…] 因此计划扔掉一个;无论如何你都会这么做。Fred Brooks

你不需要把它全部扔掉,但你需要删除其中一些。好代码不是关于第一次就做对。好代码只是不会挡路的遗留代码。

好代码容易删除。

§ 20

Acknowledgments

Thank you to all of my proof readers for your time, patience, and effort.

致谢

感谢我所有的校对者,感谢你们的时间、耐心和努力。

§ 21

Further Reading

Layering/Decomposition

On the Criteria To Be Used in Decomposing Systems into Modules, D.L. Parnas.

How To Design A Good API and Why it Matters, J. Bloch.

The Little Manual of API Design, J. Blanchette.

Python for Humans, K. Reitz.

Common Interfaces

The Design of the MH Mail System, a Rand technical report.

The Styx Architecture for Distributed Systems

Your Server as a Function, M. Eriksen.

Feedback loops/Operations lifecycle

Chrome Release Cycle, A. Laforge.

Why Do Computers Stop and What Can Be Done About It?, J. Gray.

How Complex Systems Fail, R. I. Cook.

The technical is social before it is technical.

All Late Projects Are the Same, Software Engineering: An Idea Whose Time Has Come and Gone?, T. DeMarco.

Epigrams in Programming, A. Perlis.

How Do Committees Invent?, M.E. Conway.

The Tyranny of Structurelessness, J. Freeman

Other posts I’ve written about software.

(Added 2019-07-22)

Repeat yourself, do more than one thing, and rewrite everything.

How do you cut a monolith in half?

Write code that’s easy to delete, and easy to debug too.

进一步阅读

分层/分解

On the Criteria To Be Used in Decomposing Systems into Modules, D.L. Parnas.

How To Design A Good API and Why it Matters, J. Bloch.

The Little Manual of API Design, J. Blanchette.

Python for Humans, K. Reitz.

通用接口

The Design of the MH Mail System, a Rand technical report.

The Styx Architecture for Distributed Systems

Your Server as a Function, M. Eriksen.

反馈循环/运维生命周期

Chrome Release Cycle, A. Laforge.

Why Do Computers Stop and What Can Be Done About It?, J. Gray.

How Complex Systems Fail, R. I. Cook.

技术先于技术而是社会的。

All Late Projects Are the Same, Software Engineering: An Idea Whose Time Has Come and Gone?, T. DeMarco.

Epigrams in Programming, A. Perlis.

How Do Committees Invent?, M.E. Conway.

The Tyranny of Structurelessness, J. Freeman

我写的其他关于软件的文章。

(Added 2019-07-22)

Repeat yourself, do more than one thing, and rewrite everything.

How do you cut a monolith in half?

Write code that’s easy to delete, and easy to debug too.

§ 22

Contributed Translations

Пишите код, который легко удалять, а не дополнять.

要写易删除,而不是易扩展的代码.

확장하기 쉬운 코드가 아니라 삭제하기 쉬운 코드를 작성하자.

贡献翻译

Пишите код, который легко удалять, а не дополнять.

要写易删除,而不是易扩展的代码.

확장하기 쉬운 코드가 아니라 삭제하기 쉬운 코드를 작성하자.

打开原文 ↗