《代码大全》读书笔记下篇

来源:互联网 发布:windows live id大全 编辑:程序博客网 时间:2024/05/01 04:20
Subsections 
数据名称
变量
常量
基本数据类型
条件语句
循环语句
代码调整
调试
集成
 
(一)、 数据命名
     (1)、命名规则

      命名应该是面向问题而不是面向解决问题。应该说明的是”什么“而不是”怎么“。变量名称是否完全而又准确地描述了变量所代表的实体

      类名、文件名都应该是以名词的组合 tradelist tradeedit tradeview tradesave

      方法名应该是动词+名词的组合     createDate()   removeTrade()

     a、 限定词 如 data,total average 应该放在名词的后面

            eg: currDate,yesterdayDate,saleCount,firstDate,lastDate,RevenueAvg。
     b、恰当使用反义词
          add/remove begin/end create/destroy insert/delete first/last get/release increment/decrement put/get up/down lock/unlock min/max next/previous old/new open/close show/hide source/destination source/target start/stop 

    b、循环变量命名

            i、j、k、 如果在循环体内的变量要在循环外被使用,则要使用比i、j、k 更能说明问题的名词

           eg:  recordCount: 已经读取了多少记录。

   c、状态变量命名

       flag,最好不用flag作为状态变量的名字。之所以要避免使用flag作为标志名称,是因为它不能告诉你关于这个标志的任何信息。为了清楚起见,应该给标志赋值,并且用枚举类型、命名常量或当作命名常量使用的全局变量对其进行测试。
     eg:if ( DataReady )
          if ( CharacterType  PRINTABLE_CHAR )

   d、临时变量

     temp,少用temp这样没有意义的临时变量。

   e、逻辑变量

     isDone  某项工作已经完成

     isError   某个操作发生了错误

     isFond    找到了某个值

     isSuccess/complete   某一操作是否成功(完成)
    f、枚举类型命名
     当使用枚举型变量时,可以通过使用相同的前缀或后缀表示某一类型的元素都是属于同一组的,如下
 type COLOR is ( COLOR_RED, COLOR_GREEN COLOR_BLUE );
 

  (2)、命名规约

       标示全局变量   以g_作为前缀来解决  g_runTotal

       标识静态常量   以_C作为后缀来解决 runTotal_C
 
  (3)、短名称
          缩写使用的总体准则
  • 使用标准的缩写(常用缩写,如列在字典缩写表中的)。
  • 去掉所有的非大写元音字母(如Computer写成 Cmptr, Screen写成 Scrn, Integer写成Inter 等)。
  • 使用每个单词的头一个或头几个字母。
  • 截掉每个单词头一至三个字母后面的其余字母。
  • 使用变量名中每一个有典型意义的单词,最多可用三个单词。
  • 每个单词的第一个和最后一个字母。
  • 去掉无用的后缀——ing,ed等等。
  • 保留每个音节中最易引起注意的发音。
  • 反复交替地使用上述技术,直到变量名长度缩短至8到 20个字母为止,或者到你所用语言规定的长度为止。

(二)、变量

    1、变量的作用域

            模块作用域、函数作用域、全局作用域

         原则:(1)尽量减少变量作用域。
                    (2)把对某一变量得引用集中放置,提高程序的可读性。可以通过变量得跨度来衡量(跨度 变量重复出现中间间隔的行数之和)。和变量的存活时间(变量出现的次数,越少越好)

           eg:  initA(int a)

                   initB(int b)

                   intC(int c)

                  accountA(int a)

                  accountB(int b)

                  accountC(int c)

            平均跨度:(2+2+2)/3=2

           改进:

                    initA(int a)

                    accountA(int a)

                   initB(int b)

                   accountB(int b)

                    initC(int c)

                   account(int c)

               平均跨度:(0+0+0)/3=0  可读性强

   2、变量得赋值时间

         把变量和变量的值联系在一起是在写程序时把它们联系在一起?还是在编译、加载或者程序运行时把它们联系在一起?

         private static final int X_C=20;

         x=X_C   这是在编译得时候变量和变量值联系在一起 。
         x=47  这是在写程序时就把变量和变量值联系在一起。

         x=MaxID;或者  x=getMaxID() 都是在运行时把变量和变量值联系在一起。

         原则:越晚联系在一起越好。
 
     3、变量功能单一性
          应使每一个变量只具有一个功能。有时人们会试图在两个不同的地方使用同一变量来进行两个不同的活动。
 
