第一章 符式哲理

──────────────────────────────────────

 符式(Forth)是一種語言,也是一種作業系統。除此之外,它也是一種哲理的具 體表現。這種哲理不能被描述為符式之外的某種東西。它既不是先於符式而生,而且除 了在符式的討論下,也沒有別處可以去描述它。甚至除了符式這個名字,它也沒有別的 稱呼。

 這種哲理是什麼? 你又如何用它來解決你的軟體問題?

 在回答這些問題之前,我們不妨先後退百步,去回顧一下計算機科學家在往年所發 展的一些主要哲理。先回溯那些已經走過的腳步,然後再將符式與目前最進步的程式撰 寫原理作對照比較。

軟體優雅性的回顧史

 在撰寫程式的早期,也就是計算機還像恐龍巨獸的時代,某些天才人物只要能讓一 個程式跑得正確無誤,就已經是令人瞠目結舌的奇蹟。但是隨著計算機的進步,這種奇 妙的感覺也逐漸消失。經理人對程式設計師以及程式本身的需求也越來越多。

 隨著硬體價格持續而穩定地下降,軟體價格卻拼命上飆。程式跑得正確無誤已經不 能讓人叫好,大家所要求的是程式能快速地被發展出來,以及程式在維護上的簡便。眾 人眼光在注視正確無誤的同時,也開始注視同等重要的另一項需求,那就是所謂"雅緻" 的品質。

 在本章中,我們要將撰寫雅緻的程式所用的工具及技巧之演變史介紹給讀者。

可記性(memorability)

第一個計算機程式看來就像下面這幅模樣:

  00110101
  11010011
  11011001

 程式設計師藉著設定一連串開關的狀態,以輸入這些程式。譬如若數字是1,開關就 定在"開"的位置;數字是0,開關就在"關"的位置。這些數值就是計算機的機器指令,每 一個指令都會使計算機去執行某一項特定的動作,譬如「將A暫存器的內容移到B暫存器」 ,或「將C暫存器的內容加到A暫存器」等等。

 這實在是件令人厭煩的工作。

 厭煩卻是發明之母。聰明的程式設計師終於發現,計算機的本身其實就可以用來協助 解決這種問題。於是他們設計出一種程式,能夠將易於記憶的縮寫字母轉譯為不容易記憶 的0與1的組合。新的語言看來就像下面的模樣:

  MOV B,A
  ADD C,A
  JMC REC1

 這種翻譯程式稱為組譯器(assembler),而新的語言就稱為組合語言。藉著組合語 言指令與機器指令之間一對一的對應關係,各指令就按自己所需去組合出正確的位元圖 樣(bit pattern)。由於所用的名稱都是程式設計師所容易記憶的,因此,這些新的指 令又叫做助憶符號(mnemonics)。

能力(power)

 組合語言之特點在於,設計師所鍵入的每個命令與處理機所執行的每個指令有一對一 的對應關係。

 程式設計師在實做中發現,他們經常在程式中不同的部位,重覆地寫出一些相同的 指令組合,來完成相同的事情。如果能使用一個名字來代表這段相同的組合,豈不更好?

 這種需要促成了巨集語言的產生。其實也就是一種更為複雜的組合語言。它不但能識 別正常的指令,也能識別特定的名字(巨集)。對於每個名字,巨集組譯器能將它所代表 的那串機器指令加以組譯,就如同程式設計師將它們一個個地完全寫出來一樣。

抽象性(abstraction)

 高階語言的發明是一項重大的進步。其實它也是一種翻譯程式,但功能卻更高。 高階語言允許程式設計師寫出下列的程式碼:

  X = Y ( 456 / A ) - 2

 上式看來就像代數式。我們對這種高階語言應心存謝意,有了它,工程師才能擺脫 "位元騎師"的雅號,開始寫出像樣的程式。BASIC及FORTRAN就是這種語言的例子。

 一個高階指令就能夠編譯出幾十個機器指令,單就此點來說,高階語言的能力就遠超 過了組合語言。但更值得注意的是: 高階語言消除了原始碼(source code)與最終的機 器指令之間的線性對應關係。

 實際的指令要看原始碼的整體敘述而定。單一個操作元(operator)+ 及 = 的本身 已不具任何意義。它們祗是一個複雜的符號表示的一部份而已,而那些符號的意義卻要仰 賴語法以及各個操作元所在的位置而定。

 這種原始碼與目的碼(object code)間的非線性關係,並且藉語法才有的契合, 廣被認定是程式設計方法進展中非常寶貴的一步。但我們也將發現,這種途徑最後所提供 的束縛卻多於它所提供的自由。

 

管理能力(manageability)

 大多數的電腦程式並非僅僅是從上向下依序執行的的指令列表而已,它們也含有測試 各種不同的條件,並根據其結果而轉移到程式中的適當部位。也包括在同一段程式碼中反 覆地迴轉(looping),並藉測試以決定離開此段迴路的時機。

 組合語言和高階語言都提供了分支及迴轉的能力。在組合語言中,你使用 JUMP 指令 ;在高階語言中則使用 GO TO 命令。當這種能力被拼命地使用後,其結果就出現下圖雜 亂無章的程式:

  圖1-1  使用 JUMP 的非結構化程式碼
.本文e
  INSTRUCTION
  INSTRUCTION
  INSTRUCTION
  TEST
      JUMP
  INSTRUCTION
  INSTRUCTION
  INSTRUCTION
  INSTRUCTION
  INSTRUCTION
      JUMP
  INSTRUCTION
  INSTRUCTION
  INSTRUCTION
  INSTRUCTION
      TEST
      JUMP
      TEST
      JUMP

 BASIC及FORTRAN語言仍在廣泛地使用這種方法。但它已面臨兩種痛苦:難以撰寫,以 及在必須修改時卻難以修改。在這種通心粉似的程式中,你不可能去測試程式碼中的單 一個部位,或是想找出某些不該被執行的碼卻被執行的原因何在。

 通心粉似的程式所面臨的這些困難導至流程圖(flowcharts)的發明。這種白紙黑線 的圖形所代表的是程式執行的流程,用來幫助程式設計師去了解正在書寫中的程式碼。 不幸的是,設計師必須手動地將程式碼轉譯為流程圖,或將流程圖轉為程式碼。很多的 程式設計師都發現,老式的流程圖已經是一無用處。
 

模組性(modularity)

 結構化程式設計(Structured programming)的發明促成了極為可觀的進步。這種 方法奠基於人們對問題的觀察。他們發現,若將大問題看成是一些小問題的集合,則有 助於問題的解決[1]。一個小單位就叫作一個模組。程式是由模組所組成,模組中也包 含有模組。

 由於結構化程式設計堅持控制流程的轉向只能發生在模組的內部,從而消除了通心粉 似的寫法。你不能從一個模組中途轉移出來,再進入到另一模組的中途。

  圖1-2   結構化程式設計之一例

