0%

《代码整洁之道》读书笔记

[toc]

最近在阅读《Clean Code》,读书笔记记录于此。

什么样的代码是好代码?

  • 好代码让人赏心悦目
  • 完成功能 - 基本要求
  • 划分合理 - 低耦合、高内聚
  • 风格规范 - 代码易阅读、易维护
  • 实现高效 - 性能好
  • 简洁实用 - 避免过度设计、避免炫技

命名

  • 使用读得出来的名称
  • 避免过度缩写,例如genymdmhs
  • 名称的长短应与其作用域大小一致
  • 越是作用域大的变量的名字越应该清晰地描述其含义,便于搜索
  • 避免使用编码
  • 无需将类型或作用域等编码进变量名中,如 f_price, g_count, m_name…,否则变量类型修改之后,变量名也需要跟着更新,否则就会误导读者

函数

  • 函数的第一规则是要短小;第二规则是还要更短小
  • 最好不要超过100行,20行封顶最佳
  • 避免过多嵌套:函数的缩进层级不应该多于1层或2层
  • 函数应该做一件事。做好这件事。只做这一件事。
  • 函数参数越少越好,应尽量避免三个及以上的参数。
  • 参数数目越少,测试时需要覆盖的场景数越少,越方便测试
  • 尽量避免使用参数进行信息输出,最好直接用返回值进行输出
    • 有时候返回值比放在参数中多了一次拷贝??? RVO, NRVO
  • 使用异常代替返回错误码 ?
    • 忘了在哪本书里面看的,说C++里面尽量避免使用异常??感觉本书主要针对java编程
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      try{
      deletePage(page);
      registry.deleteReference(page.name);
      configKeys.deleteKey(page.name.makeKey());
      }
      catch(Exception e) {
      logger.log(e.getMessage());
      }
      // 上述代码 错误处理与正常流程被混为一谈。。。

      public void delete(Page page) {
      try{
      deletePageAndAllReferences(page);
      }
      catch(Exception e) {
      logError(e);
      }
      }

      private void deletePageAndAllReferences(Page page) throws Exception {
      deletePage(page);
      registry.deleteReference(page.name);
      configKeys.deleteKey(page.name.makeKey());
      }

      private void logError(Exception e) {
      logger.log(e.getMessage());
      }
      // 上述拆分在java中固然好。。但在C++中就不能不在每个子函数内部添加try{}catch(){throw...}
  • 从一开始就尽量遵照规则写好每一个函数?还是先完成功能,再打磨、重构、组装成遵循规则的函数?

注释

  • 注释本身就是一种失败?是一种代码表达能力低的表现?
  • 注释不能美化糟糕的代码;尽量用代码来阐述;避免不必要的注释
  • 不必为了注释而注释
  • 注释也会说谎,尤其当注释没能被持续维护的情况下

格式

  • 纵向格式
    • 不同的思路或表达之间应该用空白行区隔开
    • 紧密相关的代码应该相互靠近
    • 若某个函数调用了另外一个,就应该把它们放到一起,而且调用者应该尽可能放在被调用者上面。
  • 横向格式
    • 一行代码不要超过120个字符
    • 根据运算符的优先级进行空格, e.g. a*b + c*d vs. a * b + c * d

对象和数据结构

  • 对象把数据隐藏于抽象之后,曝露操作数据的接口;数据结构曝露其数据,没有提供有意义的函数。
  • 过程式代码(使用数据结构)便于在不改变现有数据结构的前提下添加新函数,面向对象代码便于在不改变既有函数的前提下添加新类。过程式代码难以添加新数据结构,因为必须修改所有函数;面向对象代码难以添加新函数,因为必须修改所有类。实际使用时需要根据情况进行选择(希望灵活地添加数据类型?还是操作行为?)。
  • The Law of Demeter:模块不应该了解它所操作对象的内部情形。方法不应调用由任何函数返回的对象的方法。

错误处理

  • 错误处理很重要,但如果他搞乱了代码逻辑,就是错误的做法
  • 可控异常违反了开放/封闭原则。如果你在方法中抛出可控异常,而catch语句在三个层级之上,你就需要在catch语句和抛出异常处之间的每个方法签名中声明该异常。这意味着对软件较低层级的修改,都将波及较高层级的签名。
  • 将第三方api打包是个良好的实践手段。当你打包一个第三方api,你就降低了对它的依赖:未来你可以不太痛苦地改用其他代码库。
  • 不要返回或者传递null值。返回null值,等于在给调用者添乱,试想如果有一处没有进行null检查,应用程序就会失控
    • 可以抛出异常,或者返回特例
  • 如果将错误处理隔离看待,独立于主要逻辑之外,就能写出鲁棒而整洁的代码。

边界

单元测试