Clean Code 摘录

纸上得来终觉浅 绝知此事要躬行。 Posted by hstk30 on September 17, 2021

Clean Code 摘录

一直以为,Java 是一门非常依赖IDE 的语言。这本书里,Bob 大叔 其实也不断的提及现代工具、IDE 等词。

第二章 有意义的命名

别用accountList 来指称一组账号,除非它真的是List类型。用accountGroup 或 bunchOfAccounts, 甚至直接用accounts都会好一些。 即便容器是个List,最好也别再名称中写出容器类型名

note: 对Python 真的适用这个规则吗?

并不非常同意2.7.2 成员前缀 的观点,在c 中,我看到的开源项目cPython Linux 都大量的在命名中加入类型前缀或后缀, 对于自己在Python 开发时,因为Python 是动态语言,因此自己也会在命名前加入类型前缀,如l_, d_, s_, 来表示常见的类型 list, dict, set, 当然s_ 可能被理解为str

读后感

  1. 命名当然是很关键,尤其是和别人讨论时。
  2. 在代码方面,书本提供的代码,其实很大一部分可以归结为: 避免在代码中直接使用常量,试着给每个常量一个有意义的名称, 这样不管对于阅读代码时搜索变量,还是阅读时对代码的理解都是极好的。

第三章 函数

函数参数尽可能的少,从编写测试用例的角度看,确实如此。每个参数都有正常情况和多个异常情况,多个参数的函数,就需要排列组合个 测试用例来完全覆盖这个函数,确实令人头大。

标识参数丑陋不堪。向函数传入布尔值简直就是骇人听闻的做法。这样做,方法签名立刻变得复杂起来,大声宣布本函数不止做一件事。

简单的重构为 if true: do_something() else: do_other() 更加直接。

函数应该只做一件事。错误处理就是一件事。

3.12 如何写出这样的函数

写代码和写别的东西很像。在写论文或文章时,你先想什么就写什么,然后再打磨它。 初稿也许粗陋无序,你就斟酌推敲,直到到达你心目中的样子。
我写函数时,一开始都冗长而复杂。有太多缩进和嵌套循环。有过长的参数列表。 名称是随意取得,也会有重复的代码。不过我会配上一套单元测试,覆盖每行丑陋的代码。
然后我打磨这些代码,分解函数、修改名称、消除重复。我缩短和重新安置方法。有时我还拆散类。同时保持测试通过。
最后我遵循本章列出的规则,我组装好这些函数。
我并不从一开始就按照规则写函数。我想没人做得到。

读后感

  1. 函数的参数当然是越少越好,这样也能体现一个函数只做一件事的原则,但是有时候一个复杂的类的初始化确实很难做到。
  2. 最后一段讲的真的非常棒。没人能在一开始就实现一个完美的、符合规则的函数。先实现一个可以跑的,再不断的分解它、优化它。

第四章 注释

注释不能美化糟糕的代码。很多时候,只需要创建一个描述与注释所言同一个事物的函数即可。

TODO 表示 我知道这里有问题,是可以改进的,但是我懒得改 😄。不过,还是要抽空定期查看todo listdo 一些。

读了这章,感觉作者有点认为注释是弊大于利的。但是,其实我觉得自己注释写的还是比较多的, 所以总想找个不能用代码来阐明注释的例子来反驳下, 其实工作中还是会经常遇到一些奇葩的需求,但是放到这里有点不合适。 不过最后作者还是举了一个算素数时取平方根作为循环限制的例子,来说明注释的必要性。 除去那些废话式、多余式的注释外,我觉得想要注释能发挥它的作用,就需要像维护代码一样维护注释, 保持代码整洁,也要保持注释整洁, 这其实就要求所有的开发者都有这样的意识。 否则,只要其中一人在修改代码后没有更新注释,可能就会误导后面的人。

尤其在一些代码都是拷贝粘贴、又只改变一点点的时候,被拷贝代码里的注释往往让以后的使用、维护者难受。 这种代码往往是一些脚本代码,属于“食之无味,弃之可惜”的,自己不想写,就直接复制一份差不多的小改一下。