做早餐

                        ↓
    ┌─────────┼─────────┐
    │         ┌───┴───┐         │
    │          │  決定:    │         │
    │          │              │          │
    │         │ 是否急迫? │         │
    │         └───┬───┘         │
    │    是┌─────┴─────┐否    │
    │     ↓                      ↓      │
    │┌──┴──┐          ┌──┴──┐│
    ││ 做麥片粥 │          │做 煎 蛋││
    │└──┬──┘          └──┬──┘│
    │      └─────┬─────┘      │
    │            ┌──┴──┐           │
    │            │ 清 洗 │            │
    │            └──┬──┘            │
    └─────────┼─────────┘
                        ↓

 舉例而言,圖1-2表示出『做早餐』模組的結構化圖形,其中包含四個副模組。在各 個副模組中,你會發現一個全然不同的複雜度,但卻不必在這個層次的模組中顯示出來。

 由於要在『做麥片粥』模組和『做煎蛋』模組之間作一選擇,因此出現了移轉上的 判斷,但流程的控制仍保持在這個較外層的模組之內。

 結構化程式設計有三個前提:

1. 每一程式都被描述成是一個由一群稱之為模組的「自己自足的功能單位」所組成的 線型順序組合。每個模組僅能有一個進入點與一個轉出點。 2. 每一模組是由一個或一個以上之功能單位所組成。每一功能單位也僅有一個進入點 與一個轉出點,而且也可視為一個模組。 3. 一個模組可能含有: a.運算,或其它模組 b.判斷結構(IF THEN 的敘述) c.迴路結構

 模組之所以只能有一個進入點與一個轉出點,是要便於你能將它們拆開,改變它的 內部,再插接回去,而不會弄亂它們與程式中其它部份之銜接。意思是說,你可以就單個 模組進行測試。而這唯有在你能確知,進入那個模組時身在何處,以及脫離那個模組時 身在何處,才有可能。

 以做早餐而言,你若不做麥片粥就做煎蛋,二選一,不能兩個都要。但你在早餐 後免不了要清洗。(我所認識的程式設計師中,有幾位仁兄用「每三個月換租一間套房」 的方式來繞過這個模組)。

 結構化程式設計的原意是要提供一種設計的途徑。模組在初期僅僅是存在於撰寫程式 者或設計者心中的一種想像的實體(imaginary entity),並非實際的原始碼單元。當 結構化程式設計的技術被用在像BASIC 一類的非結構化語言時,其結果即如圖1-3所示:

  圖1-3   以非結構化的語言行結構化的設計
 
  10  INSTRUCTION
  20  INSTRUCTION                   決定--時間急迫? 
  30   IF H=TRUE THEN GOTO 80       如果是,跳到指令 #80
  40  INSTRUCTION
  50  INSTRUCTION                   做煎蛋  
  60  INSTRUCTION
  70   GOTO 110                     跳到指令 #110  
  80  INSTRUCTION
  90  INSTRUCTION                   做麥片粥
  100  INSTRUCTION
  110  INSTRUCTION
  120  INSTRUCTION                  清洗  
  130  INSTRUCTION
 
  可寫性(writeablity)

 另一項突破 --結構化的程式語言-- 鼓舞了結構化設計的運用。這些語言的命令組 (command sets)中已經納入了控制結構,便於你寫出更具模組性模樣的程式。Pascal 就是一種這類語言,它是由Niklaus Wirth所發明用來教導學生有關結構化程式設計的 原理。

 下圖顯示出這種形式的語言如何撰寫出『做早餐』的模組。

  圖1-4  結構化語言之運用
 
  INSTRUCTION          決定--時間急迫?
  INSTRUCTION
  IF HURRIED THEN      如果
      INSTRUCTION
      INSTRUCTION          做麥片粥 
      INSTRUCTION
  ELSE                 否則
      INSTRUCTION
      INSTRUCTION          做煎蛋  
      INSTRUCTION
  ENDIF                然後
  INSTRUCTION
  INSTRUCTION              清洗  

 結構化程式語言採用了 IF 及 THEN 等控制結構操作元以確保控制流程的模組性。由 於模組中的每個動作都要完整地以指令寫出,並非僅以名稱(譬如做早餐)代表,一個撰 寫完成的程式或許有十頁,而 ELSE 很可能遠在第五頁中。因此縮排對於此種語言之可讀 性是非常的重要的。


由上而下設計(designing from the top)

 我們如何著手設計這些模組? 某種稱為「由上而下設計」的方法聲稱,模組的設計 應按次序,從最一般性、最全面性的模組開始著手,逐漸向下,直到那些雞毛蒜皮的 模組。

 由上而下設計方法的支持者都有很不光彩的因為計劃不週所導致的光陰虛擲的經驗。 他們從慘痛的教訓中學到了一件事,那就是對已經撰寫完成的程式動手修改時--著名的 打補釘(patching)--就像馬兒已經跑掉,還忙著去鎖馬廄的大門,是全然徒勞無功的。

 於是他們對這個正統的、由上而下的程式設計方法採取對付的手段:

 不要開始寫程式碼,除非你已經計劃到每一個細節。

 

 由於修改這種遵循由上而下設計原理所寫出的程式是如此困難,就必須在寫出實際 程式碼層次的模組之前,將你在初期計劃階段一時失察的地方,一個不漏地揪出來。否則 ,幾年的苦工都會白白浪費在撰寫無法使用的程式碼上。
 

副程式(subroutine)

 我們已討論過那僅被認作是抽象實體(abstract entity)的模組。但所有高階程式 語言都使用一些技術,能讓設計上的抽象模組(modules of design)被編寫為程式碼組 成的模組(modules of code)-- 也就是可賦予名稱且可由其它程式碼呼叫的離散單元。 這些單元稱之為副程式,或程序,或功能等。該使用何種名稱,則依你使用的是何種高階 語言,以及它們如何被完成而定。

 假設我們將『做麥片粥』寫成一個副程式,它的模樣可能是:

 
    procedure fix-cereal            程序  做麥片粥
    get clean bowl                   取清潔的碗
       open cereal box                  打開麥片盒
       pour cereal                      倒出麥片
       open milk                        打開牛奶
       pour milk                        倒出牛奶
       get spoon                        取湯匙
     end                            結束

 我們也可將『做煎蛋』及『清理』撰寫為副程式,然後再將『做早餐』定義為一個 呼叫這些副程式的簡單常式:

 
      procedure make-breakfast                程序 做早餐
        var h: boolean (indicates hurried)        表示時間急迫的變數
        test for hurried                          測試時間急迫
        if h = true then                          若時間急迫 則
           call make-cereal                          呼叫 做麥片粥
        else                                      否則
           call make-eggs                            呼叫 做煎蛋
        end                                       結束
        call cleanup                              呼叫 清理
      end                                     結束

 其中『呼叫 做麥片粥』的片語促使名為『做麥片粥』的副程式得以執行。當此一副 程式執行完畢後,控制又回到程式中剛才呼叫的那一點之後。副程式都會服從結構化程式 設計的規則。

 讀者一看就明白,副程式呼叫的效果,就如同副程式的程式碼是在呼叫模組中被完完 整整寫了一遍。但它與巨集組譯器產生程式碼的方式不同,副程式可以被編譯在記憶體中 任意位置以供呼叫,它並不一定要被編譯在主程式之中。(參閱圖 1-5)

  圖1-5    在記憶體中的一個主程式及副程式


 在過去這些年中,電腦科學家對此變得非常熱衷,總是在他們長篇大論的程式中使用 大量的副程式。副程式可被獨立地寫出並測試。這使得重新使用以前寫好的程式中的某些 部份變得容易些,從而也便於將程式中不同部位交給不同的程式設計師來完成。較小件的 程式碼較易於設計者的思考,也易於修正。

 當副程式被編譯到記憶體中某一位址後,你就可以多次地呼叫這同一個副程式,而不 致於因這不斷重覆的目的碼而浪費空間。因此這種明智的做法也縮小了整個程式的大小。

 不幸地,在使用副程式時,卻會使執行速度受到傷害。在跳到副程式之前的暫存器 保存,以及回來後的暫存器恢復,所額外花費的時間往往造成問題。還有更多的時間是 被耗用在,為了要在副程式之間來回傳送參數,所必需執行的程式碼上。

 副程式在被呼叫時,尤其是參數的傳遞上,也是很瑣碎繁雜的。如果想對它們獨立地 實施測試,你必須另寫一份特別的測試程式,以便對它們進行呼叫。

 因此,電腦科學家都主張適可而止地使用它們。在實做中,這些副程式都相當龐大, 以長度言,總有半頁到整頁的原始碼。
 

