2008-06-05

[BBS 舊文] Why I Prefer Procedural/Relational Over OOP

原文網址

以下是翻譯:

為什麼我使用程序導向,而不用物件導向

我的文章,大部份主題都是以打倒OOP為主,為因應大眾的要求 (或者是反彈,以你們的觀點來說),我在此詳細說明為什麼我使用程序導向,而不用物件導向的理由,主要的目的在於用p/r 取代 OOP。本文為一個大致的整理與說明,在此不舉特定的例子。個別的說明可參考散布於文章各處的超連結。

我們常說OOP將資料與行為作了整合,而其競爭對手 "循序性的程式設計法則 " 的方式,則是將他們分開處理。整合是好的、對的嗎?整合資料與行為是一件好事、還是壞事?如果我們觀察在這個世界上的其它事物,我們可以發現 "整合" 這件事情有時候只是一種 "優缺點的相互取捨"而已。"整合"有時候只是把東西弄得更複雜、更難以用相容性的東西來抽換而已。它把東西弄得更難單獨的分析。就像是吃擔子麵,你只是想吃魚丸,它卻老是與麵條糊在一起,然後事情就變得很麻煩。

老天爺將人類的腦袋分成兩個部份。一部份負責邏輯,而另一部份負責情感。(這樣說也許過於簡化,卻符合大部份的狀況),如此基於"思維型態的區分" 一定是有它的道理所在,否則上帝應該將它們 "整合" 成一部份而不是分成兩個部份。同樣的,美國人將政府部門分成三個部份:立法、司法、行政。有的人立法、有的人詮釋法律、有的人履行法律(如果他們守法的話)。因此,在這邊已經有了兩個 "本來就應該分開" 的例子。

在我的觀點,P/R 程式設計法則,將資料與行為區分成兩個部份,一定也有其設計上的優點。它提供了一個資料與資料使用者(應用程式)間分明的性質界面。雖然為了配合此性質界面,必須付出一些義務及代價,但這些代價確是值得付出的。

P/R 程式設計法則,也就只有這麼一個性質界面,也是唯一的代價。如果你遵循這個法則,到最後你會發現,跟其它的法則比起來,其實它的效益才是最大的。這個法則的限制條件並不是隨意訂下的,而是經過周密的思維而得,我的看法,它是目前世界最了不起的軟體工具技術。與GUI一樣,它是少數令人讚嘆的技術,它是讓軟體科技向前進步的基礎。

這種界面的契約就好像是現代的高速公路系統。如果大家都願意繳稅,也願意遵守交通規則,在這樣的前題下,政府就可以據以建構龐大的高速公路網,讓我們可以方便、快速的從甲地到達乙地,而我們要做的,就只是要擁有一部車。如果你不同意這樣的作法,那麼你去鄉下住住看,在馬路不多交通不便的環境下,你的想法應該會改變才是。

這P/R 程式設計原則,簡單的來說就是:"如果你將資料放在一個特定的 Struct 中,那你將可以完完全全的操控它"。代數的發明者E. Codd 讓世界見識到數學的強大力量,它將一些複雜的彼此具相關性的數字化為簡單的代數方程式,或關連性的參數。

我們可以用一個簡單的代數方程式來表示一個 "模式",這樣省時又省力。這就好像我們可以用一些預先定義好的口令來命令人數眾多的軍隊一樣,而不用對軍隊中的每個人一一解釋說明。只要一次對全體發一次口令即可,不管是對十個人或者是十億個人發號施令,命令都會被忠實的執行,這跟下達公文指示是一樣的意思。還有一種方式是將命令編碼到一個結構中再傳送,OOP多多少少會用到這種方式,在這邊我們看到OOP還是用了 "老方法",而不是一成不變的用 "比較現代" 的方式(據它們說比較不過時)。OOP本質上它的作法是,一次只命令一個士兵,一個接著一個。

不只是代數方程式可以裝載一堆具關連性質的參數,也可以將這一堆參數放在一張各欄位定義清楚的表單上(當然你須先具備相應的表單流覽器)。至少就我來看,將資料放在表單之中比將資料放在程式中來的簡單而有彈性。相對於放到表單上,將資料放在程式碼中比較難以閱讀、搜尋、交叉比對以及過濾。當然不太可能將所有的資料通通都放在表單之中(至少在目今的技術/工具來看),但是在一些實際的例子中,你還是可以將大部分的資料放在裡面。

請注意!在我見聞所謂正式的關連性法則之前,表單就已經是我偏好的方式。(關連性技術並不是我大學時所學到的課程之一) 例如,在早期我習於將參數放在程式碼中時,程式碼就像以下這個樣子:

w = foo(norf,   "exmpl", 05,    g,  nork)
  x = foo(glob,   "zorg",  33,    r,  frot)
  y = foo(zonnof, "blag",  22.5,  z,  melo)
  z = foo(fli,    "pag",   13.1,  k,  pendmend)

 

它顯示了我個人對參數運用方式的一時想法或偏好,而忽略了我後來自己一再耳提面命對於關連性法則的要求。我不知道同樣的參數,如果同時有別人來應用,它將呈現如何的風貌。我收到有人寄給我的E-mail,提到他在這些參數上碰到麻煩,它們不知道這些參數所代表的意義。我的觀察是:每個人都有不同的思維模式,包含程式設計師也一樣,彼此想法的差異性都很大。

因此,把表單與資料關連性(方程式)分開的法則,已比程式本身還重要(至少我認為),你越是把的資料放在表格中,你就越能夠去編輯、管理、改變對資料的應用方式。資料庫本身也變得更精確、有力。程式碼也就變得不用那麼講究了。

OOP法則比較像是 GOF Patterns,以程式碼為中心。傾向將這個世界上所有簡單的、複雜的資料通通都整合到程式碼裡面。而P/R的作法,資料雖然由代數方程式控制操作著,但資料本身存在程式碼以外。因此若只是要修改這些代數方程式,就變得很相對容易,比修改程式加資料整合的結構方便多了。由於只涉及代數方程式與程式間的整合,關係相對單純,意味著當要修改程式碼中的代數方程式時,所牽連的範圍比較小,不會有動一髮牽全身,搞成滿臉豆花的夢喭。

將資料與程式分開的另一好處,就是可以輕易地,讓不同的程式語言來處理同樣資料庫。Java、FORTRAN、Smalltalk和LISP 可以共用相同的 "名稱模塊" (即Structure),那是因為 "名稱模塊" 是放在於資料庫中而不是放在程式語言中。相反的,想要讓不同的程式語言,如COBOL、Visual Basic和 Java,來分享其創造的 "物件" 則幾乎是一件不可能辦到的事。

 

方程式的其它功能


方程式不只是數值關係的表現而已,還有用於判斷分發的規則也是。例如,循序型的程式碼有以下的型式:

task X {
    ....
    if [expression_A] {
       [perform something 1]
    }
    if [expression_B] {
       [perform something 2]
    }
    ....
  } // end task X

 

如果需要修改條件A下的分發方程式,就只要修改方程式的部份,其它的部份都不需要動到,這樣一來,程式的變動的幅度少之又少。

OOP意圖將行為程序附掛在結構或所謂類別成員上,像是聖誕樹樹梢上的裝飾燈炮一樣。分發程序因此就成了一種綁在樹稍分支上或數幹網路上的實質物體。基於某些理由,OOP迷似乎很喜歡這種作法。但是就我的觀察,這樣的作法其實並沒有讓程式變得比較友善、易於了解。因為分發程序總是需要一再的修改,在這種情況下,如果分發程序又牽連到其它的物件分發程序成員,那就會變得很麻煩。

因此,我比較喜歡直接引用代數關係式,而不喜歡將它們放在物件結構中(請注意OOP的作法是將代數關係式放到物件結構中),我發現直接對付代數關係式比將它們放到物件結構中來處理來得方便且可靠。這個直接的觸感,讓你切實感受了代數關係式的存在,好處就像我剛剛所提的。這直接引用代數關係式的作法是非常好用的,至少對我來說。

演化歷程的分岐P/R 和 OOP 對於 "成員管理",有著不同的歷史演化路徑。在早期Structure 剛剛誕生的時候,在C裡面它叫作Struct,在 Pascal 中它叫 Record,在Cobol 中,它叫 Data definitions,在使用打孔卡片的時代,它叫打孔卡片的Layout。

隨著程式越來越龐大,Structure 也開始變得越來越複雜。對於如何處理複雜的結構,出現了兩種不同的流派。OOP 是其中一種流派的極致表現;Database則是另外一種截然不同的流派。OOP的目的在讓程式語言能夠輕鬆的管理這些複雜的結構。

然而,Database走向的人,意識到可以用一般且通用性的方式來運用這些結構。他們運用各種方式而開發出 "通用性的結構管理"(資料管理)的工具,並分析資料庫的使用模式,因而定義出若干用於資料庫操作的固定指令,從此以後,Database的操作,就開始超乎於任何特定的應用目標、應用程式或程式語言之外。

