zoukankan      html  css  js  c++  java
  • 代码整洁之道笔记

    《代码整洁之道》

    文字ref:
    图片ref:https://www.cnblogs.com/edisonchou/p/edc_clean_code_notes.html

    Edison Zhou

    一、命名

    1.避免误导

    • "一组账号"accountListList是链表,可用accountGroup、甚至是accounts
    • 区别较小的名称,难以辨别。
    • 小写l、大写O作变量名,看起来像常量1、0

    2.做有意义的区分

    • 数字系列命名(a1、a2、a3),按真实含义命名
    • 冗余的名字,变量名别带variable、表名别带table

    3.使用可搜索的名称

    • 作用域越大名称应容易区分,越是频繁出现的变量名称得越容易搜索(越长)

    4.命名时避免使用编码

    • 类型和作用域编码
    • 匈牙利语标记法
    • "m_"前缀来表明成员变量
    • 接口和实现名称中编码。如果接口和实现必须选一个编码,宁可选实现,ShapeFactoryImp都比对接口名称编码来的好

    5.类名、方法名

    • Class是名词(短语),function是动词(短语)

    6.每个概念用一个词

    • fetch、retrieve、get约定一个

    7.别用双关语

    • add是:1+2。把值加入集合,用insertappend命名更好

    8.添加有意义的语境

    • 很少有名称能自我说明,需要用良好命名的类、函数、或者命名空间来放置名称,给读者提供语境,如果做不到的话,给名称添加前缀就是最后一招了。

    二、函数

    1.越短小越好

    • if/else/while语句的代码块应该只有一行,该行应该是一个函数调用语句
    • 函数的缩进层级小于3层

    2.只做一件事

    • 要判断函数是否不止做了一件事,就是要看是否能再拆出一个函数

    3.每个函数一个抽象层级

    4.switch语句

    • 把switch埋在较低的抽象层级,一般可以放在抽象工厂底下,用于创建多态对象。

    5.使用描述性的名称

    • 函数越短小、功能越集中,就越便于取个好名字。
    • 别害怕长名称,长而具有描述性的名称,比长注释好。
    • 别害怕花时间取名字。

    6.函数参数

    • 参数越少越好,0参数最好,尽量避免用三个以上参数
    • 参数多,写测试用例就难
    • 别用标识参数,向函数传入bool值是不好的,不止做一件事。可以将此函数拆成两个。
    • 如果函数需要两个、三个或者三个以上参数,就说明其中一些参数应该封装成类了
    • 将参数的顺序编码进函数名,减轻记忆参数顺序的负担,例如,assertExpectedEqualsActual(expected, actual)

    7.副作用(函数在正常工作任务之外对外部环境所施加的影响)

    • 检查密码并且初始化session的方法 命名为checkPasswordAndInitializeSession而非checkPassword,即使违反单一职责原则也不要有副作用
    • 避免使用"输出参数",如果函数必须修改某种状态,就修改所属对象的状态吧

    8.读写分离

    • if(set("username", "unclebob")) { ... } 的含义模糊不清。应该改为:

      if (attributeExists("username")) { 
          setAttribute("username", "unclebob");
          ...
      }
      

    9.使用异常代替返回错误码

    • 返回错误码会要求调用者立刻处理错误,从而引起深层次的嵌套结构:

      if (deletePate(page) == E_OK) {
          if (xxx() == E_OK) {
              if (yyy() == E_OK) {
                  log();
              } else {
                  log();
              }
          } else {
              log();
          }
      } else {
          log();
      }
      
    • 使用异常机制:

      try {
          deletePage();
          xxx();
          yyy();
      } catch (Exception e) {
          log(e->getMessage());
      }
      
    • try/catch代码块丑陋不堪,所以最好把try和catch代码块,抽成函数

      try {
          do();
      } catch (Exception e) {
          handle();
      }
      
    • 函数只做一件事,错误处理就是一件事。try,catch,应只出现一次。

    • 定义错误码的class需要新错误码,相关的class需重新编译、部署

    • 但如果新异常可从异常class派生出来,无需重新编译、部署。这也是开放闭合原则(对扩展开放,对修改封闭)的范例。

    10.不要写重复代码

    • 重复是软件中一切邪恶的根源。当算法改变时需要修改多处地方

    11.结构化编程

    • 只要函数保持短小,偶尔出现的return、break、continue语句没有坏处,甚至还比单入单出原则更具有表达力。goto只有在大函数里才有道理,应该尽量避免使用。

    12.如何写出这样的函数

    • 想些什么就写什么,然后再打磨这些代码,按照这些规则组装函数。

    三、注释

    • 若编程语言足够有表现力,我们就不需要注释。
    • 注释总是一种失败。
    • 代码在演化,注释却不总是随之变动。
    • 不准确的注释比没注释坏的多。

    1.用代码来阐述

    • 创建一个与注释所言同一事物的函数即可

      // check to see if the employee is eligible for full benefits
      if ((employee.falgs & HOURLY_FLAG) && (employee.age > 65))
      

      应替换为

      if (employee.isEligibleForFullBenefits())
      

    2.好注释

    • 法律信息

    • 提供基本信息,如解释某个抽象方法的返回值

    • 对意图的解释

    • 阐释。把某些晦涩的参数或者返回值的意义翻译成可读的形式(更类似标准库的代码我们无法修改):

      if (b.compareTo(a) == 1) //b > a
      
    • 警示。// don't run unless you have some time to kill

    • TODO注释

    • 放大 一些看似不合理之物 的重要性

    3.坏注释

    • 自言自语。

    • 多余的注释。

    • 误导性注释。

    • 遵循规矩的注释。

    • 日志式注释——使用代码版本控制工具git/svn。

    • 能用函数或者变量表示就别用注释:

      ArrayList moduleDependees = smodule.getDependSubsystems();
      String ourSubSystem = subSysMod.getSubSystem();
      if (moduleDependees.contains(ourSubSystem))
      
    • 位置标记。标记多了会被我们忽略掉:
      ///////////////////// Actions //////////////////////////

    • 标记右括号不如缩短函数。

      try {
          ...
      } // try
      
    • 署名注释跟不上代码的演变。

    • 注释掉的代码。

    • 信息过多。别在注释中添加有趣的历史话题或者无关的细节

    • 没解释清楚的注释。

    • 短函数的函数头注释。为短函数选个好名字比函数头注释要好。

    • 非公共API函数的javadoc/phpdoc注释。

    四、代码格式

    1.垂直格式

    • 短文件比长文件更易于理解。平均200-500行的单个文件,可构造出色的系统
    • 区隔: 封包声明、导入声明、每个函数之间,都用空白行分隔开。
    • 靠近: 相关代码应该靠近,例如类中各属性应该紧密
    • 变量声明,应靠近使用:for循环,在循环语句中声明。
    • 成员变量应该放在类的顶部声明,不要四处放置。
    • 如果某个函数A调用函数B,就应该把AB放在一起。所以B在A上方。
    • 执行同一基础任务的几个函数应在一起

    2.水平格式

    • 一行代码不必死守80字符的上限,偶尔到达100字符不超过120字符即可。
    • 区隔与靠近: 空格强调左右两边的分割。赋值两边,加空格a = 1 ;,函数名与左圆括号,不加空格func( ),乘法与加减组合时,不加空格(a*b - c)
    • 不必水平对齐。例如声明一堆成员变量时,各行不用每一个单词都对齐。
    • 短小的if、while、函数里最好也不要违反缩进规则if (xx == yy) z = 1;

    五、Class和Struct

    Class: 暴露接口,隐藏数据
    Struct: 无明显接口,暴露数据,网络传输时候多。

    1.对象与数据结构的反对称性

    • 使用过程类代码,难加数据结构,因为必须修改所有函数;
    • 使用对象难加新函数,因为必须修改所有类。但可以组合新类。
    • 万物皆对象只是个传说,有时候我们也会在简单数据结构上做一些过程式的操作。

    2.Demeter定律(最少知识原则)

    • 模块不该了解它所操作对象的内部情形
    • 方法不应调用由任何函数返回的对象 的方法。 ctxt.getOptions().getScratchDir().getAbsolutePath();
    • 人命令狗跑,非控制狗的腿,应该让狗控制狗腿

    六、异常处理

    异常处理很重要,但如果异常处理四处分散在代码中 导致逻辑模糊不清。

    1.使用异常而不是返回错误码

    • 如果使用错误码,调用者须在函数返回时立刻处理错误,但这很容易被我们忘记。
    • 错误码 =》嵌套if else

    2.先写try-catch语句

    • 当编写可能会抛异常的代码时,先写好try-catch再往里堆逻辑

    3.使用unchecked exception(java独有)

    noexceptionC++11

    4.在catch里尽可能的记录错误信息,记录失败的操作以及失败的类型。

    5.根据调用者的需要 定义不同的异常处理类

    • 同一个try里catch多个不同exception,但是catch处理的事(打日志等)是一致的,可以考虑用函数打包一下这个异常处理,针对不同异常仅需throw成同一个异常而不做任何处理,在外层catch时统一处理(打日志等)。如果仅想捕获一部分异常而放过其他异常,就使用不同的函数打包这个异常处理

    6.特例模式: 创建一个类来处理特例。

    try {
        MealExpendses expenses = expenseRepotDAO.getMeals(employee.getID());
        m_total += expenses.getTotal();// 如果消耗了餐食,计入总额
    } catch (MealExpensesNotFound e) {
        m_total += getMealPeDiem();// 如果没消耗,将员工补贴计入总额
    }
    

    异常打断了业务逻辑。可以在getMeals()里不抛异常,而是在没消耗餐食的时候返回一个特殊的MealExpense对象(PerdiemMealExpense),复写getTotal()方法。

    MealExpendses expenses = expenseRepotDAO.getMeals(employee.getID());
    m_total += expenses.getTotal();
    
    publc class PerDiemMealExpenses implements MealExpenses {
        public int getTotal() {
            //return xxx; //返回员工补贴
        }
    }
    

    7.别返回null值

    • 返回null值只要一处没检查null,应用程序就会失败
    • 当想返回null值的时候,可以试试抛出异常,或者返回特例模式的对象。

    8.别传递null值

    • 禁止传入null值。

    七、边界

    包装第三方的API,降低依赖。

    1.避免公共API返回边界接口,或者将边界接口作为参数传递给API。将边界保留在近亲类中。

    2.不要在生产代码中试验新东西,而是编写测试来理解第三方代码。

    3.避免我们的代码过多地了解第三方代码中的特定信息。

    八、单元测试

    单元测试准则

    九、类

    1.类的结构组织(顺序):

    • 公共静态常量
    • 私有静态变量
    • 私有实体变量
    • 公共函数
    • 私有工具函数

    2.类应该短小

    • 函数靠行数,类靠权责来衡量。
    • 类名,应描述权责
    • 单一权责原则: 类或者模块应该有一个权责——只有一条修改的理由(A class should have only one reason to change.)。
    • 系统应该由多小类组合****而不是非巨类**组成。
    • 类应该只有少量的实体变量,如果一个类中每个实体变量都被每个方法所使用,则说明该类具有最大的内聚性。创建最大化的内聚类不太现实,但是应该以高内聚为目标,内聚性越高说明类中的方法和变量互相依赖、互相结合形成一个逻辑整体
    • 保持内聚性就会得到许多短小的类

    3.为修改而组织

    • 类应当对扩展开放,对修改封闭(开放闭合原则)

    十、系统

    1.将系统的构造与使用分开

    • 将全部构造过程搬迁到main,涉及系统其余部分时,假设所有对象都已经正确构造。
    • 有时应用程序也需要负责确定何时创建对象,我们可以使用抽象工厂模式让应用自行控制何时创建对象,但构造能力在工厂实现类里,与使用部分分开。
    • 依赖注入(DI),控制反转(IoC)是分离构造与使用的强大机制。

    十一、优良的设计

    1.运行所有测试

    • 紧耦合的代码难以编写测试,测试写的越多,使清理代码、减少耦合

    2.消除重复

    • 两个方法提取共性到新方法中,新方法分解到另外的类里,从而提升其可见性。

    • 模板方法模式,虚函数等是消除重复的通用技巧

    3.表达意图

    • 作者把代码写清晰,其他人理解代码就越快。
    • 请多少尊重一下我们的手艺,花点时间在函数和类上

    4.尽可能少的类和方法

    • 为了保持类和函数的短小,我们可能会早出太多细小的类和方法。
    • 类和方法数量太多,有时是由毫无意义的教条主义导致的。

    十二、并发编程

    1.防御并发代码问题的原则与技巧

    • 遵循单一职责原则。分离并发代码与非并发代码
    • 限制临界区数量、限制对共享数据的访问
    • 避免使用共享数据,使用对象的副本。
    • 线程尽可能地独立,不与其他线程共享数据。

    补充

    函数的10个一

    • 一个变量只有一个用途
    • 一行代码,只写一句语句
    • 一个循环一件事
    • 一种变化,只改一处
    • 函数只有一个职责
    • 圈复杂的应该小于10
    • 独占一行
  • 相关阅读:
    【UOJ #268】【清华集训2016】数据交互(动态DP)
    【UOJ #267】【清华集训2016】魔法小程序(前缀和)
    【UOJ #266】【清华集训2016】Alice和Bob又在玩游戏(SG函数+01Trie)
    【CSP-S 2019题解】
    【CSP 2019游记】
    【CSP-S 2019模拟题解】
    sql语句: update和sql函数的冲突
    http协议之实践巩固(深度篇一)
    不错的开发工具做下记录
    javascrpt之this指向问题
  • 原文地址:https://www.cnblogs.com/yan1345/p/Note_of_Clean_Code.html
Copyright © 2011-2022 走看看