代码整洁之道-读书笔记

412 阅读8分钟

整洁代码的定义

  • 一致的风格比“正确”的风格更重要。
  • 代码应当易于理解;
  • 代码的写法应当使别人理解它所需的时间最小化。

命名

  1. 不能提供更多额外信息的词汇不要用。 比如一个 Product 类,还有一个 ProductInfo 和 ProductData,其中 Info 和 data 并不能提供更多的信息。Info和 data 就像是 a\an\the 这种词都是冗余。

  2. 避免误导,就是程序名和变量名要名符其实。比如 OrderList 就应该是一个 list,而不应该是一个对象。函数的名称要和函数所做的动作一致。假如发现无法有很好的函数名称形容这个函数,那说明这个函数做的事情太多了,需要进行拆分。

  3. 名称宁愿长点,也不要缩写成别人无法理解的单词。

  4. 不要前缀! 因为代码读的越多,眼中就慢慢会自动忽略前缀。

  5. 类名和对象名应该是名词或者名词短语。例如:Customer、WikiPage、AddressParser。类名不应该是动词。避免:Manager,Processor 这种类名,因为他是比较笼统的概念。

  6. 方法名应该是动词或者动词短语。如 PostPayment, Delete 。初始化一个类时

    Complex fulcrumPoint = new Complex(20, 30)
        
    //这种方式会更好
        
    Complex fulcrumPoint = Complex.FromRealNumber(20,30)
    

    可以考虑将对应的构造器设为 priate,强制使用各种方式

  7. 每个概念对应一个单词。给每个抽象出来的对象或者操作规定一个名称。比如fetch、retrieve、get他们都是获取的意思时,要给出对应的场景单词。注意区分insert、append并替代add使用。尽可能使用email代替emailAddress,因为后者几乎没有提供比前者更多的信息。

  8. 对于词义比较广泛的,需要进行限制,比如 get、add、manage 等。

    • get 是获取对象属性
    • fetch 从远程获取数据
    • load 从文件或者存储中获取数据
    • query 从数据库里获取数据
    • calculate 计算出来的数值
    • find 从数组中查找数据
    • createparsebuild 从数据生成数据

    常用单词:

    add/remove        increment/decrement       open/close
    begin/end         insert/delete             show/hide
    create/destory    lock/unlock               source/target
    first/last        min/max                   start/stop
    get/put           next/previous             up/down     
    get/set           old/new
    
    新增: add, new, create, make, build, generate
    更新: update, set, transform, 
    获得:get, load, fetch, calculate, find, search, filter, query
    删除:remove, delete, clear, 
    接口:Ajax
    
  9. 很少有名称是可以自我说明的,比如 name, 所以需要有上下文环境,上下文环境可以是名称前缀,当然抽象成类更好。

函数

  1. 函数的第一规则是短小,短才方便阅读、维护和设计。(每个人都经历过读不懂自己代码的尴尬)
  2. 函数只做一件事情。做好这件事情,只做这一件事情,如登录验证时,函数用来验证username和password,在验证之后顺便给用户初始化些其他东西。会导致这个函数在其他时候无法验证用户信息。
  3. 函数的抽象层级要统一,就是函数内操作的颗粒度要统一。比如一个函数要渲染页面, render(View) 是高层次抽象,然后 printString(String) 这里就是底层的实现,他们应该要进行分层。就像我们设置里面,先进入控制面板,然后找到所属的模块,然后细节的操作。
  4. 函数参数,最理想的参数数量是0,应该避免三个参数以上的函数,有足够特殊的里有才能用三个以上的参数。参数太多可以用责任链设计模式。
  5. 禁止标识参数,就是 Flag 或者 Switch,不准像函数传入布尔值。这标明了函数没有做一件事情,假如为 true 会这么做,假如为 false 会这么做。
  6. 如果函数参数看来需要两个或者三个以上,那说明其中一些参数应该被封装成类了。
  7. 函数要么做什么事,要么回答一些事。但二者不可兼得。把动作查询分开。当然有时候动作的结果就是我们查询的对象,这时候直接返回。但是不要既做动作,又对结果进行额外的查询。
  8. try...catch...中的代码块,应该抽离成函数
  9. 不要重复!重复是复杂度的根源之一。很多原则和实践规则都是为了避免重复
  10. 函数名称应该描述清楚函数作用,避免频繁去看文档,这对于短小的函数来说不难办到,如果很难命名可能需要思考函数是否有依照以上原则设计(你一个函数可能做了很多事情)。并且名称的命名应该不容易与其他函数名称形成混淆。如:add()在calculator中意思是加,而在List中就不应该用add表示插入集合了,应该用insert或append。简单来说就是一个概念对应一个词,并且始终如一。

