程序员应该如何写优雅代码,整洁代码,迭代优化 任重道远

如何让自己的代码更加优雅,如何让自己的代码越来越健壮,如何让自己跳出天天都在处理bug的怪圈。如何让自己过了2个月后看自己编辑的代码依然轻轻松松就知道它是干嘛的。

一个开发了50年的老程序员Bob大叔告诉你就得这么干。

内容整理自Robert C. Martin的《代码整洁之道》

 

第一章 整洁代码

1,整洁代码力求集中,每个函数、每个类和每个模块都全神贯注于一件事。

2,整洁代码简单直接,从不隐藏设计者的意图。

3,整洁代码应当有单元测试和验收测试。它使用有意义的命名,代码通过其字面表达含义。

4,消除重复代码,提高代码表达力。

5,时时保持代码整洁。

 

第二章 有意义的命名

1,使用体现本意的命名能让人更容易理解和修改代码。

2,编程本来就是一种社会活动。

3,尽力写出易于理解的代码

 

第三章 函数

1,一个函数应该只做一件事(高内聚),无副作用。

2,自顶向下阅读代码,如同是在阅读报刊文章。

3,长而具有描述性的函数名称,好过描述性的长注释。

4,少用输出参数。

5,拒绝boolean型标识参数。

例: CopyUtil.copyToDB(isWorkDB) --> CopyUtil.copyToWorkDB(), CopyUtil.copyToLiveDB()

6,使用异常代替返回错误码,错误处理代码就能从主路径代码中分离出来得到简化。

7,写代码很像是写文章。先想怎么写就怎么写,然后再打磨:分解函数、修改名称、消除重复。

8,编程其实是一门语言设计艺术,大师级程序员把程序系统当做故事来讲。使用准确、清晰、富有表达力的代码来帮助你讲故事。

 

第四章 注释

1,别给糟糕的代码加注释----重写吧。

2,把力气花在写清楚明白的代码上,直接保证无需编写注释。

3,好的注释:

法律信息

提供信息

解释意图

警示

TODO注释

 

第五章 格式

1,代码格式很重要。代码格式关乎沟通,而沟通是专业开发者的头等大事。

2,向报纸格式学习代码编写。

 

第六章 对象和数据结构

1,对象把数据隐藏于抽象之后,只提供操作数据的函数。

数据结构暴露其数据,没有提供有意义的函数。

2,The Law of Demeter:模块不应去了解它所操作的对象内部细节。

 

第七章 错误处理

1, 使用异常而非返回错误码.

2, try-catch-finally, log出错信息.

3, 不要返回null,不要传递null。

NULL Object模式, 例:Collections.emptyList();

 

第十章 类

1,自顶向下原则:让程序读起来就像是一篇报纸文章。

2,method可以是protected,以便于单元测试。

3,SRP:类或模块应有且仅有一个加以修改的原因。类名应准确描述其职责。高内聚。

4,开放闭合原则OCP、依赖倒置原则DIP

5,变量名、方法名、类名都是给代码添加注释的一种手段。

 

第十二章 迭代前进

1, 紧耦合的代码难以编写单元测试。

2,单元测试消除了对清理代码会破坏代码的恐惧。

3,写出自己能理解的代码很容易,软件项目的主要成本在于长期维护。

4,代码应当清晰表达其作者的意图;测试代码可以通过实例起到文档作用。

 

第十四章 逐步改进

1,编程是一种技艺。要编写整洁代码,必须先容忍脏代码,然后清理!

2,写出好文章就是一个逐步改进的过程

 

第十五章 JUNIT内幕

1.条件判断应该封装,从而更清晰的表达代码意图

2.函数变量不要和成员变量同名

3.否定式比肯定式更难理解

4.建议每个函数的定义都正好在其被调用的位置后面

 

 

 

C1:不恰当的信息

修改历史记录只会用大量过时而无趣的文本搞乱源代码文件。通常作者,最后修改时间等元数据不应该在注释中出现。注释只应该描述有关代码和设计的技术性信息

 

C2:废弃的注释

过时、无关和不正确的注释就是废弃的注释。如果发现废弃的注释,最好尽快更新或删除掉。

C3:冗余注释

如果注释描述的是某种充分自我描述了的东西,那么注释就是多余

i++;//increment i

 

C4:糟糕的注释

如果要编写一条注释,就花时间保证写出最好的注释

 

