軟體設計的思維
林俊杰 著 Jul 17 15:33:32 1994
同【物件導向的天空】一文, 本文亦曾發表於「建中青年」刊物第 94 期。
(繼續)
第5.1節 程式語言
程式語言依照年代和特性的先後可以分成四類。各代有各代的特色,也因此在軟體
設計方式上有不同的方法:
第一代 組合語言/機械語言
最原始的程式只是一堆 CPU 所能瞭解的指令集,它們都是數字,不過實在很難瞭解,
像“23 EA 73 66”這樣的數字代表什意義呢?除了 CPU 和天才外,恐怕沒人能瞭解,
因此我們便用一些簡字符號來代表某個機械碼指令,例如 80X86 的 MOV、ADD 等等。
它的特點便是非結構化、極低階的運算方式、但執行速度極快,不過很難為人了解,因此
在設計上常常造成很多困難, 不過許多較好的編譯器都會提供巨集 MACRO,提供較佳的
設計途徑。
第二代 非結構化語言
這代語言的黃金時期是在 1960 和 1970 年代。這時代的作業系統正在發展分時系統
,硬體環境相當嚴苛。BASIC、FORTRAN、COBOL.....都是這類語言的代表。他們的特色是
非結構化,也就是說 GOTO 用的很多,此外缺乏模組化的結構。大家可以看看 BASIC 是
什樣子的,大概也就差不多了。
第三代 結構化程式語言
第三代程式語言起於 1960 年末。這代語言都具有嚴密的區塊結構、更抽象的資料
封包方式、結構化的語法結構、適當的資料形態、程序與函數的結構相當完整。Pascal
是最具代表性的程式語言,事實上它當初設計的目的便是指導人們以結構化的思考方式
來寫作程式,也因此成為研究計算機科學者必修的語言。C語言一直是我的最愛,它本來
是貝爾實驗室用來發展UNIX作業系統的,但目前許多應用軟體也用C語言來寫(例如
dBASE)。C語言不但具有像 Pascal 的結構,它本身具備了一些低階的特性,五十餘個
強有力的運算子,使得這個語言的結構更是完美。
第三代語言還有兩種較特別的族類,一個是人工智慧 (AI) 用的程式語言,另一種是
物件導向程式語言 (Object Oriented Programming Language, OOPL)。人工智慧語言可以
用 LISP 和 PROLOG 為代表。這些語言的特點在於它們對符號處理能力很強,PROLOG 對於
階層性的串列還可以自動回溯。OOPL 則應以 SmallTalk 為代表。Smalltalk 和C一樣也
是貝爾實驗室的產品。它允許我們將有關的資料與程序(OOP 術語叫方法 METHOD)捆成
一包“物件 Object”,相類似的新物件可以經由“遺傳”舊物件而來。整個 Smalltalk
就有三四百個庫存的物件,一位 Smalltalk 的設計師只要遺傳這些物件就可以了。最近
漸趨熱門的 C++ 則是在這股 OOP 風潮下,由原先的C語言加強而成的。
第四代程式語言 4GLs
第四代程式語言又較第三代來的高階並且更抽象化。以前程式設計師必需費時於設計
很多演算法。但經由第四代程式語言,設計師只要描述它所要的需求(requirement),
就可以輕易地設計出程式來,例如 dBASE 等。不過目前大部份 4GL 都用在資料庫或商業
方面,並不易運用於其它的範圍,例如系統程式的設計。不過等將來人工智慧的技術發展
成熟了,相信 4GL 必然會成為主流。
筆者強烈地推薦以第三代以後的語言來設計程式,第三代語言的好處在於它能夠很
簡單將各類演算法實作出來,並且又具有易於除錯和維護的優點。第四代語言則可以節省
大量的時間在細節的設計上。第二代語言如 BASIC 或 FORTRAN 要真和 Pascal 或C比起
來,實在是很可憐的。
第5.2節 考慮系統
筆者認為有時真實世界的作業環境對於程式設計者常常會是一場夢魘。此話怎講?
因為我們常常看到教科書上不斷的提示說:降低程式對於電腦(機器)的依賴性。這句話
實在說得很曖昧。假如你今天設計一個在PC上使用的電動玩具,你將會發現各機器的差
異是如此的大,光顯示器常見就四、五種了,還有各家音效卡也不一樣,透過 BIOS 來作
繪圖的工作似乎是很愚笨的一件事,因為像 F-19 這類模擬飛行的遊戲玩起來將會像騎三
輪車一樣缺乏速度感!與系統的相依性越低,可能就必需降低不少效率!這真是一個讓人
傷腦筋的問題。我們實在不得不承認﹕一套軟體的發展的確受限於電腦硬體環境的限制。
筆者提出一些解決的參考方案:將與硬體高度相關的部份獨立成模組;在規劃程式的當初
最好考慮將要採取的系統或是周邊裝置。不過,仔細想想,大部份程式實在不是很有必要
依靠機器,頂多也是一部份罷了!
另一個 PC 使用者常見的問題是在於『編譯器』的問題!特別常見於用TC的人身上
。雖然程式語言幾乎都有國際標準的語法(例如 ANSI, ISO 等),但移植到各種電腦、
作業系統、編譯器上就會出現『方言』,這導致程式原始碼(source code)的可攜性降
低。這就像是湖南老鄉見到山東大漢一樣,雖然講的都是中國話,但溝通上實在有困難。
解決這個問題有兩點建議:(一)當你開始寫程式時,就必需按標準來寫,即使有部份要
用到方言的語法,必須分開獨立並且要詳細的指明。(二)下定決心,將終身大事托付給
你的編譯器吧。
第6節 撰寫程式的風格
程式設計本質上至少有一半具有藝術創作的特性,好的程式往往就是一件藝術品。
而你,電腦藝術家,具有你自己的風格,你自己的創意,因此你的作品將會處處顯露出
你獨特的個性。
當然這麼說並不是要你將程式『畫』得像畢卡索的作品一樣,但至少你的程式應該
具備一些特性:明確的定義、詳盡正確的註解、善用縮格與空白等等。當然你可以培養出
你特殊的氣質,展現在你的程式中。
註 解
───
註解是說明程式碼內容的文字,明白而清晰的註解使得程式碼容易閱讀與瞭解,
很多人多懶於撰寫註解,他們常見的藉口不外:
我的程式本來就很容易瞭解了
我當然看得懂我自己寫的程式了
諸如此類都不過是推拖之辭罷了!人們常常忘記一些瑣碎的問題,這當然也包括他們
自己的程式在內。到底這個函數的參數代表什麼意義呢?他的傳回值又是什麼?螢幕的
左上角是(0,0)還是(1,1)? 這些都應是註解的內容之一。但我們須要寫多少
的註解?註解的內容又該有多詳細呢? Planger 說:『好的註解不能代替壞的編碼,
然而好的編碼是否就能夠取代註解,那就不得而知了。』爛的註解就像撒旦一樣,會給你
錯誤的指引。通常的規則是把註解分兩類來寫:前言性註解(prologue comments)和功能
註解(functional comments)。前言式的註解一般是放在模組的前端來指明該模組的技術
簡明資料,大概包含了:
(一)功能和目的
(二)介面說明
A. 說明參數意義
B. 說明傳回值的意義
C. 所需參考到的模組
(三)資料的規格和限制
變數的使用方式和範圍,例如標明螢幕左上端是 (1,1)....
(四)發展紀錄
誰設計了這個模組,修改的日期和所修改的內容等等歷史資料
功能性註解則是安插於程式碼中來說明某一區塊程式碼的功能。寫註解並不是寫文章
,不必寫的長篇大論,但也不要太簡略到用幾個簡字符號。有部份註解的內容可能是在
除錯時留下來的,這些註解最好在除錯完成後便去除,以免造成誤解。另外關於註解要用
英文還是中文來寫,筆者倒認為“易懂”才是重點,用中文不一定會比較爛。
變數的命名
─────
常常看許多人用了一些毫無意義的變數名稱,像 A,B,C,D,X,Y,Z,PP,SS,TT..
亂七八糟,而這種情況特別常見於初學者。這種習慣不是很好,甚至比那些沒寫註解的
差上一級,當然要是註解沒寫,變數名稱又亂寫的,真應該好好反省了。變數的命名應該
以明白易瞭解為主。許多人為什麼用無意義的符號來做為變數名稱的原因是因為嫌多打
幾個字太累,或是佔空間等等。不過還有一種是較誇張的行為,就是變數名稱寫的有夠長
,例如:AmountOfYear、KiloMeterPerSecond等等,的確是有點矯枉過正,不過還是比
那些用無意義簡字符號來的好。在為變數命名的當初,最好能建立一個『字典』來說明
縮寫與展開字的關係。例如:
KM = Kilo meter = 公里
Sec = second = 秒
rec = record = 紀錄
當然最好能夠將該變數的意義附加在表格後面,建立一個『變數字典』,方便程式
設計工作的進行。
縮 格
───
在結構化的程式中,縮格可以很明顯的分別出每一個區塊,和各區塊間的關係。
例如說:
.... ....
..... .....
if 邏輯判斷 then if 邏輯判斷 then
begin begin
..... 敘述 ..... 敘述
.... ....
..... .....
for 迴圈控制 for 迴圈控制
begin begin
..... .....
....敘述 ....敘述
..... .....
..... .....
end end
.... ....
...敘述 ...敘述
end end
.... ....
....... .......
有『鋸齒』狀的縮格 缺乏『鋸齒』狀的縮格
縮格與不縮格的效果相當的明顯,你要縮多少格都沒啥關係,現代的編譯器都會自動
辨識,但是對於某些程式語言有『欄位』限制的,例如 FORTRAN 等等,會稍微麻煩點,
但可以盡量的作出這個效果來。另一個焦點是在於像 begin 和 end 這一類“標示區塊”
起迄點的位置。以C來說,它以{ 表示 begin,以 }來象徵 end,結果筆者看到的寫法
就有一大堆:
┌───────┐┌──────┐┌───────────┐
│for (......) {││for (.....) ││for (.) { .... .... } │
│ ..... ││{ ││ │
│ ...... ││ ..... ││ │
│ .... ││ ....... ││ │
│} ││} ││ │
└───────┘└──────┘└───────────┘
<<幾種C程式的縮格>>
不勝枚舉啊!筆者是喜歡用中間那個方式,有些教科書則是用第一種,第三種則常見
於只有一行敘述的函數中,這種情況在C++的教本中特別常見。用縮格時不要擔心這是
否影響整個原始碼的長度等等,這是多餘的。較長的原始碼不會導致程式的效率降低,
但亂七八糟的原始碼是鐵定會出問題的。
運算式
───
從兩個方面來看一個運算式的寫法﹕第一是運算式本質上的複雜度,第二是表示的
方法。以第一個角度來看,這樣的式子:
x = 26 * ( 22 + y * 7 ^ z + ( 164 / 64 mod 7 ) )
實在不太理想,因為太多的括號和不明顯的本意讓人讀起來很辛苦,並且,沒有經過
最佳化處理的式子也會降低不少效率,例如說:
x = 2/3 + 5/3
在這個式子中,電腦必需做兩次的浮點數(簡而言之就是帶小數)的計算,分別是
2/3=0.666666 和 5/3=1.666666,但如果你寫成 x = ( 2 + 5 ) / 3 ,只需做一次
7/3=2.333333 的浮點計算了,而電腦作整數計算遠較浮點計算快,這樣寫效率便提高了。
第二個觀點是表示的方法。特別以C語言為例是因為它所有的運算符號是眾語言中
數一數二多的,例如以下的邏輯判別式來說實在有夠曖昧:
!x == 3 && y != 6
但是加了括號後就明白多了:
( (!x) == 3 ) && ( y != 6)
善用括號和空白是很重要的!
第7節 除錯與測試
除錯常常是設計師頭痛的來源。每個程式幾乎都不免有錯,不論是生手或老手的作品
都一樣。我們在設計程式之初總會有美麗的憧憬:當我作好規劃,然後編碼,再修改一些
『小錯誤』,這個程式就完成了!事實不然,或許你寫程式的速度的確很快,但你永遠也
無法知道你將會花多少時間和精力在除錯上,這真是一個可悲的事實!另一方面,『除錯
』這件工作即使是在科學昌明的今日,也缺乏一套好方法,和一些好工具來協助我們除錯
,雖然新的除錯工具不斷的在出現,但除錯依舊還是一件令人沮喪的工作。
一般而言,錯誤被區分為語法上的錯誤和邏輯上的錯誤。語法錯誤的原因主要來自
打字錯誤或一些無意義的語法,較嚴重的可能是指令或變數名稱的誤用,不過這種錯誤
一般而言都不太麻煩,絕大多數語法上的錯誤都會由編譯器偵測出來。因此我們較關心
是邏輯上的錯誤。
在講除錯前先說說測試。測試和除錯是兩碼子事,測試工作主要是在運用一些假設的
狀況,來查驗程式是正常或有錯誤,因此測試的方式和測試的樣本就很重要了,因為沒有
正確的發現程式有錯,問題將會在日後如山洪一樣的暴發出來。當測試工作一開始進行,
並不會斷然的立即測試一整個系統是不是正常,因為你一定會發現有錯,但找不出錯誤在
哪,因為程式實在太大了。一般我們會先進行模組測試,等一個個的模組都驗證無誤後,
系統或許也應該沒問題了。測試程式的方式有主要兩種策略:黑箱 (Black box) 和白箱
(White box) 兩種測試方法,互有利弊。
白箱
──
白箱測試的方式就好像拍攝模組的X光照片後,再仔細的分析。有四個主要的測試
方向:(一)至少將模組中所有的獨立路徑測試過一次。(二)測試所有邏輯判斷的流程
。(三)以『邊界值』來測試模組,例如對於位元組的資料型態,則以邊界值0和ffh
來測。(四)測試內部所有的資料結構。簡而言之,白箱測試就是將模組的內容作執行
路徑的分析,因此模組就像是玻璃屋一樣,一覽無遺。理論上白箱測試應該每一條路徑
都須檢查,但實際上卻有遺漏的可能,特別是在大型的模組中,白箱測試的工作更是複
雜,不過白箱測試仍是必需的。
黑箱
──
黑箱測試用來判定程式是否正確的作了我們要它作的事情。經由這個測試程序,可以
驗證程式是否達到我們的要求。例如說我們要測試一臺“換鈔機”,你可以分別投入各種
幣值的鈔票,換出對應的零錢出來。這個時候,用來測試的數據就很重要了!因為你的
測試數據不可能包含將來所有可能發生的情況,但你卻要保證測試能找出所有的錯誤!當
然這似乎是不可能辦到的。所以我們可以拿以前真正實際應用時的數據,例如說拿百元鈔
換十元幣,五百元換五角等等。拿實際數據來測有個好處,就是常見的正確情況和常見的
錯誤情況都包含在裡面了,因此可以證明程式在真實情況下的可信度。當然你也可以產生
隨機資料,不過這些數據可能就不會常見了,例如說拿一億元換一角的情況並不太可能
發生。
黑箱測試和白箱測試看來似乎是相反的測試方法,前者是藉由測試的資料和結果來
驗證,後者則是仔細的查驗程式中每一個步驟。兩種方法都有存在的價值,因此它們的
效用是互補而非互斥的。白箱測試通常應用於程式設計初期,黑箱測試則應用在軟體完成
後。
錯誤是可以分類的,特別是可以用出現頻率來分別,一直發生,常常發生,偶而發生
,甚至發生過一次後就再也不發生的錯誤。而搜索錯誤的方式也有一些常見的方式:
暴力法、往前搜尋法、原因消除法。暴力法是最常見、最沒效率的方法,除錯的人總是
一步一步的從頭追蹤程式,把記憶體中的變數甚至檔案的內容都印出來,或是在程式中
很多地方顯示執行時的情況......當然這種方法可以找到錯誤,但需要很長的時間和很多
精力,不過我們還是會運用這個方法,特別是在一切都絕望的時候。
往前搜尋法或許是較好的方案。從錯誤發生的地方往前找看看到底那裡不對勁。在
程式較小的場合或許管用,但程式太大了就困難了。最科學的當屬原因消去法。我們有
系統的去分析一個錯誤發生的原因,作一些可能的假設。就像是汽車發不動了,你不會
急忙的把車子支解,而是會想想看那些零件的故障會導致車輛無法發動,例如火星塞、
分電盤......。
以上提的三種除錯的方法都有適當的軟體工具來配合,例如單步追蹤器,或中斷點
除錯......等。
不可否認的,除錯的工作是一件令人痛苦而不耐的工作,特別是對於原程式設計者
而言,要他承認自己的程式有錯,真是一件丟臉的事,但事實就是如此,錯誤就在你的
程式中。因此從人性面來考量的話,除錯的工作或許該交由別人來做,但有誰會比設計者
本人更了解這個程式呢?話說至此,筆者還是認為設計師本身應該能夠坦誠的面對問題,
並且不要蓄意的去保留自己的秘密,這點應該是很重要的。
第八節 結語
軟體設計是件高度創造性的工作,因此創意是不可缺的。但創意的實際化是否需要
一套方式來規範呢?或許僵硬的限制是扼殺創意的的元兇,但缺乏一套記錄、表示創意的
方法,將會讓創意白白的從指縫間流走。或許你看完這篇文章後會說:『我從來都不依照
這些法則來寫我的程式啊!』是的,許多人都是如此,但也因此他們程式的發展都有個
上界,這個上界可能是程式的大小或複雜度等等。當然真實的設計工作不會這麼死板,
你可以自由的運用這些軟體發展的策略,來設計你的程式,要知道,草率的設計和編碼
所導致的結果總是痛苦的除錯!
很明顯的,限於篇幅,筆者不可能將程式設計的方式完全寫出來,讀者有興趣當作
更進一步的研究。不過累積自己的經驗是很重要的,這些實務上的經驗幾乎是很難從書本
上習得,特別是對我們中國人而言,很多設計的理論完全是國外研究出來的,但諸如中文
的處理,卻是完全本土化的一種東西,但國內真正在將技術本土化的人並不多。附帶一提
的,在模組化設計的方法中,有一個由 Stevens 所提出來分別模組緊密性的方法,這個
方式完全就是架構在英文文法上,對中文並不完全適合,因此筆者就捨棄不提了。
對於創造完美的程式,永遠是設計師們追求的目標。你呢?
- May 18 Fri 2012 16:39
軟體設計的思維 (二之二)
close
全站熱搜
留言列表