写点什么

【得物技术】乘风破浪—优雅代码四部曲

用户头像
得物技术
关注
发布于: 2020 年 08 月 17 日
【得物技术】乘风破浪—优雅代码四部曲

作者:阿福德(得物技术部)



引言

你见过超过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,这样就能写出优雅的代码!

我们要面向现实编程,并进行必要的抽象,但抽象一定要符合世界的规律。



发布于: 2020 年 08 月 17 日阅读数: 169
用户头像

得物技术

关注

得物APP技术部 2019.11.13 加入

关注微信公众号「得物技术」

评论

发布
暂无评论
【得物技术】乘风破浪—优雅代码四部曲