好代码的六诫:编写经得起时间考验的代码
已发表: 2022-03-11大约半个世纪以来,人类一直在努力研究计算机编程的艺术和科学。 与大多数艺术和科学相比,计算机科学在许多方面仍然只是一个蹒跚学步的孩子,会走进墙壁,绊倒自己的脚,偶尔还会把食物扔到桌子上。
由于其相对年轻,我认为我们尚未就“好代码”的正确定义达成共识,因为该定义仍在不断发展。 有人会说“好代码”是具有 100% 测试覆盖率的代码。 其他人会说它速度超快,性能出色,并且可以在使用 10 年的硬件上正常运行。
虽然这些对于软件开发人员来说都是值得称赞的目标,但是我冒险将另一个目标加入其中:可维护性。 具体来说,“好代码”是组织(不仅仅是其作者!)易于维护的代码,并且比编写它的 sprint 寿命更长。以下是我在我的文章中发现的一些内容在美国和国外的大公司和小公司担任工程师的职业生涯似乎与可维护的“好”软件相关。
戒律#1:以你希望别人的代码对待你的方式对待你的代码
我远不是第一个写下代码的主要受众不是编译器/计算机的人,而是下一个必须阅读、理解、维护和增强代码的人(从现在起六个月后不一定是你)。 任何值得他们付出的工程师都可以生成“有效”的代码; 优秀工程师的不同之处在于,他们可以高效地编写可维护的代码,以长期支持业务,并具有以简单、清晰和可维护的方式解决问题的技能。
在任何编程语言中,都可能编写好代码或坏代码。 假设我们根据编程语言编写好代码的程度来判断编程语言(无论如何,它至少应该是最重要的标准之一),任何编程语言都可以是“好”或“坏”,具体取决于它的使用方式(或滥用方式) )。
许多人认为“干净”且可读的语言的一个例子是 Python。 该语言本身强制执行某种程度的空白规则,并且内置的 API 丰富且相当一致。 也就是说,有可能创造出无法形容的怪物。 例如,可以定义一个类并在运行时定义/重新定义/取消定义该类上的任何方法(通常称为猴子补丁)。 这种技术自然会导致最好的 API 不一致,最坏的情况是无法调试怪物。 人们可能会天真地认为,“当然,但没有人这样做!” 不幸的是,他们这样做了,并且在您遇到大量(并且流行!)库之前浏览 pypi 并不需要很长时间,这些库(ab)广泛使用猴子补丁作为其 API 的核心。 我最近使用了一个网络库,它的整个 API 会根据对象的网络状态而变化。 例如,想象一下,调用client.connect()
有时会收到MethodDoesNotExist
错误,而不是HostNotFound
或NetworkUnavailable
。
戒律#2:好的代码很容易部分和整体地阅读和理解
好的代码很容易被其他人部分和全部阅读和理解(以及未来的作者,试图避免“我真的写了那个吗?”综合症)。
“部分”我的意思是,如果我在代码中打开一些模块或函数,我应该能够理解它的作用,而不必同时阅读整个代码库的其余部分。 它应该尽可能直观和自我记录。
不断引用影响代码库其他(看似不相关)部分行为的微小细节的代码就像阅读一本书,您必须在每个句子的末尾引用脚注或附录。 你永远不会通过第一页!
关于“本地”可读性的其他一些想法:
封装良好的代码往往更具可读性,在每个级别上分离关注点。
名字很重要。 激活快速思考和慢速思考的系统 2 方式,大脑在这种方式中形成想法,并将一些实际的、仔细的想法放入变量和方法名称中。 额外的几秒钟可以带来可观的红利。 一个命名良好的变量可以使代码更加直观,而一个命名不佳的变量可能会导致虚假和混乱。
聪明是敌人。 在使用花哨的技术、范式或操作(例如列表推导或三元运算符)时,请注意以使代码更具可读性而不是更短的方式使用它们。
一致性是一件好事。 风格的一致性,无论是在如何放置大括号方面,还是在操作方面,都大大提高了可读性。
关注点分离。 一个给定的项目在代码库的不同点管理着无数的本地重要假设。 尽可能少地将代码库的每个部分暴露给这些问题。 假设您有一个人员管理系统,其中人员对象有时可能有一个空姓氏。 对于在显示人员对象的页面中编写代码的人来说,这可能真的很尴尬! 除非您维护一本“我们的代码库具有的尴尬且不明显的假设”的手册(我知道我不知道),否则您的显示页面程序员不会知道姓氏可以为空,并且可能会使用空指针编写代码当最后一个名字为空的情况出现时,它会出现异常。 而是使用经过深思熟虑的 API 和合约来处理这些情况,您的代码库的不同部分使用这些 API 和合约来相互交互。
戒律#3:好的代码有一个深思熟虑的布局和架构,使管理状态变得显而易见
国家是敌人。 为什么? 因为它是任何应用程序中最复杂的部分,需要非常慎重和深思熟虑地处理。 常见问题包括数据库不一致、部分 UI 更新(其中新数据未在任何地方反映)、无序操作,或者只是在意复杂的代码,到处都有 if 语句和分支,导致代码难以阅读,甚至更难维护。 将状态放在一个需要非常小心处理的基础上,并且在如何访问和修改状态方面非常一致和慎重,可以极大地简化您的代码库。 某些语言(例如 Haskell)在程序和句法级别强制执行此操作。 如果您拥有不访问外部状态的纯函数库以及引用外部纯功能的有状态代码的小表面区域,您会惊讶于代码库的清晰度可以提高多少。

