写点什么

阿里工程师告诉你:什么是好的代码?

  • 2019-08-27
  • 本文字数:3768 字

    阅读完需:约 12 分钟

阿里工程师告诉你:什么是好的代码?

一句话概括

衡量代码质量的唯一有效标准:WTF/min —— Robert C. Martin



Bob 大叔对于好代码的理解非常有趣,对我也有很大的启发。我们编写的代码,除了用于机器执行产生我们预期的效果以外,更多的时候是给人读的,这个读代码的可能是后来的维护人员,更多时候是一段时间后的作者本人。


我敢打赌每个人都遇到过这样的情况:过几周或者几个月之后,再看到自己写的代码,感觉一团糟,不禁怀疑人生。


我们自己写的代码,一段时间后自己看尚且如此,更别提拿给别人看了。


任何一个傻瓜都能写出计算机可以理解的代码。唯有写出人类容易理解的代码,才是优秀的程序员。—— Martin Fowler


所以,谈到好代码,首先跳入自己脑子里的一个词就是:整洁


好的代码一定是整洁的,给阅读的人一种如沐春风,赏心悦目的感觉。


整洁的代码如同优美的散文。—— Grady Booch

好代码的特性

很难给好的代码下一个定义,相信很多人跟我一样不会认为整洁的代码就一定是好代码,但好代码一定是整洁的,整洁是好代码的必要条件。整洁的代码一定是高内聚低耦合的,也一定是可读性强、易维护的。

高内聚低耦合

高内聚低耦合几乎是每个程序员员都会挂在嘴边的,但这个词太过于宽泛,太过于正确,所以聪明的编程人员们提出了若干面向对象设计原则来衡量代码的优劣:


  • 开闭原则 OCP (The Open-Close Principle)

  • 单一职责原则 SRP (Single Responsibility Principle)

  • 依赖倒置原则 DIP (Dependence Inversion Principle)

  • 最少知识原则 LKP (Least Knowledge Principle)) / 迪米特法则 (Law Of Demeter)

  • 里氏替换原则 LSP (Liskov Substitution Principle)

  • 接口隔离原则 ISP (Interface Segregation Principle)

  • 组合/聚合复用原则 CARP (Composite/Aggregate Reuse Principle)


这些原则想必大家都很熟悉了,是我们编写代码时的指导方针,按照这些原则开发的代码具有高内聚低耦合的特性。换句话说,我们可以用这些原则来衡量代码的优劣。


但这些原则并不是死板的教条,我们也经常会因为其他的权衡(例如可读性、复杂度等)违背或者放弃一些原则。比如子类拥有特性的方法时,我们很可能打破里氏替换原则。再比如,单一职责原则跟接口隔离原则有时候是冲突的,我们通常会舍弃接口隔离原则,保持单一职责。只要打破原则的理由足够充分,也并不见得是坏的代码。

可读性

代码只要具有了高内聚和低耦合就足够好了吗?并不见得,我认为代码还必须是易读的。好的代码无论是风格、结构还是设计上都应该是可读性很强的。可以从以下几个方面考虑整洁代码,提高可读性。

命名

大到项目名、包名、类名,小到方法名、变量名、参数名,甚至是一个临时变量的名称,其命名都是很严肃的事,好的名字需要斟酌。


名副其实


好的名称一定是名副其实的,不需要注释解释即可明白其含义的。


  /**  * 创建后的天数  **/  int d;
复制代码


  int daysSinceCreation;
复制代码


后者比前者的命名要好很多,阅读者一下子就明白了变量的意思。


容易区分


我们很容易就会写下非常相近的方法名,仅从名称无法区分两者到底有啥区别(eg. getAccount()getAccountInfo()),这样在调用时也很难抉择要用哪个,需要去看实现的代码才能确定。


可读的


名称一定是可读的,易读的,最好不要用自创的缩写,或者中英文混写。


足够短


名称当然不是越长越好,应该在足够表达其含义的情况下越短越好。

格式

良好的代码格式也是提高可读性非常重要的一环,分为垂直格式和水平格式。


垂直格式


通常一行只写一个表达式或者子句。一组代码代表一个完整的思路,不同组的代码中间用空行间隔。


