正確的編程姿勢(shì)

最近兩個(gè)星期,我使用 plantuml (貝爾實(shí)驗(yàn)室出品了一個(gè)超級(jí)繪圖工具 graphviz, 這是一個(gè)包裝版)把我的繪圖項(xiàng)目做了一次全面的接口和類的可視化。使用了很多設(shè)計(jì)模式,包括:橋接、裝飾器、生成器、抽象工廠。繪制完后,圖像是很美的,接口之間的交互和參數(shù)定義清晰優(yōu)雅。很漂亮!

然并卵!

這個(gè)項(xiàng)目在開(kāi)發(fā)之處已經(jīng)違反了我的一些感覺(jué),對(duì)于程序設(shè)計(jì)的感覺(jué)。從我對(duì)數(shù)據(jù)庫(kù)和服務(wù)器的多年經(jīng)驗(yàn),使用基于數(shù)據(jù)表和數(shù)據(jù)解釋的抽象結(jié)構(gòu),你總能獲得最簡(jiǎn)單易用可擴(kuò)展的軟件結(jié)構(gòu)。

不過(guò),這個(gè)繪圖項(xiàng)目真的很復(fù)雜,涉及了很多的多態(tài)和關(guān)聯(lián)。比如,在一個(gè)長(zhǎng)的列表中存儲(chǔ)種類不同的圖形,這些圖形存儲(chǔ)的繪圖數(shù)據(jù)和相關(guān)信息都不同,我需要把這些數(shù)據(jù)視做同一種類型,然后迭代它們,選出需要的一個(gè)并且使用它的相關(guān)信息。所以,我嘗試使用學(xué)術(shù)界的設(shè)計(jì)模式來(lái)解決其中的問(wèn)題。

當(dāng)項(xiàng)目變得很龐大的時(shí)候,我意識(shí)到設(shè)計(jì)模式屁都不是。諸如橋接、裝飾器以及其他,都是建立在一種假設(shè),假設(shè)你的父組件和子組件總是可以忽略對(duì)方的細(xì)節(jié),而可以統(tǒng)一的處理它們。比如,面包有奶油味、抹茶味、水果味,面包又有低檔材料、高檔材料,那么你可以把味道和材料分為兩個(gè)不同的接口,然后各自抽象,并且組合這兩個(gè)接口生成更豐富的面包,比如低檔材料的抹茶味面包。但是,真實(shí)的編程世界中,這樣的理想狀態(tài)非常少。在真實(shí)的編程世界中,面包還想要更多的東西,比如奶油味的有糖,抹茶味的沒(méi)有糖,有糖的面包放在左邊柜臺(tái)上,沒(méi)有糖的面包放在右邊柜臺(tái)上??吹搅税桑瑥?fù)雜度升級(jí)了,柜臺(tái)跟面包有沒(méi)有糖是綁定的。這意味著,如果你想像前面那樣抽象兩個(gè)接口---味道和材料,那你現(xiàn)在必須考慮柜臺(tái)。因?yàn)榈蜋n材料的抹茶味面包是沒(méi)有糖的,放在右邊柜臺(tái)。現(xiàn)在,你不得不抽象出味道和柜臺(tái)的關(guān)系。在上面的接口之上再增加一層。每當(dāng)你的需求復(fù)雜一點(diǎn),這種層就會(huì)升級(jí)。比如,紅糖面包和白糖面包。

總之,就算設(shè)計(jì)模式避免了類繼承的爆炸,但是也避免不了抽象層級(jí)的復(fù)雜。

所以,我覺(jué)得我又不會(huì)編程了。于是,我盡可能的重新思考這些設(shè)計(jì),并且重新在網(wǎng)絡(luò)上搜尋曾經(jīng)支持我的設(shè)計(jì)論調(diào):面向數(shù)據(jù)結(jié)構(gòu)編程而不是對(duì)象。如果不是為了這個(gè)繪圖項(xiàng)目,我絕對(duì)不會(huì)冒險(xiǎn)再一次使用設(shè)計(jì)模式和面向?qū)ο蟆?/p>

