老師有沒有講?不要用 magic number!
老師有沒有講?不要用 magic number!哼,摔鍵盤。
前陣子[1]碰到一個奇怪的 bug,程式死在不應該出錯的地方,找半天找不到原因。最後靈機一動,發覺到每次跑,都死在同一筆 test data (跑一輪要很久,以小時計)。可是,檢查該筆 test data,又找不到任何特殊的地方。最後只好把目標放在,該筆 test data 的「序號」,才發覺又是新同事幹的好事。
我們的程式,跑一筆 test data,需要先配一塊資料結構,使用這個資料結構進行運算,最後再釋放這塊資料結構。為了效率的考量,我們選擇由 caller 負責準備這塊資料結構的記憶體。
也就是說,不像 fopen() 那樣,回傳一個 FILE* 指向 library 所配置的記憶體,而是由呼叫端,也就是應用程式,負責準備這塊記憶體,最後也由呼叫端,負責釋放。這樣的壞處是,資料結構的大小與內容,可以被呼叫端看見,違反了資訊隱藏的原則。但相對地,應用程式可以視需要,改用如全域變數等方式,而迴避了動態配置記憶體的效能負擔[2]。
舉例來說,針對 command-line 傳來的各個 test case,我們可能測試如下:
int main(int argc, char* argv[])
{
for (int i = 1; i < argc; ++i) {
FooStruct foo;
memset(&foo, 0, sizeof(foo));
InitFoo(&foo);
CalcFoo(&foo, argv[i]);
UninitFoo(&foo);
}
return 0;
}
因為程式可能會以 single-thread 跑 N 次,或是以 multi-thread 分散開來跑,所以,這樣的架構不可行,多個 thread 可能同時存取到同一個 foo。因此,新同事選擇將 FooStruct 放在全域變數裡。如下:
FooStruct foo[1789];
int main(int argc, char* argv[])
{
for (int i = 0; i < (argc - 1); ++i) {
InitFoo(&(foo[i]));
CalcFoo(&(foo[i]), argv[i + 1]);
UninitFoo(&(foo[i]));
}
return 0;
}
然後當我們換了一組更多筆數的 test data 時,程式就爆炸了。
據說當初在開這個測試用程式的規格時,有講到 test data 的來源、數量甚至測法,要能夠在 run-time 時指定。



3 Comments
當初選 1789 有什麼特別的意義嗎?
詳細數字其實不是 1789,事過境遷這些細節我也不太確定了。不過,這個數字是當時慣用的一組 test set 的筆數。
1789年在法國爆發的一場革命 -- "法國大革命"
http://en.wikipedia.org/wiki/French_Revolution
Post a Comment