(三)、常量
     (1)、避免" 奇异数"(magic numbers) 。“奇异数”指的是出现在程序中间的不加解释的常数。如00 47524。改进:用命名常量来代替它。public static final maxi = 100。
      (2)、采取预防被"0" 除的措施。考虑除数是否有可能是零。如果存在这种可能性的话,应加入防止被“ 0”除的代码。
      (3)、明显进行类型转换。当程序中有不同类型间的转换时,要确保这种转换是明显的。
      (4)、避免混合类型比较。如果 x是浮点数而 i是一个整型数,那么以下检验 :if ( i=x ) then ⋯⋯ 

 

(四)、基本数据类型

       1、浮点数 :  (1)、不要在数量级相差太大的数之间进行加减运算。

                               (2)、避免相等运算

                                        float x=5.5;

                                        float sum=0;

                                         for(int i=0;i<10;i++){

                                                 sum=i+0.5;

                                                  if(sum==x) then ....     

                                                  else .......

                                          }

                               //改进 增加逻辑函数,当两个值足够接近时返回True
                                                boolean equal(float x,float y){

                                                  if(x-y<0.0001)  return true;   //说明他们是相等的

                                                  else return false;

                                  }

 
      2、逻辑变量:把一逻辑表达式赋值给某一变量,主要用来解决复杂的逻辑判断。

                          if(element<0||element>100||element==80){

                                     then ...

                           }

                           //改进

                         boolean finished=element<0||element>100||element==80;

                         if(finished){

                                    then .....

                         }

 

(五)、条件语句

           if.. else....      switch()   case default

         (1)、 简单的if...else...语句的几个原则:

                      a、现写正常的语句后写异常的语句。

                      b、正常情况放在if后面异常情况放在else后面。
                      c、对于复杂的条件可以用逻辑语句或逻辑函数来代替。
                      d、把最常见的情形放在最开始。把最常见的情形放在最开始,提高了寻找常见情况的效率。
 
         (2)、case语句
              不要为了用case 语句而去定义伪交量。一个 case语句应当用在易被归类的简单数据上。假如数据不简单,用 if-then-else代替 case。伪变量显得很乱,应当避免使用。
                      case 语句中的缺省语句最好是不用做处理的操作,只是放一些诊断信息而已。
 

 (六)、循环语句

        (1)、 do-while  while-do    for     

       其中  for 循环是用于知道了循环条件要执行多少次,而 while不知道要执行多少次,而是每次重复之前检查是否满足循环条件。循环的初始化工作最好放在一起且在循环的开始部分。

         (2)、 break、continue 判断条件最好写在开始部分。
                 continue 语句是if then 语句的简写形式,而后者可能使循环的余下部分不被执行。
               continue 放在循环的头前检查。 continue语句的一种很好的用法是把它放在循环的头部检查,若满足某一条件才执行循环体。如下例程序,循环要做的是先读入记录,然后看是否满足一定条件,若不满足,就放弃这个记录;若满足,就处理这个记录。这时把 continue放在循环的头部:
               continue比较安全的伪代码程序:
while(not eof(File)) do
read( Record, File )
if( Record.type<>TargetType ) then
continue
{process record of TargetType}
...
end while 
这种用continue 的方法可使你避免在用 if语句时,需把循环体往后缩几列的做法。但是若 continue出现在循环体的中间或结尾,那最好还是用 if-then替代。 
 