public class Demo {    @Resource    private List<Handler> handlerList;    private Map<TypeEnum, Handler> handlerMap = new ConcurrentHashMap<>();
@PostConstruct private void init() { if (!CollectionUtils.isEmpty(handlerList)) { for (Handler handler : handlerList) { handlerMap.put(handler.getType(), handler); } } }
publicResult<Map<String, Object>> query(Long id, TypeEnum typeEnum) { Handler handler = handlerMap.get(typeEnum); if (null == handler) { return Result.returnFailed(ErrorCode.CAN_NOT_HANDLE); } return handler.query(id); }}
复制代码


如果去掉了空行,可读性大大降低。


  public class Demo {      @Resource      private List<Handler> handlerList;      private Map<TypeEnum, Handler> handlerMap = new ConcurrentHashMap<>();      @PostConstruct      private void init() {          if (!CollectionUtils.isEmpty(handlerList)) {              for (Handler handler : handlerList) {                  handlerMap.put(handler.getType(), handler); } } }      public Result<Map<String, Object>> query(Long id, TypeEnum typeEnum) {          Handler handler = handlerMap.get(typeEnum);          if (null == handler) {              return Result.returnFailed(ErrorCode.CAN_NOT_HANDLE);          }          return handler.query(id); }  }
复制代码


类静态变量、实体变量应定义在类的顶部。类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter 方法。


水平格式


要有适当的缩进和空格。


团队统一


通常,同一个团队的风格尽量保持一致。集团对于 Java 开发进行了非常详细的规范。(可点击下方阅读原文,了解更多内容)

类与函数

类和函数应短小,更短小


类和函数都不应该过长(集团要求函数长度最多不能超过 80 行),过长的函数可读性一定差,往往也包含了大量重复的代码。


函数只做一件事(同一层次的事)


同一个函数的每条执行语句应该是统一层次的抽象。例如,我们经常会写一个函数需要给某个 DTO 赋值,然后再调用接口,接着返回结果。那么这个函数应该包含三步:DTO 赋值,调用接口,处理结果。如果函数中还包含了 DTO 赋值的具体操作,那么说明此函数的执行语句并不是在同一层次的抽象。


参数越少越好


参数越多的函数,调用时越麻烦。尽量保持参数数量足够少,最好是没有。

注释

别给糟糕的代码加注释,重构他


注释不能美化糟糕的代码。当企图使用注释前,先考虑是否可以通过调整结构,命名等操作,消除写注释的必要,往往这样做之后注释就多余了。


好的注释提供信息、表达意图、阐释、警告


我们经常遇到这样的情况:注释写的代码执行逻辑与实际代码的逻辑并不符合。大多数时候都是因为代码变化了,而注释并没有跟进变化。所以,注释最好提供一些代码没有的额外信息,展示自己的设计意图,而不是写具体如何实现。


删除掉注释的代码


git 等版本控制已经帮我们记录了代码的变更历史,没必要继续留着过时的代码,注释的代码也会对阅读等造成干扰。

错误处理

错误处理很重要,但他不能搞乱代码逻辑


错误处理应该集中在同一层处理,并且错误处理的函数最好不包含其他的业务逻辑代码,只需要处理错误信息即可。


抛出异常时提供足够多的环境和说明,方便排查问题


异常抛出时最好将执行的类名,关键数据,环境信息等均抛出,此时自定义的异常类就派上用场了,通过统一的一层处理异常,可以方便快速地定位到问题。


特例模型可消除异常控制或者 null 判断


大多数的异常都是来源于 NPE,有时候这个可以通过 Null Object 来消除掉。


尽量不要返回 null ,不要传 null 参数


不返回 null 和不传 null 也是为了尽量降低 NPE 的可能性。

如何判断不是好的代码

讨论了好代码的必要条件,我们再来看看好代码的否定条件:什么不是好的代码。Kent Beck 使用味道来形容重构的时机,我认为当代码有坏味道的时候,也代表了其并不是好的代码。

代码的坏味道

重复


重复可能是软件中一切邪恶的根源。—— Robert C.Martin


Martin Fowler 也认为坏味道中首当其冲的就是重复代码。


很多时候,当我们消除了重复代码之后,发现代码就已经比原来整洁多了。


函数过长、类过大、参数过长


过长的函数解释能力、共享能力、选择能力都较差,也不易维护。


过大的类代表了类做了很多事情,也常常有过多的重复代码。