持續的精製(successive refinement)

 一種極度仰賴副程式的程式設計途徑稱為持續之精製 [2]。其中的概念在於:開始 時,先賦予資料結構及程序一些較自然的名稱,並以它們寫出一份骨架式版本(skeletal version),然後再為那些已取名的程序寫出它們個別的版本。你持續這種過程到更低的 細節層次,直到那些程序只能用電腦語言本身的敘述去撰寫出來時為止。

 每一步驟,程式設計師都要決定該用那些演算法(algorithm)以及該採用那些資料 結構。演算法的選定與相關的資料結構的選定要同時並行。

 若某一途徑行不通,程式設計師就應儘可能地向來處回溯,再開始。

 對持續精製應注意的是:你不可能實際地執行程式中任何一部份,除非程式中最低層 次之組件已撰寫完成。更精確地說,你無法測試你的程式,除非你已全部設計完畢。

 另外要注意的是:這種持續精製的方法迫使你去將各個層次的控制結構通通寫出, 然後才讓你向下一層次進行。

 

結構化設計(structured design)

 七十年代中期,電腦工業界對我們所描述的這些概念一一加以試驗,但結果仍令人 失望。維護軟體所需費用 --面對變革中的環境,繼續保持軟體的功能-- 已超過軟體本身 費用的一半,有人估計,甚至高達百分之九十!

 人人同意,這種慘不忍睹的結果肇因於程式不完整的分析,以及設計上的考慮欠週, 並非種因於結構化程式設計的本質謬誤。當計劃案遲遲交下,既不完整又不正確,那些 未曾預見的意外卻都讓程式設計師去背了黑鍋。

 因此,學者們很自然地從設計階段上去強調:「下一次,讓我們把事情想的更週全 一些」。

 大約就在此時,一種新的哲理出現了,那是發表在一篇題為〈結構化設計〉的論文 中 [3]。其中所述原理之一可在下面一段文字中看出:

 就不同的設計方式,要評估何者較能降低除錯及修改的時間,簡單性(simplicity) 被推薦來做為一個主要的量度依據。簡單性可由一個系統依後面所說的原則分為幾個 較小的組件而得以增強: 各個組件要能夠在被考慮、被實現、被修正、被改變時, 對系統中的其它組件做最少的考慮,並產生最小的影響。

 當一個問題被分割為一些簡單的模組後,程式就會變得容易寫,容易修改,也容易 了解。

 然而什麼是模組?該在什麼樣的基礎上去做分割?〈結構化設計〉一文提出設計模組 的三個要素。


功能上的強度(functional strength)

 第一個要素是所謂"功能上的強度",它是對一個模組中一切陳述之意圖的一致性 (uniformity)的量度。如果模組中所有的陳述足以被看作是在共同地從事一個單一的 工作,它們就是一種功能性組合(functional bound)。

 想判明一個模組中的陳述是否為功能性組合,只要問下面這些問題即可判明:首先 要問,你能使用一個句子將它的意圖描述出來嗎?如果不能,這個模組很可能就不是功能 性組合。然後要問有關這個模組的四個問題: 

1. 這番描述是否必須用到一個複合句(compound sentence)? 2. 這番描述是否牽涉到時間用詞,諸如"首先","其次","然後"等等? 3. 是否緊接動詞之後,用到一般性或非特定的受詞(nonspecific object)? 4. 這番描述是否用到類似"初值設定"一類的字眼?這種字眼暗示在同一時 間會有很有多不同的功能要完成。

如果上列四句任一答案為"是",這個模組在組合的程度上就是比功能性組合為弱的 一種。弱形組合包括:

巧合性組合: 某些陳述僅僅是碰巧出現在同一模組中。 邏輯性組合: 該模組具有幾個相關功能,而仰賴旗標或參數去決定應 執行何項功能。 暫時性組合: 該模組擁有幾個發生在同時的一群陳述,如"初值設定", 卻無其它關連。 通訊的組合: 該模組含有一群都參考到同一資料組(same set of data) 的陳述。 順序性組合: 一個陳述的輸出作為下一個陳述的輸入之處。

我們所舉『做麥片粥』模組所展現的就是功能性組合,因為它可被看成在做一件事, 雖然它含有好幾個副程式。


耦合(coupling)

 結構化設計的第二個要素是有關耦合的問題。一個模組如何影響其它模組的行為, 耦合是一種量度。強力的耦合被看作是一種惡形。最糟的狀況是一個模組實際上是在修改 另一模組的程式碼。即使藉著傳送控制旗標到其它模組以企圖控制它們的功能,也是危險 的事。可被接受的耦合是資料耦合(data coupling),它牽涉到從一個模組傳送資料 (並非控制資料)到另一個模組。即使是如此,模組間的資料介面若能儘可能地求其簡化 ,系統就更容易建構及維護。

 若資料可被很多模組存取(例如總體變數),則表示這些模組間有強固的耦合。 設計師一旦要更換某一模組時,其它的模組就要冒著副作用的危險。

 最安全的資料耦合是將局部變數(local variable)從一個模組以參數方式傳送到 另一模組。實際上,呼叫模組對受命模組所說的話是:「我要你使用我放在X及Y的變數中 的資料。在做完之後,我希望你將答案放在Z的變數中。沒別的人會來使用這些變數。」

 如我們所知,支援副程式的傳統語言,都具有將引數在模組間傳來傳去的精巧辦法。

 

階層式輸入--處理--輸出設計(Hierachical Input-Process-Output Designing)

 第三個要素是有關設計上的程序。設計師都得到忠告,要他們採用由上向下的設計 途徑,但在初期不必太注意控制結構。有關判斷上的設計(decision designing)不妨 等到爾後模組的細節設計時再說。在初期的設計中,反而要集中注意力在程式的階層 (哪些模組呼叫哪些模組)以及模組與模組間資料的傳送上。

 為協助設計師依著這種新的方式思考,就有了圖形表示的結構圖(structure chart )的發明。(有一種稍微不同形式的,叫做HIPO圖,是取hierachical input-process- output 中各字首而得)結構圖包含階層表及輸入輸出表兩部份。圖1-6顯示這兩大部份。 主程式叫做 DOIT,它擁有三個受命模組,三者又各自呼叫身居其下的其它模組。讀者 一看就明白,這種設計強調從輸入到輸出的變換(transformation)。圖上部為階層表, 其中數字與下邊輸入輸出表之數字對應。在第一點(READ模組)之輸出為A,第二點 (TRANSFORM-TO-B模組)之輸入為A,輸出為B。

  圖1- 6  結構圖之一型