我當(dāng)然搜到了一大堆 Linus 排斥面向?qū)ο蠛?C++ Java 的話語(yǔ),從感覺(jué)上,這些就是我面臨設(shè)計(jì)困難時(shí)候的感覺(jué)。我曾經(jīng)無(wú)數(shù)次這樣解決我的程序設(shè)計(jì)。

git的設(shè)計(jì)其實(shí)非常的簡(jiǎn)單,它的數(shù)據(jù)結(jié)構(gòu)很穩(wěn)定,并且有豐富的文檔描述。事實(shí)上,我非常的贊同應(yīng)該圍繞我們的數(shù)據(jù)結(jié)構(gòu)來(lái)設(shè)計(jì)代碼,而不是依據(jù)其它的,我認(rèn)為這也是git之所以成功的原因之一。[...] 依我的觀點(diǎn),好程序員和爛程序員之間的差別就在于他們認(rèn)為是代碼更重要還是數(shù)據(jù)結(jié)構(gòu)更重要。

在龐大的項(xiàng)目中,人們對(duì)不是自己開(kāi)發(fā)的模塊并不了解,能快速理解其他模塊中函數(shù)的確切含義才能提高開(kāi)發(fā)效率。而C++引入的各種抽象則使代碼非常依賴上下文,想理解一段代碼,需要看多得多的上下文。

面向?qū)ο笳Z(yǔ)言以對(duì)象為核心,加一些相關(guān)聯(lián)的方法,簡(jiǎn)直是囈語(yǔ)。重要的東西應(yīng)該是數(shù)據(jù)結(jié)構(gòu),對(duì)象本身有啥重要?真正有意思的,是在不同類型的不同對(duì)象交互而且有鎖規(guī)則的時(shí)候。但是,即使是這時(shí)候,封裝什么“對(duì)象接口”也絕對(duì)錯(cuò)誤,因?yàn)椴辉偈菃我粚?duì)象的問(wèn)題了。

有趣的是,這里有一篇另外一位前輩的很早的文字,推在 Google+ 上,來(lái)自 Unix 核心創(chuàng)建者之一 Rob Pike:

原文鏈接
A few years ago I saw this page: http://www.csis.pace.edu/~bergin/patterns/ppoop.html

Local discussion focused on figuring out whether this was a joke or not. For a while, we felt it had to be even though we knew it wasn't. Today I'm willing to admit the authors believe what is written there. They are sincere.

But... I'd call myself a hacker, at least in their terminology, yet my solution isn't there. Just search a small table! No objects required. Trivial design, easy to extend, and cleaner than anything they present. Their "hacker solution" is clumsy and verbose. Everything else on this page seems either crazy or willfully obtuse. The lesson drawn at the end feels like misguided epistemology, not technological insight.

It has become clear that OO zealots are afraid of data. They prefer statements or constructors to initialized tables. They won't write table-driven tests. Why is this? What mindset makes a multilevel type hierarchy with layered abstractions better than searching a three-line table? I once heard someone say he felt his job was to remove all while loops from everyone's code, replacing them with object stuff. Wat?

But there's good news. The era of hierarchy-driven, keyword-heavy, colored-ribbons-in-your-textook orthodoxy seems past its peak. More people are talking about composition being a better design principle than inheritance. And there are even some willing to point at the naked emperor; see http://prog21.dadgum.com/156.html for example. There are others. Or perhaps it's just that the old guard is reasserting itself.

Object-oriented programming, whose essence is nothing more than programming using data with associated behaviors, is a powerful idea. It truly is. But it's not always the best idea. And it is not well served by the epistemology heaped upon it.

Sometimes data is just data and functions are just functions.

--- Rob Pike (One of the Unix creators (Ken Thompson, Dennis M. Ritche, and Rob Pike))

幾年前我看到了這個(gè)網(wǎng)頁(yè): http://www.csis.pace.edu/~bergin/patterns/ppoop.html

