最新发布《数智时代的AI人才粮仓模型解读白皮书(2024版)》,立即领取! 了解详情
写点什么

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

  • 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:006257

评论

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

字节前端必会面试题

夏天的味道123

JavaScript 前端

跟我一起学mybatis

楠羽

mybatis 笔记 9月月更

软件测试 | 测试开发 | 专项测试技术初识Hook

测吧(北京)科技有限公司

测试

软件测试 | 测试开发 | 测试人生 | 从传统行业到名企大厂,薪资翻倍,我做到了

测吧(北京)科技有限公司

面试

LG稳居高地再布新棋 顺应需求领跑高端家电市场布局新生态

Geek_2d6073

干货 | 环境问题还是测试的老大难?两个步骤轻松搞定

霍格沃兹测试开发学社

干货| app自动化测试之Andriod微信小程序的自动化测试

霍格沃兹测试开发学社

手把手教你如何进行拆解竞品

产品海豚湾

产品经理 需求分析 竞品分析 9月月更 市场调研

软件测试 | 测试开发 | MockServer 服务框架设计

测吧(北京)科技有限公司

MockServer

同频共振数据时代,AntDB数据库与永洪科技完成产品互认证

亚信AntDB数据库

数据库 AntDB 国产数据库 AntDB数据库

DevSecOps 落地三部曲|小孩子才做选择,极狐GitLab 安全、高效全都要

极狐GitLab

DevOps 运维 安全 DevSecOps 极狐GitLab

Kyligence 联合创始人兼 CEO 韩卿荣获金融科技风云人物奖

Kyligence

金融科技大会

干货 | 背熟这些 Docker 命令,面试再也不怕啦~

霍格沃兹测试开发学社

TDengine 如何进行 SQL 写入?官方最全教程来了

TDengine

数据库 时序数据库 企业号九月金秋榜

解读《Benchmarking Hybrid OLTP&OLAP Database Systems》| StoneDB学术分享会

StoneDB

数据库 国产数据库 StoneDB 企业号九月金秋榜 9月月更

干货|APP自动化Android特殊控件Toast识别

霍格沃兹测试开发学社

软件测试 | 测试开发 | 测试人生 | 从外行到外包,从手工测试到知名互联大厂测开 这个90后小姐姐是怎么腾飞的?

测吧(北京)科技有限公司

测试人生

2022 世界人工智能大会|人工智能与开源技术先锋论坛成功举办

Kyligence

人工智能大会 先锋科技论坛

干货 | 移动端App自动化之App控件定位

霍格沃兹测试开发学社

软件测试 | 测试开发 | 测试人生 | 毕业2年,拒绝独角兽入职名企大厂涨薪10万+,这个95后小姐姐好飒

测吧(北京)科技有限公司

测试人生

干货 | 测试人职场晋升“潜规则”:15 年经验资深测试经理的职场忠告

霍格沃兹测试开发学社

CeresDB 技术架构简介

TRaaS

#开源项目

软件测试 | 测试开发 | 测试人生 | 拿到多个 offer 从了一线互联网公司并涨薪70%,90后小哥哥免费分享面试经验~

测吧(北京)科技有限公司

测试人生

干货 | 读懂 Appium 日志,让测试效率翻倍!

霍格沃兹测试开发学社

软件测试 | 测试开发 | 测试人生 | 疫情之下,1个月内涨薪50%拿下亿级流量金融上市公司新 offer,我柠檬了~

测吧(北京)科技有限公司

测试

WAIC|九章云极DataCanvas公司携因果学习技术成果精彩亮相!

九章云极DataCanvas

人工智能 开源 因果学习

软件测试 | 测试开发 | 项目倒排,跟工期不足say byebye~

测吧(北京)科技有限公司

测试人生

「九章云极DataCanvas」完成C+轮融资,用云中云战略引领数据智能基础软件升级

九章云极DataCanvas

机器学习 数据智能

干货 | 解决 App 自动化测试的常见痛点(弹框及首页启动加载完成判断处理)

霍格沃兹测试开发学社

干货 | 谁懂这篇文,玩游戏还会卡顿?

霍格沃兹测试开发学社

软件测试 | 测试开发 | 测试人生 | 年薪50w+ 并入职名企大厂,这是双非学历小哥哥给自己30岁的礼物

测吧(北京)科技有限公司

测试人生

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