注释

  1. 注释不能美化糟糕的代码!
  2. 注释只应该出现在理解困难或者容易出错的地方,能不用就不要用。
  3. 不准保留注释掉的代码,统统删干净

格式

  1. 统一的缩进格式
  2. 代码行应该尽可能短小,最好不要超过120字
  3. 代码格式要团队进行统一,使用编辑器工具进行限制
  4. 类的组织,首先应该是常量, 然后是属性,然后是公共函数,对应的私有函数要放在调用者附近,公共函数应该在变量之后。

对象和数据结构

  1. 类并不是用取值和赋值将私有变量对外使用。二是要暴露抽象接口,让用户无需了解数据结构就可以使用。
  2. 对象把数据隐藏于抽象之后,暴露操作数据的函数。数据结构暴露起数据结构,没有提供有意义的函数。
  3. 过程式代码(使用数据结构的代码),难以修改数据结构,因为会影响函数,但是容易修改函数。
  4. 对象式代码,难以添加新函数,因为会影响很多类。
  5. 模块不应该了解对象内部的数据结构,对象不应该通过存取器暴露内部结构。
  6. 数据对象,只有公共变量,没有函数的类。被称为数据传送对象。作用就是将原始数据转化为类的操作,比如 ActiveRecord

错误处理

  1. 使用异常而不是返回码。 意思是主函数调用子函数时,子函数不应该返回 true 或者 false 等返回码,这样主函数还需要进行判断,而应该直接抛异常。由统一的错误处理进行处理。
  2. 不要返回 null 值!不要传递 null 值

范围

  1. 一个类要有其对应的范围。不能啥都干。比如 StoreHandler 只应该做返回值是Store 的操作,获取 Store 的 Price 应该放到 PriceHandler
  2. 使用接口类限制边界

书中对于函数的设计其实和类的设计思想是类似的,所以类应该功能单一且小巧,越小耦合性越低,最后用门面模式组合起来向外提供API就好了

  1. 类的单一权责。系统应该由很多短小的类而不是少量巨大的类构成。
  2. Controller 过于臃肿,而且 controller 里面的方法不便于其他 controller 进行调用。所以将 controller 逻辑提取出来,根据函数所获得的结果进行汇总,然后控制逻辑层,防止逻辑层大爆炸。为了防止逻辑层大爆炸,就要求逻辑层有比较想尽的文档,在编写之前可以先查阅文档是否已经存在有类似的函数。对与逻辑层的起名不要包含业务层信息。仅描述对资源的获取,包含三个部分,请求的资源,请求的条件,请求返回的格式

系统

  1. 把系统的整体构造和业务使用分开。不要让构造影响使用,也不要让程序的运行反过来影响构造。这也是Spring这么应用广泛的原因之一,Spring Core就是个类容器。
  2. 把业务逻辑和检查或日志方案分离,不然纠缠在一起的代码会很难看懂和修改。Spring AOP也解决了这个问题。

测试

  1. 测试函数(方法)也应该短小(如果函数原本够小的话测试函数自然会小)
  2. 每个类最好都测试下,测试时间会比以后debug时间少。从底层函数一点点测上去,以后debug能迅速定位。
  3. 测试类应该保存下来,方便每次修改后进行测试