好的代码很容易删除!

18-12-24 banq
                   

编程是从浪费生命中学到的可怕教训,编写易于删除但不易扩展的代码。

“每一行代码都是在没有理由的情况下编写的,有自己的弱点,并且偶然间会被删除” Jean-Paul Sartre的ANSI C编程。

编写的每一行代码都需要付出代价:维护。为避免大量代码成本消耗,我们构建了可重用的软件,代码重用的问题在于它会妨碍以后改变主意

您拥有的API消费者越多,您引入更改时必须重写的代码越多。同样,您依赖第三方api的次数越多,您在更改时就会受到的影响越大。管理代码如何组合在一起,或哪些部分依赖于其他部分,是大型系统中的一个重要问题,随着项目的老化,它会变得更加困难。

如果我们将“代码行”看作“每行的付出花费与成本”,那么当我们删除代码行时,我们就会降低维护成本。我们应该尝试构建一次性软件,而不是构建可重用的软件。

我不需要告诉你删除代码比编写代码更有趣。

编写易于删除的代码:重复自己以避免创建依赖关系,但不要重复自己来管理它们。

对代码进行分层:从简单易用但笨拙的部分构建简单易用的API。拆分代码:将难以编写和可能更改的部分与其余代码隔离开来,并相互隔离。不要硬编码每个选择,也许允许在运行时更改一些。但是不要试图同时做所有这些事情,也许不要在第一时间编写这么多代码。

第0步:不要编写代码

最容易删除的代码是您首先应该避免编写的代码,只编写重要的!

...

第1步:复制粘贴代码

构建可重用代码是事后更容易做到的事情,在代码库中使用了几个例子,而不是你以后可能想要的远见。从好的方面来说,你可能只是使用文件系统重新使用了很多代码,为什么要担心这么多?一点点冗余是健康的。

最好复制粘贴代码几次,而不是创建库函数,只是为了掌握如何使用它。一旦你创建了一个共享API,你就会变得更难。

调用您的函数的代码将依赖于其背后的实现的有意和无意行为。使用您的功能的程序员将不依赖于您记录的内容,而是他们观察到的内容。

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

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

当你复制和粘贴足够的东西时,也许是时候把它拉到一个功能了。

代码相对于某个应用程序或项目的具体细节程度越低,重用越容易,更改或删除的可能性就越小。

您不会删除的代码的好例子是列表,哈希表和其他集合。不是因为它们通常具有非常简单的接口,而是因为它们随着时间的推移不会增加范围。

第3步:编写更多样板文件

编写库可避免复制粘贴,但我们经常最终通过复制粘贴编写更多代码来使用它们,我们给它一个不同的名称:样板文件。它很像复制粘贴,不同的是每次粘贴到不同的地方时会更改一些代码。

第4步:不要编写样板

构建简单易用的API就是将样板文件转换为库。

构建令人愉快的API并构建可扩展的API通常彼此不一致。这种关注点的分离使我们能够让一些用户满意,而不会让其他用户满意。

当你开始使用一个好的API时,分层是最容易的,但是在一个糟糕的API之上编写一个好的API是非常困难的。良好的API设计时会对使用它的程序员表示同情,分层意识到我们不能立刻取悦所有人。

分层不是关于编写我们以后可以删除的代码,而是使用难以删除的代码(不会因为业务逻辑而污染它)。

第5步:编写一大堆代码

对于更有趣或更具创造性的努力尤其如此。如果您正在编写你的第一个游戏:不要编写引擎。同样,在编写应用程序之前不要编写Web框架。第一次去写一个烂摊子。除非你是通灵者,否则你不会知道如何将它分开。

Monorepos是一个类似的权衡:你不会知道如何提前拆分代码,坦率地说,一个大错误比20个紧密耦合错误更容易部署。有时删除一个大错误比尝试删除18个较小的交错错误更容易。

成为一名专业软件开发人员是不断积累一系列遗憾和错误。而不是说可以从成功中学到什么(banq注:因为你从来没有成功过,这个和你在学校中考试100分不断成功的积累是不同的)。并不是你知道了什么是好的代码,而是糟糕的代码造成伤痕在你的脑海中是新鲜的。(banq注:跌了跟头知道好好走路)

无论如何,项目最终都会失败或成为遗留代码。写十个大便代码到时擦除起来还不如一次性擦一块粪便更快更方便。