(3)、编写循环的简单方法——从里到外
下面是一般过程,从一种情况开始,编码时先用一些文字,然后把整个代码往后退几格,套上循环;再把文字用循环,替代掉能替代的文字,如此下去,直到完成为止。整个过程完成以后,加上必需的初始化条件。因为你是从最简单情形开始的,从里向外逐级编写,你编程时也就相当于从里到外。
 
(七)、代码调整

          1、代码调整策略

代码调整只是提高程序性能的一种方法,它是对正确代码进行调整的实践,它可以使代码的运行更高效,调整指的是小规模的修改,可以影响单个类,单个子程序。

        一些代码调整的错误说法:

  •           在高级语言中,减少代码的行数可以提升所生成的机器代码的运行速度。
  •           应当随时随地的优化,容易使程序员陷入一叶障目,而忽视了整个系统全局的优化。
  •           程序的运行速度同其它正确性同等重要,在程序无法运行的情况下,不可能要求程序更小巧,更快,所
  •          有需要先让程序能运行,再调整。

       何时调整:

                 程序员应当使用高质量的设计,把程序编写正确。使模块化并易于修改,将让后期的维护工作边的很容易。在程序已经完成并正确之后,再去检查系统的性能。

        2、代码调整方法

当你考虑代码调整是否能帮助提高系统性能时,应该按如下步骤进行:
  • 用高度模块化设计开发软件,这样易于理解,修改。
  •  如果性能很差,测量系统,找出频繁执行位置。
  • 判断性能的弱点是否是由不合格的设计数据结构算法引起的,判断代码调整是否合 适;如果代码调整不合适,返回步骤1。
  • 调整步骤3中识别出来的系统中的薄弱环节,测量每一个改进,如果它不能提高系统性能就放弃重来。
  • 重复步骤2。
 

        3、代码调整技术

1、循环
1.1、避免开关
     开关指在循环体内设判断句,每次循环一次执行一下判断。如果循环执行时,判断指向不变,你可以在循环外设判断,对循环避免包含开关,将循环嵌入条件中而非相反。这里有个反切换的例子。
     循环体内有开关的例子:
for (int i = 0; i < count; i++) {if (SumType == Net) {NetSum = NetSum + Amount[i];} else {GrossSum = GrossSum + Amount[i];}}
这段中,通过每次重复,if(sumType==Net)都被测试一遍,甚至当其状态相同也要一遍检验。为提高速度,写成如下方式更好:
 不含开关的例子:
if(SumType==Net){       for(i=0,i<Count,i++){       NetSum=NetSum+Amount[i];       }}else {       for(i=0;i<Count;i++){       GrossSum=GrossSum+Amount[i];       }   }
 for 语句被多次重复,故代码占空间大些,但好处是省时。 本例的危险点在于两个循环须是并行的,若 Count 变为 Client Count,相应两处都得改变,
这对于维护程序是件苦差事。
 
1.2、冲突 或称“熔断” (fusion) ,是同一元素集被两个循环同时操作造成的。解决方法在于斩断循环,将其变成单个循环,这有个循环冲突的例子。
Basic 把会冲突的循环分离:
         for i=1 to Num
             EmployeeName(i)=" "
         next i
        for i=1 to Num
            EmployeeEarnings(i)=0
         Next i
如果你的程序有冲突竞争,你得将之合二为一。这通常意味着,循环计数器只能有一个。本例中两个循环用到 1 to Num,可能有冲突争先的危险,可以改成:
    Basic 有冲突的循环:
            for i=1 to Num
              EmploreeName(i)=" "
              EmployeeEarnings(i)=0
   next I
 
1.3、循环内工作量的最小化
写出有效率的程序段的关键在于,使循环内的工作量最小。如果你能估计出全部或部分结果语句,并仅将结果用在循环内,这么做是有理由的。通常这是一种编程的好方法,有时还会增可加可读性。
假如你有如下程序,循环内有一复杂的指针表达式:
C 循环内带有复杂的指针表达式:
for(i=0,i<Num;i++)
    {
               NetRate[i]=BaseRate[i]*Rates->Discounts->Factors->Net
    }
