好代码的定义
衡量代码质量的唯一标准是阅读该代码时说脏话的次数。—— 《Clean Code》

个人认为,好代码的唯一标准就是可读性。毕竟代码是写给人看的。
可读性和性能的权衡
可读性和性能有时候可能不能兼得,比如你要遍历一个list,对list里面的对象进行不同维度的统计,这时候有两种做法
- 遍历一次,把所有指标统计出来
- 遍历多次,分别统计不同指标
按方法1去做统计,你会发现for循环中夹杂着大量的代码,一个for可能有会很长,严重影响可读性。
按方法2去统计,可读性上会比1好很多,但是会多次遍历同一个list,看起来没有1快。
我个人遇到这种情况,会优先保证可读性。原因如下
- 性能不好,但是方案2性能是否满足业务需求,如果进行优化,方案2的遍历真的是瓶颈点吗,还是有一些阻塞的io
- 在没有io操作、且list大小可控的情况下,都是基于内存遍历
说了这么多,就是表达一个观点:在满足性能的前提下,为了可读性可以牺牲一点性能。
常用判断代码质量的标准
最常用到几个评判代码质量的标准是:可维护性、可读性、可扩展性、灵活性、简洁性、可复用性、可测试性。
其中,可维护性、可读性、可扩展性又是提到最多的、最重要的三个评价标准。
可维护性
所谓“代码易维护”就是指,在不破坏原有代码设计、不引入新的ug的情况下,能够快
速地修改或者添加代码。
实际上,可维护性也是一个很难量化、偏向对代码整体的评价标准。
不同水平的人对于同一份代码的维护能力并不是相同的。
对于同样一个系统,熟悉它的资深工程师会觉得代码的可维护性还不错,而一些新人因为不熟悉代码,修改bug、修改添加代码要花费很长的时间,就有可能会觉得代码的可维护性不那么好。
这实际上也印证了我们之前的观点:代码质量的评价有很强的主观性。
可读性
我个人认为,代码的可读性应该是评价代码质量最重要的指标之一。
我们在编写代码的时候,时刻要考虑到代码是否易读、易理解。
可读体现:代码是否符合编码规范,命名是否达意,注释是否详尽,函数是否长短合适,模块划分是否清晰,是否符合高内聚低耦合等
灵活性
- 一段代码易扩展,易复用或易用
简洁性
- 设计原则中的KISS原则(keep It Simple Stupid),尽量保持简单,代码简单,逻辑清晰,也就意味这易读,易维护。
- 思从深而行从简,真正的高手能用最简单的方法解决最复杂的问题
- 代码的简洁性是高手和菜鸟的重要分别
可复用性
- 可复用性是代码设计的重要标准,也是多种设计模式所要实现的目标,更是实现多个优秀代码特性的基础
- 设计原则中 DRY (Don’t Repeat Yourself)就是强调这一点
可测试性
- 代码的可测试性好坏,能从侧面非常准确的反应代码质量的好坏。
- 测试性差:说明设计有问题,未做到拆分不合理,未做到高内聚,低耦合
如何写好代码
写好代码可以分为两个层面,一个是道,一个是术。
“道”指的是编程的一些方法论,包括面向对象设计思想、设计原则。
“术”指的是设计模式、编码规范、重构技巧等。
好代码之“道”
面向对象的设计原则:
- 单一职责原则
- 开闭原则
- 里式替换替换原则
- 接口隔离原则
- 依赖反转原则
还有一些其他原则比如:
- DRY Don’t Repeat Yourself
- KISS keep It Simple Stupid
好代码之“术”
代码格式
团队统一好就行,比较流行的有阿里巴巴的P3C,谷歌的code style。
有意义的命名
名副其实 望文生义
给类、函数取名时,要结合系统上下文,做到望文生义
避免误导
- 避免使用与本意相悖的词
- 关键字,领域专用词 不要用来随意做变量名称
- 别用accountList来指一组账号,除非它真的是List类型。因为java里面有list容器,list结尾容易让人误以为是list容器
- 堤防使用不同之处较小的名称,比如两个名字看起来很想的类
- 注意字体,防止混淆,小写字母l与大写字母O,容易被看成一和零
做有意义的区分
- 英文名称不要拼错,这让人抓狂,尤其是你要对外暴露api时,容易使别怀疑你的专业性。
- 假设你有一个Product类。还有一个ProductData或ProductInfo类,他们的名称不同但是意思却毫无区别
- 消除冗余,variable一次永远不应该出现在变量名中,table一次永远不应该出现在表名中。
使用读得出来的名称
如果名称读不出来,讨论的时候会像个傻鸟!
使用可搜索的名称
单字母名称和数字常量有个问题,就是很难在一大篇文字中搜索出来。
- 去除魔数
- 常量命名要足够望文生义,长点没关系
- 变量名称的长短应该与其作用于相对应,单字母名称仅用于短方法中的本地变量
接口和实现
IShapeFactory来命名接口,前缀字母I,完全都是废话,我当然知道这个是接口。ShapeFactory和ShapeFactoryImpl来命名接口和实现好得多。
类名
- 类名和对象名称应该是名词或者名词短语。
- 避免使用Data、Info这样的类名
- 类名不应该是动词
方法名
- 方法名应该是动词或者动词短语
每个概念对应一个词
给每个抽象概念选一个词,并一以贯之。例如Service层 查询列表都用 getListByXXX。
Dao层查询列表都用queryListByXXX
别用双关语
- 别用双关语,一词一意,避免将同一个词用于不同目的
使用解决方案领域名称
- 如果你用设计模式,取类名的时候就应该体现。比如AccountVisitor就能体现出访问者(VISITOR)模式
领域名称统一
领域驱动设计的基本概念之一,给不同对象定义好名称并统一。
不要添加没用的前缀
这个最好交给分包处理,GSDAccountAddress这样的类名,不如在gsd包下,创建AccountAddress类。
函数
函数的第一原则是短小
类和函数都不应该过长,过长的函数可读性一定差,往往也包含了大量重复的代码。
只做一件事
函数应该做一件事,做好这件事,只做这一件事。
反例 : 以get命名的方法里面,对传入的入参还进行成员变量的修改
1 | public Object getXXX(Param param){ |
使用描述性的名称
- 函数越短小、功能越集中,就越变于取个好名字。
- 别害怕长名称。长而具有描述性的名称,要比短而令人费解的名称好。
- 命名方式要保持一直。使用与模块名一脉相承的短语、名词和动词给函数命名。
函数参数
- 函数参数越短越好,超过5个以上的参数,用对象封装。
- 如果函数要对输入参数进行转换操作,转换结果就应该体现为返回值
- 标识参数丑陋不堪,不要在函数参数里传true,false这些标识参数,这样做等于立刻宣布本函数不止做一件事。
注释
注释存在的时间越久,就离其所描述的代码越远,越来越变得全然错误,因为程序员不能坚持维护注释。
注释不能美化糟糕的代码,对于能用代码描述的场景,优先美化我们自己的代码。
这并不是鼓励你不要去写注释,而是要明白注释的腐坏速度比代码要快,个人认为比较好的做法是,坚持维护注释。
我个人会在一些特判的业务逻辑上,把注释写好,以免以后自己再看这段代码,自己忘记了一些特殊处理的逻辑,和当时这么做的原因。
类
类成员的组织
- 公共静态常量 先出现
- 私有静态常量
- 公共成员变量(很少有这种情况)
- 私有成员变量
- 公共函数
- 私有函数
函数的组织,有些人喜欢把公共函数全部放前面,私有函数放后面。
也有人喜欢公共函数后面跟着此公共函数用到的几个私有函数。
大家阅读源码的时候,可以看看大牛们是如何对组织类里面的代码结构的。
类应该短小,并且符合单一职责,内聚
分包
如何分包其实是一个比较宽泛的问题,要取决于业务场景,可以按模块划分,也可以用三层架构的层级划分。
不管用什么方式分,最终最理想的状态包内的代码,可以做到功能最小化,不依赖包外。当进行服务拆分的时候能简单快速的迁移。
其他补充
- 避免if嵌套的层次过深,形成“金字塔”代码
- 局部变量申明尽可能靠近使用的地方
- 拆分超长表达式
参考
个人推荐阅读顺序,先从Java开发手册开始。
《阿里巴巴Java开发手册》
《代码整洁之道》
《极客时间-设计模式之美》(读了前几篇文章,感觉还不错,融合了写好代码和设计模式的内容)