Clean Code读后总结——前进者的基石

前言

作为一个程序员,再职业生涯中会写很多很多万行代码。感觉学习写代码的过程很像学习写作的过程,写大型软件就是在写一本长篇小说,那么如何定义好的代码,感觉就像是问说怎么写出好的文章。
那么什么是好的代码呢?对于产品人员来说,好的代码是只要能运行,不出现bug的就是好的代码,也就是所谓的说文以载道,诗以言志,至于辞藻是否华丽,不是最重要的。
但是别忘了,写代码的时间可能发现在那一刻,但是我们后面阅读自己代码的却经常进行,如果结构混乱,词不达意那么就往往会发生写到最后发现前面的情节都不慎了之了。
所以,写出好的代码的一个目的在于如何减少代码维护的成本,理解代码是其中之一。
另外,我们要知道没有人能够在跨度周期在几个月,甚至一年的时间内知道自己软件最后会具体怎么一个呈现。业务都是不尽相同的,根据排列组合我们没有办法一开始就穷尽所有的变化,所以我们总是会在原有代码的基础上不断修修改改,正如很多小说一样,一气呵成总是发生在极少数天才那里,即使是如红楼梦一样的惊世之作,也是批阅十载。
所以,总的来说,好的代码的原则也就是两点,一个是容易阅读,一个是容易修改。

相关内容

有意义的命名

程序代码里面除了一些约定熟成的关键字,大部分的英文字符都是被变量、函数、参数、类等构成的,而这些都是我们自己所赋予的。所以,容易阅读是命名最关注的东西,常见的一些tips如下:

  • 不要使用误导性的词语来进行命名,例如list,;,o等。
  • 在对同类型数据进行区分的时候,不要使用数字来进行。
  • 可搜索也是命名关注的点,无论是变量名还是文件名,包名等
  • 对于强类型语言来说,当前的编译环境来说(不用运行就能检测出类型错误),类型编码已经淘汰。
  • 成员前缀也是不必要的,因为都是看有意义的字段。
  • 类名和对象名应该是短语,而不应该是动词。如Customer等
  • 方法名应该是动词或动词短语。如postPayment。
  • 对于程序中经常出现的概念,取一个一以贯之的词,例如xx控制器,可以一直用controller,或者一直用manager。

函数

函数是对于程序逻辑的第一层封装,因此,同样也遵循易读,易修改原则,常见的一些tips如下:

  • 函数长度不宜过长,主要是容易阅读,同时函数越短,功能越集中,名字也更好取,更容易抽象,长而有描述性的名字比短名称好
  • 函数只做一件事,这是出于高内聚低耦合的考虑,同时也让代码更易修改,一处的改动不会影响到其他部分
  • 自顶向下阅读代码,这是处于易读的考虑,因此在对函数进行编写的时候,可以从上到下依次编写对应的抽象,再紧接着对应的实现。
  • 对于多态情况下的switch使用,对于对象不同行为的实现方式根据对象种类的不同会有n种 switch语句和对应的分支,同时违反了单一职责原则(多个修改的理由),以及开闭原则(添加和修改没办法解耦)。解决方法可以利用工厂模式,也就是对不同对象的行为定义一个统一的接口或者抽象类,将switch分支封装在工厂类下。
  • 函数参数一般是0-2最好,一个是处于理解成本的考虑,还有一个最重要的原因是多参数对于测试代码组合带来的复杂度。
    • 单参数:一般单参数有几种情况,一个是判断参数的问题,一个是将参数进行转换为另一种类型,还有就是事件。但是,对于一种传入bool的标识参数来说,这意味着在true和false的情况下都对应着不同的处理逻辑,也就是函数不只是做一件事,这是应该进行拆分为两个函数分开处理。
    • 双参数:双参数主要是注意两个参数的相对顺序,以及是否可以将两个参数转换为一个参数(成员)等
    • 参数列表:当函数需要两个及以上的参数类型,且这些组合经常出现的话,就意味着这写参数可以被封装为类了,这意味着对这组参数的封装。而对于可变参数类型,如果超过三个,那么和直接传递list类型的单个参数也没有分别,所以一般是1-3
    • 命名:对于单参数,直接采用动名词形式就好,对于双参数类型,可以在命名时记录参数的相对顺序。
    • 输出参数:在oop出现前,存在着向函数中传递参数的时候,也就是 方法名(参数),但是在oop后,可以直接利用this来代替输出参数,也就是利用 对象.防方法()的形式。
    • 在异常处理的时候,直接返回错误码会造成更深的嵌套结构,直接 try-catch返回异常就好,同时为了避免异常处理与正常流程混在一起,可以把try-catch的代码块主体抽离出来,另外形成函数。

