不管是在面向過程時代,還是在物件導向時代,任何一個功能都是由若干函數協作來實現的。一個函數由輸入、處理和輸出組成。實現這個函數的程式碼片段都假定了輸入的參數已經滿足了若干規則,在執行這個函數時,函數的代碼會拿到輸入的參數然後執行代理邏輯。下面我們就來看一個函數的代碼。
1 private static void ParseArgs(string[] arguments)
2 {
3 if (_parsedArgs)
4 {
5 return;
6 }
7 if (arguments == null)
8 {
9 arguments = CommandLineArgs;
10 }
11 //命令列格式:
12 const string usage = "[-initial 5] [-level 2]";
13 for (int i = 0; i < arguments.Length; i++)
14 {
15 //匹配前置字元
16 if (arguments[i].CompareTo(InitialStartLevelLead) == 0)
17 {
18 //step forward
19 if (++i<arguments.Length)
20 {
21 //取值
22 _initialBundleStartLevel = Convert.ToInt32(arguments[i]);
23 continue;
24 }
25 else
26 {
27 goto fail;
28 }
29
30 }
31 if (arguments[i].CompareTo(StartLevelLead) == 0)
32 {
33 //step forward
34 if (++i < arguments.Length)
35 {
36 //取值
37 _startLevel = Convert.ToInt32(arguments[i]);
38 continue;
39 }
40 else
41 {
42 goto fail;
43 }
44 }
45 }
46 _parsedArgs = true;
47 return;
48 fail:
49 throw new Exception("Invalid args,usage:"+ usage);
50 }
這個函數大致的用意是使得一個程式啟動時,允許使用者在啟動程式時可以帶上類似“-initial 5 -level 2”這樣的參數來改變程式的行為。估計我們經常會寫出類似以上的程式,有時候可能是因為經驗不足,有時候可能是偷懶。你可以找出這段小程式有哪些問題嗎?
下面我使用注釋的方式把函數中每一個小細節問題標記一下,如果有遺落也請大家補充。
private static void ParseArgs(string[] arguments)
{
if (_parsedArgs) //1 這個參數的命名不符合規則,正確的命名應該是_isArgsParsed,如果想讓參數可讀性更強,可以再將參數的限定詞聲明一下,變成_is××ArgsParsed。
{
return;
}
if (arguments == null)
{
arguments = GetCommandLineArgs();
}
//命令列格式:
const string usage = "[-initial 5] [-level 2]";
//2 硬式編碼字串。
for (int i = 0; i < arguments.Length; i++) //3 如果GetCommandLineArgs的傳回值為空白,則這行代碼會出現NullReferenceException;此外,如果arguments.Length大於4個,我們需要給使用者提示;如果參數出現重複或者不符合要求,我們也需要給使用者提示。
{
//匹配前置字元
if (arguments[i].CompareTo(InitialStartLevelLead) == 0) //4 如果arguments[i]允許帶空格,則這行代碼會產生錯誤的執行結果
{
//step forward
if (++i<arguments.Length)
{
//取值
_initialBundleStartLevel = Convert.ToInt32(arguments[i]); //5 如果arguments[i]不是合法的整數,則這行代碼會產生轉換失敗的異常。
continue;
}
else
{
goto fail;
}
}
if (arguments[i].CompareTo(StartLevelLead) == 0) //6 問題同3
{
//step forward
if (++i < arguments.Length)
{
//取值
_startLevel = Convert.ToInt32(arguments[i]); //7 問題同4
continue;
}
else
{
goto fail;
}
}
}
_parsedArgs = true;
return;
fail:
throw new Exception("Invalid args,usage:"+ usage); //8 異常資訊不友好,沒有清楚告訴使用者是哪一個參數輸入有問題,此外在英文文法中,“args”和“usage”中間要空格,另外,給使用者的資訊不能夠隨便使用縮寫,必須使用完整的詞語;寫入程式碼字串需要放在一個統一的資源檔,並考慮I18N和L10N支援;問題2中的字串可以直接和這個Exception的字串直接合并;如果有必要的話,最好重新定義個Exception類型。
}
一個健壯的函數,需要考慮的問題不僅僅只是正確的功能。上面的這段代碼僅僅是考慮了使用者輸入是正確的情況。當使用者輸入錯誤時,以上代碼要麼出現沒有任何友好提示的異常,要麼僅給出一個不友好的錯誤資訊。使一個函數達到健壯,在編碼時,我們必須謹記:(1)不能假定函數的輸入參數是正確,必須檢查函數的輸入參數是否符合規則;(2)當函數的輸入不符合規則時,必須告訴使用者錯在哪,並告訴使用者如何輸入正確的參數;(3)考慮代碼的可讀性,遵循代碼規範。另外,測試時需要確定輸入參數的邊界,將處於邊界內和邊界外的參數都測試,以保證函數的參數不正確時函數能給出一個友好的提示。