戒律#4:好的代码不会重新发明轮子,它站在巨人的肩膀上
在可能重新发明轮子之前,请考虑一下您要解决的问题有多普遍,或者您要执行的功能有多普遍。 有人可能已经实施了您可以利用的解决方案。 如果合适且可用,请花时间考虑和研究任何此类选项。
也就是说,一个完全合理的反驳观点是,依赖并不是“免费”的,没有任何缺点。 通过使用添加了一些有趣功能的第 3 方或开源库,您将致力于并依赖于该库。 这是一个很大的承诺。 如果它是一个巨大的库,并且您只需要一点点功能,如果您升级到 Python 3.x,您真的想要更新整个库的负担吗? 此外,如果您遇到错误或想要增强功能,您要么依赖作者(或供应商)提供修复或增强功能,要么,如果它是开源的,则发现自己处于探索(潜在的大量)代码库您完全不熟悉尝试修复或修改一些晦涩的功能。
当然,您所依赖的代码使用得越好,您自己花费时间进行维护的可能性就越小。 最重要的是,您自己进行研究并自行评估是否包含外部技术以及该特定技术将为您的堆栈添加多少维护是值得的。
以下是一些您可能不应该在现代项目中重新发明的更常见的示例(除非这些是您的项目)。
数据库
找出您的项目需要哪个 CAP,然后选择具有正确属性的数据库。 数据库不再仅仅意味着 MySQL,您可以选择:
- “传统”架构 SQL: Postgres / MySQL / MariaDB / MemSQL / Amazon RDS 等。
- 键值存储: Redis / Memcache / Riak
- NoSQL: MongoDB/Cassandra
- 托管数据库: AWS RDS / DynamoDB / AppEngine 数据存储
- 繁重的工作: Amazon MR / Hadoop (Hive/Pig) / Cloudera / Google Big Query
- 疯狂的东西: Erlang 的 Mnesia,iOS 的 Core Data
数据抽象层
在大多数情况下,您不应该将原始查询写入您碰巧选择使用的任何数据库。 更有可能的是,在数据库和应用程序代码之间存在一个库,将管理并发数据库会话和架构细节的关注点与主代码分开。 至少,您的应用程序代码中不应该有原始查询或 SQL 内联。 相反,将它包装在一个函数中,并将所有函数集中在一个文件中,该文件称为非常明显的文件(例如,“queries.py”)。 例如,像users = load_users()
这样的行比users = db.query(“SELECT username, foo, bar from users LIMIT 10 ORDER BY ID”)
更容易阅读。 这种类型的集中化还使得在查询中保持一致的样式变得更加容易,并限制了在架构更改时更改查询的位置数量。
考虑利用的其他常见库和工具
- 排队或发布/订阅服务。 选择 AMQP 提供商、ZeroMQ、RabbitMQ、Amazon SQS
- 贮存。 亚马逊 S3、谷歌云存储
- 监控: Graphite/Hosted Graphite、AWS Cloud Watch、New Relic
- 日志收集/聚合。 日志,Splunk
自动缩放
- 自动缩放。 Heroku、AWS Beanstalk、AppEngine、AWS Opsworks、数字海洋
戒律#5:不要越过溪流!
编程设计、发布/订阅、演员、MVC 等有很多好的模型。选择你最喜欢的模型,并坚持下去。 处理不同类型数据的不同类型逻辑应该在代码库中物理隔离(同样,这种关注点分离概念并减少未来读者的认知负担)。 例如,更新 UI 的代码在物理上应该不同于计算进入 UI 的代码。
戒律#6:如果可能,让计算机完成工作
如果编译器可以捕获代码中的逻辑错误并防止不良行为、错误或彻底崩溃,我们绝对应该利用这一点。 当然,有些语言的编译器比其他语言更容易做到这一点。 例如,Haskell 有一个著名的严格编译器,导致程序员将大部分精力都花在编译代码上。 但是,一旦它编译,“它就可以工作”。 对于那些从未用强类型函数式语言编写过的人来说,这可能看起来很荒谬或不可能,但不要相信我的话。 说真的,点击其中一些链接,绝对有可能生活在一个没有运行时错误的世界中。 它真的是那么神奇。
诚然,并不是每一种语言都有一个编译器或语法,可以进行很多(或者在某些情况下!)编译时检查。 对于那些不这样做的人,请花几分钟时间研究您可以在项目中启用哪些可选的严格性检查,并评估它们是否对您有意义。 我最近用于具有宽松运行时的语言的一些常见的简短的、非全面的列表包括:
- Python:pylint、pyflakes、警告、emacs 中的警告
- 红宝石:警告
- JavaScript: jslint
结论
这绝不是生成“好”(即易于维护)代码的详尽或完美的诫命列表。 也就是说,如果将来我必须使用的每个代码库都遵循此列表中的一半概念,那么我的白发就会少得多,甚至可能在我生命的尽头再增加五年。 我肯定会发现工作更愉快,压力更小。