我真的不知道這篇文章到底是不是在搞笑。讀了一下,我雖然很想說(shuō)這不是一篇搞笑的文章,但是,拜托,它根本就是。讓我來(lái)跟你們講講他們?cè)诟阈κ裁窗伞?/p>

e...按照他們的話語(yǔ),我應(yīng)該稱自己為 hacker (黑客),不管我不關(guān)心這些。Hello! 你只需要一個(gè)小的不能再小的 table ! 根本不需要什么對(duì)象。樸素平凡,容易擴(kuò)展,容易清除,(比起他們的那種設(shè)計(jì))多 TM 簡(jiǎn)單。他們的 “hacker solution” 真的是又蠢又笨。他們寫(xiě)出來(lái)的那堆東西到處透漏著瘋狂和愚蠢。他們?nèi)狈夹g(shù)認(rèn)知。

很顯然,OO 的狂熱者們害怕數(shù)據(jù)。他們喜歡用語(yǔ)句或者構(gòu)造器來(lái)初始化 tables 。他們根本不寫(xiě) table-driven 的測(cè)試。Why is this? 得有多大的心才會(huì)選擇用多級(jí)并且多層的類抽象,而不去用一個(gè)小小的三行 table ? 我曾經(jīng)聽(tīng)說(shuō)有人用各種 OO 的東西替換掉 while 循環(huán)。

不過(guò)好消息是,hierarchy-driven, keyword-heavy, colored-ribbons-in-your-textook orthodoxy 這些東東快到頭了。更多的人選擇組合而不是繼承。有些人已經(jīng)重新開(kāi)始認(rèn)識(shí) OO。

面向?qū)ο缶幊陶Z(yǔ)言,其本意是使用數(shù)據(jù)和相關(guān)的行為進(jìn)行編程,這是一個(gè)很好的想法。事實(shí)確實(shí)如此。但是,這個(gè)想法并不總是最好的 idea。 這個(gè)想法并沒(méi)有完全的認(rèn)知編程的世界。

Sometimes data is just data and functions are just functions.

--- Rob Pike (Unix 創(chuàng)建者之一的 (Ken Thompson, Dennis M. Ritche, and Rob Pike))

沒(méi)錯(cuò),我們需要的就是數(shù)據(jù)的抽象和數(shù)據(jù)的解釋器。用表來(lái)存儲(chǔ)你需要的各個(gè)數(shù)據(jù),對(duì)于多態(tài),C 語(yǔ)言中簡(jiǎn)單直接干凈:union。使用這么一個(gè)簡(jiǎn)單的結(jié)構(gòu),你能存儲(chǔ)各種不同的類型,而且你只需要存儲(chǔ)他們的指針,這意味著你不會(huì)浪費(fèi)多少內(nèi)存,同時(shí)你能獲得相同內(nèi)存段但是數(shù)據(jù)不同的抽象。

然后,使用一個(gè)鏈表或者數(shù)組,把這個(gè) union 裝進(jìn)去,遍歷,cast,然后使用你需要的特定數(shù)據(jù)。

很多語(yǔ)言都有 union 的變體,現(xiàn)代語(yǔ)言中的泛型就是 union 的一種語(yǔ)法糖,但是你往往忘記了這種結(jié)構(gòu)的真正價(jià)值和用意。仔細(xì)體會(huì)下這個(gè)全新的設(shè)計(jì):

enum ShapeKind {
  skLINE, skPORT, skBOARD
}

class Shape {
  kind: ShapeKind   
  value: Line | Port | Board
  contains(x: number, y: number): boolean
}

class ShapeContainer {
  shapes: Array<Shape>
  search(x: number, y: number): [ShapeKind, Shape]
}
type
  ShapeKind = enum
    skLINE, skPORT, skBOARD

  Shape = ref object
    case kind: ShapeKind
    of skLINE:
      line: Line
    of skPORT:
      port: Port
    of skBOARD:
      board: Board
    contains: (x: number, y: number): bool

  ShapeContainer = object
    shapes: seq[Shape]

proc search(c: ShapeContainer, x: number, y: number): tuple[kind: ShapeKind, shape: Shape]
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容