C4:注释掉的代码

看到注释掉的代码,就删除它。放心源代码控制系统还会记住它

 

 

环境

E1:需要多少步才能实现构建

应当单个命令签出系统,单个命令构建它

E2:需要多少步才能做到的测试

发出单个指令就能运行全部单元测试

 

函数

F1:过多的参数

函数的参数量应该少。没参数最好。三个以上的参数非常非常值得质疑

F2:输出参数

输出参数违反直觉。期望参数用于输入而非输出。如果函数非要修改什么东西的状态不可。就修改它所在对象的状态就好

F3:标识参数

boolean参数大声宣告函数做了不止一件事。他们令人迷惑应该消灭掉

F4:死函数

永远不会调用的方法应该丢弃。保留死代码纯属浪费。别害怕删除函数。记住,源代码控制系统还会记住它

 

17.4一般性问题。

G1:一个源文件存在多种语言

理想的源文件包括且只包括一种语言。现实上,我们可能会不得不使用多于一种语言。单应该尽力减少源文件中额外语言的数量和范围。

G2:明显的行为未被实现

如果明显的行为未被实现,读者和用户就不能再依靠他们对函数名称的直觉。他们不再信任原作者,不得不阅读代码细节。

G3:不正确的边界行为

每种边界条件、每种极端情形、每个异常都代表了某种可能搞乱优雅而直白的算法的东西。别依赖直觉。追索每种边界条件,并编写测试。

G4:忽视安全。

关闭失败测试,告诉自己过后再处理,这和假装刷信用卡不用还钱一样坏。

G5:重复

每次看到重复代码,都代表遗漏了抽象。重复的代码可能成为子程序或干脆是另一个类。将将重复代码叠放进类似的抽象,增加了你的设计语言的词汇量。其他程序员可以用到你创建的抽象。编码越来越快,错误越来越少。因为你提升了抽象层级。

1.重复最明显的形态就是你不断看到明显一样的代码,可用单一方法来替代之。

2.较隐蔽的形态是不同模块不断重复出现、检测同一组条件的switch/case或if/else链。可以用多态替代之。

3.更隐蔽的形态是采用类似算法但是具体代码 行不同的模块。这也是一种重复,可以使用模板方法或者策略模式来修正。

G6.在错误的抽象层级上的代码

所有较低层级的概念放在派生类中,所有较高层级概念放在基类中。

例如:只与细节实现有关的常量、变量或工具函数不应该在基类。基类应该对这些东西一无所知。

较低层级概念和较高层级概念不应该混在一起。不能就错误放置的抽象模型撒谎。孤立抽象是软件开发者最难做到的事之一,而且一旦做错也没快捷的修复手段。

G7:基类依赖于派生类

基类应该对派生类应该一无所知。

有例外:有时,派生类数量严格固定,而基类中拥有派生类之间选择的代码,在有限状态机的实现中这种情形很多见。

G8:信息过多

设计良好的模块有着非常小的接口,让你事半功倍。设计良好的接口并不提供许多需要依靠的函数。耦合度就低。

优秀的程序员学会限制类或模块中暴露的接口数量。类中的方法越少越好。函数知道的变量越少越好。类拥有的实体变量越少越好。

隐藏你的数据,隐藏你的工具函数,隐藏你的常量和临时变量。不用创建大量方法或大量实体变量的类。不要为子类创建大量受保护变量和函数。尽量保持接口紧凑。

G9:死代码

删掉。

G10: 垂直分隔

变量和函数应该在靠近被使用的地方定义。本地变量应该正好在其首次被使用的位置上面声明,垂直距离要断。本地变量不应该在被使用之处几百行以外声明。

私有函数应该刚好在其首次被使用的位置下面定义。

G11:前后不一致。

如果在特定函数中用名为response的变量来持有HttpServletResponse对象,则在其他用到HttpServletResponse对象的函数中也用同样的变量名。

前后一致,一旦坚决贯彻。就能让代码更加易于阅读和修改。

G12:混淆视听

没有实现的默认构造器有什么用处呢。他只会用无意义的杂碎搞乱对代码的理解

没有用到的变量,从不调用的函数,没有信息量的注释等等,这些都是应该移除的废物。

保持源文件整洁,良好地组织,不被搞乱。

G13:人为耦合

不互相依赖的东西不应该耦合。

对于在特殊类中声明一般目的的static函数。普通的枚举类不应该在特殊类中包括。

