對程式人來說,撰寫程式的目標就是要完成功能,不論任務是要撰寫一整個系統、一個程式庫、一個模組,還是一個函式,或者所撰寫的程式碼單位是什麼,在實際開發時,總會有個功能性的目標,希望所完成的程式碼,能夠提供特定的功能。
初學者最重要的進階議題:打造更穩固的程式碼
對初學程式設計的程式人來說,撰寫程式碼時,不可避免地,會將所有的焦點及心力放在達成功能,畢竟這是最起碼的要求,倘若連功能都不能完全實作出來,那麼更甭提其他事情了。
隨著經驗漸漸增多、技巧逐步純熟,在撰寫程式碼時,他們所關心的事情,慢慢地,就會不僅僅限於如何實作出功能。例如能不能讓程式碼執行得更有效率,以及如何讓程式碼更具可讀性、更容易維護,或許還會關心如何讓程式碼容易修改、容易擴充。這些事情都是在滿足基本的溫飽(達成功能)以外的額外考量。
這幾個例子,都是許多初學者會涉獵到的進階議題。此外,或許有一個主題,是比較少探討到的,但我相信它的重要性,不在上述進階議題之下。這個主題就是如何打造更穩固的程式碼。
程式開發除了情天情節,也要想到雨天情節
所謂程式碼的穩固性(Robustness),意指程式碼進到一個非常態的執行狀態時,能否適宜地因應。此處所指的非常態的執行狀態,有可能是資料庫連線無法建立、無法開檔或寫檔、所接收到的一個變數指標為Null……等。
而所謂適宜地因應,或許是平和地終止系統,並留下清楚的訊息,以指出所遭遇的異常狀態。或許是停止執行並回傳相對應的錯誤代碼,也有可能是嘗試修復系統所遭遇的異常情境,使它再度回到正常的狀態。
當程式人投注所有的心力,試著要達成程式的功能時,焦點幾乎都會放在程式的正常狀態。一般來說,我們會將所預期的正常執行路徑稱為晴天情節(Sunny Day Scenario),而異常的執行路徑則稱為雨天情節(Rainy Day Scenario)。當我們只關心如何達成程式的功能時,幾乎就只考慮到晴天情節,也就是假設所有的條件、參數以及狀態,都落在我們預期的理想範圍中。
你有沒有過這樣子的經驗:有些程式人總是能交出看起來似乎能夠運作的程式碼,倘若針對他的程式碼做一些簡單的測試,他的程式碼看起來就像是依照規格般地將功能實作完畢。但是,倘若將這段程式碼放到雨天情節下執行,會發現它不僅不能正常地反應,有時甚至會讓整個系統無預警終止(最常見的,莫過於存取空指標,而造成的記憶體違規存取動作)。
雨天情節:假設使用者有可能是隻猴子
或許剛入門時,你會覺得光是在晴天情節下達成功能,就是一件不簡單的任務。但是,當實作經驗增多,會發現在各式各樣的雨天情節下讓系統生存下去,難度相形之下更高。各式各樣的雨天情節,正是突顯出程式人功力高下的關鍵因素。
優秀的程式人所寫下的程式碼,在達成需要的功能之後,同時也一併考慮一些可能遭遇到的異常情境,並且適度地加以處理,不致於絲毫放任系統進入不可預期的狀態,並且做出不可預期的行為,甚至是無預警地墜毀。
這中間不僅僅只是技巧面上的差別,更重要的是思路方向的完全扭轉。多年前,我曾寫過一篇名為「未慮對,先慮錯」的文章,是在探討這種截然不同的思路方向。下棋時,人的思考習慣是「未慮勝,先慮敗」,也就是在落子前還沒想到勝過對手的情境,卻已經把各種可能的失敗情況思慮透徹。撰寫程式碼也是一樣,「未慮對,先慮錯」代表你的思路中,對雨天情節的考量,在正常情況下的晴天情節之前。
有許多程式人處理一個物件時,不會考慮到這個物件可能是Null的情況;當他們拿到一個陣列的索引值時,不會事先想到這個索引值可能超出陣列可允許的存取範圍;當他們開啟一個檔案時,不會計畫到這個檔案或許不存在;當他們試著建立一個網路連線時,不會注意到連線可能會建立失敗;他們甚至不會取得所呼叫的大多數函式的回傳值,以進一步檢查這些函式的叫用動作是否失敗。因為他們的焦點完全就只放在最理想的正常執行路徑上。不過,天氣總不是天天都放晴,有時多雲有時陣雨,甚至還會颳颱風。
只考慮晴天情節的程式碼,若是埋藏在系統中,像是未爆彈,隨時都可能被引爆的。倘若測試案例的涵蓋範圍不夠大,那麼便有可能無法查覺潛在的問題。一旦遇上了下雨天,系統就要出事了。這樣的程式碼大大降低了系統的穩固性。
當你撰寫程式碼的態度是「未慮對,先慮錯」時,基本上是以懷疑與不信任的眼光來看待所面對的一切。對於每個變數,你會先在心中琢磨,它們的值是不是有可能是非法的值。對於每次的函式叫用動作,你都會假設它們有可能執行失敗。你甚至會考慮到,其他人所提供的函式,或許還包含實作上的錯誤。
就像當你實作自己的函式時,會假設使用這個函式的客戶端程式,有可能根本就是胡亂使用,對方所傳進函式的引數完全都是非法的值。當你實作與使用者相接的程式時,你會假設使用者根本就是毫無章理地胡亂輸入,或許你還假設使用者有可能是隻猴子。
極限編程即是一種「未慮對,先慮錯」的思考習慣
近年來,有許多人(例如極限編程XP的愛好者)主張採用測試先行的開發方式。也就是說,在撰寫真正的程式碼前,先寫好測試這段程式碼的測試案例。事實上,這便是一種「未慮對,先慮錯」思考習慣的展現。
撰寫測試案例和撰寫程式碼時會抱持的心態,兩者恰好相反。撰寫程式碼時容易正向思考,偏向思考如何在正常情況下達成功能。但撰寫測試案例時,腦子裡想的,往往淨是如何提供各種程式可能沒有考慮到的情境條件。
當你採用測試先行的開發方式時,會在撰寫真正的程式碼之前,仔細地在腦中將可能的各種情況一一展開。那麼,當你真正進到撰寫程式碼的階段時,自然而然會將更多的可能情況納入考量,並且加以處理。
擴展思考的範圍,有助於寫出穩固的程式
有許多程式人會利用所謂的Assertion(維護),來檢查程式所能處理的執行狀態。在某些語言中,它是以函式庫的方式實作,或是巨集,而像在Java程式語言中,甚至提供了專門的算式語法。不論究竟是以何種方式實作Assertion,目的都是讓函式的撰寫者檢查函式執行時的狀態是否符合預期,例如檢查所傳入的某一個指標不為Null。
雖然Assertion僅在開發除錯階段才會起作用,並且被視為是一種開發時除錯的有用工具,而在正式的釋出版本中,並不含Assertion的機制。
但是對程式人而言,養成在程式碼中運用Assertion的習慣,意謂著在撰寫程式碼的同時,總是更徹底思考了撰寫的程式碼,所能夠因應的執行狀態,並且明白地表明了程式碼所不能接受的執行狀態。這種思維方式,自然有助於程式人將思考的範圍擴展得更為廣泛,因而更有可能寫出較為穩固的程式碼。
作者簡介:
王建興
清華大學資訊工程系的博士研究生,研究興趣包括電腦網路、點對點網路、分散式網路管理、以及行動式代理人,專長則是Internet應用系統的開發。曾參與過的開發專案性質十分廣泛而且不同,從ERP、PC Game到P2P網路電話都在他的涉獵範圍之內。
熱門新聞
2025-01-26
2024-04-24
2025-01-25
2025-01-26
2025-01-24
2025-01-26