《构建之法》 读书笔记

《构建之法》

读书笔记

娄雨禛 PB16060356

 

第一部分 关于结对编程的体悟与实践

  在结对编程这一部分我曾讲过很多的注意点,比如代码变量命名风格、缩进风格、注释风格,前后语句次序风格,等等。然而这里还有一些新的东西。代码风格这个老掉牙的话题咱们先搁置不谈,而说说在结对编程中同样重要的其他注意点。

 

  代码复审

  代码复审是一门学问。良好而有序的复审将帮助我们快速排查问题和增进代码可读性,而低劣的复审则纯粹在浪费时间。这里拿我在个人项目中的代码举一个例子。

 

  1 void anothermain(char *fileString)
  2 {
  3     char *offset = fileString;
  4     char move;
  5     char temp;
  6 
  7     int count;
  8     do
  9     {
 10         if(temp = *offset)
 11         {
 12             if(temp >= 32 && temp <= 126)
 13             {
 14                 Characters++;
 15                 if(temp >= 65 && temp <= 122)
 16                 {
 17                     if(temp >= 91 && temp <= 96)
 18                     {
 19                         offset++;
 20                         continue;
 21                     }
 22                     else
 23                     {
 24                         offset++;
 25                         word[0] = temp;
 26                     }
 27                 }
 28                 else if(temp >= 49 && temp <= 57)
 29                 {
 30                     offset++;
 31                     while(*offset >= 49 && *offset <= 57)
 32                     {
 33                         Characters++;
 34                         offset++;
 35                     }
 36                     while((*offset >= 49 && *offset <= 57) || (*offset >= 65 && *offset <= 90) || (*offset >= 97 && *offset <= 122))
 37                     {
 38                         Characters++;
 39                         offset++;
 40                     }
 41                     continue;
 42                 }
 43                 else
 44                 {
 45                     offset++;
 46                     continue;
 47                 }
 48             }
 49             else
 50             {
 51                 if(*offset == 10)
 52                     enterNum++;
 53                 offset++;
 54                 continue;
 55             }
 56         }
 57         else
 58             break;
 59         if(temp = *offset)
 60         {
 61             if(temp >= 32 && temp <= 126)
 62             {
 63                 Characters++;
 64                 if(temp >= 65 && temp <= 122)
 65                 {
 66                     if(temp >= 91 && temp <= 96)
 67                     {
 68                         offset++;
 69                         continue;
 70                     }
 71                     else
 72                     {
 73                         offset++;
 74                         word[1] = temp;
 75                     }
 76                 }
 77                 else if(temp >= 49 && temp <= 57)
 78                 {
 79                     offset++;
 80                     while(*offset >= 49 && *offset <= 57)
 81                     {
 82                         Characters++;
 83                         offset++;
 84                     }
 85                     while((*offset >= 49 && *offset <= 57) || (*offset >= 65 && *offset <= 90) || (*offset >= 97 && *offset <= 122))
 86                     {
 87                         Characters++;
 88                         offset++;
 89                     }
 90                     continue;
 91                 }
 92                 else
 93                 {
 94 
 95                     offset++;
 96                     continue;
 97                 }
 98             }
 99             else
100             {
101                 if(*offset == 10)
102                     enterNum++;
103                 offset++;
104                 continue;
105             }
106         }
107         else
108             break;
109         if(temp = *offset)
110         {
111             if(temp >= 32 && temp <= 126)
112             {
113                 Characters++;
114                 if(temp >= 65 && temp <= 122)
115                 {
116                     if(temp >= 91 && temp <= 96)
117                     {
118                         offset++;
119                         continue;
120                     }
121                     else
122                     {
123                         offset++;
124                         word[2] = temp;
125                     }
126                 }
127                 else if(temp >= 49 && temp <= 57)
128                 {
129                     offset++;
130                     while(*offset >= 49 && *offset <= 57)
131                     {
132                         Characters++;
133                         offset++;
134                     }
135                     while((*offset >= 49 && *offset <= 57) || (*offset >= 65 && *offset <= 90) || (*offset >= 97 && *offset <= 122))
136                     {
137                         Characters++;
138                         offset++;
139                     }
140                     continue;
141                 }
142                 else
143                 {
144                     offset++;
145                     continue;
146                 }
147             }
148             else
149             {
150                 if(*offset == 10)
151                     enterNum++;
152                 offset++;
153                 continue;
154             }
155         }
156         else
157             break;
158 
159         if(temp = *offset)
160         {
161             if(temp >= 32 && temp <= 126)
162             {
163                 Characters++;
164                 if(temp >= 65 && temp <= 122)
165                 {
166                     if(temp >= 91 && temp <= 96)
167                     {
168                         offset++;
169                         continue;
170                     }
171                     else
172                     {
173                         offset++;
174                         word[3] = temp;
175                         for(count = 4; isLetterOrNum(*offset); offset++, count++)
176                         {
177                             Characters++;
178                             word[count] = *offset;
179                         }
180                         word[count] = '\0';
181                         NumOfWords++;
182                         SolveTheWord(word);
183                     }
184                 }
185                 else if(temp >= 49 && temp <= 57)
186                 {
187                     offset++;
188                     while(*offset >= 49 && *offset <= 57)
189                     {
190                         Characters++;
191                         offset++;
192                     }
193                     while((*offset >= 49 && *offset <= 57) || (*offset >= 65 && *offset <= 90) || (*offset >= 97 && *offset <= 122))
194                     {
195                         Characters++;
196                         offset++;
197                     }
198                     continue;
199                 }
200 
201                 else
202                 {
203                     offset++;
204                     continue;
205                 }
206             }
207             else
208             {
209                 if(*offset == 10)
210                     enterNum++;
211                 offset++;
212                 continue;
213             }
214         }
215         else
216             break;
217 
218 
219     }
220     while(*offset != '\0');
221 }

 

  这是我在进行匆忙的数据结构转型时书写的非常潦草的代码。虽然潦草,但还是包含了我的很多想法,比如,进肯能利用“流式操作”减少机器的循环流程,提高代码效率,尽管这样会使代码变得非常难读。很快,我就意识到了,这样的操作虽然节省了机器执行的时间,却大大浪费了我自己的时间。这段代码的可读性非常的差,以至于我自己常常被绕晕。其中杂乱的命名方式就不多说了,简直不忍卒读。由于是一次个人作业,又到了马上要提交的截止日期,我只关心这段程序的执行结果是否正常(作用是判断是否为一个单词并进行储存),因此在最后输出结果正确之下,我就没有再对它进行优化和修改。

  而如果这换成一次结对编程,将会怎样呢?

  是的,代码复审就登场了。那么下面,我就结合这个例子,简要说说应该怎样利用复审,改进代码。

 

  Step1:“自己改+你说我看”:不符合共同协定的编码风格的地方,一律修改

  我们需要在一开始就商定好代码风格,比如哪些地方需要换行,变量的命名规则等等。

  然后,先过自己这一关——自己对代码进行修改。比如在上面的例子中,函数名为anothermain,这是什么鬼意思?这种根本不知其所以然的命名,必须根除。还有像enternum这样的变量,也会让人摸不着头脑,应该改为characterNum这种规范的命名。

  在自己认为没有大毛病之后,还需要对方对自己进行挑错。在这里,挑错要注意一个原则:不要在鸡蛋里挑骨头,要本着找出有意义的错误的原则,认真查错。比如,“这样的函数命名会不会和以后的命名冲突,从而引起歧义?”

  Step2:探讨潜在的逻辑问题,预防“逻辑灾害”的发生

  上面这段代码的逻辑完全是给机器看的,对于人类阅读者来说,几乎看不下去。这时候,虽然程序在执行的时候结果没有问题,代码还是得改。把从头执行到尾的“流式操作”改为稍微慢一些却容易阅读地多的代码,我们可以增加一些执行很小功能的函数,然后反复调用它们。这样,将得到容易阅读得多的代码。

  Step3:修改完后也不松懈,继续进行经验总结

  总结这个步骤,是让我们“事半功倍”的不二捷径。它不容忽视

 

  在修改之后,代码变成了下面这样。

 1 void WordCheck(FILE *fp)
 2 {
 3         bool stop = false;
 4         bool isEmpty = false;
 5         char ch;  // ch gets the character one by one
 6         short check = 0;  // to check if the first four characters are letters
 7         short i = 0;
 8  
 9         ch = fgetc(fp);
10         if(ch == EOF)
11                  isEmpty = true;
12  
13         for(; !stop; ch = fgetc(fp))  // if it is not the end of the text
14         {
15                  if(ch >= 32 && ch <= 126)
16                          characterNum++;
17                  if(ch == '\n')
18                          lineNum++;
19  
20                  if(check < 4)  // to check the first four characters
21                  {
22                          if(ch == EOF)
23                          {
24                                   stop = true;
25                                   if(isEmpty == false)
26                                           lineNum++;
27                          }
28  
29                          else if(IsLetter(ch) == true)
30                          {
31                                   ++check;
32                                   tempWord[i] = ch;
33                                   ++i;  // search for the next
34                          }
35                          else
36                          {
37                                   i = 0;
38                                   check = 0;
39                                   ClearTemp();
40                          }
41                  }
42                  else  // first four characters are all letters, ready to store
43                  {
44                          if(IsSeparator(ch) || ch == EOF)  // have met a separator, store the word
45                          {
46                                   i = 0;  // roll back to the beginning in the next search
47                                   check = 0;  // roll back to the beginning in the next search
48  
49                                   wordNum++;  // have found another word
50                                   StoreWord();  // store the word
51                                   ClearTemp();  // prepare for the next search
52  
53                                   if(ch == EOF)
54                                   {
55                                           stop = true;
56                                           if(isEmpty == false)
57                                                   lineNum++;
58                                   }
59                          }
60  
61                          else  // have not met a separator, keep searching
62                          {
63                                   tempWord[i] = ch;
64                                   ++i;  // search for the next
65                          }
66                  }
67         }
68 }

 

  根据运行结果显示,修改后的代码运行时间是原来的两倍,但我们却获得了清爽得多的函数。这应该是一个好的结果,至少在对速度的要求不高的情况下。

  然而,如果我们进行过程序优化,就会发现,这是一个很令人诧异的结果——代码的运行时间变成了原来的两倍!这究竟是怎么回事呢?

  原来,在新的函数中,我采取了反复调用子函数的措施来精简代码。而在我的子函数中,我为了继续精简代码,又套用的新的子函数,这里进行了两次的套用。而在判断是否为一个单词并进行储存的时候,这种层层调用的次数是超乎想象的。正是这里头的反复嵌套,大大拖慢了程序的运行速度。

  接下来是进行修改,在不损失代码简洁性的前提下进行代码优化,其中主要是性能优化。我首先要做的是把两层的函数嵌套改为一层

  注意,在这里我并没有把子函数放上来,是出于阅读的简洁性和直观性——直接把我的实践结论告诉大家,而不是用代码和大家继续兜圈子。

  通过将两层的函数嵌套改为一层,我的子函数从原来的16行变成了42行,可以说复杂了不少,但接下来我们看一下运行效率。

  代码的运行时间大约是最初运行时间的 1.4 倍

  这个结果比第一次优化的结果好了不少,但没有达到本次代码优化的最终目的——在不损失性能的前提下进行感官上阅读体验的提升。当然,我也已经知晓了代码变慢的原因——函数嵌套和函数调用花去了太多无用的时间,接下来的优化步骤也明晰了。

 

posted @ 2018-05-08 15:48  RainLou  阅读(349)  评论(5编辑  收藏  举报