美团:怎样写出漂亮整洁的代码?聊聊clean code的编码、重构技巧( 三 )


让相同长度的代码段表示相同粒度的逻辑
这里想表达的是,尽量多地去抽取private方法,让代码具有自描述的能力 。举个简单的例子:

美团:怎样写出漂亮整洁的代码?聊聊clean code的编码、重构技巧

文章插图
 
类似这种代码,在业务代码中随处可见 。获取do1是一个方法,merge是一个方法,但获取do2的代码却在主流程里写了 。这种代码,流程越长,读起来越累 。很多人读代码的逻辑,是“广度优先”的 。先读懂主流程,再去看细节 。类似这种代码,如果能够把构造do2的代码,提取一个private 方法,就会舒服很多 。
面向对象设计技巧贫血与领域驱动
不得不承认,Spring已经成为企业级JAVA开发的事实标准 。而大部分公司采用的三层/四层贫血模型,已经让我们的编码习惯,变成了面向DAO而不是面向对象 。
缺少了必要的模型抽象和设计环节,使得代码冗长,复用程度比较差 。每次撸代码的时候,从mApper撸起,好像已经成为不成文的规范 。
好处是上手简单,学习成本低 。但是每次都不能重用,然后面对两三千行的类看着眼花的时候,我的心是很痛的 。关于领域驱动的设计模式,本文不会展开去讲 。回归面向对象,还是跟大家share一些比较好的code技巧,能够在一个通用的框架下,尽量好的写出漂亮可重用的code 。
个人认为,一个好的系统,一定离不开一套好的模型定义 。梳理清楚系统中的核心模型,清楚的定义每个方法的类归属,无论对于代码的可读性、可交流性,还是和产品的沟通,都是有莫大好处的 。
为每个方法找到合适的类归属,数据和行为尽量要在一起
如果一个类的所有方法,都是在操作另一个类的对象 。这时候就要仔细想一想类的设计是否合理了 。理论上讲,面向对象的设计,主张数据和行为在一起 。这样,对象之间的结构才是清晰的,也能减少很多不必要的参数传递 。
不过这里面有一个要讨论的方法:service对象 。如果操作一个对象数据的所有方法都建立在对象内部,可能使对象承载了很多并不属于它本身职能的方法 。
例如,我定义一个类,叫做person,这个类有很多行为,比如:吃饭、睡觉、上厕所、生孩子;也有很多字段,比如:姓名、年龄、性格 。
很明显,字段从更大程度上来讲,是定义和描述我这个人的,但很多行为和我的字段并不相关 。上厕所的时候是不会关心我是几岁的 。如果把所有关于人的行为全部在person内部承载,这个类一定会膨胀的不行 。
这时候就体现了service方法的价值,如果一个行为,无法明确属于哪个领域对象,牵强地融入领域对象里,会显得很不自然 。这时候,无状态的service可以发挥出它的作用 。但一定要把握好这个度,回归本质,我们要把属于每个模型的行为合理的去划定归属 。
警惕static
static方法,本质上来讲是面向过程的,无法清晰地反馈对象之间的关系 。虽然有一些代码实例(自己实现单例或者Spring托管等)的无状态方法可以用static来表示,但这种抽象是浅层次的 。说白了,如果我们所有调用static的地方,都写上import static,那么所有的功能就由类自己在承载了 。
让我画一个类图?尴尬了……画不出来 。
而单例的膨胀,很大程度上也是贫血模型带来的副作用 。如果对象本身有血有肉,就不需要这么多无状态方法 。
static真正适用的场景:工具方法,而不是业务方法 。
巧用method object
method object是大型重构的常用技巧 。当一段逻辑特别复杂的代码,充斥着各种参数传递和是非因果判断的时候,我首先想到的重构手段是提取method object 。所谓method object,是一个有数据有行为的对象 。依赖的数据会成为这个对象的变量,所有的行为会成为这个对象的内部方法 。利用成员变量代替参数传递,会让代码简洁清爽很多 。并且,把一段过程式的代码转换成对象代码,为很多面向对象编程才可以使用的继承/封装/多态等提供了基础 。
举个例子,上文引用的代码如果用method object表示大概会变成这样:
美团:怎样写出漂亮整洁的代码?聊聊clean code的编码、重构技巧

文章插图
 
面向接口编程
面向接口编程是很多年来大家形成的共识和最佳实践 。最早的理论是便于实现的替换,但现在更显而易见的好处是避免public方法的膨胀 。一个对外publish的接口,一定有明确的职责 。要判断每一个public方法是否应该属于同一个interface,是很容易的 。


推荐阅读