C++雜思錄——風格的選擇 (轉)

worldblog發表於2007-12-12
C++雜思錄——風格的選擇 (轉)[@more@]

風格的選擇

【警告】我目前從事嵌入式開發,文章中的觀點受到我淺薄開發的強烈影響,各位請抱著批判的觀點看待。另外,此文的以專案實際開發為衡量標準,請不要以僅在理論中存在的理想標準來評價本文的觀點。

【大師語錄】

Herb Sutter(1998):...在我們公司的開發中,大量使用的是封裝,包容,訪問控制,ADT,不大用繼承和多型。我們經常使用STL,template...至於異常,我通常都用new (nothrow) Classname!;-)  ...

Douglas Lea(1995): 也許對C++最好的看法是把它看成一系列較小、較簡單的語言。包括:一種介面定義語言,一種資料抽象語言,一種靜態型別的面向語言,一種過程化語言(也就是C)。它的每個方面都有一些缺陷...,讓這些模型和特性彼此互動協作會導致嚴重的複雜性。通常,對付這種複雜性的最佳手段是始終堅持使用少量的幾個設計和慣用技術(就是今天所謂的——譯者),這些技術又組合成為更加通用和有效的技術。(設計開發中的)一個首要目標是儘可能遠離這個語言中的陰暗角落。

【正文】


  Bjarne Stroustrup說,C++有四個部分:better C,ADT,OO,和GP。雖然現在也有一些新的風格被證明可以在C++中運用,比如functional, generative,meta programming等等,但是在實際工程中,主流的風格就是這四種。我們通常使用OO風格進行開發,但是,嚴格來說,目前C++中的所謂OO風格是一種典型的混合風格。

  是不是可以這樣認為,C++實際上是幾種不同風格的語言集合。也就是說,你可以把它看成幾種不同的語言,可以只使用其中一種語言進行完整的軟體開發。如果同時雜合使用一種以上的風格,則複雜性會大幅度地增加。我個人認為,這是C++在實踐中難於控制的一個主要原因。混合使用不同風格,就好像在一個源裡混合使用多種不同的語言,複雜和不一致性必然暴露。當然,C++獨特的魅力正在於混合風格程式設計的強大威力。這正是一把雙刃劍,雖然具有潛在的強大威力,但是通常來說也是導致專案混亂的重要原因。

  我認為以下面的原則進行實際開發,將可以在一定程度上規避風險:

1). 在任何一個單個的時間點,只使用一種程式設計風格。

2). 以一種風格為主風格,用它來組織整體模組的開發。

3). 在遇到特別適合另一種風格的典型場景,可以用一個子模組包裝該場景,然後在該子模組中使用該風格,但記住遵循要求1,避免混合風格。此外,必須透過封裝手段將該模組包裝起來,以符合主體風格的要求。比如說,主風格是better C,在某個子模組中用到了物件導向,則應當使這個子模組從整體上看來像是一個普通的C過程。

4). 在個別場合,混合風格的確有很大的好處。但是,這種情形是比較少見的,一般來說比較成功的實踐已經總結成patterns,所以在工程實際中,可以強行規定,只有在符合某個patterns的情況下才可以謹慎地使用混合風格,嚴禁擅自創造新的混合用法。

  現在來討論一下究竟應當如何劃分C++風格。Stroustrup對C++風格的分類是從語言開發者的角度進行的。如果我們以下面的原則進行分類,我認為會得出不同的結果:

1) 每一種風格必須構成一個完整的子語言,具有完備性,可以單獨使用這一子語言開發任何軟體,有經過歷史驗證的成功經驗。

2) 每一種風格必須相對簡單,有一致的、簡單的、得到認可和驗證的原則。

3) 每一種子語言必須能夠在現實世界中找到相對應的其他語言。

  依據以上原則,我將C++劃分為三個半子語言:

1)  Better C, 只增加過載、引用型別、預設引數等簡單特性的類C子集。對應ANSI C語言。

2)  ADT C++,即C with Class,整個由平面化的具體類(concrete class)物件構成,無繼承,無多型。對應Ada 83語言。

3) IDL C++,我稱之為Interface-Oriented,典型範例是COM模型。

