2013年1月4日 星期五

[軟體工程]如何寫出意圖使人犯錯的程式碼

因為一些因素,讓我學習到一段程式碼,這段程式碼就是這篇主題的主角,意圖使人出錯的程式碼。
這段code的規格是
初始化玩遊戲後,確保有一個使用者在遊戲裡面,所以要在init結束前呼叫join。
至於規格為什麼要這樣設計,不要問我XD
 原本的code是python的。我把python的code轉成用c++來表達,所以function前面都會有virtual,以符合python情形。




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<span class="com">#include</span><span class="pln"> </span><span class="str"><cstdio></span><span class="pln">
</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Father</span><span class="pun">{</span><span class="pln">
</span><span class="kwd">public</span><span class="pun">:</span><span class="pln">
 </span><span class="kwd">virtual</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> init</span><span class="pun">(){</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"father init start\n"</span><span class="pun">);</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"father init end\n"</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">join</span><span class="pun">();</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
 </span><span class="kwd">virtual</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> </span><span class="kwd">join</span><span class="pun">(){</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"father join start\n"</span><span class="pun">);</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"father join end\n"</span><span class="pun">);</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span><span class="pln">
 
</span><span class="kwd">int</span><span class="pln"> main</span><span class="pun">(){</span><span class="pln">
 </span><span class="typ">Father</span><span class="pln"> father</span><span class="pun">;</span><span class="pln">
 father</span><span class="pun">.</span><span class="pln">init</span><span class="pun">();</span><span class="pln">
 
</span><span class="pun">}</span><span class="pln">
</span>


坦白講,這段程式碼看起來還好。除了一個function做兩件事情外。也沒什麼。執行順序也如我們的預期

father init start 
father init end
father join start 
father join end

可是如果他是基底類別。會有子類別繼承他

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<span class="com">#include</span><span class="pln"> </span><span class="str"><cstdio></span><span class="pln">
</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Father</span><span class="pun">{</span><span class="pln">
</span><span class="kwd">public</span><span class="pun">:</span><span class="pln">
 </span><span class="kwd">virtual</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> init</span><span class="pun">(){</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"father init start\n"</span><span class="pun">);</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"father init end\n"</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">join</span><span class="pun">();</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
 </span><span class="kwd">virtual</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> </span><span class="kwd">join</span><span class="pun">(){</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"father join start\n"</span><span class="pun">);</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"father join end\n"</span><span class="pun">);</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span><span class="pln">
 
