[CodeComplete]创建一个函数需要理由吗

以下为<<代码大全2>>[第七章高质量的子程序]的摘录

编程中什么是标准,相信大家都没有办法给出一套成系统的理论,而《代码大全》的作者就是在为我们描述从设计到实现诸多大家或意识到而没有深究,又或者还没有意识到的问题,通过系统的方式为大家展开了软件开发中诸多细节。希望对大家能都所帮助!

本章探讨了以下问题:

   创建子程序的正当理由

   在子程序层上展开设计

   起个好名字

   子程序可以写多长

   如何使用子程序的参数

   使用函数时要特别考虑的问题

        什么时候使用函数,什么时候使用过程

   宏子程序和内联子程序 (内容不错)

这里的子程序指的是完成一个特定目的的方法或过程,大家通常在什么状况下决定创建子程序?这个答案可能很难清楚。但在<<编码大全>>中有完整的分析.

1.创建子程序的正当理由

 a.降低复杂度

     可以通过创建子程序来隐藏一些信息,使得在实现时不必过多的考虑这些信息。当程序写好后甚至可以忘记这些细节,只要还记得如何使用。

  b.引入中间、易懂的抽象

      把一段代码放入一个命名恰当的子程序中,是说明这段代码的最好方法之一。如:

      if(node <> NULL) then

         while (node.next <> NULL ) do

             node = node.next

             leafName = node.name

         end while

      else

         leafName=""

      endif

      如果改成这条语就会更容易理解:

      leafName = GetLeafName( node )

 

      这个函数名GetLeafName提供更高层次的抽象,从而使代码更具可读性,也更易于理解,同时降低原来包含上面代码的子程序的复杂度。

  c.避免代码重复

    如果在两段子程序内编写相似的代码,就意味着代码分解(decomposition),这就是错误。此时应当将两段子程序中的重复代码提取出来,将其中相同部分放入一个基类,然后将差异代码放入派生类中。还有另一种方法(如C语言开发),将相同的代码放入新的子程序中,然后让其余的代码来调用这个子程序。这样的代码更加可靠,因为对于验证代码的正确性,你只需要检查一处代码,同时这样也会使改动更为可靠,因为你可以避免需要做相同的修改时,却做了一些略有不同的修改。

  d.支持子类化(subclassing)

     覆盖(override)简短而规整的子程序所需新代码的数量,要比覆盖冗长而混乱的子程序更少。如果你能让可覆盖的子程序保持简单,你在实现派生类的时候也会减少犯错的机会。

  e.隐藏顺序

     如一个程序通常是从先从用户那里读取数据,然后再从一个文件中读取辅助数据,那么,无论是从用户那里读取数据的子程序还是从文件中读取数据的子程序,都不应该依赖另一个子程序是否已执行。一个明确的例子是先读取栈顶的数据,然后减少stackTop变量的值,这时就应当把两行代码放到一个叫PopStack()的子程序中。把这种信息隐藏起来,总比让它们在系统内到处散布要好很多。

  f. 隐藏指针操作

      指针操作的可读性通常都很差,而且也容易出错。通过把这些操作隔离在子程序内部,你就可以把精力集中于操作的意图本身,而不是指针操作的细节。同时如果此类和都能在一个位置完成,那么你对代码的正确性就会更有把握。如果你发现了比指针更合适的数据类型,也可以对程序做出修改,而不用担心会破坏了那些原本要使用指针的代码。

  g.提高可移植性

     可以用子程序来隔离程序中不可移植的部分,从而明确识别和隔离示来的移植工作。

 h.简化复杂的布尔判断

      为了理解程序的流程,通常并没有必要去研究那些得杂的布尔判断的细节,应当把这些判断放入函数中,以提高代码的可读性,因为:(1)这样就把判断的细节放到一边了; (2)一个具有描述性的函数名字可以概括出该判断的目的。

 i. 改善性能 

     通过使用子程序,你可以只在一个地方优化代码。把代码集中在一处可以更方便地查出哪些代码的运行效率低下。同时在一处优化,就能使用到(无论直接或间接)该子程序的所有代码都从中受益。

  j.确保所有的子程序都小 (NO)

     如果有这么多好的理由来把代码写成子程序,这一点就没有必要了。