或許,這種設計方法的最大貢獻就在於它了解到,控制流程的決定不應該做為設計上 的主導。我們以後會了解,流程控制祇是問題的表象。在需求(requirements)上的一小 點變動,就足以深深地改變程式的控制結構,幾年的工作也跟著一起泡湯。不過,如果 設計是以其它的主體為導向,譬如資料流程,則原計劃上的一個變動就不會造成如此大災 難似的影響。


資訊隱藏(information-hiding)

1974 年,David L. Parnas博士在一篇文章中說 [4],用來做為拆解(decomposing )模組的準則不應該依流程上的步驟來分,而應以可能會被改變的資料單元為準。模組 應該被用來隱藏這些會改變的資訊。 讓我們先檢視這個隱藏資訊的重要概念:假設你要為公司寫一本作業程序的手冊, 下面列出其中的片段來討論: 

  Sales Dept. takes order                       業務部門拿到訂單
  sends blue copy to Bookkeeping                送藍色拷貝(第二聯)到薄記部門
  orange copy to Shipping                       橘色拷貝(第三聯)到送貨部門
  Jay logs the orange copy in the red binder    Jay 將橘表登錄入他桌上的
  on his desk, and completes packing slip.      紅色卷宗內,並開列裝箱清單

    大家都認為這套程序是正確的,於是分發到公司內人手一冊。

    然後Jay辭職了,新進的人是Marilyn ,訂單的複本也改成綠色和黃色,不再是藍色和橘色。紅色卷宗已裝滿,現在用的是黑色。你那全本手冊已變成廢物。其實你可以避免這件事的發生,只要你原來寫的不是"Jay"而是"送貨員",不是"藍色拷貝"和"橘色拷貝",而是"薄記部門拷貝"和"送貨部門拷貝"等等。

   此例說明,為了在變動的環境中保持正確性,那些變化無常的細節事項就應排除於程序之外。若有需要,這些細節可以被記錄在其它地方。例如,人事部門每隔一週左右就發出一份人員狀況表,上面列著員工的姓名及職務,若有人想知道誰是那位送貨員,從表上一查便知。人員有變動,這份表就跟著修改。

    這種技術對軟體之撰寫非常重要。一個程式一旦能正確地執行,為什麼要去改它?理由可多了。你也許要在新的裝備上去執行這個程式;它就必須改到能配合新的硬體。

   某個程式也許執行的不夠快或功能不夠強,不合使用者的需要。大多數的設計小組發現自已是在寫一"家族"(families)的程式;也就是說,為他們特殊應用的領域中的程式寫了好幾個相關的版本,每個都祗是它原來版本的一些變化。

    為了將資訊隱藏的原理應用到軟體上,程式中的某些細節就該侷限在一個單一的地點,而且任何有用的資訊單元(piece of information)應該祗做一次表示(expressed)。違犯此一信條的程式就是犯下了重複(redundancy)的罪過。雖然硬體的重複(備份電腦等等)有助於系統的安全性,但資訊的重複卻是危險的。

    任何一個有經驗的程式設計師都會告訴你,凡可能在將來的版本中會改變的數值,應該以"常數"來定義它,而且在程式中使用它時,應使用它的名稱而不是數值。舉例來說,表示報表紙寬度的行數就應該定義為一個常數。即使是組合語言,也提供了EQU及LABEL的命令,來為地址及位元圖樣等等的這類數值定出名稱。

  好的程式設計人員也會將資訊隱藏的概念應用在副程式的發展上,以確保各個模組儘可能地少知道其它模組的內部。當代的程式設計語言,諸如C,Modula 2及Edison等,都已將此一概念用在他們的程序架構之中。

    但Parnas將此一概念擴展得更遠。他建議將此一概念伸展到演算法(algorithm)及資料結構上去。其實,資訊的隱藏 --而非判斷結構或呼叫階層-- 才應該是用於設計上的主要基礎!

  結構的表象性 (The Superficiality of Structure)

  Parnas 提出了拆解(decomposing)的兩個準則:

  a.可能(雖然目前尚未計劃到)再被使用,以及   b.可能(雖然尚未計劃到)改變。

這種對"模組"的新看法與傳統的看法不同。這種"模組"是一群常式的集合,通常是 很小的常式,他們共同去隱藏問題的某些方面的有關資訊。 另有兩位作者也用不同的方式談到這同一概念,用的是"資料抽象化"(data abstraction)這個字眼 [5]。他們以堆疊為例來說明。這個堆疊模組包含了一組常式, 用來啟動這個堆疊,推入一個數值到堆疊上,由堆疊取出一個數值,決定堆疊是否為空的 ,等等。這個多程序的(multiprocedure)模組將堆疊如何被架構而成的有關資訊隱藏, 不讓應用程式中其它模組知悉。這些程序被看作是一個單一的模組,因為它們是相互依存 的。你無法在改變堆入方式時,而不同時改變取出方式。 在此一概念中,"使用"(uses)一詞扮演了重要的角色,Parnas博士在一篇論文中曾 說 [6]:

凡已經達到某種程度的"雅緻"的那些系統...其能達到雅緻的道理在於,它們能 讓系統中的某些部份去"使用"其它的部份... 若確有這樣的階層式的順序存在,則各個階層就提供了該系統一個可供測試及可供 使用的副糸統(testable and usable subset)... 這種~FB7;"使用"的階層的設計(the design of the "uses" hierachy)應該是要投入 設計心力的主要地方之一。將系統區分為可供獨立地呼叫的副程式這件事,必須和 有關如何"使用"它們的決定同行並進,因為二者互相影響。

一個設計,若其中之模組係按控制流程或控制順序(control flow or sequence) 予以組合,則並不適於接受設計上的更動。結構(structure),在控制流程階層 (control-flow hierarchy)的意義上看來,是表象的。 一套設計,若其中之模組是按那些可能變動的事物予以組合,就足以因應各種變動。

 

回顧與前瞻

在本章中,我們要對符式的基本特性作一複習,然後將它們和我們已看過的那些傳統 方法對照比較。下面是符式碼之一例:

    : 做早餐    急嗎?  如果  做麥片粥  否則  做煎蛋  然後  清理 ;

這在結構上與第6頁所列做早餐之程序相同。 『急迫嗎?』,『做麥片粥』,『做煎蛋』,以及『清理』等詞均是先前已經予以 定義過的字(在中文裡,就是一個詞)。就某種程度而言,符式也呈現我們所討論過的 那些特徵:助憶符號(mnemonic),抽象性(abstraction),能力(power),結構化 控制操作元(structured control operators),強固之功能性組合(strong functional binding),有限之耦合(limited coupling)以及模組性(modularity)等 等。但以模組性來說,我們所見到的或許就是符式最關緊要的突破。

符式程式中最小的元素並非一個模組,或一個副程式,或一套程序,而是一個"字"。

甚至於,符式中沒有所謂的副程式,主程式,公用程式或管理程式等等,它們的呼叫 方式都不盡相同。在符式中的每樣東西都只是一個字。 在我們深入了解一個以字為基礎的新環境的重要意義之前,不妨先學習符式的兩項發 明 --有它們才能造就出這種環境。
 

隱含式的呼叫(implicit calls)

首先,呼叫是隱含的。你不必說『呼叫 做麥片粥』,你只說『做麥片粥』就夠了。 在符式中,『做麥片粥』的定義知道自己是個什麼樣的字,以及該用哪種程序去叫出它 自己。 如此一來,變數及常數,系統功能,公用程式,甚至任何由使用者定義的命令或資料 結構都可以簡單地用名字"呼叫"出來。
 

