
翻译自:Applying the Linus Torvalds "Good Taste" Coding Requirement
在最近一次对 Linus Torvalds ( Linux 之父 ) 的采访视频中,约 14 分 20 秒的位置,他提到了带着“好品味”编程的观点。好品味?采访者对其细节进行了追问,而 Linus 则是早有准备。
他首先展示了一个代码段。不过这不是一个有着“好品味”的代码。这个代码段是个坏样例,用作对比的原始代码。
remove_list_entry(entry) { prev = NULL; walk = head; // Walk the list while (walk != entry) { prev = walk; walk = walk->next; } // Remove the entry by updating the // head or the previous entry if (!prev) head = entry->next; else prev->head = entry->next; } 无品味的代码示例
这是个 C 语言实现的函数,用于移除链表中的某个节点。共 10 行代码。
他让我们注意底部的 if 语句。就是这条 if 语句招致了他的批评。
我暂停了视频,研究了一下幻灯片。最近我有写过一些类似的代码。觉得 Linus 实际上就是在说我的品味差。我只好暂时放下我的自尊心,继续看视频。
Linus 向观众解释,从链表中移除一个节点时,有两种情况需要考虑。如果节点处于链表头,会于节点处于链表中的处理方式有所不同。而这就是那个 if 语句”坏品味“的来源。
不过既然他也承认这种特殊处理是必须的,那它到底坏在哪里?
接着,他又向观众展示了第二张幻灯片。这是他实现的同一个函数,不过是有着“好品味”的。
remove_list_entry(entry) { // The "indirect" pointer points to the // *address* of the thing we'll update indirect = &head; // Walk the list, looking for the thing that // points to the entry we want to remove while ((*indirect) != entry) indirect = &(*indirect)->next; // .. and just remove it *indirect = entry->next; } 好品味代码示例
最初的 10 行代码现在缩减为了 4 行。
但是重点并不在于代码行数的减少。重点是那个 if 语句消失了,不再被需要。代码重构后, 无论节点在链表中的什么位置,都可以采用相同的处理过程来进行移除。
Linus 解释了这段新代码对于边界情况的消除才是重点。采访继续,进入了下个话题。
我研究了一会这个代码。 Linus 是对的。第二个幻灯片所展示的代码更好。如果这是一道测试编码好坏的题目,那我可能已经挂了。消除条件判断的想法从未在我的脑子里出现过。而且我也像那坏例子一样写了不止一次了,因为我经常进行链表处理操作。
这个关于品味的展示,好处不仅仅在于教会你如何从一个链表中去除节点,它还使你去思考你曾经写过的代码,你曾经在程序中写过的小小算法或许还有改进的空间,并且以一种你从未想过的方式出现。
所以最近我审阅项目代码时,这成为了我的关注点。很巧的是,这个项目也是使用的 C 语言。
就我的理解而言,“好品味”的关键是消除边界情况,而这些情况一般表现为条件判断语句。越少使用条件判断,你的代码“品味”就会更好。
下面有个我对代码进行改进的例子分享给大家。
以下是我写的一个算法,用于初始化网格边缘的点,该网格使用一个多维数组表示:grid[rows][cols] 。
这段代码的作用是初始化网格的边缘座标点 即顶部一行,底部一行,左侧一列,和右侧一列。
为了完成工作,一开始我遍历了网格中的每个点,并且利用条件来判断他们是否处于边缘。代码如下:
for (r = 0; r < GRID_SIZE; ++ r) { for (c = 0; c < GRID_SIZE; ++ c) { // Top Edge if (r == 0) grid[r][c] = 0; // Left Edge if (c == 0) grid[r][c] = 0; // Right Edge if (c == GRID_SIZE - 1) grid[r][c] = 0; // Bottom Edge if (r == GRID_SIZE - 1) grid[r][c] = 0; } } 虽然这段代码可以正常工作,回头来看,确实有点问题:
Linus 应该会觉得,这可真没品味。
所有我对它进行了一些修补。花了些时间后,我可以降低它的复杂度,使其只需要一个包含四个条件判断的 for 循环。这只是在复杂度上改进了一点点,却极大地提升了性能,因为它只循环了 256 次,每个边缘上的座标点对应一次循环。
for (i = 0; i < GRID_SIZE * 4; ++ i) { // Top Edge if (i < GRID_SIZE) grid[0][i] = 0; // Right Edge else if (i < GRID_SIZE * 2) grid[i - GRID_SIZE][GRID_SIZE - 1] = 0; // Left Edge else if (i < GRID_SIZE * 3) grid[i - (GRID_SIZE * 2)][0] = 0; // Bottom Edge else grid[GRID_SIZE - 1][i - (GRID_SIZE * 3)] = 0; } 这的确是一次改进。但是代码真是太丑陋了。而且并不易于理解。因此,我对这段代码并不满意。
1 ianva 2016 年 11 月 11 日 是一个良好的数据抽象解决问题的例子,但还缺少一个良好的过程抽象 |
2 iEverX 2016 年 11 月 11 日 我会写四个循环 |
3 TerrenceSun 2016 年 11 月 11 日 这样呢? for (i = 0; i < GRID_SIZE; ++i) { grid[0][i] = grid[GRID_SIZE-1][i] = grid[i][0] = grid[i][GRID_SIZE-1] = 0; } |
4 holyghost 2016 年 11 月 11 日 |
5 justfly 2016 年 11 月 11 日 这种感觉的培养,不止局限在代码层面,而是一种找到问题的本质和化繁为简的一种思想。 其实在生活中遇到的各种事情,分析业务需求,日常写业务时都可以锻炼的,很多时候会明显感到是相通的。 |
6 zhy0216 2016 年 11 月 12 日 via iPhone 第一个例子 不知道用 python 怎么做 .. |
7 cnnblike 2016 年 11 月 12 日 确实有道理 |
8 lsmgeb89 2016 年 11 月 12 日 via Android 可以说是品味问题也不是,对有些人来说即使他意识到了他的处理方法不够 general ,他也很难想到方法二。 |
9 lsmgeb89 2016 年 11 月 12 日 via Android 具体看了下代码,感觉方法二要看一会才能理解,需要对二级指针有很深的理解。 |
10 alexapollo 2016 年 11 月 12 日 不认为 linus 的这个做法是对的 |