2.似乎过于简单而没必要写成子程序的操作

编写有效的子程序时,一个最大的心理障碍是不情愿为一个简单的目的而编写一个简单的子程序。写一个只有两三行代码的子程序可能看来有些大材小用,但经验表明,一个很好而又小巧的子程序会十分有用。

  小的子程序有许多优点。其一便是它们能够提高其可读性。我曾在一个程序的十多处写了下面的代码:

  points = deivceUnits * (POINTS_PER_INCH / DeviceUnitsPerInch())

多数人都能看懂这是从设备单位(device unit)到磅数(point)的转换。但是它还可以更清楚些,所以我创建了一个程序:

  Function DeviceUnitsToPoints(deviceUnits:Integer):Integer

      DeviceUnitsToPoints = deivceUnits * 

                (POINTS_PER_INCH / DeviceUnitsPerInch())

  End Function

   在用这个子程序取代了那些直接嵌入计算的代码(inline code)之后,程序中那十几行代码就差不多成了下面:

  points = DeviceUnitsToPoints( deviceUnits)

这行代码更具可读性--甚至已经达到自我注解的地步。

这个例子还暗示出把简单操作写成函数的另一个原因:简单的操作常常会变成复杂操作。如在某些情况下,当某个设备使DeviceUnitPerInch()返回0,就意味着必须考到除零的情况,为此就需再多写3行代码。而在以子程序实现前则需要付出数十倍的工作量。

3.起个好名字

注:函数名不要用拼音,那样会有方言发音的问题。

a.描述子程序所做的所有事情

子程序的名字应当描述其所有的输出结果以及副作用(side effects).如果一个子程序的作用是计算报表总额并打开一个输出文件,那么把它命名为CompuseReportTotals就还不算完整。ComputeReportTotalAndOpenOutputFile()是很完整,但是又太长且显得有点傻。如果你写的是有一些副作用的子程序,那就会起出很多又长又傻的名字。解决的方法不是改成其它名字,而应该换一种方式编写程序,直接解决问题。

b.避免使用无意义的,模糊或表述不清的动词  