删除所有代码比分段删除它更容易。

第6步:将代码分解成碎片

大泥球是最容易建造的,但维护成本最高。(banq注:泥球的意思是所有泥土都粘在一起,代指依赖关系,蜘蛛网的依赖)。原理可以整体上更容易一次性删除的东西现在不可能分段删除。(因为都粘在一起了)

我们将代码分层以分离职责,从特定平台到特定领域,我们需要找到一种方法来分离逻辑。

虽然单一责任原则暗示“每个模块应该只处理一个难题”,但更重要的是“每个难题只由一个模块处理”

当模块做两件事时,通常是因为改变一部分需要改变另一部分。拥有一个简单接口的可怕组件通常比两个需要仔细协调的组件更容易。

一个你可以删除部分同时不需要不重写其他部分的系统通常才被称为松散耦合。

即使对变量进行一次硬编码也可以是松耦合,或者在变量上使用命令行标志。松耦合是指能够在不改变太多代码的情况下改变主意。

HTTP也有松耦合的例子:在HTTP服务器前放置一个缓存。将图像移动到CDN,只需更改指向它们的链接即可。没有打破浏览器。

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

在将软件分解为较小的部分时,必须考虑软件如何处理故障。这样做说起来容易做起来难。

Erlang / OTP在选择处理故障方面相对独特:监督树。粗略地说,Erlang系统中的每个进程都由一个主管启动并观察。当进程遇到问题时,它会退出。当进程退出时,由主管重新启动。(这些管理程序由引导程序启动,当主管遇到故障时,它由引导程序重新启动)

关键的想法是,它快速失败并重新启动比处理错误更快。像这样的错误处理可能看起来是违反直觉的,通过在错误发生时放弃来获得可靠性,但是再次关闭事物具有抑制瞬态故障的诀窍。

错误处理和恢复最好在代码库的外层完成。这被称为端到端原则。端到端原则认为,在连接的远端处理故障比在中间的任何地方更容易。如果你有任何处理,你仍然需要做最后的顶级检查。如果每个层都必须处理错误,那么为什么还要在内部处理它们?

文件系统和数据库都是远程存储的更好例子。使用文件系统,您可以使用一组固定的操作,但可以操作多个对象。

尽管SQL看起来像是一个比文件系统更广泛的接口,但它遵循相同的模式。集合上的许多操作以及要操作的大量行。虽然您不能总是将一个数据库替换为另一个数据库,但通过任何自制查询语言更容易找到适用于SQL的数据库。

松散耦合的其他示例是具有中间件或过滤器和管道的其他系统。例如,Twitter的Finagle使用通用API进行服务,这允许通用超时处理,重试机制和身份验证检查毫不费力地添加到客户端和服务器代码中。

首先,我们对代码进行分层,但现在其中一些层共享一个接口:一组通用的行为和操作,具有各种实现。松散耦合的良好示例通常是均匀接口的示例。

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

第7步:继续编写代码

能够在不处理旧代码的情况下编写新代码,可以更轻松地尝试新想法。

谷歌浏览器是他们带来的好处的一个壮观的例子。他们发现,保持常规发布周期最困难的部分是合并长期存在的功能分支所花费的时间。

设置功能标志是以后改变主意的一种方法。虽然功能标记被视为实验功能的方法,但它们允许您在不重新部署软件的情况下部署更改。

通过能够在不重新编译的情况下打开和关闭新代码,可以将更大的更改分解为更小的合并,而不会影响现有代码。由于新功能在同一代码库中出现在前面,因此当长时间运行的功能开发会影响代码的其他部分时,它会更加明显。

功能标志不仅仅是命令行开关,它还可以将功能版本与合并分支分离,并将功能版本与部署代码分离。能够在运行时改变主意变得越来越重要,因为推出新软件可能需要数小时,数天或数周。询问任何SRE:任何可以在夜间叫醒你的系统都是值得在运行时控制的系统。

你不是在迭代,而是有一个反馈循环。您正在构建可重复使用的模块,而不是将组件隔离以进行更改。处理变更不仅仅是开发新功能,而是摆脱旧功能。

我所讨论的策略 - 分层,隔离,通用接口,组合 - 不是要编写好的软件,而是如何构建可以随时间变化的软件。

好的代码不是第一次就做好。好的代码只是不会妨碍遗留代码。

好的代码很容易删除。