好代码的六条戒律:编写经得起时间考验的代码
/**
* 谨献给Yoyo
*
* 原文出处:https://www.toptal.com/software/six-commandments-of-good-code
* @author dogstar.huang <chanzonghuang@gmail.com> 2016-10-16
*/
人类奋斗在计算机编程的艺术和科学大约只有半个世纪。与大多数艺术和科学相比,计算机科学在许多方面仍然只是一个小孩,走路会撞墙,给自己的脚绊倒,偶尔在桌子上扔食物。作为其相对年轻的结果,我不认为我们对什么是“好代码”的正确定义有所共识,因为该定义继续在演进。有些人会说“好代码”是具有100%测试覆盖率的代码。其他人会说,它超级快,有一个杀手级的性能,并能兼容运行在10年前的硬件上。虽然这些都是软件开发人员值得称赞的目标,但我尝试抛出另一个混合的目标:可维护性。具体来说,“良好的代码”是一个组织(而不仅仅是它的作者!)容易且易于维护的代码,并且活得会比编写它所用的sprint还要长。以下是我在职业生涯中,任职在美国或国外,在大公司或小公司,作为一名工程师发现的一些东西,这些似乎与可维护,“好”软件相关联。
永远不要满足于只是“工作”的代码。而要编写高级的代码。
诫律#1:以你希望其他代码对待你的方式对待你的代码
我远非第一个人写道,你的代码的主要受众不是编译器/计算机,而是下一个必须阅读、理解、维护和增强这些代码的人(不一定是6个月后的你)。任何对得起他们工资的工程师都可以产出“工作”的代码; 一个卓越的工程师的区别是,他们可以高效地编写可维护的代码,以从长远上来支持业务,并且有能力以简单,清晰和可维护的方式解决问题。
在任何编程语言中,都有可能写出好的代码或坏的代码。 假设我们通过它如何促进编写好的代码(它至少应该是顶级标准之一)来评判一个编程语言,取决于它如何被使用(或滥用),任何编程语言都可以是“好”或“坏”。
许多人认为“干净”和可读的语言的一个例子是Python。该语言本身强制实施一定程度的空格规则,内置的API是丰富且相当一致的。也就是说,有可能创造难以形容的怪物。例如,可以定义一个类,并在运行时定义/重定义/未定义该类上的任何和所有方法(通常称为猴子补丁)。这种技术自然充其量会导致不一致的API,最坏的情况是不能调试的怪物。有人可能天真地认为,“确实,但没有人这样做!”不幸的是,还真的有人这么做,在你遇到大量(和流行的!)广泛使用(滥用)猴子补丁作为他们API的核心的类库之前,浏览pypi不需要花很长时间。我最近使用了一个网络库,其整个API根据对象的网络状态而改变。想象一下,例如,调用client.connect()
,有时会得到一个MethodDoesNotExist
错误,而不是HostNotFound
或NetworkUnavailable
。
诫律#2:好的代码容易阅读和理解,不管是局部还是整体上
好的代码容易阅读和理解,不管是局部还是整体上,有些人这样说(包括未来的作者,试图避免“我真的写过这个吗?”综合症)。
对于“部分”,我的意思是,如果我在代码中打开一些模块或函数,我应该能够理解它的作用,而不必阅读其余全部的代码库。它应该尽可能直观且自文档(self-documenting)。
不断从其他(似乎不相关)代码库的部分引用影响行为的细节的代码,就像阅读一本书,你必须在每个句子结尾处引用脚注或附录。你永远不会到达第一页!
关于“本地”可读性的一些其他想法:
- 良好封装的代码往往更可读,在各个层级上分离关注点。
- 名称很重要。激活思考快与慢,2种系统方式,是大脑形成思想并把一些实际的、仔细的想法转换成变量和方法名称。少数额外的秒数可以产生重要的红利。一个命名良好的变量可以使代码更直观,而命名不当的变量可能导致头痛和混乱。
- 聪明是敌人。 当使用花哨的技术,范例或操作(如列表推导或三元运算符)时,请谨慎使用它们,要使代码更易于阅读,而不仅仅是更短。
- 一致性是件好事。在样式上的一致性,包括如何放置括号方面和在操作方面,都能大大提高可读性。
- 关注点分离。 一个给定的项目在代码库的各个点管理着无数本地重要的假设。代码库的每一部分暴露尽可能少的关注。 假设您有一个人员管理系统,其中人物对象有时可能具有无效的姓氏。 对于在显示人物对象的页面中编写代码的人,这可能真的很尴尬! 除非你维护一份手册:“尴尬和非明显的假设我们的代码库有”(我知道我不会)你的显示页面上程序员不知道姓氏可以为null,可能要写一个空指针,当出现姓氏为空的情况时会出现异常。 而通过仔细考虑的API和契约来处理这些情况,您的代码库的不同部分可以互相交互。
诫律#3:好的代码有一个好的思想布局和架构,使得管理的状态显而易见
状态是敌人。 为什么? 因为它是任何应用程序中最复杂的一部分,需要非常刻意和周详地处理。常见的问题包括数据库不一致,新数据没有在任何地方得到反映的部分UI更新,无序操作,或者只是带有if语句和无处不在的分支的复杂代码而导致难以阅读,甚至难以维护代码。将状态置于基座上,需要非常仔细地处理,并且对状态如何被访问和修改非常一致和谨慎,大大简化了您的代码库。 一些语言(例如Haskell)在语义和语法层面上强制执行这些。如果你有不访问外部状态的纯函数库,你会对代码库的清晰度改善如此之多感觉非常惊讶,然后是一个引用外部纯函数的有状态代码的小表面区域。
诫律#4:好的代码不会重塑轮子,而是站在巨人的肩膀上
在即将重新发明轮子之前,想想你要解决的问题,或者想要执行的功能是多么常见。有人可能已经实现了一个可以利用的解决方案。花些时间思考和调研任何这样的选择,看下是否适合或可用。
也就是说,一个完全合理的反论点是,依赖不是来自“免费”而没有任何缺点。通过使用第三方或开源库以便添加了一些有趣功能的,你将承诺并成为依赖于该库。这是一个很大的承诺; 如果它是一个巨大的类库,而你只需要其中一小部分功能,你真的想要背负一升级就更新整个类库的负担吗,例如,Python 3.x? 此外,如果你遇到缺陷或想要增强功能,你或者依赖于作者(或供应商)提供修复或增强,又或者,如果它是开源的,要找到自己探索一个你完全不熟悉却要尝试修复或修改一个晦涩的功能的(潜在大量的)代码库的位置。
当然了,你所依赖的代码使用得越好,需要投资给维护的时间就越少。总而言之,亲自去做调研、做出自己对是否包含外部技术和特定技术会增加多少维护工作量的评估是值得的。
以下是一些非常通用的东西,这些不应该在你的项目(除非这些就是你的项目)中、在现代中重复发明。
数据库
找出你的项目需要哪些CAP,然后选择有恰当属性的数据库。数据库不再单单是指MySQL,你还可以选择:
- “传统”模式的SQL:Postgres / MySQL / MariaDB / MemSQL / Amazon RDS等
- 键值存储:Redis / Memcache / Riak
- NoSQL:MongoDB/Cassandra
- 托管数据库:AWS RDS / DynamoDB / AppEngine Datastore
- 海量数据(Heavy lifting): Amazon MR / Hadoop (Hive/Pig) / Cloudera / Google Big Query
- 疯狂的东东(Crazy stuff):Erlang’s Mnesia, iOS’s Core Data
数据抽象层
在大多数情况下,你不应该编写任何你所选择使用的数据库的纪录查询。更有可能的是,已经有那么一个类库,坐在这个数据库和你的应用代码之间,分离了管理具体数据库会话和模式细节,与你的主要代码之间的关注点。至少,你不应该让纪录查询和SQL内嵌在你的应用代码中间。相反,把它封装到一个函数并且集中全部函数到一个叫起来意图很明显的文件里(例如:“queries.py”)。例如,像users = load_users()
这样的一行代码,要远比users = db.query(“SELECT username, foo, bar from users LIMIT 10 ORDER BY ID”)
更容易阅读。集中化的类型也使得在你的查询中保持一致性、限制随着模式变化而变化查询的地方的个数更为容易。
其他通用类库以及可考虑利用的工具
- 队列或发布/订阅服务。你可随意挑选AMQP providers, ZeroMQ, RabbitMQ或Amazon SQS
- 存储。Amazon S3,谷歌云存储(Google Cloud Storage)
- 监控:Graphite/Hosted Graphite,AWS Cloud Watch, New Relic
自伸缩性(Auto Scaling)
- 自伸缩性。Heroku, AWS Beanstalk, AppEngine, AWS Opsworks, Digital Ocean
戒律#5:不要乱来!
目前已经有很多很好的编程设计模型,例如发布/订阅,参与者,MVC等。选一个你最喜爱的,然后坚持用它。代码库里处理不同数据的不同逻辑类型应该在物理上是隔离的(再一次,此关注点分离的概念减少了未来阅读者的认知负担)。例如,更新UI的代码应该和计算哪些需要在UI上展示的代码,在物理上是分开的。
戒律6:尽可能主电脑来干活
如果编译器可以捕捉代码的逻辑错误,并且可以防止糟糕的行为、bug,或者彻底的崩溃,我们绝对应该利用起来。当然,有些语言的编译器比其他语言做起来更加容易。例如,Haskell有一个著名严格的编译器,从而导致程序员尽了很大的努力才能使得编写的代码通过编译。一旦它编译通过,“它就能工作了”。对于那些从来没有用强类型函数式语言写过代码的人来说,这看起来是荒谬的或者是不可能的,但不!要![轻(https://news.ycombinator.com/item?id=8392945)]!信!我的话。真的,点击上面这些链接,绝对有可能活在一个没有运行时错误的世界里。真的就是那么神奇。
诚然,并不是每个语言都会让它自己做那么多(或者在某些语言里一点都没有!)编译期间的检查。对于那些没有检查的,花几分钟点调研一下有哪些可选的严格检测可用在你的项目中,并且评估它是否对你有意义。这里列出了一个简短的、非全面的列表,其中包含了我最近在宽松运行时为语言进行检测的一些通用工具:
结论
对于拥有一份产出“好”(例如,维护简单)代码,这绝称不上是详尽无遗的清单。那就是说,假设将来每一个我负责的代码库至少遵循了这份清单一半以上的概念的话,我可能会有更少灰头发并且可能可以延长5年的寿命。当然,我也可以找到更多乐趣、更少压力的工作。
------------------------
本作品采用知识共享署名-非商业性使用-相同方式共享 3.0 未本地化版本许可协议进行许可。
- 本文翻译作者为:dogstar,发表于艾翻译(itran.cc);欢迎转载,但请注明出处,谢谢!