原文
**导读:**编程是一门创造性的工作,是一门艺术。我们每天与代码打交道,为什么普通码农辛苦一年只拿十万,而高级架构师年薪百万。最主要的就是我们敲出来的代码有差别,差别在意大部分码农敲出来坏的代码,而高级架构师能敲出优雅的好的代码。我们每天都会敲代码,但当被问道什么是好的优雅的代码时,大家可能会先愣一下,然后给出的回答要么比较空泛,要么比较散,没办法简单明了地概括出来。显然,这个问题并没有唯一的标准答案,谁都可以谈论自己的理解。要成为合格的架构师最基本的要求是能写好的优雅的代码,所以必须要知道什么是优雅代码。这篇文章我来分享一下阿里系高级架构师对于好的优雅代码的理解。
一句话概括
衡量代码质量的唯一有效标准:WTF/min —— Robert C. Martin
年薪十万与年薪百万程序员写的代码的区别
Martin(Bob大叔)曾在《代码整洁之道》一书中说:当你的代码在做 Code Review 时,审查者要是愤怒地吼道:“What the fuck, is this shit?”、“Dude, What the fuck!”等言辞激烈的词语,那说明你写的代码是 Bad Code,如果审查者只是漫不经心的吐出几个:“What the fuck?”,那说明你写的是 Good Code。
衡量代码质量的唯一标准就是每分钟骂出“WTF”的频率。
我敢打赌每个人都遇到过这样的情况:过几周或者几个月之后,再看到自己写的代码,感觉一团糟,不禁怀疑人生。
我们自己写的代码,一段时间后自己看尚且如此,更别提拿给别人看了。
一、好的优雅的代码
我们如何来形容好的优雅的代码?好的优雅的代码一定具备以下特征:
- 精简代码,可读性高
- 逻辑清晰
- 高内聚,低耦合
- OOP三大特征(封装、继承、多态)
1、精简代码,可读性高
任何一个傻瓜都能写出计算机可以理解的代码。唯有写出人类容易理解的代码,才是优秀的程序员。—— Martin Fowler
assert((!(bucket = findBucket(key))) || !bucket.isOccupied());
上面这行代码虽然比较短,但是难以阅读。为了更好地阅读,我们做如下修改:
bucket = findBucket(key);
if(bucket != null)
{
assert(!bucket.isOccupied());
}
减少代码行数是一个好目标,但是让阅读代码的事件最小化是个更好的目标。
但是这些词没有任何指导意义,我准备从最基本的概念入手。
所以,谈到好代码,首先跳入自己脑子里的一个词就是:精简。
好的代码一定是精简的,给阅读的人一种轻松愉快感觉。
2、逻辑清晰
对代码的逻辑层次要有感觉。
比如大体上,一个程序会分三个层次:界面层,逻辑层,数据层。简化后一般也有两个层次:界面和逻辑层。
逻辑层是去掉外表的,内在的,实质的东西。一般来说,就是表现为对数据的一组操作。
而界面层,是关注程序应该如何和用户沟通的。比如可视的视窗,图表,控件等。它是内部逻辑的呈现,也是用户和内部逻辑沟通的桥梁。
区分这两个层次的好处,一个是这两个层次所注重的核心内容有所不同,用到的技巧或者指导方法有所差别。第二点是,可以将问题解构和局部化,减轻开发难度。第三点,有助分开来修改内容,比如界面层挪动一下,改变一下形式,并不需要修改逻辑层的;而逻辑层改进一下算法,也不会影响界面层的代码。
对代码的逻辑层次有感觉,以上的要求只是很基本的,编写代码要时时刻刻对当前代码所代表的逻辑层次要有“感觉”,要能意识到这段代码和上一段代码是否在某种标准下,处在同一个层次。比较经典的范例如:互联网的7层协议,还有操作系统的层次分部等。编写代码要善于归纳这些层次,才能建构一个优美的结构。
3、高内聚低耦合
高内聚低耦合几乎是每个程序员员都会挂在嘴边的,但这个词太过于宽泛,太过于正确,所以聪明的编程人员们提出了若干面向对象设计原则来衡量代码的优劣:
- 开闭原则 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)
这些原则想必大家都很熟悉了,是我们编写代码时的指导方针,按照这些原则开发的代码具有高内聚低耦合的特性。换句话说,我们可以用这些原则来衡量代码的优劣。
但这些原则并不是死板的教条,我们也经常会因为其他的权衡(例如可读性、复杂度等)违背或者放弃一些原则。比如子类拥有特性的方法时,我们很可能打破里氏替换原则。再比如,单一职责原则跟接口隔离原则有时候是冲突的,我们通常会舍弃接口隔离原则,保持单一职责。只要打破原则的理由足够充分,也并不见得是坏的代码。
4、OOP三大特征
4.1封装
尽可能隐藏一个模块的实现细节(属性名称,属性是否可变,算法,数据结构,数据类型)
访问控制只是为了防止程序员的无意误用,不打算,也无法防止程序员的故意破坏
4.2继承
继承使用不当会破坏封装,造成信息泄露
先考虑组合,在考虑继承
继承是 behaves-like-a
, is-substitutable-for
的关系,不是 is-a
或 is-a-kind-of
的关系
4.3多态
- 相同的实现代码适用不同的场合
- 不同的实现代码适用相同的场合
二、如何判断不是好的代码
讨论了好代码的必要条件,我们再来看看好代码的否定条件:什么不是好的代码。Kent Beck 使用味道来形容重构的时机,我认为当代码有坏味道的时候,也代表了其并不是好的代码。
代码的坏味道
► 重复
重复可能是软件中一切邪恶的根源。—— Robert C.Martin
Martin Fowler 也认为坏味道中首当其冲的就是重复代码。
很多时候,当我们消除了重复代码之后,发现代码就已经比原来整洁多了。
► 函数过长、类过大、参数过长
过长的函数解释能力、共享能力、选择能力都较差,也不易维护。
过大的类代表了类做了很多事情,也常常有过多的重复代码。
参数过长,不易理解,调用时也容易出错。
► 发散式变化、霰弹式修改、依恋情结
如果一个类不是单一职责的,则不同的变化可能都需要修改这个类,说明存在发散式变化,应考虑将不同的变化分离开。
如果某个变化需要修改多个类的方法,则说明存在霰弹式修改,应考虑将这些需要修改的方法放入同一个类。
如果函数对于某个类的兴趣高于了自己所处的类,说明存在依恋情结,应考虑将函数转移到他应有的类中。
► 数据泥团
有时候会发现三四个相同的字段,在多个类和函数中均出现,这时候说明有必要给这一组字段建立一个类,将其封装起来。
► 过多的 if...else
或者使用 switch
过多的 if...else
或者 switch
,都应该考虑用多态来替换掉。甚至有些人认为除个别情况外,代码中就不应该存在 if…else 。
三、总结
本文首先一句话概括了我认为的好的优雅代码的必要条件:精简,逻辑清晰,高内聚,低耦合,接着具体分析了坏代码的特点,什么样的代码不是好的代码。仅是本人的一些见解,希望对各位以后的编程有些许的帮助。
对于如何保持代码整洁,离不开设计模式和代码重构,多阅读开源社区的代码,比如最近微信开源的MMKV
就可以读来学习,像世界同行大佬学习交流如何优雅的写代码,也可以读一些经典的书籍如《代码整洁之道》、《重构改善既有代码的设计》、《重构改善既有代码的设计》等等。
Reference
写在最后
欢迎大家关注鄙人的公众号【麦田里的守望者zhg】,让我们一起成长,谢谢。