花点时间研究应该在什么地方声明函数、常量和变量。不要为了方便随手放置。然后置之不理。

G14:特性依恋

类的方法应该只对其所属类的变量和函数感兴趣,不应该垂青其他类的变量和函数。但是有时也无法避免,可以适当妥协。

G15:选择算子参数。

没有什么比在函数末尾遇到一个fasle参数更为可憎的事情。遇到整个情况,可以将这个函数切割成多个函数。

使用多个函数通常优于向单个函数传递某些代码来选择函数行为。

G16: 晦涩的意图。

 

G17:位置错误的权责

代码应该放在读者自然而然期待它所在的地方。

G18:不恰当的静态方法

如果用静态函数,确保没机会打算让它有多态行为。

G19:使用解释性变量

让程序可读的最有力方法之一就是将计算过程打散成在用有意义的单词命名的变量中放置的中间值。

G20:函数名称应该表达其行为。

如果函数向日期添加5天并且修改该日期,就应该命名addDaysTo或者increaseByDays

如果函数返回一个表示5天后订单日期,而不修改日期实体就该叫做daysLater或daysSince。

如果你必须查看函数的实现(或文档)才知道他做什么,就该换个更好的函数名。

G21:理解算法

在你认为自己完成某个函数之前,确认自己理解了它是怎么工作的。通过全部测试还不够好,你必须知道解决方法是正确的。

获得这种知识和理解的最好路径,往往是重构函数。得到某种整洁而足具表达力、清楚呈示如何工作的东西。

G22: 把逻辑依赖改为物理依赖

G23:用多态替代if/else 或则switch/case

G24:遵循标准约定

推荐《阿里巴巴java开发手册》

G25:用命名常量替代魔术数

G26:准确

1.期望某个查询的第一次匹配就是唯一匹配可能过于天真

2.用浮点数标识货币几乎就是犯罪了。等等

在代码中做决定时,要确认自己足够准确。明确自己为和要这么做。如果遇到异常如何处理。

如果你打算调用可能返回null的函数,确认自己检查了null值

如果查询你认为是数据库唯一的记录,确保代码检查不存在其他记录。

G27:结构甚于约定

坚守结构甚于约定的设计决策。

比如用到良好命名的枚举的switch/case要弱于用于抽象方法的基类。没人会被强迫每次都以同样方式实现switch/case语句,但基类却让具体类必须实现所有的抽象方法。

G28:封装条件

如果没有if或while语句的上下文,布尔逻辑就难以理解。应该把解释了条件意图的函数抽离出来。

G29:避免否定性条件。

否定式比肯定式难明白一些。

G30:函数只该做一件事情。

G31:掩蔽时序耦合。

每个函数都产出下一个函数所需要的结果,这样一来就没理由不按顺序调用了。这点额外的复杂度却暴露了该情况真正的时序复杂性

G32:别随意

G33 :封装边界条件。

边界条件难以追踪,把处理边界条件的代码集中到一处,不要散落于代码中。我们不想见到四处散落的+1 和-1字样。

G34:函数应该只在一个抽象层上。

G35:在较高层级放置可配置数据

G36:避免传递浏览

我们不想让摸个模块了解太多其协作者的信息。

如果A与B协助,B与C协作,我们不想让A了解C的信息。

 

 

java

J1:通过使用通配符避免过长的导入清单。

J2:不要继承常量,别利用继承欺骗编程语言的作用范围规则,应该使用静态导入。

J3:枚举VS常量

名称

N1:采用描述性名称

N2:名称应与抽象层级相符

N3:尽可能使用标准命名法

N4:无歧义的名称

N5:为较大作用范围选用较长名称

N6:编码编码:不要用匈牙利命名法玷污你的名称

N7:名称应该说明副作用 不要用名称掩蔽副作用。

不要用简单的动词来描述做了不止一个简单动作的函数。

createOrReturnOos

 

测试

T1:测试不足

T2:使用覆盖率工具

T3:别略过小测试 小测试容易编写,其文档上的价值高于编写成本。

T4:被忽略的测试就是对不确定事物的疑问。

T5:测试边界条件

T6:全面测试相近的缺陷,可能发现缺陷不止一个

T7:测试失败的模式有启发性

T8:测试覆盖率的模式有启发性

T9:测试应该快速。

 

你可能感兴趣的:(code)