
理想的代码主题经常在经验丰富的程序员中引起争议。 得到Parallels RAS开发总监Igor Marnat的意见更加有趣。 根据削减,他的作者对宣布的话题的看法。 好好享受

作为引言,我想讨论为什么我决定写这篇简短的文章。 在编写它之前,我从标题向几个开发人员提出了一个问题。 我与大多数人一起工作了五年多,但与之合作的时间却有所减少,但我无条件地相信他们的专业知识和经验。 所有的工业发展经验都超过十年,每个人都在俄罗斯和国际公司,软件制造商工作。
一些同事发现很难回答(有些人仍然认为),另一些同事立刻举了一两个例子。 对于那些提供示例的人,我问了一个澄清的问题:“实际上是什么引起了这种钦佩?” 答案与我的小研究的下一阶段的结果一致。 我在网上搜索了该问题的答案,并采用了与文章标题相似的不同表述。 所有文章的回答方式与我的同志回答的方式大致相同。
开发人员的答案以及所发现文章的措辞与代码的可读性和结构,逻辑结构的优美性,现代编程语言的所有功能的使用以及遵循某种设计风格有关。
当我为自己问有关“神典”的问题时,答案立即从潜意识浮出水面。 我立即想到了已经使用了很长时间(十多年前)的两个代码示例,但是我仍然感到钦佩和崇敬。 考虑了每个人钦佩的原因后,我制定了几个标准,下面将进行讨论。 我将继续讲讲第一个例子,但我想更详细地分析第二个例子。 顺便说一句,在所有开发人员的史蒂夫·麦康奈尔(Steve McConnell)的手册“
Perfect Code ”中,都在不同程度上考虑了所有这些条件,但是本文明显简短。
90年代的例子
我将提到的第一个示例与v42bis调制解调器协议的实现有关。 该协议开发于80年代末-90年代初。 该协议的开发者所体现的一个有趣的想法是在不稳定的(电话)通信线路上传输期间实现信息流的压缩。 流压缩和文件压缩之间的区别是根本的。 压缩文件时,存档器可以完全分析数据集,确定压缩和编码数据的最佳方法,并将整个数据写入文件,而不必担心可能的数据和元数据丢失。 反过来,解压缩时,数据集又可以完全访问,因此校验和可确保完整性。 使用在线压缩时,存档器只能访问一个小的数据窗口,不能保证不会丢失任何数据,需要重新安装连接并初始化压缩过程很常见。
该算法的作者找到了一个优雅的解决方案,其描述实际上需要
几页 。 已经过去了很多年,但是算法开发人员提出的方法的优美和优雅让我印象深刻。
这个示例仍然没有引用这样的代码,而是引用了算法,因此我们将不对其进行详细介绍。
Linux是一切的负责人!
我想更详细地分析第二个完美代码示例。 这是Linux内核代码。 在编写本文时,该代码控制着
前500名超级计算机中的500台超级计算机的运行,该代码在世界上每秒钟一部电话上运行,并且控制Internet上的大多数服务器。
例如,考虑
Linux内核中的memory.c文件,
该文件属于内存管理子系统。
1.资料易于阅读。 它们以非常简单的风格编写,易于遵循且难以混淆。 大写字符仅用于预处理器指令和宏,其他所有内容均以小写字母书写,名称中的单词用下划线分隔。 除了根本没有样式外,这可能是最简单的编码样式。 同时,该代码是完全可读的。 缩进和注释方法可以从任何内核文件的任何部分看到,例如:
static void tlb_remove_table_one(void *table) { smp_call_function(tlb_remove_table_smp_sync, NULL, 1); __tlb_remove_table(table); }
2.代码中没有太多注释,但是这些注释通常很有用。 通常,它们描述的不是代码中已经显而易见的动作(无用注释的经典示例是“ cnt ++; //增量计数器”),而是该动作的上下文-为什么在此处执行,为什么在执行,为什么在这里,使用什么假设,将其连接在代码的其他位置。 例如:
void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm, unsigned long start, unsigned long end)
内核中注释的另一种用法是描述更改的历史记录,通常是在文件的开头。 内核的历史已经有将近30年了,阅读一些地方很有趣,您会觉得自己是故事的一部分:
3.内核代码使用特殊的宏来验证数据。 它们还用于检查代码工作的上下文。 这些宏的功能类似于标准断言,不同之处在于,如果条件为真,则开发人员可以覆盖执行的操作。 内核中数据处理的一种通用方法-检查来自用户空间的所有内容,如果数据错误,则返回相应的值。 在这种情况下,可以使用WARN_ON向内核日志发出记录。 在调试新代码并在新体系结构上启动内核时,BUG_ON通常非常有用。
BUG_ON宏通常会导致寄存器和堆栈的内容被打印,并在发生相应调用的上下文中停止整个系统或进程。 如果条件为真,则WARN_ON宏仅向内核日志发出一条消息。 还有宏WARN_ON_ONCE和其他一些宏,它们的功能从名称中就很明显。
void unmap_page_range(struct mmu_gather *tlb, …. unsigned long next; BUG_ON(addr >= end); tlb_start_vma(tlb, vma); int apply_to_page_range(struct mm_struct *mm, unsigned long addr, … unsigned long end = addr + size; int err; if (WARN_ON(addr >= end)) return -EINVAL;
在使用前检查从不可靠来源获得的数据的方法,可以预见和确定系统对“不可能”情况的响应,这大大简化了系统的调试和操作。 您可以将这种方法视为“尽早失败”原则的一种实现。
4.内核的所有主要组件都通过简单的界面(虚拟文件系统/ proc /)为用户提供有关其状态的信息。例如,内存状态信息在/ proc / meminfo文件中可用
user@parallels-vm:/home/user$ cat /proc/meminfo MemTotal: 2041480 kB MemFree: 65508 kB MemAvailable: 187600 kB Buffers: 14040 kB Cached: 246260 kB SwapCached: 19688 kB Active: 1348656 kB Inactive: 477244 kB Active(anon): 1201124 kB Inactive(anon): 387600 kB Active(file): 147532 kB Inactive(file): 89644 kB ….
以上信息在内存管理子系统的几个源文件中收集和处理。 因此,第一个MemTotal字段是sysinfo结构的totalram字段的值,该字段由
page_alloc.c文件的
si_meminfo函数填充。
显然,组织收集,存储并为用户提供对此类信息的访问需要开发人员的努力和系统的一些开销。 同时,在开发过程和代码操作中,方便,轻松地访问此类数据的好处是无价的。
几乎所有系统的开发都应从收集和提供有关代码和数据内部状态信息的系统开始。 这将大大有助于开发和测试过程以及以后的运行。
正如
Linus所说 :“糟糕的程序员担心代码。 好的程序员担心数据结构及其关系。“
5.在提交之前,所有开发人员都会阅读和讨论所有代码。 记录并更改了源代码的历史记录。 对任何行的更改都可以追溯到它的发生-更改的内容,更改的对象,时间,原因,开发人员讨论了哪些问题。 例如,代码memory.c中https://github.com/torvalds/linux/commit/1b2de5d039c883c9d44ae5b2b6eca4ff9bd82dac#diff-983ac52fa16631c1e1dfa28fc593d2ef中的更改是受https://bugzsh_b.bg?bg_b。对代码进行了小幅优化(如果内存已被写保护,则不会进行启用内存写保护的调用)。
对于使用代码的开发人员来说,了解此代码的上下文,创建代码的假设,更改的时间和时间始终很重要,以便了解哪些情况可能受到他将要进行的更改影响。
6.记录和访问了内核代码生命周期的所有重要元素 ,从
编码风格开始
, 以发行稳定内核版本的
内容和时间表结束。 每个想要以一种或多种身份使用内核代码的开发人员和用户都具有与此相关的所有必要信息。
这些时刻对我来说似乎很重要,基本上,它们决定了我对核心代码的热情。 显然,该列表很短,可以扩展。 但是我认为,从开发人员的角度来看,以上列出的几点与任何源代码生命周期的关键方面有关。
最后我想说的是。 核心开发人员很聪明,经验丰富,他们成功了。 经过数十亿Linux设备的验证
作为内核开发人员,使用最佳实践并阅读Code Complete!
Z.Y. 顺便说一句,您个人对理想代码的标准是什么? 在评论中分享您的想法。