整洁代码的定义
- 一致的风格比“正确”的风格更重要。
- 代码应当易于理解;
- 代码的写法应当使别人理解它所需的时间最小化。
命名
-
不能提供更多额外信息的词汇不要用。 比如一个 Product 类,还有一个 ProductInfo 和 ProductData,其中 Info 和 data 并不能提供更多的信息。Info和 data 就像是 a\an\the 这种词都是冗余。
-
避免误导,就是程序名和变量名要名符其实。比如 OrderList 就应该是一个 list,而不应该是一个对象。函数的名称要和函数所做的动作一致。假如发现无法有很好的函数名称形容这个函数,那说明这个函数做的事情太多了,需要进行拆分。
-
名称宁愿长点,也不要缩写成别人无法理解的单词。
-
不要前缀! 因为代码读的越多,眼中就慢慢会自动忽略前缀。
-
类名和对象名应该是名词或者名词短语。例如:Customer、WikiPage、AddressParser。类名不应该是动词。避免:Manager,Processor 这种类名,因为他是比较笼统的概念。
-
方法名应该是动词或者动词短语。如 PostPayment, Delete 。初始化一个类时
Complex fulcrumPoint = new Complex(20, 30) //这种方式会更好 Complex fulcrumPoint = Complex.FromRealNumber(20,30)可以考虑将对应的构造器设为 priate,强制使用各种方式
-
每个概念对应一个单词。给每个抽象出来的对象或者操作规定一个名称。比如fetch、retrieve、get他们都是获取的意思时,要给出对应的场景单词。注意区分insert、append并替代add使用。尽可能使用email代替emailAddress,因为后者几乎没有提供比前者更多的信息。
-
对于词义比较广泛的,需要进行限制,比如 get、add、manage 等。
get是获取对象属性fetch从远程获取数据load从文件或者存储中获取数据query从数据库里获取数据calculate计算出来的数值find从数组中查找数据create、parse、build从数据生成数据
常用单词:
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 -
很少有名称是可以自我说明的,比如 name, 所以需要有上下文环境,上下文环境可以是名称前缀,当然抽象成类更好。
函数
- 函数的第一规则是短小,短才方便阅读、维护和设计。(每个人都经历过读不懂自己代码的尴尬)
- 函数只做一件事情。做好这件事情,只做这一件事情,如登录验证时,函数用来验证username和password,在验证之后顺便给用户初始化些其他东西。会导致这个函数在其他时候无法验证用户信息。
- 函数的抽象层级要统一,就是函数内操作的颗粒度要统一。比如一个函数要渲染页面, render(View) 是高层次抽象,然后 printString(String) 这里就是底层的实现,他们应该要进行分层。就像我们设置里面,先进入控制面板,然后找到所属的模块,然后细节的操作。
- 函数参数,最理想的参数数量是0,应该避免三个参数以上的函数,有足够特殊的里有才能用三个以上的参数。参数太多可以用责任链设计模式。
- 禁止标识参数,就是 Flag 或者 Switch,不准像函数传入布尔值。这标明了函数没有做一件事情,假如为 true 会这么做,假如为 false 会这么做。
- 如果函数参数看来需要两个或者三个以上,那说明其中一些参数应该被封装成类了。
- 函数要么做什么事,要么回答一些事。但二者不可兼得。把动作查询分开。当然有时候动作的结果就是我们查询的对象,这时候直接返回。但是不要既做动作,又对结果进行额外的查询。
- try...catch...中的代码块,应该抽离成函数
- 不要重复!重复是复杂度的根源之一。很多原则和实践规则都是为了避免重复
- 函数名称应该描述清楚函数作用,避免频繁去看文档,这对于短小的函数来说不难办到,如果很难命名可能需要思考函数是否有依照以上原则设计(你一个函数可能做了很多事情)。并且名称的命名应该不容易与其他函数名称形成混淆。如:add()在calculator中意思是加,而在List中就不应该用add表示插入集合了,应该用insert或append。简单来说就是一个概念对应一个词,并且始终如一。
注释
- 注释不能美化糟糕的代码!
- 注释只应该出现在理解困难或者容易出错的地方,能不用就不要用。
- 不准保留注释掉的代码,统统删干净
格式
- 统一的缩进格式
- 代码行应该尽可能短小,最好不要超过120字
- 代码格式要团队进行统一,使用编辑器工具进行限制
- 类的组织,首先应该是常量, 然后是属性,然后是公共函数,对应的私有函数要放在调用者附近,公共函数应该在变量之后。
对象和数据结构
- 类并不是用取值和赋值将私有变量对外使用。二是要暴露抽象接口,让用户无需了解数据结构就可以使用。
- 对象把数据隐藏于抽象之后,暴露操作数据的函数。数据结构暴露起数据结构,没有提供有意义的函数。
- 过程式代码(使用数据结构的代码),难以修改数据结构,因为会影响函数,但是容易修改函数。
- 对象式代码,难以添加新函数,因为会影响很多类。
- 模块不应该了解对象内部的数据结构,对象不应该通过存取器暴露内部结构。
- 数据对象,只有公共变量,没有函数的类。被称为数据传送对象。作用就是将原始数据转化为类的操作,比如 ActiveRecord
错误处理
- 使用异常而不是返回码。 意思是主函数调用子函数时,子函数不应该返回 true 或者 false 等返回码,这样主函数还需要进行判断,而应该直接抛异常。由统一的错误处理进行处理。
- 不要返回 null 值!不要传递 null 值
范围
- 一个类要有其对应的范围。不能啥都干。比如 StoreHandler 只应该做返回值是Store 的操作,获取 Store 的 Price 应该放到 PriceHandler
- 使用接口类限制边界
类
书中对于函数的设计其实和类的设计思想是类似的,所以类应该功能单一且小巧,越小耦合性越低,最后用门面模式组合起来向外提供API就好了
- 类的单一权责。系统应该由很多短小的类而不是少量巨大的类构成。
- Controller 过于臃肿,而且 controller 里面的方法不便于其他 controller 进行调用。所以将 controller 逻辑提取出来,根据函数所获得的结果进行汇总,然后控制逻辑层,防止逻辑层大爆炸。为了防止逻辑层大爆炸,就要求逻辑层有比较想尽的文档,在编写之前可以先查阅文档是否已经存在有类似的函数。对与逻辑层的起名不要包含业务层信息。仅描述对资源的获取,包含三个部分,请求的资源,请求的条件,请求返回的格式
系统
- 把系统的整体构造和业务使用分开。不要让构造影响使用,也不要让程序的运行反过来影响构造。这也是Spring这么应用广泛的原因之一,Spring Core就是个类容器。
- 把业务逻辑和检查或日志方案分离,不然纠缠在一起的代码会很难看懂和修改。Spring AOP也解决了这个问题。
测试
- 测试函数(方法)也应该短小(如果函数原本够小的话测试函数自然会小)
- 每个类最好都测试下,测试时间会比以后debug时间少。从底层函数一点点测上去,以后debug能迅速定位。
- 测试类应该保存下来,方便每次修改后进行测试