隱含式的資料傳遞(implicit data passing)

其次,資料的傳遞也是隱含的。產生此一作用的機構就是符式的資料堆疊(data stack)。符式自動地將數值堆入堆疊;凡需要數值作為輸入參數的那些字就從堆疊取得 數值;凡產生數值輸出的那些字,就將數值堆入堆疊。堆入及取出(push and pop)的 字眼並不存在於符式語言中。如此,我們就能寫出

   : 做它     取得C  轉換為D  輸出D ;

並且相信『取得C』會得到C並推入堆疊,『轉換為D』會從堆疊取出C,將它變換後, 再將D堆入堆疊。到最後,『輸出D』就會從堆疊上取出D,並將它寫出。符式讓我們省掉 在程式碼中寫下傳送資料的指令,從而使我們專注於資料轉換的功能性步驟。 由於符式語言利用堆疊傳遞資料,字就可以巢居於字中。任何字可將數值堆入堆疊, 或從堆疊取出,而不會攪亂較上一層的字與字間的資料流程(只要那個字不會用掉或留下 任何非預期的值)。因此,堆疊在支援結構化及模組化程式設計的同時,也提供了一個 傳送本地引數(local arguments)的簡單機構。 符式從我們的程式中消除了"字如何被呼叫","資料如何被傳遞"等等的細節。剩下的 是什麼? 描述問題的"字"罷了。 有了字,我們就能充分地擴展Parnas的建議-- 按可能改變的事物將問題拆解,且讓 各個"模組"儘可能由許多小功能(small functions)組成,多到足以隱藏該模組有關的 資訊。在符式中,為了要做到這一點,可以依需要儘量地定義出字來,不論每個字是多麼 簡單。 從一個符式的應用程式中可能讀到:

      20 旋轉 左 砲塔

很少有其它的語言會鼓勵你去寫出一個叫做『左』的副程式,僅僅是用來作為一個 修飾子(modifier)而已,或者一個叫做『砲塔』的副程式,目的只在為一個硬體命名。 由於符式的字比副程式容易使喚(僅僅命名,不必呼叫),一個符式程式拆解而成 的字數往往就比傳統程式拆解而成的副程式的數目多了許多。

 

元件化的程式設計 (Component Programming)

有了一大群的簡單的字,就方便於以下要介紹的所謂"元件化的程式設計"技術的 運用。為了說明,讓我們再稍事回顧前兩節剛討論的所謂"可能會變動的事物"。在一個 典型的系統中,幾乎每樣事物都可能改變:終端機及列表機等輸出入裝置,UART晶片, 作業系統,資料結構,資料表示方式及演算法(algorithm)等等。 問題在於:「我們該如何做,才能將這些變動所造成的影響減到最小?要跟著這些 變動而變動的其它事物的最小集合是什麼?」 答案為:「是以最小集合的形式存在著的一組互相交互作用的資料結構及演算法, 它們分享著大家如何集體工作的祕密」,我們稱這種單位為一個元件(component)。 一個元件就是一種資源(resource)。它可能是一件硬體,例如 UART 晶片或者 一個硬體堆疊。或者,這個元件也可能是一個軟體資源,例如一個佇列(queue), 一個字典,或一個軟體堆疊。 所有的元件都牽涉到資料物件(data objects)及演算法(algorithms)。不論這 個資料物件是實體的(如一個硬體暫存器)或抽象的(如一個堆疊地址或一個資料庫的 欄位),都無關緊要;而演算法是以機器指令來描述,或是以問題導向的詞(譬如『做 麥片粥』及『做煎蛋』)來描述,也是無關緊要。 圖1-7是將結構化程式設計的結果和元件化的程式設計的結果相對照。我們所關心 的是用來描述記錄,提供編輯命令,提供讀/寫常式的那些元件,而不在意那些名為 read-record (讀記錄),edit-record (編修記錄)及 write-record(寫記錄)的 模組。 我們做了什麼?我們是在發展過程中插進了一個新的步驟: 在設計時,先依元件 完成了拆解,然後在實現時(implementation)再描述出順序(sequence),階層 (hierachy)及輸入--處理--輸出。不錯,這是額外的一步,但我們卻有了更細一層的 拆解-- 不僅是拆成一片片,更是一塊塊。

  圖 1-7  結構化程式設計和元件化的程式設計二者之對照

順序式/階層式的設計(Sequential/Hierarchial Design)
-----------------------------------------------------

                  ┌─────┐
                  │ UPDATE- │更新記錄
                  │ RECORD   │
                  └──┬──┘
      ┌────────┼────────┐
┌──┴──┐    ┌──┴──┐  ┌──┴──┐
│  READ-   │    │  EDIT-   │  │  WRITE-  │
│ RECORD   │    │ RECORD   │  │ RECORD   │
└─────┘    └─────┘  └─────┘
   讀取記錄          編修記錄          寫出記錄

依元件而行的設計(Design by COMPONENTS)
--------------------- : 更新記錄 記 錄 讀 編 輯 記 錄 寫 ;

                    ┌──────┐
  ┌──────┐  │            │  ┌──────┐
  │ STRUCTURE  │  │   EDITOR   │  │ READ/WRITE │
  │    OF      │  │            │  │            │
  │  RECORDS   │  └──────┘  │  ROUTINES  │
  └──────┘      編輯器       └──────┘
   記錄的結構                          讀寫常式

假定我們在程式寫好之後需要改變記錄的結構。以順序式、階層式的設計來說, 這項改變就會影響到全部三個模組;但在元件化的設計中,這項改變就祗會侷限在 「紀錄的結構」這個元件中。使用此一元件的任何程式碼都不必知悉此項改變。 除了維護上的方便,這種做法的另一好處在於:同一組中的各個程式設計師可以 被交付不同元件的設計任務,而有較少相互間的依賴。元件化設計的原理不但可用在 團隊管理上,也可用在軟體設計上。 我們將那組用來描述一個特定元件的字稱為一個詞集(lexicon)。(詞集的意義 之一是「一套專屬於感興趣的某一特定領域的字集」。)詞集就是你在外界用來與 此一元件溝通的介面。(圖1-8)

本書所說的詞集,係專指元件內那些以名稱的形式供元件外部所使用的字的集合。 元件內也有一些專為支援這些字而定義出的字,我們稱這些支援的定義為"內字"。 詞集以名稱的形式,提供了資料物件及演算法在邏輯上的等值品(logical equivalents)。詞集隱去了元件的資料結構及演算法-- 也就是將"它如何做"(How it does)的部份隱而不顯,公諸於世的僅僅是以簡單的字所表示出來的元件的概念性模型 --"它做什麼"(What it does)而已。 然後,這些字又成為描述較高層次的那些元件的資料結構及演算法所用的語言。 那時,一個元件的"做什麼"(what)就變成較高層次元件的"如何做"(how)。 以符式撰寫程式,整個應用程式所包含的,除了元件之外就一無其他。

圖1-9顯示 一個機器人的應用程式可能被拆解成的形式。

