编程规则 - 4 方法设计规则

 摘要:方法设计原则 类设计原则 编程规范 设计规则 编程指导      参阅:概述   命名

4 方法

    从早期面向过程的编程到目前面向对象的编程,永恒的还是方法,面向过程的编程称为函数,实际上,面向对象的静态方法也基本等同于函数;也就是说无论何种编程语言,方法是软件的核心;面向对象的进步在于将方法(行为)和属性进行封装,使行为和属性以类的形式组织起来,能够更好地表达应用领域;事实上,软件系统就是对应用领域的抽象表达,你的表达方式与接近客观领域,你的表达力就会越强,表达就会越简洁,越易于理解。

    方法是对行为细节的表述,方法有机地组织起来、协同工作形成一个软件系统,尽管我们非常强调数据结构、类体系结构,它们的确在系统中至关重要,但这些仅构成系统的骨架和躯干,方法才是这个系统的灵魂,方法是系统具有了活力和生命。既然方法如此重要,那么我们在设计方法时应该遵循哪些原则呢?

4.1 起个好名

    名字的重要性我们在前面已经描述过来,方法的名称要能够恰当地描述它做的事,后面我们会介绍方法要功能单一,这样的方法也比较容易起一个简洁响亮的名称,当你一时找不到一个简洁的好名时,可以起一个通俗一点、贴切的长名,这也比令人费解的短名要好很多,当有更恰当的名字时,及时重构,多花些时间起个恰当名称是非常值得的,追求一个好名能帮助你改进设计,通常你很难为你的方法起恰当的名称时,往往说明该方法的职责不清,需要拆分和从新设计。

4.2 只做一件事、做好一件事

    这一点非常重要,一个方法一定要专注地只做一件事、做好一件事,只做一件事是强调方法不要做与其职责无关的事(不要替别人做事,软件系统不需要雷锋),做好一件事,是强点方法必选负责把这件事全面、完善地处理好(不要本该自己做的事推给别人,软件系统不喜欢推诿),例如输入数据的合法性检查、各种错误及异常的处理、处理后数据的合法性检查等。

4.3 方法体内的逻辑在同一个抽象层次上

         这是一个非常重要的概念和方法设计指导原则,希望我们一定要弄懂它的含义并遵照执行,我们大多数人在设计和编写方法是违背该原则,从而造成方法混乱、层次不清,即不利于阅读也不利于维护。为了让大家搞清楚这个原则的含义,下面举例说明:

我们以客户到银行取现金的后台处理程序为例,这个例子是一个经过高度简化的示意程序,实际的程序要比这细致的多。首先给出一个违背此规则的取款算法,该方法试图将全部取款的业务处理逻辑包含在一个过程中(有些程序员甚至认为这样写代码能够一气呵成,是一种非常好的编程习惯,因此大家一定要特别注意,要深刻认识到这样做是非常糟糕的,是要坚决摒弃的),我们会发现取款的业务逻辑细节和业务步骤混合在一起,非常的混乱,以后任何取款处理逻辑的变化都会对该方法进行修改,这样就会为将来的维护和发展带来更多的麻烦;同时这样的程序也不便于阅读,它违背了一个方法只做一件事的原则,不同抽象层面的逻辑混在一起,很难给读者一个清晰的整体概念。

         下面是梳理后的取款处理方法:

         看到这个变化有的程序员马上会说,你看越变越复杂了吧,还是一气呵成的好!哪么我们来认真分析一下:

1)          withdrawal(accNo,amount,passWord) 方法变的简单和清晰了,从这个方法中我们可以清晰地看出取款的处理过程;它几乎完全和需求描述的一致,具体细节都被隐藏了,以后无论合法性检查还是借记或贷记账户的逻辑怎样变化,该方法是稳定的了。你可能还会问,这个方法好像做了好几件事啊,它不是也违背只做一件事的原则吗?不,它只做了一件事—取款处理,它里面做的每个动作都是在同一个抽象层次上的处理步骤。注意同一个抽象层次就是这里的重要概念和方法设计的重要原则,一个方法内部的处理动作必须在同一个抽象层面,不能粗细混搭。

2)          我们对withdrawal()方法的改进有了共识后,再来看看这样做带来的更多好处,细节被隐藏到具体小方法中,以后哪变改哪,这好处前面已经提到。而更大好处在于我们提炼出了借记、贷记和记明细帐的公共方法,而在银行业务中各种涉及资金的业务最终都要记账,或借记或贷记,都要记明细帐,我们有了这三个统一的记账方法,可以说你就已经建立了银行核心系统的核心算法,(当然实际系统的核心记账模块要比这细致和丰富的多),事实上其他几个方法也具有很强的通用性,这才是我们要求按方法设计原则进行设计的深层次目的,它会给我们带来更大的价值。

 

4.4 方法要短小

    有了前面的规则,该条规则就很自然了,方法要短小,再完成本抽象层次的任务前提下,越短越好,其实方法短小并不是目的,目的是让你遵守前几条规则,你遵守了前面的规则,方法自然就短小了,为了量化该规则,要求一般方法体不允许超过30行,最长不允许超过60行。

4.5 方法的参数尽可能的少

    方法的参数要依据其职责确定,要达到充分且必要,过多的参数意味着过多的职责,违背只做一件事的原则,会带来若干麻烦,因此要求:

类方法的参数最好为零,其次为一,再次为二,三个以上尽量避免。

提供给其他类使用的静态方法,根据其职责确定,不必要的参数一定要删除。

4.6 避免副作用

