Write code that is easy to delete, not easy to extend.
编写易于删除、不易扩展的代码。
“Every line of code is written without reason, maintained out of weakness, and deleted by chance” Jean-Paul Sartre’s Programming in ANSI C.
“每一行代码都是毫无理由地编写的,出于弱点而维护,并且偶然被删除”让-保罗·萨特(Jean-Paul Sartre)的《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.
API 的使用者越多,您必须重写以引入更改的代码就越多。同样,你越依赖第三方API,当它发生变化时你遭受的损失就越大。管理代码如何组合在一起,或者哪些部分依赖于其他部分,是大型系统中的一个重要问题,并且随着项目的老化,它会变得更加困难。
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
我今天的观点是,如果我们想计算代码行数,我们不应该将它们视为“产生的行数”,而应将其视为“花费的行数” 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.
我不需要告诉你删除代码比编写代码更有趣。
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。拆分代码:将难以编写和可能更改的部分与代码的其余部分以及彼此隔离。不要对每个选择进行硬编码,也许允许在运行时更改一些选择。不要尝试同时做所有这些事情,也许一开始就不要编写这么多代码。
Step 0: Don’t write code
第 0 步:不要编写代码
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.
代码行数本身并不能告诉我们太多信息,但其数量级可以为 50、500、5,000、10,000、25,000 等。一百万行代码块将比一万行代码块更烦人,而且明显更多时间、金钱和精力来更换。
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.
即便如此,最容易删除的代码还是您一开始就避免编写的代码。
Step 1: Copy-paste code 第 1 步:复制粘贴代码
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.
最好复制粘贴代码几次,而不是创建一个库函数,只是为了了解如何使用它。一旦你将某些东西变成了共享 API,你就很难改变它了。
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.
删除函数内的代码比删除函数更简单。
Step 2: Don’t copy paste code
第 2 步:不要复制粘贴代码
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”.
当您复制并粘贴某些内容足够多次后,也许是时候将其拉到一个函数中了。这是“从我的标准库中拯救我”的东西:“打开一个配置文件并给我一个哈希表”,“删除这个目录”。这包括没有任何状态的函数,或具有一点全局知识(如环境变量)的函数。最终出现在名为“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.
另外:创建一个 util
目录并将不同的实用程序保存在不同的文件中。单个 util
文件总是会增长,直到它变得太大并且难以分割为止。使用单个 util
文件是不卫生的。
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.
代码对于您的应用程序或项目来说越不具体,它们就越容易重用,并且更改或删除的可能性就越小。库代码,例如日志记录、第三方 API、文件句柄或进程。您不会删除的其他很好的代码示例是列表、哈希表和其他集合。并不是因为它们通常具有非常简单的界面,而是因为它们的范围不会随着时间的推移而扩大。
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.
我们不是让代码变得易于删除,而是试图让难以删除的部分尽可能远离易于删除的部分。
Step 3: Write more boilerplate
第 3 步:编写更多样板文件
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.
您正在编写更多行代码,但您正在将这些代码行编写在易于删除的部分中。
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.
当图书馆需要满足所有人的需求时,样板文件效果最好,但有时重复太多。是时候用一个对策略、工作流程和状态有意见的库来包装您的灵活库了。构建简单易用的 API 就是将样板文件转变为库。
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.
这并不像您想象的那么罕见:最受欢迎和最受欢迎的 python http 客户端之一 requests
是提供更简单界面的成功示例,由底层更详细的使用库 urllib3
提供支持。 requests
迎合使用http时的常见工作流程,并向用户隐藏了许多实际细节。同时, urllib3
进行管道传输、连接管理,并且不会向用户隐藏任何内容。
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.
当我们将一个库包装在另一个库中时,并不是说我们隐藏了细节,而是我们分离了关注点: requests
是关于流行的 http 冒险, urllib3
是关于为您提供选择自己的冒险的工具。
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.
我并不提倡您出去创建 /protocol/
和 /policy/
目录,但您确实希望尝试使 util
目录摆脱业务逻辑,并在其之上构建更易于使用的库更容易实现的。您不必写完一个库才能开始在上面编写另一个库。
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.
包装第三方库通常也很好,即使它们不是协议式的。您可以构建适合您的代码的库,而不是在整个项目中锁定您的选择。构建一个易用的 API 和构建一个可扩展的 API 通常是相互矛盾的。
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.
这种关注点的分离使我们能够让一些用户满意,而不会让其他用户感到不可能。当您从一个好的 API 开始时,分层是最容易的,但是在一个坏的 API 之上编写一个好的 API 却非常困难。好的 API 是在设计时考虑到将使用它的程序员的感受,而分层意味着我们意识到我们无法立即取悦所有人。
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).
分层并不是要编写稍后可以删除的代码,而是要使难以删除的代码变得易于使用(不会被业务逻辑污染)。
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.
原因是什么?有时,删除一个大错误比尝试删除 18 个较小的交错错误更容易。很多编程都是探索性的,犯几次错误并迭代比第一次就做对要快。
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.
对于更有趣或更具创造性的努力尤其如此。如果您正在编写第一个游戏:不要编写引擎。同样,在编写应用程序之前不要编写 Web 框架。第一次去写得乱七八糟。除非你有通灵能力,否则你不会知道如何分解它。
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.
Monorepos 是一种类似的权衡:你不知道如何提前拆分你的代码,坦率地说,一个大错误比 20 个紧密耦合的错误更容易部署。
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.
当您知道哪些代码将很快被放弃、删除或轻松替换时,您可以走捷径。特别是如果您制作一次性客户网站、活动网页。任何有模板并印出副本的地方,或者填补框架留下的空白的地方。
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.
删除所有代码比分段删除更容易。
Step 6: Break your code into pieces
第 6 步:将代码分成几部分
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
[开始]列出困难的设计决策或可能改变的设计决策。每个模块都被设计为向其他模块隐藏这样的决定。 D·帕纳斯
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.
当一个模块做两件事时,通常是因为更改一个部分需要更改另一部分。拥有一个具有简单接口的糟糕组件通常比需要在它们之间进行仔细协调的两个组件更容易。
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.
例如,Microsoft Windows 为此目的提供了内部和外部 API。外部 API 与桌面程序的生命周期相关,内部 API 与底层内核相关。隐藏这些 API 为 Microsoft 提供了灵活性,而不会在此过程中破坏太多软件。
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 也有松散耦合的示例:将缓存放在 HTTP 服务器前面。将您的图像移动到 CDN,只需更改它们的链接即可。两者都不会破坏浏览器。
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.
HTTP 的错误代码是松散耦合的另一个例子:Web 服务器上的常见问题具有唯一的代码。当您收到 400 错误时,再次执行将得到相同的结果。 500可能会改变。因此,HTTP 客户端可以代表程序员处理许多错误。
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
我决定,不情愿地使用 L a T e X。在存在软件错误的情况下打造可靠的分布式系统。阿姆斯特朗,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.
Erlang/OTP 在如何选择处理故障方面相对独特:监督树。粗略地说,Erlang 系统中的每个进程都是由主管启动和监视的。当一个进程遇到问题时,它就会退出。当进程退出时,它会由主管重新启动。
(These supervisors are started by a bootstrap process, and when a supervisor encounters a fault, it is restarted by the bootstrap process)
(这些supervisor由bootstrap进程启动,当supervisor遇到故障时,由bootstrap进程重新启动)
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?
错误处理和恢复最好在代码库的外层完成。这就是所谓的端到端原则。端到端原则认为,处理连接远端的故障比处理中间任何地方的故障更容易。如果你内部有任何处理,你仍然需要做最后的顶层检查。如果顶层的每一层都必须处理错误,那么为什么还要在内部处理它们呢?
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.
错误处理是将系统紧密结合在一起的多种方式之一。还有许多其他紧耦合的例子,但单独挑出一个设计糟糕的例子有点不公平。 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.
在 IMAP 中,几乎每个操作都是雪花,具有独特的选项和处理。错误处理是痛苦的:错误可能在另一个操作的结果中途出现。
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.
IMAP 生成唯一的令牌来标识每条消息,而不是 UUID。这些也可能在操作结果中途发生变化。许多操作不是原子的。我们花了超过 25 年的时间才找到一种可靠的方法,将电子邮件从一个文件夹移动到另一个文件夹。有一种特殊的 UTF-7 编码,还有一种独特的 base64 编码。
I am not making any of this up.
这一切都不是我编造的。
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 配合使用的数据库会更容易。
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.
松散耦合的其他示例是具有中间件、过滤器和管道的其他系统。例如,Twitter 的 Finagle 使用通用的服务 API,这允许轻松地将通用超时处理、重试机制和身份验证检查添加到客户端和服务器代码中。
(I’m sure if I didn’t mention the UNIX pipeline here someone would complain at me)
(我确信如果我没有在这里提到 UNIX 管道,有人会抱怨我)
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.
松散耦合的代码不一定易于删除,但更容易替换,也更容易更改。
Step 7: Keep writing code
第 7 步:继续编写代码
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.
Google Chrome 是其带来的好处的一个很好的例子。他们发现保持定期发布周期最困难的部分是合并长期存在的功能分支所需的时间。
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.
功能标志不仅仅是一个命令行开关,它是一种将功能发布与合并分支解耦以及将功能发布与部署代码解耦的方法。当推出新软件可能需要数小时、数天或数周的时间时,能够在运行时改变主意变得越来越重要。询问任何 SRE:任何可以在晚上叫醒您的系统都值得在运行时进行控制。
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.
好的代码很容易删除。
Acknowledgments 致谢
Thank you to all of my proof readers for your time, patience, and effort.
感谢所有校对读者的时间、耐心和努力。
Further Reading 进一步阅读
Layering/Decomposition 分层/分解
On the Criteria To Be Used in Decomposing Systems into Modules, D.L. Parnas.
关于将系统分解为模块的标准,D.L.帕纳斯。
How To Design A Good API and Why it Matters, J. Bloch.
如何设计良好的 API 及其重要性,J. Bloch。
The Little Manual of
API Design, J. Blanchette.
API 设计小手册,J. Blanchette。
Python for Humans, K. Reitz.
《Python 之于人类》,K. Reitz。
Common Interfaces 通用接口
The Design of the MH Mail System, a Rand technical report.
MH 邮件系统的设计,兰德技术报告。
The Styx Architecture for Distributed Systems
分布式系统的 Styx 架构
Your Server as a Function, M. Eriksen.
你的服务器作为一种功能,M. Eriksen。
Feedback loops/Operations lifecycle
反馈循环/操作生命周期
Chrome Release Cycle, A. Laforge.
Chrome 发布周期,A. Laforge。
Why Do Computers Stop and What Can Be Done About It?, J. Gray.
为什么计算机会停止运行以及可以采取什么措施?,J. Gray。
How Complex Systems Fail, R. I. Cook.
复杂系统如何失败,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.
所有迟到的项目都是一样的,《软件工程:一个时代已经过去又过去的想法?》,T. DeMarco。
Epigrams in Programming, A. Perlis.
编程警句,A. Perlis。
How Do Committees Invent?, M.E. Conway.
委员会如何发明?,M.E.康威。
The Tyranny of Structurelessness, J. Freeman
无结构的暴政,J.弗里曼
Other posts I’ve written about software.
我写过关于软件的其他帖子。
(Added 2019-07-22) (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.
编写易于删除且易于调试的代码。
Contributed Translations 贡献翻译
Пишите код, который легко удалять, а не дополнять.
编写易于删除、不易添加的代码。
확장하기 쉬운 코드가 아니라 삭제하기 쉬운 코드를 작성하자.
编写易于删除的代码,而不是易于扩展的代码。