你甚至可以說,每個詞集都是一個有特定目的之編譯程式,它被撰寫出來的唯一 目的,就是要以最有效率及最可靠的方式,去支援較高層次的應用程式碼。 話說回來,符式核心本身並不支援任何元件。也沒有這個必要。元件是程式設計師 拆解後的產品。 重要的是,我們必須了解,一個詞集可以被較高層次的任何一個、或全部的元件所 使用。上一層的元件並不會去遮蓋那些支援它的下層元件,因而它們仍可以被其它的上 層元件所使用。反過來說,每一個詞集都可以隨心所欲地使用它下層的所有元件。機器 人移動的命令要依賴符式的核心部份,依賴它的變數,常數,堆疊操作,數學運算 等等 ,其依賴程度之深,就如同其它任何元件一般。 這種方法的一個重要結果是:整個應用程式使用著一個單一的語法,因而使得這個 程式既容易學也容易維護。這也是我之所以用"詞集"這個名稱而不用"語言"的道理。凡是 語言都有它獨一而特定的語法。 這種「任一階層的命令都可供使用」的特性,也使得測試及除錯的程序大為簡化。 由於符式語言是交談式的(interactive),程式設計師可從"外界"鍵入並測試下列 的基層命令(primitive commands):

      右  肩  20  轉動角度

就如同測試更高層次,更複雜的命令:

      舉起  咖啡盤

同時,程式設計師也可以(若他或她想要那麼做的話)故意地將任一命令加封 (seal),包括符式語言的本身,以便在應用程式完成後,防止最終使用者的窺探。 現在,符式的方法變得很清楚了。符式程式設計的要點在於將核心語言(root language)向應用程式的方向延伸,並定義出足以用來描述所要解決的問題的新字。 專為機器人、庫存控制、靜態分析等等特定應用而設計的程式語言通稱為"應用導向 語言"(application-oriented languages)。~FB7;符式卻是用來"創造"應用導向語言的一種 程式設計環境。(這句話應該是你所能找到的對符式語言最簡潔的描述)。 其實你不能用符式核心所提供的字來撰寫任何稍為複雜的應用程式;以一個"語言" 來說,它是不夠強大的。你所應該做的是在符式中,寫出你自己的語言,從而將你對 問題的了解予以模型化,也從這個過程中,你才能雅緻地描述出它的解決方案。


對誰隱藏?(Hide From Whom?)

由於當代主流的語言對資訊隱藏這件事都有不同的詮釋,我們應該先予澄清。 我們是對什麼,或對誰去隱藏資訊? 即使傳統語言中最新的 MODULA 2 也向退步的作法屈膝,以確使模組能將自己的 內常式(internal routines)及資料結構對其它模組隱藏,其目的是在獲取模組的獨立 (最小的耦合)。這種恐懼似乎是耽心模組都像是敵對的團體,會奮力地去攻擊對方。 或者說,像一群掠奪成性的邪惡幫派,在外專找那些善良的資料結構家族下手搶劫。 符式對這種事根本不耽心。隱藏資訊的目的,在我們看來,不過是藉著局部化各 元件內部所可能改變的事物為手段,以儘量減小一個在設計上的變動所會產生的影響。 一般而言,符式程式設計師寧可將程式放在自己的掌握下,而不願使用任何技術去 實體地(physically)將資料結構隱藏起來。(雖然如此,有一項既高級而又簡單的 技術將Modula型式的模組加入到符式語言內,那就是由Dewey Val Shorre所實現的, 僅用了三行的程式碼就完成了的)[7]。
 

將資料結構的建構方式隱藏起來(Hiding the Construction of Data Structures)

我們曾注意到符式中的兩項發明,是它們使我們所描述的那種方法成為可能,那就是 隱含式的呼叫及隱含式的資料傳遞。而第三個要點卻能使一個元件內部的資料結構,用先 前已經定義的元件描述出來。這個要點就是記憶體的直接存取。 假設我們定義了一個名為『蘋果』的變數:

        變數 蘋果

我們可以存放一個數目到此變數中,以表示現在有幾個蘋果:

        20 蘋果 存入

我們可以顯示這個變數的內容:

        蘋果 ?

也可以將數目加1:

        1 蘋果 加存

『蘋果』這個字祗有一個功能:將存放著蘋果數量之痕記(tally)的那個記憶位址 放到堆疊上。我們不妨將此痕記認作一個"事物"(thing),而用來設定此一痕記, 讀取痕記內容,以及增加此痕記之刻痕的那些字,都可被認作是"行動"(actions)。 (譯註:tally 為劃有刻痕之木塊,以為記帳。在此就是儲存資料用的記憶體。) 符式藉著讓資料結構的地址得以傳送到堆疊上,並且提供了"取出"及"堆入" 的命令,才能夠巧妙地將"事物"從"行動"中分開。 我們已討論過,依著那些可能改變的事物以進行設計的重要性。假定我們已經用 『蘋果』這個變數寫了一大堆的程式碼,也到了該交卷的時刻,卻發現我們應該要保有 兩種不同蘋果的計數,紅的和綠的! 我們不必發火而握起拳頭,只要記得『蘋果』的功能就行了:它提供一個地址。 如果我們需要兩個痕記,『蘋果』這個字就可以依據我們目前所談論的是哪一種蘋果而 提供兩個不同的地址。因此,我們定義出『蘋果』的更為複雜的版本如下:

         變數 顏色        ( 指著目前所談論的痕記的指標 )
         變數 紅          ( 紅蘋果的痕記 )
         變數 綠          ( 綠蘋果的痕記 )
         : 紅的   ( 設定蘋果種類為紅 )     紅 顏色 存入 ;
         : 綠的   ( 設定蘋果種類為綠 )     綠 顏色 存入 ;
         : 蘋果   ( -- 得到目前蘋果痕記之位址 )    顏色 讀取 ;
  圖1-10  改變間接指標

         ┌──────────────────────────┐
         │RED                          ┌───┐          │
         │                            顏色└───┘          │
         │                        ┌───┐       ┌───┐ │
         │                      紅└───┘     綠└───┘ │
         ├──────────────────────────┤
         │GREEN                     ┌───┐        │
         │                            顏色└───┘          │
         │                         ┌───┐       ┌───┐│
         │                       紅└───┘     綠└───┘│
         └──────────────────────────┘

在此,我們重新定義了『蘋果』。現在它從一個叫做『顏色』的變數取出內容,以作為痕記的地址。『顏色』是個指標(pointer),它或指向變數『紅』,或指向變數
  『綠』。這兩個變數才是真正的痕記。

      如果我們先說『紅的』,我們就可用『蘋果』指到紅蘋果。如果我們說『綠的』,我們就可用『蘋果』指到綠蘋果。(圖1-10)
      我們不必去改變使用『蘋果』的任何程式碼的語法,我們仍然可以說:

        20 蘋果 存入
         1 蘋果 加存

現在再回顧一下,看看我們做了什麼。我們已經將『蘋果』由一個變數定義改變  為一個冒號定義,而卻未影響到它的用法。符式語言讓我們對使用『蘋果』的程式碼隱藏了『蘋果』是如何被定義的細節。在原先的程式碼中看來像一件"事物"(一個變數)的東西,卻在元件中被實實在在地改變成一個"行動"(一個冒號定義)。

    符式鼓勵人們採用抽象的資料形態(data types),其作法是讓資料結構能使用較低階層的元件來加以定義。祗有符式語言,由於從程序中消除了 CALL,由於她讓位址及資料隱含地經由堆疊來傳遞,更由於她提供了直接存取記憶體的能力,故唯有她可以提供這種層次的資料隱藏。

    符式不太關心某樣東西是一個資料結構抑或是演算程序。如此的一視同仁使得我們這些程式設計師在建造描述我們的應用所需的語句時,得到無法估量的自由。

    我傾向於將任何傳回一個地址的字,譬如『蘋果』,看作是一個"名詞",而不在意它是如何被定義的。從事一個明顯行動的字就是一個"動詞"。

    在我們所舉的例子中,『紅的』及『綠的』只可叫作形容詞,因為它們修飾『蘋果』的功能。以下這個片語

   紅的 蘋果 ?