3.5) GP C++, 利用模板技術形成了一種庫和元件的實現語言。這不是一種完整子語言,一方面因為可以把它看成是ADT C++的一種延伸,另一方面它必須依附於其他風格而發揮作用。

  顯然,我這裡遺留了一個最重要的風格,也就是我們通常所說的“傳統物件導向”風格,由Smalltalk,等語言所展示的,由MFC等類庫經過多年實踐論證了的一種風格:靠龐大的繼承樹抽象和組織各種資料型別,靠繼承和組合實現程式碼複用。這種風格為什麼沒有被我提及呢?

  因為我認為這種風格實際上是一種混合風格!可以認為是在試圖融合上述第2、3和3.5種風格。在前述的三條原則裡,它嚴重地違背了第二條。由於C++的靜態本質,由於C++缺乏天然的類庫和垃圾收集機制,使得在C++中進行Smalltalk風格的程式設計非常非常困難,以至於為了克服這些困難,C++實際上發展出了一套不同於Smalltalk、Java風格的獨特的“物件導向”程式設計風格。這套風格歷經近15年實踐,應該說有成功有失敗,雖然出版了大量的著作,至今沒有形成簡單的、一致的、可仿效的風格指導。從某種意義上說,如此多的C++物件導向程式設計指導書籍十幾年常盛不衰,恰恰說明這種風格的困難程度和難以仿效性。就我個人而言,我已經不再以這種風格為指導思想了。我不會再拼命地構造繼承樹,思考哪些函式應該是虛擬函式這類問題了。

  你可以認為“為了複用程式碼而進行的繼承”是這種風格的標誌。請注意,ADT C++允許組合,對於繼承則應該想盡一切辦法避免。而IDL C++的典型代表COM,根本就不支援這種繼承,它支援的只是介面的複用。

  當然,這並不是要否定十幾年來C++在物件導向方面發展的成績。但是,如果你現在從頭開始規劃一個完整的專案,那麼我認為如果選擇這種雜合風格,是不太明智的。但是這種風格也有兩個典型的使用場景:

1) 有一個完整的支援。比如MFC。雖然這種風格本身有很多技術難點,但是MFC這樣的框架已經幫你克服了一部分,給你營造了一個類似Smalltalk那樣的、相對舒適環境,這時候可以使用這種風格。但是通常要認識到,這類框架在克服不少技術難點的同時,引入了一些新的問題,有時是更加難以對付的問題,所以要明智,並且做好充分準備。

2) 符合經典模式。如果遇到某個典型的“物件導向”場景,已經有了成熟的、優秀的、現成的、文件化了的設計解決方案,則可以有選擇的、謹慎地使用之。我指的主要就是GoF和其他一些設計模式。這裡所謂的“經典模式”數量絕對不會太多,但是卻大量地、反覆地出現在設計中,並且往往復合出現。這樣的情況用已經經過驗證的設計方案來解決是非常合適的。我個人在這裡有一些實踐,覺得應該注意幾個問題。第一是要謹慎,我遇到過大量的情形,看上去很適合用某個模式來解決,但是真的用了才發現並不是這麼回事。在不適合的地方套用了錯誤的模式,會把事情弄得一團糟;二是最好將設計方案區域性化,包裝起來,從外面看不出你使用了什麼模式。三是注意問題。使用OO風格的最大障礙其實就是記憶體問題。

  其實這個口子一開,最終的設計裡仍然會出現大量的“傳統OO”風格,因為經典模式實在是太普遍了。所以主要問題是控制和包裝,因為四處氾濫的模式實際上等於重新回到混合風格。

  值得指出的是,其實在大部分的經典模式裡,並不存在“為了複用程式碼而進行的繼承”。我們可以認為,凡是合理的物件導向,必然具有介面繼承的特徵,必然出現抽象類,很可能出現多型包容。

  就我個人而言,由於從事嵌入式應用軟體和高層系統軟體的開發,出於嵌入式系統對於的極端關注,我比較傾向於下面的組合:

1) 以ADT C++作為主風格。

2) 利用GP輔助設計良好的ADT。以GP和組合實現程式碼複用。

3) 將可能的OO包裝在平面式的類中。

4) 禁止使用異常。這一點將會有專門的反思文章。

  對於PC和大型專案的開發,我覺得以IDL C++的風格作為主程式應該是更合適的。目前我還沒有這方面的實踐。但是我希望能夠儘快有一些嘗試。


結語:
  有人可能會質疑我對“複用性”的重視不足。因為傳統OO的一大立足點。對此我不予否認。我認為目前很多程式設計師對複用性問題不是考慮不足,而是考慮過度。尤其是應用程式設計師,花費太多的精力去讓自己的元件滿足未來可能的需求變化,很可能是在浪費時間。對於“複用性”這個話題,我也有一些想法,容後再述。

預想中的論題:

1. 禁止異常——在沒有異常的C++中生存

2. 複用性——三思而行

3. “標準元件”的誤區——適當恢復“自己動手,豐衣足食”的傳統


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-992033/,如需轉載,請註明出處,否則將追究法律責任。

相關文章