谈一谈代码封装

原文地址:https://blog.csdn.net/flyfeifei66/article/details/81532261

                                                                                    前言

面向对象程序设计有三个特征:封装、继承、多态,这三个特征即是语法也是手段,23种设计模板其实就是对这三种手段的灵活应用,今天不谈任何高大上的设计模式,仅仅谈谈代码封装。

                                                                                    引子

从三个特征的排序上,封装是第一位的,个人认为也是最重要的,且是后里面两个的基础。很长时间以来封装很容易被忽视,可能是有人觉得这个太简单了,封装不就是组织代码吗?不就是构造一个个类,一个个方法吗?现在是一个基于框架开发的时代,开发只是往框架里“填充”业务代码,似乎不太需要考虑太多东西,这时我觉得“封装”就很重要了,“封装”就是开发层面的“微设计”,今天说主要说下方法/函数(以下简写为F)的封装这一点,以类为单位的设计往往都上升了模块设计的高度。

最近一段时间以来,发现一些不是很令人赏心的代码,总觉得跟这个东西有关。

1、认清关系

首先来考虑一个问题,FA调用FB,那么他们是什么关系?理解他们之前的关系重要吗?如果你觉得不重要、就是调用者与被调用者关系(等于没说),就大错特错了。如果你的代码一直写的不好,我觉得很有可能就是这一点没认识清楚。想要回答这个问题,只要考虑另外一个问题,FA为什么要调用FB?——因为FA不调用FB就无法完成自己的工作,FB能帮助自己完成一些事情,也就是说FA依赖FB,他们之间的关系是依赖。这个认识会直接影响到你代码的组织。

1、1          向下原则

举例之前,再考虑另外一个问题,依赖者之间的位置或者层次是什么样的?答案是上下的,不是平行的。也就是说FB在FA下层,为FA提供服务,这个可以参照ISO七层模型来理解,也可以参考我们的框架,web层->service层->dao层。《代码整洁之道》里称之为向下原则。

这些东西容易理解,自己写代码时候确容易考虑不到。一个例子,假设有个方法有300行代码,里面有些东西可以封装子F来,你会怎么做?我相信大部分人做的都还可以。但有这么个做法,他/她把方法拆成了三个方法,F1,F2,F3,F1调用F2,F2调用F3,每个方法体里面基本上是之前方法的一段代码,比如F1是1~100行代码,F2是101~200行,F3是201到300行,三个方法需要的参数几乎都是一样的,通过形参依次传递下去,而且返回值都是void类型,他/她给方法的名字和注释也很尴尬,意思都很接近。

大家觉得这个做法怎么样?这个做法仅从形式上看就不行,每个方法都是原来方法身体的一部分。从逻辑上他们是平行的,可以认为它还是一个方法,他们之间不是依赖关系。这样封装代码仅仅是把代码挪了个地方,还不如不拆分,阅读者得自己脑补,把代码合并起来才能知道到底想干什么。把一个方法比喻成一个人,那么如果这个方法需要重构,抽离出来的方法仍然必须是个人,是个五脏六腑俱全的mini小人,而不是这个人的手或者脚。

1、2          封装不是简简单单的挪动代码

上述例子中,F1调用F2,F2调用F3,或者F1调用F2、F3都可能是合理的,关键看几个F封装的符不符合我们常见的原则,后续我们将均使用上面的例子,因为它很巧的违反了很多原则,可以帮助我们理解这些原则,一般来说有以下几个原则。

2、单一职责原则

最重要的原则,见名知意。单一职责,强调的是职责的分离,一个方法只干一件事情,只因为一个原因做修改。很多代码之所以需要重构,因为有职责扩散。所谓职责扩散,就是因为某种原因,职责P被分化为粒度更细的职责P1和P2。

从微观上讲,单一职责的方法一目了然,职责明确,利于维护。从宏观上讲是设计的要求,单一职责原则可以看作是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,职责过多,可能引起它变化的原因就越多,从而极大的损伤其内聚性和耦合度。

这个原则非常好理解,最常见的违反这个原则的例子就是写“大而全”的方法,一个方法搞定各种逻辑,各种flag,各种分支判断,到最后代码看不出主逻辑是什么了。

如果单一职责原则做的好,代码都不会太长,随便翻开Apache的开源软件源码,大部分的方法都没超过100行,当然有些核心类、管理类等,长代码还是有的。

除了大而全的方法,想想上面的F1~F3的例子,有没有违反单一原则?与大多数人职责过多相反,它是因没有职责而违反。这里有个“诡辩”,只要不是空方法,只要方法里有代码、有逻辑就有职责,这一点我不太同意。这是个职责范围的认定问题,虽然职责的认定是仁者见仁智者见智的,但是我觉得有些基本的东西是确切的,比如职责必须很明确、完整。 上述F1~F3,每个方法都没有完整明确的职责,看代码的感受就是不知道这个方法想干什么,感觉每个方法的存在意义都不大,逻辑合并起开才勉强知道想干什么,所以我认为职责模糊的方法是违反单一原则的。还有就是返回值,有返回值并且设置void的,也属于职责不明确。除非真的没有返回值可以用void,否都应该返回值。隐式返回容易让调用者遗忘哪些变量被改变了,进而引起编程bug。

3、最少知道原则

         这也是个非常重要的原则。直接借用定义:一个对象应该对其他对象保持最少的了解。通俗的来讲,就是一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息。

函数层次上也要遵循此原则,方法的开口尽可能的小,入参之间不要有依赖,一个极端的例子:一个方法有3个入参,但是第1个和第2个能计算出第3个,就是违反最少知道原则,第三个参数应该在方法体里计算出来。

入参过多很可能会违反最少知道原则,上面的F1,F2,F3的例子,形参个数多达10个。有些很复杂的场景,传递的参数如果真的必须很多的话,考虑用有意义的实体封装,这个封装其实是对入参“归类”,虽然需要的参数还是需要一一“拿到”然后放进实体里,但是归成一类逻辑上可以看成“一个”。很多公司的规范对形参个数都有要求。

 4、考虑线程安全问题

线程安全问题平时很难测试到,需要特别注意,时时注意。静态变量、单例的成员变量都是可能被多个线程访问的、资源的非原子操作(例如数据库一个值读取出来后,再update+1)等等,设计类时把考虑线程是否安全当成一个习惯。

5、好的代码是什么样的

好多人常说优雅,优雅是代码的最高境界。我们不说优雅,一般来说比较优秀的代码是什么样的?根据之前的解释,以函数为单位,个人觉得好的代码是一颗树,一颗主干分支分明、错落有致的树。入口函数可以看成树的主干,调用的函数是分支,树是逐渐细化的。如果单独看一个分支的话,它还是一颗树。试想一下,没有分支、只有主干的树是否美观?主干和分支一样粗的树是否美观?一个不平衡的树是否美观?

其实编写代码是需要一定的审美的,如果你的代码不美观,很可能你的审美有问题。

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页