我認為第二種方法是比較好的(至少對於客製化的商業軟體來說)。當OOP還在試圖讓應用程式能夠更方便的運用資料時,第二種方法已經早就不需要在這個議題上傷腦筋,並且它的運用方法也已經非常簡單。OOP為了讓資料庫的應用更為簡便,常常需要對資料庫作大量的重構的工作,但是第二種方法根本就不需要作這些額外的工作。當OOP提供了你九十九種工具來讓你打造自己的機車時,第二種方法早就給你了功能完備且已可駛出家門的汽車。

好了,在我分析了這些好處之後,也來說一說它的缺點吧!我不是說它完全沒有缺點,但是它的確是沒有什麼大的缺點。如果真的有問題發生了,還是有許多現成的工具及技術可供應用的。同時到目前為止,我還沒有碰過什麼問題,大到需要將整個專案翻過來,重新規劃的案例。

OOP提暢者最常提到的好處就是,只要切換物件,行為就隨之直接切換。OOP提暢者宣稱 "抽象化的資料來源及型式",因此可以讓使用者可以不必過慮於資料詳細型式及它的實作方式。但是我沒有看過任何實際的例子,因為使用了這種OO作法而增加了什麼重大的效益的。例如,許多場合,有些資料須要定期更新,如果是OO的作法,就是需要定期呼叫一個成員函式來重寫資料(這個資料庫還可能需要先重構),如果我想要看資料的內容是什麼,或者是想要啟動資料的更新作業,則還要去實作並呼叫Views 和 triggers成員函式來達成功能,這真是 "脫了褲子放屁==多此一舉。

那些聲稱 OOP "保護" 或 "包裹" 資料以避免或減少存取點數量,而這些存取點它們必須被追蹤或改善。他們以為,資料庫往往是廣為開放或完全裸露在外的。然而,如果它們在他們的程式碼內,重構資料庫操作函式,則它們還是製造了
相同的問題,資料庫還是為特定程式打開著。這個複雜的問題牽涉到了複雜的好壞取捨的問題。每個人都會有不同的觀點。

在那些吹捧OOP的文獻中,所描述的資料庫的缺點,其實大部份都是虛構及誇大不實的。我作了一個結論,在我的看法,直接引用資料庫方式所提供的彈性、動態、模組化等優點,遠遠超過其產生的小小缺點。沒有必要因噎廢食,想想這一個道路哲學:為了顧及到那個也許會走到的碎石子路,所以全部的人應該都要買吉普車,來應付那可能的萬分之一的機會,而犧牲了馳騁於高速公路上的性能。

有些OOP暢議者說,將OOP與RDBMS搭配在一起,雙方皆可獲得世上最大的效益。然而,我看到卻的是它們彼此衝突又重覆彼此工作的狀況。GOF模式本身就像是一個舊約聖經,一個不去好好運用資料庫的功能,卻要自己用複雜的程式碼,一個個來處理手工索引的程式,怎麼行的通。也許GOF模式是用在資料庫不存在的地方,但這似乎是不太可能的事。許多OO迷,甚至是大部份,傾向使用一種OOP資料庫(OODBMS)或者完全不使用資料庫。然而,OODBMS現在在商業應用上已經是一個失敗的產品,它已無法成為RDBMS之外的另一種替代方案。甚至有人認為OOP資料庫的觀念本就與OOP的法則本身天生就是相互矛盾的。因此,不用說也知道,OOP之於資料庫已經開始過時,前途黯淡。

在此聲明我可沒有說關連性技術與表單可以完全取代GOF或類似GOF的模式。是有一些類似的功能可以在P/R下實現,但大部份還是要靠資料庫或掌控資料庫的工具來完成。

OOP的擁護者又有分化成兩種不同陣營的傾向(有些是在中間)。第一種為 "分類學者",他們緊抱著簡單的事物分化原則,事物一旦被分化成一種類別,便與其它的類別完全互斥,而此種類別可以再度分化成彼此互斥的次類別,這即是類別繼承觀念的基礎。這卻是令我常批評它的地方,它們用太過於單一的觀點來描述真實世界的事物,這使得它在應付這些事物的變化時顯得招架不住。這是因為同一件事物,往往有許多不同的面向及觀點,互斥類別樹表示的只是其中之一的可能性。

