博客 阅读时间 8 分钟

编写高效的 C 代码:向 Linus Torvalds 学习

分享本文
Write Efficient C Code: Learn from Linus Torvalds

每个项目都有自己的风格指南:一套关于代码应如何编写的约定。有些管理者选择基本的编码规则,有些则偏好更高级的规则。然而,在许多项目中根本没有指定任何编码规则,每位开发者都各行其是。

当代码库中的所有代码都以一致的风格编写时,理解一个大型代码库要容易得多。

关于编码最佳实践的资源有很多。我们可以通过以下方式学习良好的编码规则:

  • 阅读书籍或杂志。
  • 利用在线资源。
  • 向同事学习。
  • 参加培训课程。

我们也可以与一位专家共事几个月来提升团队的编码技能。然而,找到合适的人选并不容易,而且可能会让公司花费不菲。但既然我们可以向 Linus Torvalds 这样杰出的开发者学习,又何必另寻专家呢?只需研究他开发或维护过的源代码,就能获得关于如何编写高效 C 代码的宝贵洞见。

Linus Torvalds 因创建 Linux 内核并多年担任其首席开发者而广为人知,他还创建了极受欢迎的分布式版本控制系统Git。单凭这一点,他的代码就非常值得研究 :)

走进 Git 源代码

让我们来看一段 Git 的代码片段:

关于这段代码,有以下几点观察:

  • 函数声明为 static。
  • 函数返回错误码。
  • 函数参数很少。
  • 函数尽早退出。
  • 变量声明为 static。
  • 变量名易于理解。
  • 函数非常短小。
  • 代码缩进良好。
  • 函数体内没有多余的注释:代码自己会说话。
  • 函数体缩进良好。
  • 头文件保护宏(define guard)清晰明了。

浏览 Git 源代码时,我们可以看到它的实现是多么一致:同样的最佳实践贯穿始终。为了验证这一点,让我们搜索 static 函数:

from m in Methods where m.IsStatic select m

树状图(treemap)对于概览 CQLinq 查询匹配到的代码元素非常有用;蓝色矩形代表查询结果。

几乎所有函数都声明为 static,因此它们只在声明它们的翻译单元中可见。

走进 Linux 内核源代码

让我们转到 Linux 源代码,看看下面这个函数的实现:

linux11

代码看起来非常整洁。具体来说,这个函数

  • 只有寥寥几行代码。
  • 它的签名定义得很清晰。
  • 注释写得很到位。
  • 代码缩进良好。
  • 变量名非常清晰。
  • 遵守了 const 正确性。
  • 它会检查输入参数,并在参数不满足某些条件时发出警告。

另一位开发者可能会用这样的方式实现同样的函数:

linux12

编码风格对源代码的可读性影响巨大。投入几个小时进行开发者培训,并定期开展代码评审,可以让代码更易于维护和演进。

让我们使用CppDepend来探索 Linux 内核源代码,发掘其开发者所采用的一些基本编码规则。

模块化

模块化是一种软件设计技术,它能提高软件由独立部分组成的程度;模块化代码更易于管理和维护。

对于 C 这样没有命名空间、组件或类等逻辑构造的过程式语言来说,模块化可以通过目录和文件来实现。

以下是几种可能的做法:

  • 把所有源文件放在一个目录里。
  • 将与某个模块或子模块相关的文件隔离到特定目录中。

在 Linux 内核中,开发者使用目录和子目录来对内核源代码进行模块化。

linux15封装

封装是指隐藏实现内部的函数和数据。在 C 语言中,封装通过关键字 static 实现。这些实体称为文件作用域的函数和变量。

让我们执行以下 CQLinq 查询,搜索所有 static 函数:

linux17

我们可以使用度量视图(Metric view)来直观地了解有多少函数符合。在度量视图中,代码库以树状图表示。树状图是一种用嵌套矩形展示树形结构数据的方法。CppDepend 树状图使用的树结构就是常见的代码层次结构:

  • 项目包含目录。
  • 目录包含文件。
  • 文件包含结构体、函数和变量。