此处将合适的名字赋给变量,并另配给复杂的指针表达式可以提高性能和可读性。
C 简化复杂指针表达式:
  QuantityDiscounts=Rates->Discounts->Factors->Net;
for(i=0;i<Num;i++)
    {
      NetRate[i]=BaseRate[i]*QuantityDiscount
    }
特殊变量 QuantityDiscount,很清楚地使得数组 BaseRate 乘上一个数量因数,从而计算出网络比率,从循环中表达式看并不是那么清楚。若把复杂的指针表达式赋给循环外的变量,可防止每次运算循环时,指针都被三次引用。
除了 C 编译器外,这种方法提高的效率是微不足道的,这提醒你们,开始设计代码时不必过多考虑执行速度,而应从可读性人手。 
 
1.4、标志值
如果循环判断比较复杂的话,你可以简化判断句提高效率。如果循环是为了找数,一种方法就是使用标记值,把它安插在找数程序的末尾,并且保证终止找数检索。关于使用标记值从而改善复杂测试,这里有一个典型例子,循环的检索部分检查是否找到标志值,判断是否偏离标志值。
C 检索循环内的复杂判断:
     Found=FALSE
        i=0
        while( (!found)&&(i<ElementCount))
          {
             if(Item[i]==TestValue)
              Found=True
      else
                        i++;
           }
          if(Found) {
               //found
              break;
            }
这段中,循环对每一重!found 和i<ElementCount 进行判断。 !Found 判断是用来表示已找到所要的元素。i<ElementCount 是用来避免数组溢出。循环内,Item 的每一值都被分别测试,所以每执行一次循环,执行三次判断。 这种检索循环中,若你在检索段尾设标志来终止循环,并将三个判断合为一个,那么每循环一次就可作出判断。这时你再检查每一元素。如果头一个也正是末一个,你就知道了你要找的标志值并不存在。
C 用标志值加速循环:
        InitialValue=Item[ElementCount];
        Item[ElementCount]=TestValue;
        i=0;
        while(Item[i]!=TestValue)
             {
                    i++;
       }
          Item[ElementCount]=Initialvalue;
           /* check if value was found */
             if(i<ElementCount) {
                  ......
               }
 
1.5、将循环次数多的放在里面,循环次数少的放在外层
如果有多重循环,考虑好哪一个在内哪一个在外,下面是个如何改进多重循环的例子。 
Pascal 改进多重循环:
              For Column:=1 to 100 do
                     begin
                          for Now:=1 do 5 do
                              begin
                                   sum:=sumTable[1000,column]
                              end
                       end
改善程序的关键在于,如果外层循环要比内层执行项数多,循环每执行一次,先要初始化指针,每循环一次增加一, 并随之检查计数。 循环执行总数是外循环 100 次, 内层循环 100*5=500 次,总共是 600 次。如果改成
For Column:=1 to 5 do 
              begin 
                 for Now:=1 do 100 do 
                    begin 
                       sum:=sumTable[1000,column] 
                    end 
                 end 
单单切换内外循环,就能作到内层执行 500 次,外层只执行 5 次,这样总重复
数量 505 次,从分析上,可望节省(600-5O5)/600=16%的计算量。
测试: 
System.out.println("begin:" + new Date().getTime());for (int i = 0; i < 1000000; i++) {for (int j = 0; j < 100; j++) {}}System.out.println("end:" + new Date().getTime());
begin : 1367577412046
end  1367577412156
110 毫秒
System.out.println("begin:" + new Date().getTime());for (int i = 0; i < 100; i++) {for (int j = 0; j < 1000000; j++) {}}System.out.println("end:" + new Date().getTime());
begin : 1367577384546
end  1367577384640
94 毫秒
2、逻辑
2.1、当你知道答案时就停止判断
假定你有一个语句如下:
if(5<x) and(x<10)then⋯
一旦你已经确定x小于5,就不需要再执行第二个判断了。
 
