/ 翻譯

Go To 敘述有害論

本篇為 Edsger W. Dijkstra 於 1968 年發表於 Communications of the ACM 的《Go to statement considered harmful》論文翻譯。在不久前心血來潮讀到,便順手翻譯了一下。作者從「如何由程式的『靜態』結構推導出其『動態』執行進度」的觀點出發,解釋了為什麼 go to 的濫用會對程式的理解造成困擾。

多年以來,我已對「程式設計師的品質為一個 go to 敘述(statement)密度的遞減函數」這項觀察相當熟悉。最近,我察覺到了為何使用 go to 敘述會有如此災難性的影響,並且我確信 go to 敘述應該從所有「高階語言」(也就是說,或許是除純機器語言以外的一切)中廢除。那時我並不太重視這個發現;現在我要將我的考量刊登出來,因為在最近出現這個主題上的討論上,我被強烈要求要這樣作。

我的第一個看法是,儘管程式設計師的活動(activity)在他已建構出一個正確的程式時就結束了,但是在他的程式控制下運作的行程(process)才是他活動的主要目的,因為這個行程必須實現預期中的結果;這個行程的動態行為必須符合預期中的規格。然而,一旦程式被創造了,相應行程的「製造」就被委交給了機器。

我的第二個看法是,我們的智力相當適合掌握靜態關係,並且我們在想像行程隨著時間發展的能力是相對未開化的。基於這個理由,我們應該(作為智慧的程式設計師意識到我們的極限)縮短靜態程式與動態行程之間的概念落差,使得令(在文字空間上延伸的)程式與(在時間上延伸的)行程彼此相符是盡可能地簡單的。

現在讓我們來考慮,我們會如何描繪一個行程的進度(progress)。(你可以用很具體的方式來考慮這個問題:假使一個被視為一連串行為〔action〕的行程在一個任意行為後被中止,我們必須要準備什麼資料,以讓我們能夠重新執行這個行程到相同的一點上呢?)假使程式文本為一連串純粹的賦值(assignment)敘述(由於這個討論的目的而被視為單一行為的描述),那麼在程式文本中指出兩個連續行為描述之間的一點就足夠了。(在缺少 go to 敘述的情況下,我會允許我自己在前一句中「連續行為描述(successive action descriptions)」的句法歧異:假如我們將它們剖析為「連續〔行為描述〕」,我們指的是在文本空間中的連續;假如我們剖析為「〔連續行為〕描述」,我們指的是在時間上連續。)讓我們將這種指向文本中合適之處的指標(pointer)稱為「文本索引(textual index)」。