所谓副作用就是指方法做了它不该做的事,其实这很严重,有的方法承诺只做一件事,但是它还是悄悄地干了点别的事,比方说帮“别人”做了点小事,(生活中雷锋是好榜样,软件系统中绝对不提倡雷锋精神,而是要求就做好自己的一件事)。或者对类中的某属性做了非预期的修改,这都是非常具有破坏性的,会造成奇怪的时序耦合和顺序依赖,这种错误非常难于查找好定位,因为这个函数说谎了。

4.7 消灭重复

重复是软件中一切邪恶的根源。实际上,重复不仅发生在方法的逻辑中,也会发生在数据中,例如前端使用的下拉代码与后台程序中使用的代码分别定义,就是一种重复,因此我们要努力消灭一切重复。

代码中的重复有的很明显,有的不明显,有的看似相同,实际不同,有的看似不同,实际相同,怎么能够设计的干干净净,杜绝重复,需要长期实践和努力,它体现你的抽象分析和综合能力。它需要我们整体地分析业务领域,从中抽取和沉淀出基础和共享的稳定的业务逻辑。以下给出几个简单易行的办法:

1)       当你拷贝粘贴时,你要认真思考一下,你是否在制造重复,是否有一个公共的方法需要提炼出来。

2)       当你写的逻辑有大量形式上雷同的情况,你更要深入思考,这里可能有隐式重复,例如:

If (……) {

}

 If (……) {

}

……

If (……) {

}

这样形式的语句,往往都存在重复的问题。下面以一段实际代码说明这个问题:

          ………    

if (ctlD instanceof UBVDCPSgroup) // 组

                 return Control.makeGroup(type, p, (UBVDCPSgroup) ctlD,ObjFct, bd);

   if (ctlD instanceof UBVDCPSsw)  // 单窗口

           return Control.makeSw(type, p, (UBVDCPSsw) ctlD,ObjFct, bd);

      …………………

      if (ctlD instanceof UBVDGUIspace) // 隐藏的占位控件

           return Control.makeSpace(type, p, (UBVDGUIspace) ctlD,ObjFct, bd);

   这样处理过程臃长、单调而繁琐,而且似乎没有更好的办法解决,但记住世上无难事,像这样不良的结构一定能够找到一种好的解决办法。

   首先我们建立一个控件,描述类名与实际控件类名的对照关系 CtlDesAndCtlMap,Control提供一个根据类名创建控件的方法

makeCtlByName(); 然后增加如下creatContol()方法:

     private static Control creatContol(UBVDGUI ctlD,AFComCfgObj ctlCfg,

                 char type,Composite p,AFfrObjFactory objFct,AFBindingData bd) {

           if (ctlD==null)return null;

           String vdName = ctlD.getClass().getName();

           String ctlClzName = CtlDesAndCtlMap.get(vdName);  //通过控件描述名拿到物理控件的名称

           if (ctlClzName==null)return null;

           Control ctl = null;

           try {

                 ctl = Control.makeCtlByName(type, p, ctlD, objFct, bd, prsUC,ctlClzName);

                 return ctl;

           } catch (Exception e) {

                 e.printStackTrace();

                 return null;

           }         

      }

有了上面准备,前面那段丑陋、臃长的代码就可以用如下一行代码完成了:

   creatControl(ctlD, ctlCfg, type, p, objFct, bd);

而且,以后再增加新的控件,就不需要修改程序了,只需添加相应的对照表即可,这样你可以理解和体会开闭原则了,对新增控件开放,对修改关闭;另外,提示一下,我们的应用框架中已经提供了便利的键值对配置基础方法,对于在系统启动初期就需要通过键值对获取配置信息的,要使用此方法。

    程序中出现这种类似的重复,就需要引起我们的注意,要努力用更优雅的方法铲除这种隐式重复。

还有一种较丑陋的程序过程,姑且称为赋值序列:

   User.name   = name;

   User.amount = amount;

   ……

   User.add = add;

类似这样臃长、枯燥的赋值序列,也是一种不良代码,解决的办法有两种,一是将它们封装的一个底层函数中,二是通过参数配置解决这类繁琐的赋值问题,这样不仅避免了丑陋代码,而且实现了参数化,例如我们的服务调用模块,根据统一的服务描述统一地进行参数赋值处理。

4.8 打磨你的程序

    百炼成钢,要想得到精品程序,必须反复打磨你的程序,要勇于重构、勇于修改,不过可别越改越乱,应该在规则指导下反复斟酌推敲,使代码简洁干练、清晰易读,消灭重复、专注做好一件事。

5  注释

注释属于编程细节了,具体规定应该在编码规范中明确。这里只是给出几个指导性原则:

1)  简洁、有价值

注释并不是越多越好,注释要简洁明了,注释关键是要对程序意图进行说明,这样才有价值;提出废话,例如:

   a=10; //初始话a  或 把10赋给a

这样的注释就是废话,最好没有。

2)  关键部位和环境必须加注释

类前的注释,对该类进行介绍,说明它的职责和作用;

方法前的注释,介绍方法功能和参数;

方法体内的注释一定要在关键部位进行注释,说明意图,例如:循环、分支判断等关键处要注释说明在循环做什么、对什么在进行判断。

3)  及时剔除过时、与程序不一致的注释

我们经常发现许多程序中大量存在已经与程序不一致的注释,这些注释不及时剔除危害很大,会影响阅读和判断,一定要养成习惯及时删除这些注释,它们的存在就是一种罪过,危害无穷。

猜你喜欢

转载自blog.csdn.net/xabcdjon/article/details/6708081