1. bcachefs 编码风格¶
好的开发就像园艺,代码库就是我们的花园。每天照料它们;寻找那些不合时宜或需要整理的小细节。一点点修剪就能带来很大的改善;不要等到事情失控才动手。
事情不总是需要完美——吹毛求疵往往弊大于利。但当你看到美时,要欣赏它——并让人们知道。
你不敢触碰的代码,就是最需要重构的代码。
零星的整理也能带来很大的帮助。
认真思考如何组织事物。
好的代码是可读的代码,其结构简单,不给bug留下任何藏身之处。
断言是我们编写可靠代码最重要的工具之一。如果在编写补丁集的过程中,你遇到一个不应该发生的条件(如果发生会导致不可预测或未定义的行为),或者你不确定它是否会发生,也不确定如何处理——就让它成为 BUG_ON()。不要让未定义或未指定的行为潜伏在代码库中。
到你完成补丁集时,你应该更清楚哪些断言需要处理并转换为带有错误路径的检查,以及哪些应该是逻辑上不可能的。对于逻辑上不可能的断言,保留 BUG_ON()。(或者,如果它们开销很大,可以将它们设为调试模式断言——但不要将所有东西都变成调试模式断言,这样我们就不会在事实证明你错了时陷入调试未定义行为的困境)。
断言是不会过时的文档。好的断言是极好的。
好的断言能显著且大幅地减少发现bug所需的测试量。
好的断言基于状态而非逻辑。要编写好的断言,你必须思考你的状态有哪些不变量。
好的不变量和断言在你的代码库中随处可见。这意味着你可以在提交版本中只在少数地方运行它们,但是如果你需要调试导致断言失败的问题,你可以迅速地在各处散布它们,以找到破坏不变量的代码路径。
一个好的断言检查的是编译器本可以为我们检查并消除的内容——如果我们使用的语言带有编译器可以检查的嵌入式正确性证明。这种技术今天已经存在,但要将其引入系统编程语言可能还需要几十年。但我们仍然可以在代码中融入这种思维,并通过运行时检查来记录不变量——就像使用动态类型语言的人可能会添加类型注解,逐步使其代码变为静态类型一样。
寻找使断言更简单、更高级的方法,往往会促使你使整个系统更简单、更健壮。
好的代码是你可以深入探索并了解其运行情况的代码——即内省。如果我们看不到正在发生什么,我们就无法调试任何东西。
每当我们调试时,如果解决方案不那么显而易见,并且问题在于我们因为看不到正在发生什么而不知道问题出在哪里——那首先要解决的就是这个问题。
我们拥有能在运行时高效地使一切可见的工具——其中包括 RCU 和 percpu 数据结构。不要让事物保持隐藏。
内省最重要的工具是简单的美化打印机——在 bcachefs 中,这意味着 *_to_text() 函数,它们将输出到 printbufs。
美化打印机非常出色,因为它们可以组合使用,并且你可以随处使用它们。拥有打印你正在处理的任何对象的功能,将使你的错误消息更容易编写(因此它们将实际存在)并且更具信息量。它们可以从 sysfs/debugfs 以及跟踪点中使用。
运行时信息和调试工具应该附带清晰的描述和标签,并具有良好的结构——我们不希望像 procfs 中那样,文件里只是一堆裸整数列表。调试工具的一部分作用是教育用户和新开发者了解系统的工作原理。
错误消息应尽可能地告诉你调试问题所需的一切。值得为此付出努力。
跟踪点不应该是你首先想到的工具。它们是一个重要的工具,但要始终寻找更直接的方式让事物可见。当我们不得不依赖跟踪时,我们必须知道要寻找哪个跟踪点,然后运行有问题的负载,接着筛选日志。当用户遇到问题时,这需要经历很多步骤,如果问题是间歇性的,甚至可能无法实现。
不起眼的计数器是一个极其有用的工具。它们便宜且易于使用,许多复杂的内部操作,特别是那些可能行为异常的操作(例如涉及内存回收的任何操作),一旦在每个不同的代码路径上都有计数器,就会变得出奇地容易调试。
持久化计数器甚至更好。
调试时,尽量从遇到的每一个bug中获得最大收益;不要急于修复最初的问题。寻找能让下一次处理相关bug变得更容易的方法——例如内省、新的断言、更好的错误消息、新的调试工具,并首先完成这些。寻找使系统表现更好的方法;通常一个bug会通过下游效应揭示出其他几个bug。
首先修复所有这些,最后再解决最初的bug——即使这意味着让用户等待。从长远来看,他们会感谢你,当他们理解你正在做什么时,你会惊讶于他们是多么乐意耐心等待。用户乐于助人——否则他们一开始就不会报告bug了。
与你的用户交流。不要孤立自己。
用户会注意到各种有趣的事情,仅仅通过与他们交流和互动,你就能从他们的经验中获益。
花时间做支持和帮助台工作。不要只写代码——代码只有在无故障使用时才算完成。
这也会激励你尽可能地改进你的调试工具,甚至你的文档。就像生活中的其他事情一样,你投入的时间越多,你就会做得越好,而作为开发人员的你,是最有能力改进工具以使调试变得快速和轻松的人。
警惕你如何承担并致力于大型项目。不要让开发变得以产品经理为中心。很多时候一个想法是好的,但需要等待合适的时机——然而,在你开始编写代码之前,你不会知道一个想法是否到了合适的时机。
要有扔掉很多东西,或者把它们半途而废留待日后再处理的心理准备。没有人能写出所有完美并全部交付的代码,如果你能及早注意到这一点并转向其他事情,从长远来看你会更有效率。所获得的经验和教训对你所做的所有其他工作都将是宝贵的。
但不要害怕处理那些需要对现有代码进行大量返工的项目。有时这些项目是最好的,因为它们可以引导我们使现有代码更通用、更灵活、更多功能,或许也更健壮。如果一个想法看起来会把事情搞得一团糟,就不要犹豫放弃它。
复杂的功能通常可以通过一系列重构来完成,而最终实际实现该功能的更改在最后可能只是一个很小的补丁。当这种情况发生时真是太好了,特别是当这些重构本身就能改进代码库时。这样一来,即使你想要实现的功能最终未能成功,浪费的努力也大大减少。
始终努力增量工作。始终努力将大型项目分解为可以证明自身价值的小型项目。
与其总是处理那些大型项目,不如寻找那些有用的小事情,让大型项目变得更容易。
什么可能有用这个问题是初级开发者最常走偏的地方——做一些看起来有用的事情往往会导致过度工程化。了解什么有用来自于多年的经验,或者与有经验的人交流——或者仅仅是通过阅读大量代码并寻找常见模式和问题。不要害怕扔掉东西,做一些更简单的事情。
与你的同事开发者讨论你的想法;很多时候,最好的想法来自于轻松的对话,人们在其中不害怕说“如果……会怎样?”。
不要忽视你的工具。
最重要的工具(除了编译器和我们的文本编辑器)是我们用于测试的工具。最短的编辑/测试/调试周期对于高效工作至关重要。我们通过运行代码并观察结果来学习、积累经验并发现思维中的错误。如果你的时间因为工具不好或太慢而被浪费——不要接受,去修复它。
在你的文档、提交信息和代码注释上投入精力——但不要过分。一个好的提交信息很棒——但如果信息重要到可以放在提交信息中,请问问自己,它作为代码注释是否会更好。
一个好的代码注释很棒,但更好的是那些不需要存在的注释,因为代码非常直截了当,显而易见;组织成小巧、干净、整洁的模块,函数和变量名称清晰且具有描述性,每行代码都有明确的目的。