和下面的片語是不同的
   綠的 蘋果 ?

符式的字也可以作為副詞及介系詞使用。要決定一個字在一個文句中是屬於哪一部份,並沒有太大的價值,因為符式根本不在乎。我們只要好整以暇地去享受著以人類自然的語彙來描述一個應用,是多麼輕鬆容易的那份樂趣就行了。

  但是,符式是高階語嗎?

我們從回顧簡史中得知,傳統的高階語言不但消除了命令和機器指令之間的一對一的對應,也消除了線性的對應關係,才能從組合語言中脫身而出。很明顯地,符式自認為擁有那第一項差異,但以第二項差異而言,你在一個定義中的用字順序也就成為那些命令被編譯的順序。就此點而論,符式是否還不夠資格擠身高階語言之中?在回答之前,不妨先探索一下符式方法的優點。

先聽聽符式發明人Charles Moore先生的說法:

你為某個字下了定義,電腦便了解它的意義。它之所以會了解,是因為它被"請去" (invoked) 執行某些程式碼。電腦根據每個字去採取某個行動。它不會為了爾後的需 要而將這個字擺在一邊或記在心裡。

從形而上的感覺來說,我認為這表示電腦"了解"一個字。它了解『複製』這個詞, 說不定比你了解的更深切,因為『複製』是何意義,在它的頭腦中根本不是問題。

對你具有意義的那些字和對電腦具有意義的那些字之間的關連,倒是個深奧的東西。 電腦變成了人類和概念之間的溝通載具。

原始碼(source code)和機器執行二者間能夠相互對應,其好處在於編譯器及 解譯器得以大大地精簡。此項精簡會使得性能在很多方面得以提昇,後面的章節中我們 會再予討論。 從程式設計方法論的觀點來說,符式語言的優點在於新字及新語法的容易添加。 符式語言不應被說成在"尋找"字,她是見到就執行。如果你添加新的字,符式也同樣地 會見到它並執行它。她對現有的字及你新加的字一視同仁。 更有甚者,這種"擴充性"適用於各類型的字,並不僅限於動作型的字。符式容許你 添加一些新的編譯字(compiling words),譬如提供結構化控制流程用的『IF』及 『THEN』等等。若有必要,你也很容易添加諸如『CASE』條件分枝或多出口迴路 (multiple-exit loop)等。或者,也同等重要的是,你也可以在不需要它們時將之丟 棄。 反過來說,凡依賴字的順序(word order)來了解一項敘述的意義的語言,必須 "知悉"所有合法的字,及合法的字與字之間的組合。期望它們含有你所盼望的所有的組合 方式是不切實際的。語言的存在與否是由它的製造者決定,你無法擴充它的"知識"。 實驗室的研究人員都指出,符式語言的最大好處就在於她在他們的環境中的彈性和 擴展性。詞集(lexicons)可以發展到足以隱藏連接到電腦上的各式測試裝備的有關資訊 。這種工作一旦由有經驗的程式設計師完成,研究人員就可以隨心所欲地使用由一些小字 所組成的"軟體工具箱"(software toolbox)來寫出他們在實驗上所需的簡單程式。新的 裝備出現,新的詞集也就跟著加入。 Mark Bernstein曾經描述他曾在實驗室使用過一種現成的、特定用途的程式庫時 所遭遇的問題,他說[8]:「是電腦,而非使用者,主宰了實驗」。但他針對符式寫道 :「電腦實際上是在鼓勵科學家們去修改,修理及改良軟體,去試驗及特徵化 (characterize)他們的裝備。進取精神再度成為研究員的特權。」 唯美主義者認為符式要想儕身高階語言還不夠格,其實不用他們多說,符式甚至還會 自貶身價。當代最時髦的程式語言都在強調其嚴格的語法檢查(strong syntax checking )及資料形態檢查(data typing)等等,符式卻根本不做所謂的語法檢查。既然要提供 我們所說的那種自由及彈性,她就無法告訴你應該鍵入『紅的 蘋果』,而不是鍵入『蘋 果 紅的』。因為那是你剛剛才發明的語法! 然而符式甚至是故意地去造成這種忽略,她就是要讓你一次編譯一個定義,幾秒鐘 之內就作一次。當一個定義行不通時,你能立刻發現其中錯誤之所在,快得很。當然, 你又何嘗不可在你的定義中加上適當的語法檢查功能,這得由你自己決定。 畫家的那隻畫筆並不會指出畫家的錯誤;畫家才是這件事的裁判。大廚師的那口鍋 和作曲家的那台鋼琴,都是既簡單而又聽話的玩意兒,為什麼要讓一個程式語言的心機 比你還深一層? 那麼,符式是高階語言嗎?以語法檢查這個項目來說,她三振出局。以抽象性及能力 來說,她似乎是立於"無限的"(infinite)層面-- 既要支援在輸出埠上的位元操作 (bit manipulation)那種小事,也能支援商業應用的那種大事。 你去決定吧!(符式並不在乎)


設計用的語言(The Language of Design)

「符式語言是一種設計用的語言」。對傳統的電腦科系學生而言,這句話自相矛盾。 他們認為:"人不是用語言去設計,而是用它去實現一個程式。設計先於實現"。 有經驗的符式程式設計師並不同意這種說法。在符式中,你可以寫出抽象的、設計 層面的程式碼,並且藉著將它拆解為詞集的這種好處,你還可以在任何時刻去測試它們。 一個下層元件,在程式發展的過程中,可以在已經被使用著的情況下,很容易的改寫。 起初,這個元件中的一些字,或許會以將數字印在終端機上的方式,代替對步進馬達的 控制。它們也可能會印出自己的名字,其目的是讓你知道它們已被執行到。它們也可能 什麼都不做。 遵循這種哲理,你就可以寫出一份簡單、可測試的、而又是你的應用程式的版本, 然後再持續地去修改它、精製它,直到達成目標為止。 另一個令「用程式碼進行設計」成為可能的原因是,符式語言,正如某些新語言一樣 ,摒棄了"批次編譯"(batch-compile)的發展程序(編輯--編譯--測試--編輯--編譯-- 測試)。由於回饋是即時的,媒介物(譯註:指的是編輯器,編譯器等)在這種創造性的 過程中一變而成為合夥人。使用批次編譯語言的人,心靈上的創作力很難達到創造性巨流 在胸中暢通無阻時的藝術家所能觸及的境界。 基於這些理由,符式程式設計師在和那些一直重視計劃的古典對手比起來,花在計劃 上的時間就顯得少之又少。對那些人來說,不做計劃似乎就是魯莽,就是不負責任。 傳統程式設計的環境逼使他們非做計劃不可,因為傳統程式語言無法因應變動。 不幸的是,即使在最好的環境下,人類的前瞻力也很有限(除非你有一顆很可靠的 水晶球)。過多的計劃作為反而會變成反生產力(counterproductive)。 當然,符式不會不做計劃。她容許建造原型(prototyping)。一個原型的構建就是 一種更為精細的計劃作為,正如在電子設計中使用麵包板(breadboard)一般。 從下一章中可以證明,藉實驗以求真,比起在開始時如同猜謎似的計劃要可靠得多。

