当前位置: 首页 > 科技观察

如何写出可读性高的代码?

时间:2023-03-15 09:01:13 科技观察

知道你的优先级代码可以用多种方式编写:有些运行速度快,有些占用内存少,有些更容易测试,有些可读性强。编写清晰代码的第一步是将可读性放在首位。这也意味着必须降低其他因素的优先级。如果所有的因素都被赋予了最高的优先级,那就意味着没有优先级。培养清晰的意识要想写出好的代码,首先要知道什么是好代码,要想写出清晰的代码,还必须明白什么是清晰的思维。阅读一些高质量的代码,可以让我们对好的代码有一个大概的认识。知道什么是好的代码不会阻止我们写出糟糕的代码,但它至少会让我们知道它有什么问题。在修改和编写代码时,我们最初的想法可能并不清晰。大多数情况下,我们只能在第一次完成代码后才能找到更合适的想法。重新阅读完成的代码会带来更改的空间。从解释入手如果还是想不通代码结构,可以想象一下怎么解释给别人听或者写下其中的逻辑,比如“如果账号被删除了,那么我们就需要跳过xxx。如果这个过程xxx还没结束,所以……”。然后把这套逻辑翻译成代码就很顺利了。在编写程序时,更容易引入人类通信的抽象而不是计算机的抽象。注释代码中的注释可以说明某段代码的用途,或者程序结构为什么要这样写。仅仅阅读程序并不能告诉我们作者认为的逻辑是正确的。可能有一些我们不理解的商业规则:美国以外的用户有时会在地址栏第一行的最后写上街道名称。那里可能还有一些技术技巧:以某种奇怪的方式构造查询以使Postgres正确优化它。这样的代码细节,只有了解了背后的逻辑背景,才能完全理解为什么要这样写。代码不会说话。如果我们决定跳过一些步骤,但懒得留下评论解释原因,请在两天后回到这段代码,没有人会真正知道你当时在想什么。有些代码可能要看两遍才能明白原因,但为了保险起见,不要给大脑增加不必要的负担。不要混淆层次不要混淆函数中的抽象层次。这个“欢迎”代码级别令人困惑:defwelcome(self):results=db.query('SELECTEXISTS1FROMemailsWHEREkind=?ANDuser=?','welcome_email',self.user.id,)ifresults[0]:returnsself.send_welcome_email()这段比较整洁:defwelcome(self):ifnotself.has_sent_welcome_email():self.send_welcome_email()函数中凌乱的抽象层次会迫使读者去思考代码的目的和实现方式。当前抽象层次的代码告诉我们代码在做什么,而下一层的代码告诉我们代码是如何做的。在例子中的“welcome”函数中,我们首先查看数据库中是否有之前的邮件记录,如果没有,则发送一封欢迎邮件。请注意,第二个版本中的“欢迎”功能将查询部分放到了另一个功能中,“欢迎”只关注“做什么”。这是为了让函数中的抽象层次保持在同一层级,逻辑也更加清晰。不同的功能分散在不同的抽象级别,将较低级别的实现细节委托给较低抽象级别的功能。分解函数有时将庞大的函数分解为子函数更具可读性。对于分步执行的函数,最好将函数中的每个步骤分解为子函数。对于决策等其他功能,不同的决策会导致不同的功能:有的部分负责决策,有的部分负责执行决策。分解一个函数的方法有很多种,只有不断地练习,才能一目了然地看穿哪一种是正确的。小函数有以下优点:逻辑的每一部分都有自己的函数名。知道每一块逻辑负责什么,我们就更容易找到这些函数应该放在作用域的什么地方更少的变量在运行堆栈跟踪和调试时,更清楚地看到函数做了什么小函数可以被隔离测试事实上,没有功能计算机可以正常运行,功能只是为程序员服务的,所以请利用它们。不要分解功能不要重复自己(DRY)的意思经常被过度解读。如今,提取幻数常量,以及某类决策的逻辑副本,已经被公认为标准答案。这样的重复代码真的很糟糕。对DRY的过度解读,就是面对两行重复的代码,犹如一个大敌,迫不及待想要快速摆脱它。完全避免任何重复代码意味着我们最终会得到一堆毫无意义、令人困惑的代码,这些代码的存在只是为了防止程序中出现两三行重复代码。再加上两段逻辑上不相关的代码被迫捆绑在一起,代码也更难修改。判断一段代码的重复是否可以容忍很简单:修改A段的代码,B段不变。如果程序报错,则将A和B组织成同一段代码;如果什么都没发生,那就别管它。DRY并不是说??我们需要手动压缩代码库,而是避免两段代码依赖手动同步。请记住,复制代码和创建抽象不是一回事。避免可配置的功能。最好有十个零参数的小函数,而不是一个有十个参数的函数。你一定很熟悉类似的东西:最初的clean函数,只在三个不同的地方被调用。而当我们要在第四位调用时,需要稍微调整一下,增加一个参数。但是这样一来,第一个调用者就有了一个新的函数,还需要增加两个可配置的参数。当涉及到第五个用例时,我们必须为其添加独特的参数,等等。但是反过来,我们会发现第二个调用者运行的太慢了,所以我们不得不再加一个参数来跳过一些繁琐的程序。在不知不觉中,我们整洁的、只能做一件事的函数现在有五个配置参数,现在甚至可以做事物的2的五次方!在这种情况下,整个复杂的功能拆分成子功能会好很多,每个功能只负责自己的事情。但这样一来,难免会有重复。当这些重复的部分需要保持同步的时候,我们可以利用DRY的思想,将相同的部分提取到子函数中。这时候做决定和考虑步骤就会容易很多。请记住,重复几行代码是可以的!像在不同列表上运行for循环这样的代码是可以接受的重复。这种方法的好处之一是,当其中一个用例被删除时,您可以轻松删除相应的功能,而不用深入挖掘复杂功能的逻辑试图找到相应的选项。只关注特定功能的读者也将更容易理解它们的用处。(请注意,只有当您可以对所有调用者负责时,这种方法才是正确的。如果您的功能只是公共API的一部分,那么请不要考虑使用这种方法。因为你不知道所有的用例是什么,也不知道未来的用例是什么)不要过早优化但这也是以软座为代价的赛车,低噪音和车载空调。如果我们的程序不需要做赛车,那么就不要过早地拆掉空调。逐渐熟悉程序的结构,从编写让人容易理解的代码开始,不要一上来就去挑战计算机的速度。出于同样的原因,人们不应该过早地开始概括。没有人会在不需要处理很多东西的情况下购买自卸卡车,而且当需求不多时,我们也不必提前编写额外的功能。