</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Child</span><span class="pun">:</span><span class="kwd">public</span><span class="pln"> </span><span class="typ">Father</span><span class="pun">{</span><span class="pln">
</span><span class="kwd">public</span><span class="pun">:</span><span class="pln">
 </span><span class="kwd">virtual</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> init</span><span class="pun">(){</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"child init start\n"</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">int</span><span class="pln"> ret </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Father</span><span class="pun">::</span><span class="pln">init</span><span class="pun">();</span><span class="pln">
  </span><span class="com">// do something It need.</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"child init end\n"</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> ret</span><span class="pun">;</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
 </span><span class="kwd">virtual</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> </span><span class="kwd">join</span><span class="pun">(){</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"child join start\n"</span><span class="pun">);</span><span class="pln">
  </span><span class="typ">Father</span><span class="pun">::</span><span class="kwd">join</span><span class="pun">();</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"child join end\n"</span><span class="pun">);</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span><span class="pln">
 
</span><span class="kwd">int</span><span class="pln"> main</span><span class="pun">(){</span><span class="pln">
 </span><span class="typ">Child</span><span class="pln"> child</span><span class="pun">;</span><span class="pln">
 child</span><span class="pun">.</span><span class="pln">init</span><span class="pun">();</span><span class="pln">
 
</span><span class="pun">}</span>


程式碼看起來還好,只是執行解果有點詭異。恩....子類別join發生在子類別init完成前。

child init start
father init start
father init end
child join start
father join start
father join end
child join end
child init end


假設你不care這情形,過了一段時間,你突然想新增一些東西像這樣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<span class="com">#include</span><span class="pln"> </span><span class="str"><cstdio></span><span class="pln">
</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Father</span><span class="pun">{</span><span class="pln">
</span><span class="kwd">public</span><span class="pun">:</span><span class="pln">
 </span><span class="kwd">virtual</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> init</span><span class="pun">(){</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"father init start\n"</span><span class="pun">);</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"father init end\n"</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">join</span><span class="pun">();</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
 </span><span class="kwd">virtual</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> </span><span class="kwd">join</span><span class="pun">(){</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"father join start\n"</span><span class="pun">);</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"father join end\n"</span><span class="pun">);</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span><span class="pln">
 
</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Child</span><span class="pun">:</span><span class="kwd">public</span><span class="pln"> </span><span class="typ">Father</span><span class="pun">{</span><span class="pln">
</span><span class="kwd">public</span><span class="pun">:</span><span class="pln">
 </span><span class="kwd">int</span><span class="pln"> special</span><span class="pun">;</span><span class="pln">
 </span><span class="kwd">virtual</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> init</span><span class="pun">(){</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"child init start\n"</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">int</span><span class="pln"> ret </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Father</span><span class="pun">::</span><span class="pln">init</span><span class="pun">();</span><span class="pln">
  special </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln">
  </span><span class="com">// do something It need.</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"child init end\n"</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> ret</span><span class="pun">;</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
 </span><span class="kwd">virtual</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> </span><span class="kwd">join</span><span class="pun">(){</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"child join start\n"</span><span class="pun">);</span><span class="pln">
                </span><span class="typ">Father</span><span class="pun">::</span><span class="kwd">join</span><span class="pun">();</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"special = %d\n"</span><span class="pun">,</span><span class="pln"> special</span><span class="pun">);</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"child join end\n"</span><span class="pun">);</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span><span class="pln">
 
</span><span class="kwd">int</span><span class="pln"> main</span><span class="pun">(){</span><span class="pln">
 </span><span class="typ">Child</span><span class="pln"> child</span><span class="pun">;</span><span class="pln">
 child</span><span class="pun">.</span><span class="pln">init</span><span class="pun">();</span><span class="pln">
 
</span><span class="pun">}</span>



你在子類別新增一個變數,初始化時需要給他一個值,子類別的join的時候會需要他。你會發現這樣寫,special的值不合你預期(廢話,因為join發生在special = 1之前)。

怎麼辦,把special = 1移到Father::init前面就好了。 恩....
問題好像解決了,也沒什麼呀

有一天,父類別要做一些更動,要多加一個屬性name,父類別的name是從1開始算,子類別的name是從2開始算。
像這樣


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<span class="com">#include</span><span class="pln"> </span><span class="str"><cstdio></span><span class="pln">
</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Father</span><span class="pun">{</span><span class="pln">
</span><span class="kwd">public</span><span class="pun">:</span><span class="pln">
 </span><span class="kwd">int</span><span class="pln"> name</span><span class="pun">;</span><span class="pln">
 </span><span class="kwd">virtual</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> init</span><span class="pun">(){</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"father init start\n"</span><span class="pun">);</span><span class="pln">
  name </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"father init end\n"</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">join</span><span class="pun">();</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
 </span><span class="kwd">virtual</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> </span><span class="kwd">join</span><span class="pun">(){</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"father join start\n"</span><span class="pun">);</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"name = %d\n"</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">);</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"father join end\n"</span><span class="pun">);</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span><span class="pln">
 
</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Child</span><span class="pun">:</span><span class="kwd">public</span><span class="pln"> </span><span class="typ">Father</span><span class="pun">{</span><span class="pln">
</span><span class="kwd">public</span><span class="pun">:</span><span class="pln">
 </span><span class="kwd">int</span><span class="pln"> special</span><span class="pun">;</span><span class="pln">
 </span><span class="kwd">virtual</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> init</span><span class="pun">(){</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"child init start\n"</span><span class="pun">);</span><span class="pln">
  name </span><span class="pun">=</span><span class="pln"> </span><span class="lit">2</span><span class="pun">;</span><span class="pln">
  special </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">int</span><span class="pln"> ret </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Father</span><span class="pun">::</span><span class="pln">init</span><span class="pun">();</span><span class="pln">
  </span><span class="com">// do something It need.</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"child init end\n"</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> ret</span><span class="pun">;</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
 </span><span class="kwd">virtual</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> </span><span class="kwd">join</span><span class="pun">(){</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"child join start\n"</span><span class="pun">);</span><span class="pln">
                name </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln">
  </span><span class="typ">Father</span><span class="pun">::</span><span class="kwd">join</span><span class="pun">();</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"special = %d\n"</span><span class="pun">,</span><span class="pln"> special</span><span class="pun">);</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"child join end\n"</span><span class="pun">);</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span><span class="pln">
 
</span><span class="kwd">int</span><span class="pln"> main</span><span class="pun">(){</span><span class="pln">
 </span><span class="typ">Child</span><span class="pln"> child</span><span class="pun">;</span><span class="pln">
 child</span><span class="pun">.</span><span class="pln">init</span><span class="pun">();</span><span class="pln">
 
</span><span class="pun">}</span>
1
 

你會發現,印出來的 name 值會是父類別的值再加一,而不是子類別的值再加一。怎麼辦?
在join那邊初始化?

不行,join在那邊會被呼叫很多次,所以在join初始化會一直讓那個變數被初始化
所以不能在那初始化。

所以怎麼辦?
不要呼叫父類別的函數好了,把父類別的函數程式碼直接複製到子類別好了
很好,問題解決了,只是這樣幹嘛用繼承呢?
如果你哪天父類別的init改了,恭喜你。你要把父類別的code重新複製到子類別去。
每改一次,複製一次 ...
不小心改錯,你的code就gg了。

有人會說,我就是要初始化完呼叫,那該怎麼辦呢?
事實上也不難。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<span class="com">#include</span><span class="pln"> </span><span class="str"><cstdio></span><span class="pln">
</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Father</span><span class="pun">{</span><span class="pln">
</span><span class="kwd">public</span><span class="pun">:</span><span class="pln">
 </span><span class="kwd">int</span><span class="pln"> name</span><span class="pun">;</span><span class="pln">
 </span><span class="kwd">virtual</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> init</span><span class="pun">(){</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"father init start\n"</span><span class="pun">);</span><span class="pln">
  name </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"father init end\n"</span><span class="pun">);</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
 </span><span class="kwd">virtual</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> </span><span class="kwd">join</span><span class="pun">(){</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"father join start\n"</span><span class="pun">);</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"name = %d\n"</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">);</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"father join end\n"</span><span class="pun">);</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
 
 </span><span class="kwd">int</span><span class="pln"> init_and_join</span><span class="pun">(){</span><span class="pln">
  </span><span class="com">//the functino can't be overrided.</span><span class="pln">
  init</span><span class="pun">();</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">join</span><span class="pun">();</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span><span class="pln">
 
</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Child</span><span class="pun">:</span><span class="kwd">public</span><span class="pln"> </span><span class="typ">Father</span><span class="pun">{</span><span class="pln">
</span><span class="kwd">public</span><span class="pun">:</span><span class="pln">
 </span><span class="kwd">int</span><span class="pln"> special</span><span class="pun">;</span><span class="pln">
 </span><span class="kwd">virtual</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> init</span><span class="pun">(){</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"child init start\n"</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">int</span><span class="pln"> ret </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Father</span><span class="pun">::</span><span class="pln">init</span><span class="pun">();</span><span class="pln">
  name </span><span class="pun">=</span><span class="pln"> </span><span class="lit">2</span><span class="pun">;</span><span class="pln">
  special </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln">
  </span><span class="com">// do something It need.</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"child init end\n"</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> ret</span><span class="pun">;</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
 </span><span class="kwd">virtual</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> </span><span class="kwd">join</span><span class="pun">(){</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"child join start\n"</span><span class="pun">);</span><span class="pln">
  name </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln">
  </span><span class="typ">Father</span><span class="pun">::</span><span class="kwd">join</span><span class="pun">();</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"special = %d\n"</span><span class="pun">,</span><span class="pln"> special</span><span class="pun">);</span><span class="pln">
  printf</span><span class="pun">(</span><span class="str">"child join end\n"</span><span class="pun">);</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span><span class="pln">
 
</span><span class="kwd">int</span><span class="pln"> main</span><span class="pun">(){</span><span class="pln">
 </span><span class="typ">Child</span><span class="pln"> child</span><span class="pun">;</span><span class="pln">
 child</span><span class="pun">.</span><span class="pln">init_and_join</span><span class="pun">();</span><span class="pln">
 
</span><span class="pun">}</span>


這樣問題就解決了,我們只要呼叫initandjoin()就可以了。他會幫我們完成初始化和join的動作。如果你在寫java,你還可以再initandjoin()前面加個final,防止子類別override,導致行為改變。

1 則留言:

  1. Sometimes sex toys can even have sex toy store medical makes use of if you have a sexual dysfunction or medical condition. There are many different types of|several varieties of|various sorts of} sex toys, different people|and folks} use them tons of|for lots of|for many} completely different reasons. We actually have a dildo with a suction cup for unmatched pleasure wherever may be}. Compared to other sex toys in the marketplace, utilizing a dildo might be preferable. Unlike some sex toys, asilicone dildo has a firm but flexible design.

    回覆刪除