以關連性為思維中心的觀點來看,類別樹只是Structure 眾多面貌中的其中一個可能。有可能是堆疊、樹狀、簇列…等,就看當時的需求是什麼。同樣的一筆資料有可能同時屬於堆疊、樹狀或者是簇列中的一部份。這樣的Structure對關連性的思維來說,它是可以隨意組織的。但是如果用一個死板板硬梆梆的來類別分化法來處理它,並放到程式碼中,將失去了或減少處理該資料的其它面向的能力。加上,為什麼有些資料要靠程式碼才可看得到而有些又需要經由資料查詢工具?這真是讓我感到矛盾。

 

第二種陣營,我叫它 "浪漫的理想者"。我想連它們自己應該都會同意,對於客戶的實際需求,簡單的繼承範例,在實務上都是起不了什麼作用的。

 

更不幸的,這些浪漫的理想者基本上都只是在製造複雜化的問題。這個所謂OO新玩意,在碰到實際的資料庫問題時,就不得不失去了只用簡單繼承就可以完成程式碼的理想。它們需要重新創造資料管理系統,要讓類別再去橫向整合其它的類別,一下左邊的類別,一下右邊的類別,一下上面的類別,一下下面的類別,這樣才可取得所需要的資料面向。P/R 的應用法則,就比它高明多了,在程式碼內沒有所謂的資料,而是用索引或索引鍵,你至少不用對每筆記錄都要準備一個引用索引或索引鍵,如果使用Class的方式來處理,兩百筆資料就要兩百個Class的處理動作

某些OO迷將會指出關連性的方法只能適用在資料庫面向的轉換,而不適用在演算法。然而至少在我所處的領域裡,絕大部分事物都是性質參數的集合,或者是可以輕易將其轉換成性質參數的集合的。我曾經作過將程式碼放在資料參數中的嘗試,藉以減低我對不同資料處理方式的差異性,但事實上,我老是感到這麼作是不必要或者是沒有用的。

個人以為,SQL不是一個理想的查詢語言,但是比起OOP的方法,它還是進步許多。OO迷有時常抱怨,與資料庫的連結點重覆太多次,寫起來太費力。事實跟可以不用這麼作,只有標準或傳統的方式才需要這麼作,設計準則本身並不這麼要求,所有一般性的連結點可以技巧性的定義成一個點,SQL 評論網頁對此有更詳細的說明。

我非常喜歡利用資料庫工具,來對所有的資料庫作交叉關連分析的工作,有不少專門的工具在作這些事,可以對數噸的資料作管理的工作。OOP的作法,是將資料直接從資料庫裡全部倒出來再處理,這樣程式內的緩衝區內就會像爆炸案現場一樣的亂成一堆,看起來真是可怕。我幾乎不想用這種方式來管理大量的資料,所以同樣的,我也不會想用這樣的程式碼來從事我的工作。

 

每當我想整理非樹狀結構的OOP程式碼,Class 的關連圖畫到最後,都會畫不下去。一大堆關連指向線,密密麻麻分布在整張紙上。如果是使用P/R的方式,通常不需要這麼作,至少在條理清晰的程式碼上是如此的(不幸的OOP,大部分都不需要)。如果會有需要,大部份的情況都是:在IF 或 While 條件式後方出現了goto 指令,這是一種很老舊的程式寫作習慣,現在幾乎都不用了,但看到OOP,又不禁讓我想起那個時代。當我使用OOP的時候,我老是要Key-in database 的參照到所有的Class中,這樣才可對那些資料作排序、過濾、搜尋等等的工作。如果是使用P/R的作。如果是使用P/R的方式,這些工作早就已經在資料庫工具中完善地作好了。不管是要看資料庫的概廓,還是要從事查詢。設計程式時,不管是需要那一種資料庫的查詢功能,或者是它們之間的任何組合,只要用IDE來整合就可以了,你都不需要自己來寫相關的程式碼來實現功能。

以下還有許多文章,它們對關連性資料技術有更詳細的說明,就我的觀點,它們對於那些需要處理複雜關連性的資料是有用的。如果處理對象是可以將轉換成關連資料庫的,則越是有關連性需要管理,則越是容易。

P/R設計法則的真正力量不在於程式語言本身,而在關連性的模式及思維方式。當P/R設計法則,將系統設計的複雜部份,逐漸從程式語言中抽離的時候;OOP則在程式語言上動腦筋,試圖以加強程式語言功能的方式來達到目的,結果只是把事情弄得越來越複雜。依據P/R設計法則,程式形成了簡單易讀、容易維護的面貌。它還沒有到死的時候,相反的,它已輕易找到了它自己的定位,即所謂否極泰來。

-全文完-

沒有留言:

★★★★★★ 傑森系列 ★★★★★★