2.2、查表法代替复杂判断
在一些情况下,查表法可能比沿着复杂的逻辑判断链执行更快。在复杂的判断链中,通常可将一些事物归成一类,然而按这种分类执行。
3、数据转换
尽量用整型数不用浮点数
尽量减少数组维数
尽量减少访问二维或三数组有利于减少数组访问次数,同样,循环反复用数组中的元素也适合用这种方法改进,下面是不必要的数组访问的例子:
for(DiscountLevel=0;Discountlevel<NumLevels;Discountlevel++)
{
  for(RateIdx=0;RateIdx<NumRates;RateIdx++)
  {
    Rate[Rateldx]=Rate[Rateldx]*Discount[DiscountLevel];
  }
}
当RateIdx在内循环中变化时,Discount[Discountlevel]不会改变,你可将Discount [Discountlevel]移出内循环,这样你每执行一次外循环就只有一次数组访问,而不是每执行一次内循环就有一次访问。下个例子是修改过的代码:
for (discountlevel=0;DiscountLevel<NumLevels;DiscountLevel++)
{
     ThisDiscount=Discount[DiscountLevel];
     for (RateIdx=0;RateIdx<NumRates;RateIdx++)
     {
          Rate[RateIdx]=Rate[RatIdx]*ThisDiscount;
     }
}
 
(8)、调试
发现错误的方法:
  •  使用所有数据建立假设
  •  求精产生错误的测试用例
  •  通过不同的方法再生错误
  •  产生更多的数据以生成更多的假设
  •  使用否定测试结果
  •  提出尽可能多的假设
  •  缩小可疑代码区
  •  检查最近作过修改的代码
  •  扩展可疑代码区
  •  逐步集成
  •  怀疑以前出过错的子程序
  •  耐心检查
  •  为迅速的草率的调试设定最大时间
  • 检查一般错误
  • 使用交谈调试法
  • 中断对问题的思考
  • 改正错误的方法:
  • 理解问题的实质
  • 理解整个程序
  •  确诊错误
  • 放松情绪
  • 保存初始源代码
  • 修改错误而不是修改症状
  • 仅为某种原因修改代码
  • 一次作一个修改
  • 检查你的工作,验证修改
  • 寻找相似错误
  • 调试的一般方法
  • 你是否将调试作为学习有关程序、错误、代码质量及解决问题方法的一次机会?
  • 你是否避免使用试错法,或避免采用迷信的方法?
  • 你是否认为错误是由于你的过错?
  • 你是否使用科学方法以固定间歇性错误?
  • 你是否使用科学方法发现错误?
  • 你是否每次使用不同的方法,而不是只用一种方法发现错误?
  • 你是否验证了修改信息是正确的?
  • 你是否利用了警告信息、执行剖析程序、建立脚手架方法、交互式调试等?
 
(九)、集成
集成是开发人员完成开发测试以后,而且集成过程是和系统测试一道,所以集成测试本身就很复杂。
集成频率:阶段式集成还是增量集成。
分段集成
   1. 设计、编程、检查和调试。这个步骤叫“单元开发”。
   2. 将各程序合并成一个非常大的系统,这叫“系统集成”。
   3. 检查和设计整个系统。这叫做“系统再集成”。
分段集成的一个问题是:当各程序在系统中首次被放在一起时,新的问题不可避免地显露出来,并且问题的原因可能发生在的系统的任何部位。既然你有大量的程序还没有在一起工作过,事故原因可能是不易检查的两个程序间的接口错误,或是两个程序相互作用引起的错误。所有的程序都被怀疑。
增量集成
开发系统中一个小的功能块。它可以是最小的功能块,最硬的部分,或是一个关键部分。彻底地检查,调试这部分。它将当做一个骨架,在它上缚着肌肉、神经、皮肤,组成系统的其它部分。
递增集成的优越性,当出现新问题时,错误一定出在新程序上,无论它是新程序与其余程序接口包含的错误,还是新程序和以前被集成的