abing
abing
发布于 2024-08-11 / 51 阅读
0
0

《代码整洁之道》 阅读笔记(1)

0. 写在前面

我们永远无法抛弃必要的精准性——所以代码永存。

在当下人工智能的发展下,我们可以借助 AI 写出不错的代码,可以直接根据对应的需求规约生成对应的程序,于是我们这样子就可以抛弃代码了……吗?

《人月神话》中有句话,“表达是编程的本质”,我觉得可以很好的解释这个问题。代码不仅仅是写给机器执行的,代码呈现出了需求的细节,在更细的层面上,这些细节没办法被忽略或者是抽象出来,必须将需求明确到机器可以执行的细节程度,同时,还需要表达给之后维护的人,因为需求总是在不断更新迭代的。

所以回到书中在第一章中提到的,同时也是笔记开头的引言—— “我们永远无法抛弃必要的精准性——所以代码永存。”

1. 命名

代码中的命名随处可见,因此,一个好名字的重要性足以让书把它放在第一位。

伟哥:命名其实就体现了你的思路。

  • 名副其实

    • 正确:$customerName

    • 错误:$n

    • 分析:变量名应该直接反应其存储的数据的用途

  • 避免误导

    • 正确:$record

    • 错误:$save

    • 分析:变量名不应该让人误解其实际用途

  • 有意义的区分

    • 正确:$userId, $productId

    • 错误:$id1, $id2

    • 分析:变量应能够区分不同的数据和功能,而不是让人去猜

  • 不要添加没用的语境

    • 感觉有点类似 奥卡姆剃刀定律如无必要,勿增实体Entities should not be multiplied unnecessarily

    • 正确:

      <?php
      ​
      class Cusotmer {
          public $id;
          public $email;
      }
    • 错误:

      <?php
      ​
      class Cusotmer {
          public $customerId;
          public $customerEmail;
      }
    • 分析:可以看到,无意义的将类名重复一遍的编码方式凭空增加了我们的代码长度,但是对于实际的分辨,其实并没有起到什么作用

2. 函数

  • 短小 & 只做一件事

    • 这点其实在实际编程中我个人感觉是最难做到的,因为我们在维护代码的时候,总是不可避免地去向已有的代码中插入我们的新东西,这样子就较难以去将每个函数的长度都维持在一个较小的长度,书中建议的是将代码每个函数都最好控制在三四行中。

    • 同时,这也会带来另外一个问题:当你要查看一个过程的时候,你会不可避免地在很多个函数之间跳来跳去,这样子有时候,即便代码结构是清晰的,但是给人的观感仍然不尽人意,所以上述两个要求,他们之间的平衡,我认为才是我们在实际编程中应当去做适当的取舍的

    // TODO: 插入ADS-1680优化前后的代码
  • 每个函数一个抽象层级

    他其实最主要体现了 单一职责原则(Single Responsibility Principle, SRP) ,但是具体到实际中,又会根据不同的设计模式产生不同的变化。

    • 比如在 MVC 中,我们根据三个核心组件来组织我们的代码

      • Model:数据模型,负责数据存储、检索和业务逻辑处理。

      • View:用户界面,负责展示数据(由Model提供)给用户。

      • Controller:控制器,负责接收用户的输入并调用Model和View来完成用户的请求。

    • 而在 MVP(Model-View-Presenter) 中,Presenter 充当了 View 和 Model 之间的中介,负责处理用户界面逻辑,而Model只负责数据和业务逻辑

    这时候,我们通过对比 MVCMVP 就可以很容易的分辨出每个组件中,他们在实际业务场景下,会对不同的操作有着不同的抽象层级差别。

  • 函数参数

    参数少,意味着我们如果要测试这个函数,所需要模拟的变量就越少,我们会更容易测试自己的函数是否有问题。

    Clean Coder Blog: When to Mock

    这篇文章从使用模拟对象(mocks)的时机和方法角度触发,对比了不使用模拟对象和过度使用模拟对象的两种极端情况,作者在写下本书的时候是2010年,这篇博客则是他在2014年写下的,本书中作者推崇的最理想的参数数量是0,但是不可避免的我们时常会引入其他参数来测试我们函数的行为是否符合我们的预期,所以在引入参数的时候,我们同样需要格外小心。

  • 如何写出这样的函数

    我并不从一开始就按照规则写函数

    好的代码需要打磨,单元测试是作者在书中提到的他常用的方法,一点点通过修改代码和修改测试,来将代码打磨好。

3. 注释

什么也比不上放置良好的注释来得有用,但是作者在文中提到了另外一种可能——

如果编程语言足够有表达力,或者我们长于用这些语言来表达意图,就不那么需要注释——也许根本不需要。

  • 注释不能美化糟糕的代码

    • bad :

      // 递归遍历数组,并将扁平化结果保存到全局变量$result
      function processArray($arr) {
          global $result;
          foreach ($arr as $item) {
              if (is_array($item)) {
                  processArray($item);
              } else {
                  $result[] = $item;
              }
          }
      }
      ​
      $input = [1, [2, 3], [4, [5, 6]]];
      processArray($input);
      ​
      // 结果存储在全局变量$result中
      print_r($result);
    • good:

      // 递归遍历数组并返回扁平化结果
      function processArray($arr) {
          $result = [];
          foreach ($arr as $item) {
              if (is_array($item)) {
                  $result = array_merge($result, processArray($item)); 
              } else {
                  $result[] = $item;
              }
          }
          return $result;
      }
      ​
      $input = [1, [2, 3], [4, [5, 6]]];
      $result = processArray($input);
      ​
      print_r($result);

      可以看到,在第一份代码中,我们采用的是利用全局变量的方法,而第二份代码则采用的是返回值,如果我们每次都要检查函数的结果,则要再次点进这个函数,才能看到对应的变量的名称,而采用返回值,则没有这个困扰,第一份代码的注释并没有起到帮助我们的作用,原因就在于,这第一份代码的可维护性和可读性本身就很差。

  • 用代码来阐述

    频繁引入注释有时候只会让代码的表示失去原本的表达力。有时候,我们可以直接利用代码,就表达出我们的意思,这时候就不必引入注释。

    • bad:

      // 检查字符串是否为空白、null 或 undefined
      if (trim($input) == '' || $input == null || $input == 'undefined') 
    • good:

      if (isNullOrEmpty($input))

    但是这时候也同样需要注意到代码的复用性,如果当前的这部分代码复用性不高,仅仅是某个单点调用的话,则在对应处标明注释,反而能够在这部分的代码中,显得更为直观,后续修改的时候,也能够一目了然。

  • 注释掉的代码

    注释掉的代码堆积在一起,就像破酒瓶底的渣滓一般

    现在的源代码管理系统已经非常完善了,对于旧的,我们现在已经不需要的代码,最好的选择就是把它删除它,因为如果保存,后来的维护者总会想着:“代码依然放在那里,一定有其原因,而且这段代码很重要,不能删除。”,最终旧的代码越积累越多,最终会在后来的人阅读代码的时候,绊倒后来的人。



评论