當我們包含條件子句(conditional clause)(if B then A)、替代子句(alternative clause)(if B then A1 else A2)、由 C. A. R. Hoare 所引入的選擇子句(choice clause)(case[i] of (A1, A2, ···, An)[1]、或者由 J. McCarthy 引入的條件表示式(B1 -> E1, B2 -> E2, ···, Bn -> En[2],行程的進度依然由一個單一文本索引所描繪的事實仍舊成立。

一旦我們將子程式(procedure)納入我們的語言中,我們就得承認一個單一的文本索引是不再足夠的了。在一個文本索引指向一個子程式主體(body)的內部時,動態進度僅有在我們也給出我們涉及的是哪一個子程式的呼叫時才能被描繪出來。具有子程式時,我們能夠透過一個文本索引的序列來描繪行程,這個序列的長度等同於子程式呼叫的動態深度。

現在讓我們考慮重複(repetition)子句(像是,while B repeat A 或者 repeat A until B)。邏輯上來說,這類子句現在是多餘的了,因為我們能夠藉著遞迴(recursive)子程式來表示重複。基於現實考量,我不希望排除它們:一方面,重複子句在如今有限的機器上能被實作得很好;另一方面,被稱為「歸納法(induction)」的推導模式讓我們有很好的能力,以維持我們在由重複子句產生的行程上的掌握。在包含了重複子句的情況下,文本索引不再足以描述行程的動態進度。然而,在每個重複子句的進入點,我們可以與一個稱為「動態索引」的東西聯繫起來,計算對應於當前重複的序數。由於重複子句(就如同子程式呼叫)可以被巢狀(nestedly)應用,我們發現行程的進度總是能夠以一個文本與/或動態索引的(混合)序列來唯一地描繪。

重點是這些索引的值在程式設計師的控制之外;無論他是否想要,它們都會(由他的程式寫下、或是行程的動態演進)產生。它們提供了獨立的座標來描述行程的進度。

為什麼我們需要這種獨立座標?原因是––這似乎是循序行程所固有的––我們可以僅就行程的進度來解析一個變數的值。若是我們想要數一間最初為空的房間裡頭的人數,假設為 n,那麼無論我們看到誰進入這間房間,我們都可以透過將 n 加一來做到這件事。在我們觀察到某人進入房間,但尚未執行隨後的 n 值遞增的中間時刻,其值便等於房間裡的人數減一!

go to 敘述的濫用導致的直接後果是,找到一個有意義的座標集以描述行程的進度會變得非常困難。通常,人們也會考慮使用某些精心挑選的值。但這是不可能的,因為這些要被理解的值的意義是相對於這個進度的!當然,儘管使用了 go to 敘述,你依然可以藉由一個計數器,以計算自程式開始後執行的行為數量來唯一地描述進度(即,一種正規化的時鐘)。這種座標的困難在於,儘管是唯一的,也完全沒有幫助。在這種座標系統中,定義所有過程中的時刻(比如說,n 等於房間中的人數減一)會變得非常複雜的事情!

go to 敘述太原始了;它太容易致使我們產生程式中的混亂。你可以尊敬並感激這些被視為限制其使用的子句。我並沒有主張這些提到的子句能全面滿足所有需求,但無論被建議的子句是什麼(例如,中斷〔abortion〕子句),它們都該滿足一個程式設計師獨立座標系統能夠被維護,以有用且可管理的方式描述行程的需求。

以一個公平的答謝來收尾是很難的。我能判斷我的思想已經受了誰的影響嗎?很顯然我並非沒受到 Peter Landin 與 Christopher Strachey 的影響。最後,我想(如同我清楚記得的那樣)紀錄 1959 年初在哥本哈根的 pre-ALGOL 會議上,Heinz Zemanek 是如何明確地陳述他對於 go to 敘述是否應該與賦值敘述一視同仁的疑慮。一定程度上,我責怪自己沒有得出他看法的結果。

go to 敘述不可取的看法並不新鮮。我記得我曾讀過將 go to 敘述的使用限制在警報退出(alarm exits)的明確建議,但我已無法追朔其出處;它大概是由 C. A. R. Hoare 所撰。Wirth 與 Hoare 一同在 [1, Sec. 3.2.1.] 於推動 case 建構的相同方向上發表了一個看法:「像是條件一樣,它比起 go to 敘述與 switch[3]更加清楚地反映了一支程式的動態結構,而且它消除了在程式中引入大量標籤(label)的需要。」

在 [2] 中,Guiseppe Jacopini 似乎已經證明了 go to 敘述(邏輯上)的不必要。然而,將任意流程圖或多或少機械地轉譯成無跳轉(jump-less)的作法並不被推薦。產生的流程圖並不預期比原本的那個更加易懂。

參考資料

  1. Wirth, Niklaus, and Hoare C. A. R. A contribution to the development of ALGOL. Comm. ACM 9 (June 1966), 413-432.
  2. BÖhm, Corrado, and Jacopini Guiseppe. Flow diagrams, Turing machines and languages with only two formation rules. Comm. ACM 9 (May 1966), 366-371.

  1. [譯註] 整個表達式的結果會是 Ai,譬如說 i = 2 時的結果為 A2↩︎

  2. [譯註] B1B2 等的求值結果為 truefasle,整個表達式的結果會是第一個為 trueBx 對應的表達式 Ex↩︎

  3. [譯註] ALGOL 的 switch 是一個包含跳轉標籤的陣列結構型別。 ↩︎