如何编写可读代码

2021-02-06 20:16:09

这些是我在尝试编写简洁易读的代码时所考虑的事情。

有许多方法可以编写任何代码。有些会运行得更快,有些会占用更少的内存,有些会更易于测试。有些会更清楚。

这意味着您必须降低其他方面的优先级,例如速度。没有优先事项而不是其他优先事项(当一切都优先时,什么都没有)。

写得好需要知道好的写作是什么样的,而创建清晰的代码则需要知道清晰的代码是什么样的。阅读备受好评的代码可以使您对好的外观有所了解。

对于清晰的代码,良好的理解不会阻止您编写难以理解的代码,但会告诉您哪些部分闻起来不正确。

关于如何编写代码的第一个想法很少会是最清楚的。

在完成写下第一个版本的思想工作之后,通常更容易找到一种易于编写代码的方式。重新阅读您刚刚写的内容将有助于提出改进建议。

如果您不确定如何组织代码,请先向别人(或橡皮鸭)讲解,然后再进行说明。写下来:“好吧,如果删除了用户,或者订单已经在处理中,我们需要跳过它……”进行解释并将其转换为代码。

在布置代码时,最好是在人际交流方面进行思考,而不要在计算机抽象方面进行思考。

添加注释,以解释代码为何执行其正在执行的操作或以其结构化方式进行结构化。

仅阅读逻辑并不能告诉您为什么作者认为这是正确的逻辑。您可能不知道某些业务原因-也许美国境外的用户有时将街道号码放在地址第一行的末尾。也许有一些技术细节-这个查询以这种怪异的方式构造,以说服Postgres正确地优化它。这些是代码本身不存在的附加上下文。

如果代码不存在,则无法自我记录。如果您决定不编写任何代码,并且不留下任何解释原因的注释,那么您将什么都没有解释。

即使仅通过阅读代码就可以理解其原因,也可以很容易地避免进行艰苦的脑力劳动。

混合的抽象级别使读者可以在思考正在做的事情和如何实现它之间进行选择。

当您谈论代码做什么时,您所谈论的是当前的抽象级别。当您谈论代码的工作方式时,您正在谈论的是抽象的下一层。

在welcome方法中,它的作用是发送欢迎电子邮件(如果尚未发送)。如何确定是否已发送电子邮件是要查询过去电子邮件记录的数据库。请注意,第二版的welcome将“如何”转移到一个单独的方法。它只关心“什么”,这意味着它停留在一个抽象层次上。

使每个函数处于一个抽象级别,并将较低级别的细节委派给较低抽象级别的方法。具有单一抽象级别的方法往往读起来就像是关于正在发生的事情的故事。

通过将大型函数分解为较小的函数,可以使它们(有时!)更具可读性。

有时,该函数的作用类似于一系列步骤,在这种情况下,可以很好地为每个步骤提取一个函数。在其他时候,有不同的决定要做出,每个决定都可以在不同的功能中做出。也许功能的某些部分像决策一样起作用,而某些部分像采取行动一样起作用。您可以使用许多不同的维度来分解功能。需要练习才能善于看到正确的使用方法。

逻辑的每一位都有一个名称。这使您更容易知道逻辑的各个方面是什么,并帮助您找到逻辑的所在。

当您查看堆栈跟踪或运行调试器时,很容易分辨出程序在想什么。

计算机可以正常工作,完全没有任何功能。为了程序员的存在而存在函数,因此请充分利用它们。

现在,将魔术数提取为常数并获得用于做出特定决策的逻辑副本是一个很好的主意。重复这些代码是个坏主意。

当碰巧共享少量行的两个功能成为重复数据删除的目标时,DRY开始走得太远。完全避免重复的行意味着您最终将得到混乱的,无意义的抽象,这些抽象仅用于容纳那几条共享行。这使得代码难以更改,因为两个不相关的代码片段的结构将被捆绑在一起。

是否对某些代码进行重复数据删除的测试很简单:如果更改了一个代码而不更改另一个代码,会发生什么不好的事情?如果答案是肯定的,则为其提供唯一的真理来源。如果没有,请考虑不理会它。

DRY的目的不是在代码库上运行手动压缩过程,而是避免依赖关系,在该依赖关系中需要手动保持两部分代码同步。请记住,重复数据删除与创建抽象不是一回事。

我敢肯定,您已经看过这样的故事:您从一个在三个不同地方调用的干净函数开始。您想在第四位使用它,但是它需要做一些稍有不同的事情,因此您添加了一个配置参数。然后,第一个调用者获得一项新功能,需要另外两个配置参数。第五个用例将添加其自己的特殊参数。呼叫者2的速度太慢,因此您添加了另一个参数来跳过部分工作。

不知何故,您一开始就做一件事情的干净函数现在有5个配置参数,并且可能执行2 ^ 5 = 32种不同的事情(或更多)!

最好有多个功能,每个功能都只做一件事。

一旦有了单独的功能,当然会有重复。当这些共享部分需要保持同步时,请应用DRY并将其提取到共享功能中。如果该功能已细分为决策和步骤子功能,则这会更容易。

记住,几行重复就可以了!如果每个单独的函数在列表中都有其自己的for循环,则这是非常可以接受的重复。

这种方法的一个优势是,当一个用例消失时,您可以轻松删除相关功能。您无需深入研究复杂功能的逻辑即可弄清楚用于该特定选项集的部分。

专用功能的读者会发现,它更容易理解其功能。

(请注意,只有控制了函数的所有调用者,这才是正确的方法。如果函数是公共API的一部分,则此处的理由并不适用,因为您不知道所有用例是什么或将是什么用例是。)

赛车的行驶速度比普通汽车要快,但是要牺牲硬座,发出很多噪音和缺少空调的代价。如果您不知道自己的功能将需要成为赛车,请不要剥离空调。让动物安心-专注于编写易于阅读的代码,而不是易于计算机运行的代码。

过早的概括也是如此。 如果您不需要拖运大量的东西,则不会购买自卸车,因此也不应使您的代码能够满足可能永远不会发生的各种需求。