参数过长,不易理解,调用时也容易出错。


发散式变化、霰弹式修改、依恋情结


如果一个类不是单一职责的,则不同的变化可能都需要修改这个类,说明存在发散式变化,应考虑将不同的变化分离开。


如果某个变化需要修改多个类的方法,则说明存在霰弹式修改,应考虑将这些需要修改的方法放入同一个类。


如果函数对于某个类的兴趣高于了自己所处的类,说明存在依恋情结,应考虑将函数转移到他应有的类中。


数据泥团


有时候会发现三四个相同的字段,在多个类和函数中均出现,这时候说明有必要给这一组字段建立一个类,将其封装起来。


过多的 if…else 或者使用 switch


过多的 if…else 或者 switch ,都应该考虑用多态来替换掉。甚至有些人认为除个别情况外,代码中就不应该存在 if…else 。

总结

本文首先一句话概括了我认为的好代码的必要条件:整洁,接着具体分析了整洁代码的特点,又分析了好代码的否定条件:什么样的代码不是好的代码。仅是本人的一些见解,希望对各位以后的编程有些许的帮助。


我认为仅仅编写出可运行的代码是远远不够的,还要时刻注意代码的整洁度,留下一些漂亮的代码,希望写的代码都能保留并运行 102 年!


本文转载自公众号淘宝技术(ID:AlibabaMTT)


原文链接


https://mp.weixin.qq.com/s/AjubL4vVhFa_FIlaopLVCA


2019-08-27 08:007204

评论

发布
暂无评论
发现更多内容

阿里云 MSE 基于 Apache APISIX 的全链路灰度方案实践

API7.ai 技术团队

阿里云 MSE APISIX 网关 全链路灰度

从链上数据出发,分析stETH脱锚之后的市场动态

区块链前沿News

eth Hoo

读《Software Systems Architecture》(14)—— Evaluating the Architecture

术子米德

架构师成长笔记

在线文本保留中文提取过滤工具

入门小站

工具

读《Software Systems Architecture》(11)—— Using Styles and Patterns

术子米德

架构师成长笔记

APISIX 助力中东社交软件,实现本地化部署

API7.ai 技术团队

API网关 社交软件 APISIX Summit

基于 Apache APISIX 的自动化运维平台

API7.ai 技术团队

自动化运维 APISIX 网关 APISIX Summit

GetX 状态管理从入门到入迷

岛上码农

flutter ios 前端 安卓 6月月更

读《Software Systems Architecture》(16)—— The Context Viewpoint

术子米德

架构师成长笔记

如何修改 Rancher v2.6 的 Rancher Server IP 地址

Rancher

Kubernetes k8s rancher

全球Top 2!腾讯计算机视觉能力再突破

脑极体

GoLang简单易用的json value读取工具!还并发安全

Krysta

Go json 简单清楚 方便

数仓开发人员的价值体现

奔向架构师

数据仓库 数据模型 6月月更

linux之我常用的20条命令(之二)

入门小站

Linux

读《Software Systems Architecture》(15)—— Introduction to the Viewpoint Catalog

术子米德

架构师成长笔记

盘点攻防演练中红队的主要工具(下)

穿过生命散发芬芳

6月月更 攻防演练

js中的变量提升和函数提升

北洋

android 6月月更

数据库每日一题---第13天:寻找病患

知心宝贝

数据库 云计算 前端 后端 6月月更

读《Software Systems Architecture》(17)—— The Functional Viewpoint

术子米德

架构师成长笔记

文档管理系统对于企业有哪些优势

小炮

uni-app深入学习之模板运用【day4】

恒山其若陋兮

6月月更

计算机网络之IP协议与以太网

未见花闻

6月月更

三点微服务标准化要素

阿泽🧸

微服务 6月月更

接口测试使用Python装饰器

伤心的辣条

Python 程序人生 软件测试 自动化测试 接口测试

读《Software Systems Architecture》(12)—— Producing Architectural Models

术子米德

架构师成长笔记

python简介

左手の明天

Python Python解释器

凿开数据冰层,透出智能时代的光:华为云与开发者的结伴旅行

脑极体

读《Software Systems Architecture》(13)—— Creating the Architectural Description

术子米德

架构师成长笔记

阿里工程师告诉你:什么是好的代码?_文化 & 方法_马飞翔_InfoQ精选文章