对象与数据结构

  • 数据抽象: 不愿意暴露具体操作数据实现的逻辑可以利用函数进行一次封装。
  • 过程式代码(数据结构代码)和面向对象代码的不同在于过程式代码可以在不修改原有代码的基础上新增方法,而面向对象更适用于新类型的添加。
  • 数据传送对象:DTO,只有公告变量,没有函数的类。通常用于与数据库通信的部门。

  • 类的抽象也应该符合单一职责,也就是只有一个修改的理由。理想的状态是多个短小的类互相协同而不是少数巨大的类。
  • 类的内聚一方面体现在内部方法对于内部变量操作的程度,操作的变量越多,说明变量越黏聚在类上,这将得到许多短小的类。
  • 隔离依赖主要在于怎么对容易变更的部分进行抽象,提供接口等等。

系统

  • 将系统的构造与使用分开,否则如果在程序中有许多种类似的情况,根据全局设置策略就会缺乏模块组织性,通常也会有很多重复代码。
  • 一个方法是将全部的构造过程搬迁在main函数中进行处理 ,mian函数创建系统需要的对象,再传递给应用程序,只管使用。
  • 对于大型软件的编写来说,一开始就将所有的组件和系统构造得很好是很难的,我们应该只去实现今天的用户故事,在后续再不断地扩展系统,实现新的用户故事,这也是迭代和重构。

迭进

这一部分其实主要就是说一点,在代码和模块越来越多的时候也,不断重构。

代码的坏味道

  • 注释
    • 不恰当的信息:无用的,重复的
    • 废弃的注释:过时的注释意味着代码的跌进后注释并没有怎么改变
    • 注释掉的代码
  • 环境
    • 多步实现的构建 : 单个命令进行系统的签出
    • 需要多步才能做到的测试:应该能够使用单个指令就可以运行全部的单元测试
  • 函数
    • 过多的参数:参数0-3差不多
    • 输出参数:如果函数要修改什么变量的状态,直接修改它所在的对象的状态就好了
    • 布尔值参数:这使用函数不止做了一件事
  • 一般性问题
    • 不正确的边界行为:在开发的时候,不要只写出能工作的函数,也要考虑或证明代码在所有的边界情形下都能真正工作。
    • 重复:重复的代码可能会是一个子程序或者一个类,出现在概念上重复的代码意味着抽象的遗漏。
    • 在错误的抽象层级上的代码:在继承关系中,注意将较低层级概念放在派生类中,较高层级概念放在基类中。比如说只与细节实现有关的常量,变量或者工具函数不应该在基类中出现。软件设计要求分离不同层级的概念,放在不同的容器中,这些容器有时是基类或派生类,有时是源文件模块或组件,不管如何,底层级概念不能和高层级概念混在一起。
    • 基类依赖派生类:主要在有限状态机的实现中会出现这样的问题。
    • 位置错误的权责:例如很多变量应该放在哪里的问题,根据最小惊异原则,变量应该放在读者期望它在的地方,或者看函数名称。
    • 不恰当的静态方法:静态方法不应该在单个实体上操作,而更多作为一个工具函数的作用不与特定对象绑定,另外对于一些需要多态实现的方法,也应该使用非静态函数。
    • 函数名称应该表达其行为
    • 用多态替代if/else或者switch/case:在使用switch之前,先考虑使用多态。或者在case语句中,创建多态对象,避免对于多个方法选择上使用多个switch的情况。
    • 封装条件:将关于条件判断的表达式进行封装会更容易理解
    • 避免否定性条件:使用肯定性条件会更好
    • 函数应该只做一件事:函数不应该只做一件事,应该转换为多个更小的函数
    • 掩盖时序耦合:这种情况下,使用有参返回也可以。
    • 在较高层级上放置可配置数据

总结

在编写大型软件的时候,我们常常对于过高的复杂度以及过高的理解成本而望而却步,这直接影响了我们代码跌进的速度和效率,软件开发是一个增量跌进的过程,因此,降低每一个环节的理解成本和减少每一个模块之间的耦合就是我们的终极目的,也是一个良好的编码习惯的养成。在这个基础上,去看看其他更美丽的风景吧。

你可能感兴趣的:(撸书之旅,开发语言,编程)