第五章 格式

  1. 主次分明,在源文件最顶部给出高层次概念和算法。具体细节在下面渐次展开。
  2. 善用空行,用空行来划分具体函数内的层次,像C 一般将局部变量定义放在函数最前面,然后空一行写具体逻辑,最后空一行returnPython 虽然推荐在需要使用变量的时候再定义它,但是一些具体变量,如打开文件,初始化类等都可以放在函数的开头,一些临时的变量, 如for 循环的index 计数变量,就放在对应代码块的前面即可。这样划分函数中的代码也有利于抽象出更小的函数。

每个程序员都有自己喜欢的格式规则,但如果在一个团队中工作,就是团队说了算。

个人感悟

第六章 对象和数据结构

过程式代码(使用数据结构的代码)便于在不改动既有数据结构的前提下添加新函数,面向对象代码便于在不改动既有函数的前提下添加新类。 过程式代码难以添加新数据结构,因为必须修改所有函数。面向对象代码难以添加新函数,因为必须修改所有类。

过程式面向对象 没有好坏,根据具体的工作性质选择合适的即可。

note: 说实话,隐藏实现的思路没看太懂┓( ´∀` )┏

得墨忒耳律

得墨忒耳律认为,类C的方法f 只应该调用以下对象的方法:

  • C
  • 有f 创建的对象
  • 作为参数传递给f 的代码
  • 有C 的实体变量持有的对象

方法不应调用有任何函数返回的对象的方法。换言之,只跟朋友谈话,不和陌生人谈话。 也可以说是,只调用和类C 关联依赖自身 的方法。

避免以下代码:

final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();

第七章 错误处理 第八章 边界

略过

第九章 单元测试

构造-操作-检验(BUILD-OPERATE-CHECK)模式。

  • 构造测试数据
  • 操作测试数据
  • 检验操作是否得到期望的结果

最重要的要求:可读性、可读性、可读性维护、维护、维护。 测试代码和生产代码一样重要,它需要被思考、被设计和被照料。 它该像生产代码一般保持整洁。

反思

之前,基本也是按这些规则写的单元测试,除了维护,基本上平均每个函数有对应3个测试用例(成功,失败,异常,边界情况), 但是因为很多都是涉及数据库相关的操作,在测试对应函数前,需要先初始化一个数据库环境(主要是数据)。 而你插入数据后必定需要查询对应数据变更是否符合预期,所有就需要很多与数据库操作相关的测试代码。 而且,还得模拟数据库的数据,为测试准备甚至不止一份的数据。最后,单元测试确是变得不太好维护了。修改对应函数时, 需要对应维护这个函数涉及的数据库表的数据。关于这个问题,其实我一直都有关注,但是至今还没有得到非常好的思路。

2023 更新:把数据库操作包装成 DAO (Data Access Object)对象,然后在单元测试中Mock DAO 中测试函数使用的方法, 使得跑单元测试时并不依赖具体的数据库,对于算法模型的处理也类似。

第十章 类

还是短小、短小、短小

单一权责原则(SRP)认为,类或模块应有且只有一条加以修改的理由。这个应该算是OOP设计中最为重要的概念之一了。 但是,我基本上在开始设计 、构思和写代码之前都不会刻意去考虑这个原则,只有在blame 别人代码时才想到这个原则😬。 不过,幸好我还是有自己的一套设计方法,而且,竟然和Bob 大叔 描述的差不多,即10.2.3 保持内聚性就会得到许多短小的类

基本步骤:

  1. 先按3.12 如何写出这样的函数 中的方法,先试着写个大函数。其实都不用等写完这个大函数,你基本就有相应的拆分成小函数的欲望了, 再加上单元测试的驱动,拆分成为必然。
  2. 在将大函数拆成多个小函数时往往就需要传递相应的参数变量,你还会发现,这些小函数还往往有多个相同的参数变量, 这个时候就是创建一个类的时候了(其实就是创建了一个命名空间,使得这些函数共享这些变量)。
  3. 最后,再加上单元测试的驱动,看看能不能改进一下,是不是有的函数不太好做单元测试,如果不好做单元测试,说明代码还有改进的地方。

想不到,我自己的一套居然和Bob 大叔 的如此相似。

第十二章 迭进

  • 运行所有测试
  • 不可重复
  • 表达了程序员的意图
  • 尽可能减少类和方法的数量

不想看了