語言的性能(The Language of Performance)

雖然性能不是本書之主要論題,但符式的初學者也該被保證,符式語言的這些優點 並非僅是哲理上的。總括來說,符式語言在速度、功能及簡潔性上,勝過其它任何高階 語言。
 

速度(speed)

符式雖然是一種交談式語言,她卻執行著編譯過的程式碼。因而她執行的速度比同屬 交談式語言的BASIC快了將近十倍。 符式藉著"線串程式碼"(threaded code)[9,10,11]的方式使字的執行能達到最佳 化的效果。令模組微小化而產生很小塊的程式碼,所要付出的速度上的代價相當地輕微。 她的執行速度稍遜於組合語言程式碼,那是因為內部的地址解譯器(負責解譯冒號 定義中的地址列表)可能在基元字(primitive words)的執行上消耗將近50%的執行 時間,所耗時間多寡視所用的CPU而定。 但在大型應用程式中,符式的速度已經很接近組合語言程式。速度加快之原因有三: 首先,也是最重要的原因,符式是簡單的。符式使用的資料堆疊,已經大大降低了為 傳送字與字間的引數(arguments)而在性能上所付出的代價。在大多數的語言中,模組 間引數的傳遞往往成為抑制程式性能的主要原因。 其次,符式容許你既可使用高階語言亦可使用機器語言為字下定義。不論你用哪一種 ,都不需要特別的呼叫程序。你可以先用高階定義的方式寫出一個新的定義,經驗證為正 確後,在不更改任何使用它的程式碼的情形下,用組合語言再將它改寫。一個典型的應用 程式中,或許有20%的程式碼在80%的時間中被執行。唯有那最常用到、最攸關時間的常 式,才需要以機器碼予以定義。符式系統的核心就是以大量的機器碼定義所寫成的。因此 ,你幾乎不需要使用組合語言來定義應用程式中的字。 最後,符式的應用程式比純粹以組合語言寫出的程式有較好的設計。符式程式設計師 佔優勢之處,在於使用符式語言的原型製作的能力,能夠在決定何種設計最符合需要之前 ,先嘗試各種不同的方法。由於符式鼓勵修改,她又何嘗不可以稱為是能夠被最佳化的 語言。 符式並不保證應用程式的執行速度,她只提供給程式設計師一個具有創造性的環境, 讓他(她)們去設計出快速執行的應用程式。

 

能耐(capability)

符式能做其它任何語言所能做的任何事-- 而且總是輕鬆一些。 在低階的部份,幾乎所有的符式系統都含有組譯器。它們支援控制結構操作元,以便 使用結構化程式設計之技術去撰寫條件判斷及迴路。它們通常容許你去撰寫中斷程式-- 甚至可以用高階定義來撰寫中斷程式,如果你想那麼做的話。 有些符式系統是多工的,允許你隨心所欲地加上前景或背景的工作。 符式可以寫出能執行於任何作業系統上的程式。如果有人高興,符式何嘗不可任由他 寫出自給自足的作業系統,甚至包括它自己的終端機驅動程式及磁碟驅動程式。 藉助於一套符式交叉編譯器(cross-compiler)或目標編譯器(target-compiler) ,你就可以用符式去創造出一套全新的符式系統,使用於同一台電腦上,或者用在完全 不同的電腦上。既然符式是以符式撰寫出來,你就有想像不到的機會,根據你的應用程式 的需求,去撰寫出自己的作業系統。
 

大小(size)

這牽涉到兩種考慮:符式系統的根的大小及編譯完成的符式應用程式的大小。 符式的核心是頗有彈性的。在一個嵌入式系統的應用中,用來執行應用程式的核心 部份可被縮小為1K左右。在一個充分發展的環境中,一個含有解譯器、編譯器、組譯器、 編輯器、作業系統及其它一切支援的多工系統,平均可大到16K,仍留下足夠的空間給 應用程式之用。(某些以較新型的處理器構成的符式,使用了32位元地址空間,容許極大 型的應用程式使用)。 與此相類似的情形是,用符式編譯出的應用程式總是都很小-- 通常小於同等功能的 組合語言程式。其原因則要歸功於線串程式碼的架構。每參考到一個先前定義過的字, 不論它是多麼強而有力的定義,只佔用了一個16位元組的空間。 符式令人興奮的新領域是符式晶片的生產,例如以符式為基礎,編號為RTX2000的 微處理器晶片。此晶片的機器指令可以直接執行符式的基元字,而且具有並行處理的能 力。在10MHz的工作頻率下,有10-40 MIPS的性能表現。唯有符式的架構及簡潔才能使 以符式為基礎的微晶片變為可能。

結語

符式常被視為具有不尋常的特性,在架構上及哲理上與其它任何流行的語言完全不同 。相反的,符式卻納入了各現代語言一直自誇的許多原理。結構化設計,模組化,以及 資訊隱藏等等都是今日熱門的術語。 某些新一代的程式語言甚至更接近符式的精神。以C語言為例,它就容許程式設計師 使用C語言或組合語言去為新的功能下定義,正如符式的作法。也如同符式一樣,大部份 的C語言也是以功能來定義的。 然而符式語言和其它現代語言相比,卻將模組化及資訊隱藏等概念伸展得更遠。 她甚至將字的呼叫及局部引數的傳遞的方式也一併予以隱藏。 完成了的程式碼,變成了集中著交互作用的一群字,它們為抽象的思想作了最純淨 的詮釋。其結果是符式設計師趨於有較高的生產力,能撰寫出更為簡潔、更有效、更易於 維護的程式。 符式或許不是終極的語言。但我相信,如果真會有所謂終極語言的話,符式在和其它 任何語言比較之下,應該是最為接近那種語言的語言。

參考書籍

       1. O.J. Dahl, E.W. Dijkstra, and C.A.R. Hoare,
          "Structured Programming", London, Academic Press, 1972.
       2. Niklaus Wirth, "Program Development by Stepwise Refinement",
          Communications of the ACM, 14, No. 4 (1971), 221-27.
       3. W.P. Stevens, G.J. Myers, and L.L. Constantine,
          "Structured Design", IBM Systems Journal, Vol.13, No.2, 1974.
       4. David L. Parnas, "On the criteria To Be Used in Decomposing
          Systems into Modules", Communications of the ACM, December 1972.
       5. Barbara H. Liskov and Stephen N. Zilles, "Specification Techniques
          for Data Abstractions", IEEE Transactions on Software Engineering,
          March 1975.
       6. David L. Parnas, "Designing Software for Ease of Extension and
          Contraction", IEEE Transaction on Software Engineering, March 1979.
       7. Dewey Val Shorre, "Adding Modules to FORTH",
          1980 FORMAL Proceedings, p.71.
       8. Mark Bernstein, "Programming in the Laboratory", unpublished paper
          1983.
       9. James R. Bell, "Threaded Code", Communications of ACM, Vol.16,
          No.6, 370-72.
      10. Robert B.K. DeWar, "Indirect Threaded Code", Communications of ACM,
          Vol.18, No.6, 331.
      11. Peter M. Kogge, "An Architectual Trail to Threaded-Code Systems",
          Computer, March, 1982.