有些动词的含义非常灵活,可以涵盖几乎任何含义。像HandleCalculation(), PerformServices(0,OutputUser(),ProcessInput()和DealWithOutput()这样的子程序名字根本不能说明子程序在做什么。当然,当handle用做事件处理这一特定的技术含义时是个例外。

如果将HandleOuput()改为FormatAndPrintOutput()就更容易看清这个子程序的功能。

c.不要仅通过数字来形成不同的子程序名字    

 有个程序员把所有的代码都写成一个大的函数,然后为每15行代码创建一个函数,并把它们分别命名为Part1,Part2等。这种创建子程序和给子程序命的做法实在是骇人听闻。

d.根据需要确定子程序名字的长度   

研究表明,变量名的最佳长度是9到15个字符。子程序通常比变量更为复杂,因此,好的子程序名字通常也会更长一些。另一方面,子程序名字通常是跟在对象名字后,这实际上为其免费提供了一部分名字。总之,子程序名的长短要视该名字是否清晰易懂而定。

e.给函数命名时要对返回值有所描述   

 函数有返回值,因此函数的命名要应该针对其返回值进行。如比cos(),customerID.Next(),和pen.CurrentColor()都是不错的函数名,它人精确地表述了该函数将要返回的结果。

f.给过程起名时使用语气强烈的动词加宾语的形式

  一个具有功能内聚性的过程通常是针对一个对象执行一种操作。过程的名字应当能反映该过程所做的事情,而一个针对某对象执行的操作就需要一个动词+宾语形式的名字,如PrintDocument() CalcMonthlyRevenues(),CheckOrderInfo()和RepaginateDocument()等。

g准确使用对仗词

  命名时使用对仗词的命名规则有助于保持一致性,从而也提高可读性。像first/last这样的词组就容易理解;而像FileOpen和_lclose这样的组合就容易使人迷惑。下面列出一些常见的对仗词组:

add/remove   increment/decrement  open/close

beign/end   insert/delete     show/hide

create/destroy   lock/unlock   source/target

first/last   min/max   start/stop

get/put   next/previous   up/down

get/set   old/new

h.为常用操作确立命名规则

 在某些系统里,区分不同类别的操作非常重要。而命名规则往往是指示这种区别的最简单也是最可靠的方法。

在我做过的一个项目的代码里,每个对象都被分配了一个唯一标识。我们忽视了为返回这种对象标识的子程序建立一个命名规则,以至了有了下面这些子程序名字:

  employee.id.Get()

  dependent.GetId()

  supervisio()

  candidate.id()

其中Employee类提供了其id对象,而该对象以进而提供了Get()方法;Dependent类提供了GetId方法; Supervisor类则把id和为它的默认返回值;到了项目中期,已经没人能记住哪个对象应该用哪些子程序了,但此时已经撰写了太多的代码。这样一来项目组中每个人都不得不花费不必要的精力,去记住每个对象上采用的获取id的语法细节。而这些问题完全可以通过建立获取id的命名规则而避免。

4.子程序可以写多长

注:书中首先列举历年对子程序代码长度对质量影响的研究成果!

如果要编写一段超过200行代码的子程序,那你就要小心了。对于超过200行代码的子程序来说,没有哪项研究发现它能降低成本或降低出错率,而且超过200行后,你迟早会在可读性方面遇到问题。

*关于函数复杂度,可以参考另一篇文章

高质量的子程序的Check List:

大局事项

   Y/N   创建子程序的理由充分吗?

   Y/N   一个子程序中所有适于单独提出的部分是不是已经被提出到单独的子程序中了?

   Y/N  过程的名字中是否使用了强烈、清晰的"动词+宾语"词组?函数的名字是否描述了其返回值?

   Y/N   子程序的名字是否描述了它所做的全部事情?

   Y/N   是否给常用的操作建立了命名规则?

   Y/N   子程序之间是否有较松的耦合?子程序与其他子程序之间的连接是否是小的(small)、明确的(intimate)、可见的(viaible)和灵活的(flexible)?

   Y/N   子程序的长度是否是由其功能和逻辑自然确定,而非是依照任何人为的编码标准?

参数传递事宜:

   Y/N  整体来看,子程序的参数表是否表现出一种具有整体性且一致的接口抽象?

   Y/N   子程序参数的排列顺序是否合理?
是否与类似的子程序的参数排列顺序相符?

   Y/N   接口假定是否已在文档中说明?

   Y/N   子程序的参数个数是否没超过7个?

   Y/N   是否用到了每个参数?

   Y/N   子程序是否避免了把输入参数用做工作变量?

   Y/N   如果子程序是一个函数,那么它是否在所有可能的情况下都能返回一个合法的值?

时间: 2024-11-01 02:22:32

[CodeComplete]创建一个函数需要理由吗的相关文章

sqlpus 中创建一个函数打印出表空间的大小

做这个实验的目的是为了熟悉函数返回值在sqlplus中的显示,嘿嘿! 我写了个很简单的函数,在sqlpus 中操作的,偷了一下懒我用sys用户测试: 步聚如下: 1.创建一个函数 SQL> conn / as sysdba Connected. SQL> show  user; USER is "SYS" SQL> CREATE OR REPLACE FUNCTION f_tpsum(intpn IN VARCHAR2)  2  return VARCHAR2 IS

变量-使用MFC单文档,怎样创建一个类并在里面填写函数

问题描述 使用MFC单文档,怎样创建一个类并在里面填写函数 使用MFC单文档,现在想新建一个类(系统初始已经创建好doc/view等类),并把NavView.cpp里面的变量传入这个新类里面,并创建一个函数处理这个变量,请问应该怎么操作? 1.应该创建什么基类?2.怎样把变量传到新建的类里面?3.怎样把新建基类里面的变量值传到其他类里面? 解决方案 从类向导里边就可以创建一个新的类.为这个类指定一些友元函数,设置Public权限就可以了.如果只是单纯的传变量,那么直接把类变量权限设置为公有的就可

DB2中创建一个获取汉字拼音首字母的SQL函数

有些时候我们会有这样的需求,要求使用字母从a至z对一组数据进行索引,如果数据的格式全部是半角的英文则很容易实现,但若是对一组中文数据进行索引则会引起一点小的麻烦,数据在录入数据库的时候可能并没有指定一个索引字母,这就要求应用程序可以自动生成用于索引的信息. 一般对于中文数据的索引,采用词组的首汉字拼音的首字母,例如: 词组 索引字母 --- ----- 熊猫 x 白暨豚 b 藏野驴 z 在DB2中并没有提供相应的函数可以取得汉字拼音的首字母,我们可以利用数据库针对中文字符集的排序功能创建一个这样

c语言-C语言里想要用函数创建一个新的字符数组,并使其等于原有的一个字符数组该怎么做?

问题描述 C语言里想要用函数创建一个新的字符数组,并使其等于原有的一个字符数组该怎么做? #include #include #include char map[4][4]; char creat()//创建一个新的字符数组 { char *maze=(char)malloc(sizeof(map)); return maze; } void main() { int i,j; for(i=0;i<4;i++) { gets(map[i]); } char *maze=creat(); strc

创建一个存储函数,返回指定员工的姓名,薪水和年收入

/* 创建一个存储函数,返回指定员工的姓名,薪水和年收入 */ create or replace function queryEmp2(eno in number, empname out VARCHAR2,empsal out NUMBER) --返回年收入 return NUMBER as begin   select ename,sal into empname, empsal from emp where empno=eno;   --返回年收入   return empsal*12

如何创建一个JavaScript弹出DIV窗口层的效果_javascript技巧

在本教程中,我将用最通俗的语言和最简洁的代码给大家演示如何创建一个JavaScript弹出DIV窗口层的效果. 创建一个弹出DIV窗口可能是现在网站/网页制作中最常碰到的问题之一.传统的JavaScript弹窗已经不适合目前网站的设计理念了,理由有二:首先,不友好--生硬的弹出对话框且伴随着"哐"的一声对用户体验是个很大的挑战:其次,兼容性不够强--有相当多的浏览器屏蔽了这种JS的Alert()方法.于是,一个良好用户体验的网站需要一种更合理的解决方案--使用很少的HTML代码,很少的

使用Java Swing 创建一个XML编辑器

xml|创建 我想您一定对XML有所了解,说不定您现在还跃跃欲试想写一段XML文本呢,可是现在能找到的跨平台的.免费的XML编辑器太少了.所以在本文中,我想介绍一下或者说带您一步一步的开发一个简单的XML编辑器,当然我们要用到一些最常见的Java 2 Swing组件,不过这些都是免费的,有些是JDK中的,有些是可以从网上下载的.我想通过本文,你就可以创建一个属于你自己的XML编辑器. 先让我介绍一下本文辑写的思路.首先我想简要的讨论一下XML和为什么树型结构比较适合用来显示XML,然后我们来看一

创建一个ASP通用分页类

创建|分页 转自"蓝色理想" http://www.blueidea.com/tech/program/2004/1989.asp.ASP分页一直是一个众说纷坛的话题,而且也没有一个太有效的方法.今天在CSDN的BLOG里看到了这个ASP分页类,(http://blog.csdn.net/xiangbo520/archive/2004/09/22/113539.aspx),只是提供了源码,而且源码中部分标签已被作为HTML显示了,想复制下来也不太容易,所有到蓝色理想找到了一篇全面一点的

创建一个ASP分页类(一)文章部分

创建|分页 创建一个ASP通用分页类 平波 从开始学习到使用ASP到现在也写了不少程序了,最令人头痛的是写数据分页,每次都是由于几个变量名或几个参数的不同,因而需要每次都写哪一段冗长而又繁杂的分页代码,代码长了使得程序的可读性变差,容易出差,调试半天也找不出错在哪里,所以慢慢的我开始使用一些网上的提供的分页函数或分页类.的确省事不少,但是通常的函数和类的做法都是就数据显示部分也封装了起来,每次为了达到自己需要的显求效果要去改动函数或者类的本身,所以使用起来也不是怎么方便,自己写的分页改起来已经够