7

《代码整洁之道》

 3 years ago
source link: https://blog.duval.top/2020/11/14/%E4%BB%A3%E7%A0%81%E6%95%B4%E6%B4%81%E4%B9%8B%E9%81%93/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

如何写出整洁代码是一名优秀程序员的必修课。

整洁的代码有助于项目提高迭代质量,减轻历史包袱;同时也能让别人更易看懂代码,促进团队协作分工。

《代码整洁之道》一书里边有不少观点值得采纳。本文摘录书本主要内容,以备后忘。详细建议阅读原著。

有意义的命名

  • 命名应当名副其实,能准确体现对象的含义;
  • 避免误导性命名。比如:不要使用”l”、”1” 以及其他专有名称;
  • 命名应该做有意义的区分。比如:ProductInfo 、ProductData和Product没区别,moneyAmount和money没区别;
  • 使用可以读出来的命名。比如:使用generationTimestamp,而不是genymdhms;
  • 使用可以搜索的命名。比如:”e” 或者魔法数在代码中难以搜索;
  • 避免使用编码(包括匈牙利标记法、成员前缀或接口实现标记等等)。比如:IShapeFactory无论是作为子类或者其实现,其首字母的标记”I”都是累赘的,应该使用ShapeFactory;
  • 类名或者对象名应该是名词,如:Customer、WikiPage、Account、AddressParser。避免使用含义不确切的名词:Manager、Processor、Data、Info等,更不应使用动词;
  • 方法名应该是动词或者动词短语;
  • 添加有意义的语境。比如:firstName、secondName、street、houseNumber等零散字段,可以增加前缀”addr-“作为语境,明确暗示它们构成一个地址;更好的办法是封装为Address类;
  • 不要添加无用的语境。比如开发一个名为GSD的系统,不应该给所有的类都加上GSD前缀;
  • 函数第一规则:短小
  • 函数的缩进层次要控制的两层之内;
  • 函数应该做一件事,做好这件事,只做一件事;
  • 使用抽象工厂类隐藏switch语句;
  • 使用描述性命名,长而具有描述性的命名,要比短而令人费解的命名要好;
  • 函数参数越少越好,当参数数量需要两个、三个或三个以上的时候,就说明其中一些参数可能应该封装为类;
  • 标志参数丑陋不堪,不应该向函数传入布尔值,而是拆分为两个方法。比如:setStatus(Boolean start) 应该拆分为 start() 和 stop();
  • 分隔询问和指令。比如if (set(“username”, “unclebob”)… 应该拆分为:
          if (attributeExists("username") {
              setAttribute("username", "unclebob");`
              ...
          }
  • 使用异常替代返回错误码;
  • try/catch代码块会搞乱代码结构,应该将try和catch代码块的主体部分抽离出来形成独立函数。比如:
          try {
              deletePageAndAllReferences(page);
          } catch (Exception e) {
              logError(e);
          }
  • 避免重复的代码;
  • 恰当的注释是用来弥补我们用代码表达意图时所遭遇的失败。如果发现自己需要写注释,先复盘下是否可以通过代码表达而不是注释;
  • 注释不能美化糟糕的代码。与其花时间编写解释糟糕代码的注释,还不如花时间清理下糟糕的代码;
  • 通过良好的代码来阐述逻辑。比如:
    • 良好代码:
        if (employee.isEligibleForFullBenefits())
    • 而不要采用糟糕注释:
        // check to see if the employee is eligible for full benefits
        if ((employee.flags & HOURLY_FLAG) && employee.age > 65))
  • 好注释
    • 提供信息的注释
    • 对意图的解释
    • TODO注释
    • 公共API的Javadoc
  • 坏注释
    • 多余的注释
    • 误导性注释
    • 循规式注释
    • 日志式注释
    • 废话式注释
    • 注释掉的代码
    • HTML注释
    • 非公共API的Javadoc
  • 单个文件控制在恰当的长度(比如200~500行);
  • 源文件应该像报纸一样。名称应该简单且一目了然。名称本身应该足够告诉我们是否在正确的模块中。源文件最顶部应该给出高层次概念和算法。细节应该往下渐次展开,直至找到源文件中最底层的函数和细节。
  • 源文件内部在垂直方向上,不同概念之间应该有间隔。比如:import块、import static块、变量定义、方法区等等,它们之间应该有空行间隔;
  • 紧密相关的代码应该相互靠近。比如:
    • 不必在变量之间插入无用的注释;
    • 变量声明应该尽可能靠近其实用的位置;
    • 循环中的控制变量应该尽量在循环语句中声明;
    • 被调用的函数应该在调用函数的下方;
    • 概念相关的代码应该放在一起;
  • 每行代码长度控制在120个字符以内;
  • 代码行中恰当使用空格。比如:逗号后加一个空格;
  • 统一的缩进风格;

对象和数据结构

  • 数据抽象以避免曝露数据细节,以抽象形态表述数据;
  • 过程式代码便于在不改动既有数据机构的前提下添加新函数。面向对象代码便于在不改动既有函数的前提下添加新类;
  • 反之亦然,过程式代码难以添加新数据结构,因为必须修改所有函数。面向对象代码难以添加新函数,因为必须修改所有类;
  • 使用异常而非返回码;
  • 不要在方法中返回null值,而是抛出异常或返回特例对象;
  • 不要向方法传递null值;
  • 不要遵循TDD三定律(所谓的编写生产代码前先编写单元测试);
  • 保持测试代码的整洁,抽象隐藏测试准备细节,突出测试逻辑;
  • 单元测试中优先考虑简介,再考虑执行效率;
  • 每个测试有且只有一个断言;
  • 类应该保持短小;
  • 类应该遵守单一权责原则(SRP);
  • 类内部应该保持高内聚(保持每个方法都操作内部的一个或者多个变量);
  • 系统的构造和使用分开的办法:

    • 分离构造过程,创建系统所需的对象,并传递给应用程序,应用程序只管使用;
    • 在应用程序中使用抽象工厂方法来隐藏构建细节;
    • 使用依赖注入来分离构造和使用;
  • Java AOP三种机制:

    • Java代理,常用的字节码操作库有:CGLIB、ASM、Javassist;
    • 纯Java AOP框架;
    • AspectJ;
  • 运行所有测试;
  • 不可重复;
  • 调整代码,确保具有良好的表达力;
  • 尽可能少的类和方法;
  • 对象是过程的抽象,线程是调度的抽象;
  • 并发防御性原则
    • 单一权责原则;
    • 限制数据作用域;
    • 使用数据副本;
    • 线程应尽可能独立;
  • 了解Java并发安全库;
  • 了解并发执行模型;
  • 保持同步区域微小;

味道与启发

  • 不恰当的信息
  • 废弃的注释
  • 冗余的注释
  • 糟糕的注释
  • 注释掉的代码
  • 需要多步才能实现的构建
  • 需要多步才能做到的测试
  • 过多的参数
  • 标志参数(布尔值参数)
  • 不被调用的死函数

一般性问题

  • 一个源文件存在多种语言
  • 明显的行为未被实现
  • 不正确的边界行为
  • 忽视安全(防御性编程)
  • 在错误的抽象层级上的代码
  • 基类依赖于派生类
  • 信息过多(类中方法过多、函数变量过多等等)
  • 不执行的代码
  • 垂直距离过大(变量、函数应该在靠近使用的地方定义)
  • 前后不一致(不同地方的相似概念应该用相似一致的命名,如方法名processVerificationRequest和processDeletionRequest)
  • 混淆视听的代码(如:没有实现的默认构造函数、没用的变量、无调用的函数、无信息量的注释)
  • 人为耦合。(不相互依赖的东西不应该耦合)
  • 特性依恋。(类的方法只应对所属类中的变量和函数感兴趣,不应该垂青其他类中的变量和函数)
  • 选择算子参数。选择算子可能是boolean、枚举、整数等形式,通常可以拆分为多个小函数
  • 晦涩的意图,包括联排表达式、匈牙利语标记法、魔术数等
  • 位置错误的权责
  • 不恰当的静态方法。如果的确需要静态方法,那么请确保没机会打算让它有多态行为
  • 使用解释性变量
  • 函数名称应该表达其行为
  • 把逻辑依赖改为物理依赖。(比如某些写死的静态变量,改为从实体的get方法获取)
  • 用多态替代If/Else或Switch/Case
  • 团队成员应遵循标准约定
  • 用命名常量替代魔术数
  • 封装布尔条件
  • 避免否定性布尔条件
  • 函数只做一件事
  • 掩蔽时序耦合
  • 封装边界条件(比如level+1、level-1)
  • 函数应该只在一个抽象层级
  • 在较高层级放置可配置数据
  • 通过使用通配符避免过长的导入清单(import)
  • 不要继承常量
  • 优先使用枚举而不是常量
  • 采用描述性名称
  • 名称应该与抽象层级相符
  • 尽可能使用标准命名法
  • 无歧义的名称
  • 作用范围较大的名称选用长名称(范围小的可以用短名称,比如循环变量i)
  • 避免编码式命名(比如匈牙利语标记法等)
  • 名称应该说明副作用(比如createOrReturnOos优于getOos)
  • 使用覆盖率工具包
  • 别略过小测试
  • 被忽略的测试就是对不确定事物的疑问
  • 测试边界条件
  • 全面测试相近的缺陷
  • 测试失败的模式有启发性
  • 测试覆盖率的模式有启发性
  • 测试应该快速

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK