誰更穩(wěn)定?

前兩天,有人專程跑到我的文章《類與封裝》留言,說數(shù)據(jù)結(jié)構(gòu)更加抽象,更加穩(wěn)定,因而OO封裝make sense。為了證明其觀點,還專門引用了Fred Brooks在《人月神話》里的敘述:

Show me your flowcharts, and conceal your tables, and I shall continue to be mystified; show me your tables and I won't usually need your flowcharts: they'll be obvious.
-- Fred Brooks, "The Mythical Man Month", chapter 9

這位朋友的觀點其實并不鮮見:我已經(jīng)見過太多的反OO碼農(nóng),以此為證據(jù),來支持其“OO無用,封裝無用,對數(shù)據(jù)結(jié)構(gòu)的直接依賴更好...”諸如此類的結(jié)論。而那位朋友更是在回復(fù)中對眾人呼吁:作為程序員,不要學(xué)什么OOSOLID……

而每次聽到這類宗教戰(zhàn)爭般的言論,我都會一邊苦笑,一邊心里嘀咕:這些人真正負(fù)責(zé)過復(fù)雜一點的系統(tǒng)開發(fā),交付和維護(hù)么?

事實上,強(qiáng)調(diào)數(shù)據(jù)結(jié)構(gòu)算法更重要的觀點,我還可以舉出更多。

比如Linus Torvalds一封郵件里所表達(dá)的觀點:

(*) I will, in fact, claim that the difference between a bad programmer
and a good one is whether he considers his code or his data structures
more important. Bad programmers worry about the code. Good programmers
worry about data structures and their relationships.

再比如,在《The Art of Unix Programming》里,也表達(dá)了類似的觀點:

Rule of Representation: Fold knowledge into data so program logic can be stupid and robust.

Even the simplest procedural logic is hard for humans to verify, but quite complex data structures are fairly easy to model and reason about. To see this, compare the expressiveness and explanatory power of a diagram of (say) a fifty-node pointer tree with a flowchart of a fifty-line program. Or, compare an array initializer expressing a conversion table with an equivalent switch statement. The difference in transparency and clarity is dramatic. See Rob Pike's Rule 5.

Data is more tractable than program logic. It follows that where you see a choice between complexity in data structures and complexity in code, choose the former. More: in evolving a design, you should actively seek ways to shift complexity from code to data.

The Unix community did not originate this insight, but a lot of Unix code displays its influence. The C language's facility at manipulating pointers, in particular, has encouraged the use of dynamically-modified reference structures at all levels of coding from the kernel upward. Simple pointer chases in such structures frequently do duties that implementations in other languages would instead have to embody in more elaborate procedures.

如果愿意,我還可以舉出更多。

我從未懷疑過這些觀點的正確性,而這也正是我的觀點。

首先,所有這些被引用的觀點,其實都是在強(qiáng)調(diào):數(shù)據(jù)的清晰性

任何一個有經(jīng)驗,有sense的程序員,都會承認(rèn)數(shù)據(jù)結(jié)構(gòu)的重要性。一個良好定義的數(shù)據(jù)結(jié)構(gòu)以及它們之間的關(guān)系,往往比算法更清晰。

這是因為,一個良好的數(shù)據(jù)結(jié)構(gòu)定義所要表達(dá)的概念,以及概念之間的關(guān)系,是一種高度結(jié)構(gòu)化的信息。而我們?nèi)祟惖拇竽X,最善于理解的就是這類信息。相對于不那么結(jié)構(gòu)化的,代表算法的流程圖實體關(guān)系圖所需的智商指數(shù)要低的多。

而反過來,有了這些高度結(jié)構(gòu)化的數(shù)據(jù)結(jié)構(gòu)之后,我們就更容易推理和理解圍繞這些數(shù)據(jù)結(jié)構(gòu)的算法。

這也正是我們在分析一個業(yè)務(wù)領(lǐng)域,建立其領(lǐng)域模型時,靜態(tài)視圖:包含(概念實體)以及類與類之間的關(guān)系,對于理解一個領(lǐng)域至關(guān)重要的原因。

領(lǐng)域?qū)ο竽P?><div   id=領(lǐng)域?qū)ο竽P?/div>

當(dāng)然,這一切,都是建立在一個良好的領(lǐng)域分析基礎(chǔ)上的。作為咨詢師,我見過太多團(tuán)隊,根本不重視數(shù)據(jù)結(jié)構(gòu)的定義,完全不考慮數(shù)據(jù)結(jié)構(gòu)所代表的概念,也不考慮數(shù)據(jù)的內(nèi)聚性,只見到系統(tǒng)堆滿了隨機(jī)而凌亂的數(shù)據(jù)結(jié)構(gòu),從而讓系統(tǒng)極難理解。

這也正是為何Linus強(qiáng)調(diào):糟糕的程序員更關(guān)注代碼(算法);而優(yōu)秀的程序員更關(guān)注數(shù)據(jù)結(jié)構(gòu)和它們之間的關(guān)系

數(shù)據(jù)結(jié)構(gòu)定義的重要性,怎么強(qiáng)調(diào)都不為過。

數(shù)據(jù)結(jié)構(gòu),相對于算法,不僅更清晰,在大多數(shù)情況下甚至?xí)?strong>穩(wěn)定。

首先,我們先看一個簡單的例子。如下代碼定義了一個數(shù)據(jù)結(jié)構(gòu)Rectangle

struct Rectangle
{
    unsigned int height;
    unsigned int width;
};

不難發(fā)現(xiàn),這個用來表現(xiàn)矩形的數(shù)據(jù)結(jié)構(gòu),是非常清晰的。

而圍繞它的算法,相對于它的數(shù)據(jù),卻更加不穩(wěn)定。比如,現(xiàn)在某個需求需要求它的周長,因而,我們需要提供一個算法:

unsigned int calcPerimeter(Rectangle* rect)
{
    return (rect->height + rect->width) * 2;
}

當(dāng)然,也可以將算法實現(xiàn)為:

unsigned int calcPerimeter(Rectangle* rect)
{
    return rect->height * 2 + rect->width * 2;
}

或者:

unsigned int calcPerimeter(Rectangle* rect)
{
    return rect->height + rect->height + rect->width + rect->width;
}

不難看出,對于同一個需求,我們可以基于同一個數(shù)據(jù)結(jié)構(gòu),給出不同的算法實現(xiàn)。因而,在這個例子中,數(shù)據(jù)結(jié)構(gòu)算法更清晰,也更穩(wěn)定。

但這是否就意味著,封裝對于Rectangle就沒有意義?

對于一個軟件系統(tǒng),單純的數(shù)據(jù)結(jié)構(gòu)是沒有太多意義的(除非它只是一個數(shù)據(jù)展現(xiàn)系統(tǒng))。數(shù)據(jù)結(jié)構(gòu)和算法,都是為客戶的根本需要而服務(wù)。沒有客戶的需要,則數(shù)據(jù)結(jié)構(gòu)和算法,無論誰更清晰,更穩(wěn)定,都沒有任何意義。一個數(shù)據(jù)結(jié)構(gòu)該怎么定義,一個算法該如何設(shè)計,這一切都是從客戶的需要出發(fā),結(jié)合各種約束,程序員作出的選擇而已。

比如,同樣都是矩形,如果現(xiàn)在我們正在做的是一個畫圖系統(tǒng),則其數(shù)據(jù)并不必然使用widthheight來表示,這時候,使用坐標(biāo)位置,或向量來表示矩形,會是更合理的選擇。

因而,盡管在不同領(lǐng)域里,有可能都能挖掘出相同的領(lǐng)域概念,以及相同的領(lǐng)域概念間關(guān)系。但其具體數(shù)據(jù)(屬性),卻會伴隨著不同領(lǐng)域的需求不同而不同。

另外,即便在同一個領(lǐng)域,對于同樣的業(yè)務(wù)需求,當(dāng)定義數(shù)據(jù)結(jié)構(gòu)時,往往也會由于性能,空間,便利性等非功能性需求和設(shè)計約束,而作出不同的決定。比如,同樣都是1..N的關(guān)系,我究竟該選擇Array還是List?如果選擇List,改選單向,還是雙向?對于每個有經(jīng)驗的C,C++開發(fā)者,這都是做一個真實系統(tǒng)時經(jīng)常需要考慮的問題。

我們已經(jīng)知道,Linus極其重視數(shù)據(jù)結(jié)構(gòu)的定義,如果我們?nèi)タ?code>Linux Kernel的設(shè)計,就能知道,其數(shù)據(jù)結(jié)構(gòu)的選擇,和數(shù)據(jù)間的關(guān)聯(lián)選擇,會多大程度上受到非功能因素的影響。否則,那些數(shù)據(jù)結(jié)構(gòu)的定義會更加清晰,穩(wěn)定和簡單。

但你無論如何選擇,最終都是為了滿足客戶的業(yè)務(wù)需要。

回到我們的Rectangle。從需求出發(fā),我們的系統(tǒng)存在Rectangle這個概念,那么客戶需要這個概念的真正原因是什么?是Rectangle數(shù)據(jù)結(jié)構(gòu),還是calcPerimeter內(nèi)部的算法

答案是:都不是

客戶真正需要,也真正依賴的是API: unsigned int calcPerimeter(Rectangle* rect),而不是Rectangle數(shù)據(jù)結(jié)構(gòu),更不是calcPerimeter算法實現(xiàn)

雖然數(shù)據(jù)結(jié)構(gòu)算法實現(xiàn)更穩(wěn)定,但它再穩(wěn)定,相對于API,也依然只是一種實現(xiàn)細(xì)節(jié)

而讓客戶向著更穩(wěn)定的方向依賴(參見《變化驅(qū)動:正交設(shè)計》),從而依賴API,而不是直接依賴數(shù)據(jù)結(jié)構(gòu),這就是封裝的核心價值。

封裝
封裝

而如果不進(jìn)行封裝,客戶擁有訪問數(shù)據(jù),并定義算法的自由,就會讓客戶同時依賴數(shù)據(jù)結(jié)構(gòu)和算法。無論你認(rèn)為數(shù)據(jù)結(jié)構(gòu)更不穩(wěn)定,還是算法更不穩(wěn)定,總之都會讓用戶直接依賴在不穩(wěn)定的事物上。同時,在大產(chǎn)品下,極易造成重復(fù),這會進(jìn)一步導(dǎo)致更嚴(yán)重的耦合(見《類與封裝》)。

當(dāng)數(shù)據(jù)結(jié)構(gòu)算法還在爭論誰更抽象,更穩(wěn)定時,API笑了。

而最最重要的部分,在Grady Booch著名的《面向?qū)ο蠓治雠c設(shè)計》中,對OOP定義的第一個要點則是:

利用對象作為面向?qū)ο缶幊痰幕具壿嫎?gòu)建塊,而不是利用算法

這與把Procedure看做Building Block面向過程范式,把Function看做Building Block函數(shù)式范式相比,如果我們認(rèn)為數(shù)據(jù)結(jié)構(gòu)算法更穩(wěn)定是一個事實,那么毫無疑問,面向?qū)ο?/strong>才是更加尊重這個事實的范式。

因而,從數(shù)據(jù)結(jié)構(gòu)算法更穩(wěn)定出發(fā),不僅不應(yīng)該得到OO無用的結(jié)論,而應(yīng)該恰恰相反:OO是在已有的范式中,最符合軟件問題本質(zhì)的選擇。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 文章作者:Tyan博客:noahsnail.com | CSDN | 簡書 翻譯論文匯總:https://gith...
    SnailTyan閱讀 10,032評論 0 8
  • 常用關(guān)鍵字。 以上是java中的常用關(guān)鍵字,最低要求眼熟。 標(biāo)識符 1. 標(biāo)識符可以由字母、數(shù)字、下劃線(_)、美...
    Sunshine_YL閱讀 269評論 0 0
  • 這是一篇總結(jié)帖,歡迎朋友們補(bǔ)充~ 1.病從口入,禍從口出。 2.路遙知馬力,日久見人心。 3.地低成海,人低成王。...
    仙人掌cactus閱讀 1,172評論 1 5
  • 感恩大自然的恩典,春光無限,氣候適宜,充滿了生命的暖意 感恩合伙人夫妻兩的信任,讓一切成為最好的安排 感恩老公關(guān)鍵...
    祺予閱讀 149評論 1 3
  • Dispatch Queues的生成可以有這幾種方式: 1.dispatch_queue_tqueue=dispa...
    Ryan_RH閱讀 387評論 0 0