V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Coding.NET 轻量级社交
开源项目广场
使用帮助
意见反馈
darluc
V2EX  ›  Coding

如何拥有 Linus Torvalds 所说的编码“好品味”

  •  1
     
  •   darluc · 2016-11-11 14:14:57 +08:00 · 5744 次点击
    这是一个创建于 2723 天前的主题,其中的信息可能已经有所发展或是发生改变。

    翻译自: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;
      }
    }
    

    虽然这段代码可以正常工作,回头来看,确实有点问题:

    1. 复杂度 —— 在二重循环中使用 4 个条件判断语句,看起来有些过于复杂了。
    2. 效率 —— 如果 GRID_SIZE 等于 64 ,这个循环迭代了 4096 次,只是为了给 256 个边缘点赋值。

    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;
    }
    

    这的确是一次改进。但是代码真是太丑陋了。而且并不易于理解。因此,我对这段代码并不满意。

    点击阅读全文

    10 条回复    2016-11-12 19:58:03 +08:00
    ianva
        1
    ianva  
       2016-11-11 15:00:35 +08:00
    是一个良好的数据抽象解决问题的例子,但还缺少一个良好的过程抽象
    iEverX
        2
    iEverX  
       2016-11-11 16:33:41 +08:00
    我会写四个循环
    TerrenceSun
        3
    TerrenceSun  
       2016-11-11 19:50:30 +08:00
    这样呢?
    for (i = 0; i < GRID_SIZE; ++i) {
    grid[0][i] = grid[GRID_SIZE-1][i] = grid[i][0] = grid[i][GRID_SIZE-1] = 0;
    }
    holyghost
        4
    holyghost  
       2016-11-11 20:51:14 +08:00
    justfly
        5
    justfly  
       2016-11-11 21:00:47 +08:00
    这种感觉的培养,不止局限在代码层面,而是一种找到问题的本质和化繁为简的一种思想。

    其实在生活中遇到的各种事情,分析业务需求,日常写业务时都可以锻炼的,很多时候会明显感到是相通的。
    zhy0216
        6
    zhy0216  
       2016-11-12 00:43:00 +08:00 via iPhone
    第一个例子 不知道用 python 怎么做 ..
    cnnblike
        7
    cnnblike  
       2016-11-12 00:56:32 +08:00
    确实有道理
    lsmgeb89
        8
    lsmgeb89  
       2016-11-12 10:45:53 +08:00 via Android
    可以说是品味问题也不是,对有些人来说即使他意识到了他的处理方法不够 general ,他也很难想到方法二。
    lsmgeb89
        9
    lsmgeb89  
       2016-11-12 10:57:25 +08:00 via Android
    具体看了下代码,感觉方法二要看一会才能理解,需要对二级指针有很深的理解。
    alexapollo
        10
    alexapollo  
       2016-11-12 19:58:03 +08:00
    不认为 linus 的这个做法是对的
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5297 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 08:19 · PVG 16:19 · LAX 01:19 · JFK 04:19
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.