树状图视图为展示 CQLinq 查询结果提供了一种实用的方式,让我们可以直观地看到查询涉及的类型。

linux2

可以看到,许多函数都声明为 static。

现在让我们搜索 static 字段:

linux3

与函数一样,许多变量也声明为 static。

在 Linux 内核源代码中,只要函数和变量需要在文件作用域内保持私有,就会使用封装。

用结构体存储数据模型

在 C 语言编程中,函数使用变量来完成处理;这些变量可以是:

  • 静态变量。
  • 全局变量。
  • 局部变量。
  • 结构体中的变量。

每个项目都有自己的数据模型,可能被许多源文件使用。使用全局变量是一种选择,但通常更好的做法是把相关的数据归入结构体。

让我们搜索基本类型的全局变量:

linux4

符合条件的变量只有寥寥几个,而其中一些也许可以归入结构体,比如(elfcorehdr_addr 和 elfcorehdr_size)或(pm_freezing 和 pm_nosig_freezing)。

让函数短小精悍

下面是来自Linux 编码风格网页关于缩进的说明:

Functions should be short and sweet, and do just one thing.  They should
fit on one or two screenfuls of text (the ISO/ANSI screen size is 80x24,
as we all know), and do one thing and do that well.

The maximum length of a function is inversely proportional to the
complexity and indentation level of that function.  So, if you have a
conceptually simple function that is just one long (but simple)
case-statement, where you have to do lots of small things for a lot of
different cases, it's OK to have a longer function.

让我们搜索代码超过 30 行的函数。

linux14

只有少数方法的代码超过 30 行。

函数参数数量

NbParameters > 8 的函数可能调用起来很痛苦,还可能降低性能。另一种替代方案是提供一个专门用于传递参数的结构体。

linux7

只有两个函数的参数超过八个。

局部变量数量

NbVariables 大于 8 的方法可能难以理解和维护。NbVariables 大于 15 的方法则极其复杂,应当拆分成更小的方法,除非它们是由工具自动生成的。

linux9

只有五个函数的局部变量超过 15 个。

避免定义复杂函数

有许多度量可用于识别复杂函数;代码行数(NBLinesOfCode)、参数数量和局部变量数量是其中最基本的几个。

还有其他一些有用的度量可以识别复杂函数:

  • 圈复杂度是一种流行的过程式软件度量,等于一个过程中可能发生的决策数量。
  • 嵌套深度是定义在方法上的度量,表示方法体内嵌套最深的作用域的深度。
  • 最大嵌套循环数等于函数中循环嵌套的最大层数。

这些度量的最大可接受值在很大程度上取决于团队的偏好;没有放之四海皆准的阈值。

让我们搜索可以作为重构候选的函数:

linux8

只有极少数函数可以算作复杂函数。

命名规范

不存在通用的命名规范;每个项目都可以采用最适合自身需求的规范。最重要的是始终如一地应用所选规范。

在 Linux 中,结构体必须以小写字母开头。我们可以检查整个内核源代码是否都遵守了这一约定——执行以下查询:

linux5

只有四个结构体以“_”而非小写字母开头。

缩进

缩进对于让代码易于阅读非常有用;以下是来自Linux 编码风格网页关于缩进的说明:

Rationale: The whole idea behind indentation is to clearly define where
a block of control starts and ends.  Especially when you've been looking
at your screen for 20 straight hours, you'll find it a lot easier to see
how the indentation works if you have large indentations.

Now, some people will claim that having 8-character indentations makes
the code move too far to the right, and makes it hard to read on a
80-character terminal screen.  The answer to that is that if you need
more than 3 levels of indentation, you're screwed anyway, and should fix
your program.
结语

探索知名的开源项目是提升编程技能的绝佳方式,尤其当这些项目由专家开发和维护时更是如此。您甚至不需要下载和构建项目——直接在 GitHub 上浏览代码即可。

分享本文