【得物技术】乘风破浪—优雅代码四部曲
作者:阿福德(得物技术部)
引言
你见过超过1000行代码的方法吗?
你有没有读过非常晦涩、要很长时间才能读明白的代码?
你是否写过或者曾经听说过一些链接泄漏,文件句柄未释放的问题?
我可以打包票,只要你工作了几年,一定见过这些代码或问题。
你总是写这样的代码,小心别人带上📷约你去爬山。。。
本人工作已来,针对有味道的代码有了一些自己的思考,总结了如下四条如何编写优雅代码的方式方法。
今天就从切实从代码的角度, 一步步认识高逼格代码的应有的样子。完成产品的功能你还只是个青铜,做到下面的四部曲,你才是个王者!
第一部曲:关注词性,规范命名
一个小故事
今天你去相亲,
你李阿姨给你介绍了一个姑娘,她跟你说,姑娘的名字叫做王铁蛋^_^
你先是愣了一愣,然后结结巴巴地说:我现在还不想要女朋友,谢谢你,李阿姨。
之后你从朋友那里得知,那个姑娘是一个特别水灵水灵的小姐姐,长得有点像迪丽热巴,并且已经有男朋友了。
编了上面这样一个故事,你们肯定早就知道我想要说什么了,没错,我今天要说的就是变成中命名的重要性。
关于命名,网上有许多文章讲如何进行命名,也有一些规范性的描述:
1、命名需要有意义的名字
2、如果一个名字需要注释,则说明,这个名字没有揭示其本质。
3、命名需要一致性性
4、概念类似的东西需要使用类似的名字
这里我要说的是,命名与词性的关系。
接口、类与词性
1、接口命名应该使用名词或形容词来命名
接口可以说是一种协议,而协议:可以是什么东西,或者什么样的,前者应该就是名词,后者就是形容词。
我们可以打开jdk源码看看常用的java.util包下所有的接口以及其词性:
Collection -------------名词
Comparator ------------名词
Deque ----------------名词
Enumeration -----------名词
EventListener ----------名词
Formattable -----------形容词
Iterator ---------------名词
List ------------------名词
ListIterator ------------名词
Map -----------------名词
NavigableMap ----------名词
NavigableSet -----------名词
Observer --------------名词
PrimitiveIterator --------名词
Queue ----------------名词
RandomAccess ---------形容词
Set ------------------名词
SortedMap ------------名词
SortedSet -------------名词
Spliterator ------------名词
可以看出,jdk中也基本遵循这种规范,既都是用名词或形容词来对接口进行命名。
2、类应该使用名词来命名
当我们初学java时,就被灌输以下的内容:
java是面向对象的语言,面向对象是一个非常庞大的话题,但是任何庞大的话题都有其核心的组成:类与对象。类是对某一类具有共性特征对象的抽象,而对象是类的一个具体实例。
既然类是某一类事物的共性的抽象概念,从语文的角度来看,可以简写为“类是概念”, 既然是概念,那“类”必然是名词了。
我们查看jdk中java.util包中所有的类(class)时,可以看到类都是通过名词来命名的,如下图。
常用的类命名
在编程中,我这里总结了一些常用类命名英文单词作为后缀。
XxxProcessor
XxxExecutor
XxxConverter
XxxHandler
XxxResolver
XxxBuilder
XxxProvider
XxxFilter
XxxListener
XxxContext
XxxManager
XxxExtension
XxxAdapter
XxxFactory
XxxWrapper
方法与词性
类中除了构造方法,其他的方法都应该是动词,如下是随意找到两个类, 可以看到这两个类的所有方法都是动词。
一个是org.apache.ibatis.executor.BaseExecutor, 另一个是io.opentracing.util.GlobalTracer
属性与词性
属性命名应该使用名词或形容词来命名。
第二部曲:使用金字塔原理,避免大方法
"想清楚,说明白,知道说什么,怎么说",这是我们每个人希望达到的境界,毕竟在这如此忙碌的生活中,没有人有心情听你罗里吧嗦一大堆,最后又云里雾里,不知道你到底想表达什么。我们写代码也是一样。
「一个小故事」
书中举了这样一个例子:
你要出去买份报纸,你的妻子让你顺便买点东西回来,
她说:“看到电视上有那么多葡萄的广告,我现在特想吃葡萄。”
你穿衣服时,她又说:“也许你可以再买点牛奶。”
你穿上大衣,
她说:“咱家的土豆不够了应该再买点土豆,对了鸡蛋也没有。”
你走到门口,她又说:“再买些胡萝卜,也可以买些桔子。”
你按下电梯按钮,妻子说:“还有咸鸭蛋。”
你走进电梯,妻子又说:“再买些苹果和酸奶。”
请问,你记住妻子都要你买什么了吗?大概除了第一个葡萄和最后的酸奶,别的都没记住吧。没记住是因为你太笨吗?显然不是,这是因为妻子让你买的东西超过了七个,而且这些东西并没有被归类。
这意味着,当大脑发现需要处理的项目超过4个或5个时,就会开始将其归类到不同的逻辑范畴中,以便于记忆。
就好比这九种食物,葡萄 橘子 牛奶 咸鸭蛋 土豆 苹果 鸡蛋 酸奶 胡萝卜
大脑会自动地找出逻辑关系,进行抽象概括,将九种食物分成三组,这样我们无需记住九种食物,而仅需记住九种食物分别属于的三个组。这样不但可以减少大脑的负荷,而且也提高了我们思维抽象程度。
我们在写代码的时候,如果不进行归类,所有的代码都平铺开来,你可以很容易写出一个超大的方法。你是不是也经常写出超大的方法?
超大(比如超过200行)的方法有很多的“好处”:
1、可以让读者云里雾里,读不懂你的代码。
2、可以让你自己后续预估更多的时间来对其进行改造。
3、可以拖住测试,让测试花更多的时间,进行更大范围的回归测试。
把大象装进冰箱
把大象装进冰箱只需要三步,1、打开冰箱门,2、把大象装进去,3、关上冰箱门。然后每一步都是有很多的组成的,比如:打开冰箱门需要两步, 1、拉住冰箱门把手,2、手往外用力拉。
上述功能的伪代码如下:
持续重构,防止腐化
我们在写业务代码时, 由于业务的演化,代码也一直在演化,代码会越写越长。若不想让代码由于变得太长而不可维护,就需要采用“金字塔原理”来进行持续重构。
如果不进行归类,所有的代码都平铺开来,你可以很容易写出一个超大的方法。一个方法从”肥胖“到“健美” 应该经历下图的过程:
将“金字塔原理”用在我们的代码编写上,可以降低我们编写代码的复杂度, 也可以让读者更好的阅读,对与不想要关系细节的读者,只需要看最外层的代码就行,而不需要深入到最底层。
就像我们在读代码时,大部分时候是不会关系jdk的底层实现的,并且更少的时候会去关注java native方法使用C++的实现。
第三部曲:关注相关性,紧贴主题
一个类就是一个主题的抽象,类中的属性和方法需要紧紧的围绕着这个主题。那么问题来了,相关性你傻傻分的清楚嘛……(o^^o)。
飞机有一些部件:机翼、尾翼、发动机、起落架等等。航站楼与飞机有关联,但是在讲飞机部件的上下文中,航站楼就完全没有关系了。
相关性分为:直接相关,间接相关,无关。
围绕主题
一个类就是一个主题的抽象,类中的属性和方法需要紧紧的围绕着这个主题。
看一下jdk中java.util.concurrent.ExecutorService这个类:
这个接口是线程池的一个接口,我们从名字可以看到,它是一个执行器服务,再看里面的方法,看到看到都是围绕执行器服务来的,不会有任何与执行器不相关细的方法存在。
我们在看一下非常常用的接口:java.util.List
这个接口里面的所有方法,都是针对的“列表”这个主题。添加到列表、从列表删除、清空列表,列表是否包含,列表是否为空,从列表中获取等等。
直接关联
方法、属性需要直接与类的“主题”关联,这里举一个例子:
上面代码中的类是FlowFactory, 从类的名称来可以直到,这个类是产生Flow的,但是这个类实际在在创建图(makeGraph),据了解得知,Flow的底层是由图实现的;
我们前面提到,一个类的方法要与主题直接关联;更好的做法是,FlowFactory与Flow直接关联,而Flow与Graph直接关联,FlowFactory与Graph不能有关联。这就需要对Graph进行一个封装。
这里另外一个例子:
先解释一下,PinkOperateItem是PINK中的一个领域对象(“操作对象”),这个操作对象表示仓库中一个需要经过一系列处理(收货、分拣、拍照、质检、鉴别、绑证绑扣等)的商品,比如一双鞋。
这个操作对象有可以有很多的属性和行为,但是不应该有overTime()这样的一个方法,这里overTime表示是超时的意思(在预售项目中,卖家商品超时送到仓库),这里超时其实是收货超时,也就是对入库单的超时收货。
首页操作对象上不应该有“超时”这个概念,也就是“超时”这个概念不应该与操作对象相关联。它们之间没有相关性。
第四部曲:注意对称性,避免埋坑
世界具有对称性
我们的世界是对称性的,有“男”就有“女”, 有“上”就有“下”,有“物质”就有“反物质”
程序具有对称性
缺失对称性会有什么问题呢,仅仅是不够elegant嘛?no no no,就像下面右图的代码,一不小心就埋了个坑。
我们来看一下下面的代码,这是一个非常常见的问题,左边的代码,在同一个方法中进行lock和unlock。
在右边的方法中,testA方法调用testB进行lock,然后又直接unlock。这样做,代码运行肯定是没有问题,但是会埋坑,后人其他人(也可能是你自己)在另外的地方调用了testB,就可能不去unlock。
我们都知道编码中,一旦你打开一个文件句柄,你就必须关闭它。从连接池中获取一个连接,就需要归还这个连接;开启了一个事务,就需要提交或者回滚这个事务。这些都是对称的。
上面说的是在一个方法中,我需要考虑到对称性,对类来说,存在这样的对称性,如下:
上面是Filter类,我们可以看到,这个类,有init方法,同时就会有destroy方法。
又比如:java.io.File类, 有create文件方法,同时就有delete文件的方法。
常用对称性词汇
总结一波对称性词汇,助你命名无忧~~
写在最后
我们通过代码来描述这个多彩的世界。
我们的世界是有机的、有序的,对称的。
世界是怎么样的,我们就怎么去写Code,这样就能写出优雅的代码!
我们要面向现实编程,并进行必要的抽象,但抽象一定要符合世界的规律。
版权声明: 本文为 InfoQ 作者【得物技术】的原创文章。
原文链接:【http://xie.infoq.cn/article/f18d4e6f3ad1f5646d21a4711】。文章转载请联系作者。
评论