天天看點

精益程式設計:Write Lean Programs

oo makes code understandable by encapsulating moving parting, but fp makes code understandable by minimizing moving parts. -michael feathers
精益程式設計:Write Lean Programs

需求1:在倉庫中查找所有顔色為紅色的産品

指令式(imperative)

缺乏編譯時類型安全性檢查

實作類型

寫死

重複設計

需求2:在倉庫中查找所有顔色為綠色的産品

<code>copy-paste</code>是大部分程式員最容易犯的毛病,為此引入了大量的重複代碼。

為了消滅<code>hard code</code>和重複代碼,得到可重用的代碼,可以引入簡單的參數化設計。

需求3:查找所有重量小于10的所有産品

大部分程式員依然會使用<code>copy-paste</code>解決這個問題,拒絕<code>copy-paste</code>的陋習,最具實效的一個辦法就是把<code>copy-paste</code>的快捷鍵失效,當每次嘗試<code>copy-paste</code>時提醒自己做更好的設計。

為了消除兩者重複的代碼,通過簡單的參數化往往不能完美解決這類問題,相反會引入額外的複雜度。

日常工作中這樣的實作手法非常普遍,函數的參數清單随着需求增加不斷增加,函數邏輯承擔的職責越來越多,邏輯也變得越來越難以控制。

為此需要抽取出隐藏的概念,使其周遊的算法與查找的标準能夠獨立地變化,将行為參數化。

此刻<code>findproducts</code>的算法邏輯得到封閉。

通過可複用的<code>functor</code>來封裝各種變化,讓變化的因素控制在最小的範圍内。

使用者的接口也變得簡單多了,而且富有表現力。

精益程式設計:Write Lean Programs

這是經典的<code>oo</code>設計,如果熟悉設計模式的讀者對此已經習以為常了。設計模式是好東西,但常常被人依葫蘆畫瓢,死闆照抄,甚至被濫用。事實上,引入或去除設計模式是一個很自然的過程。與大師們交流,問究此處為何引入設計模式,得到的答案:直覺。忘記所有設計模式吧,管它是不是模式,如果設計是簡單的,它這就是模式。

至此,代碼另外還有一個明顯的壞味道,<code>colorspec</code>和<code>belowweightspec</code>都需要繼承<code>productspec</code>,都需要定義一個構造函數和一個私有的字段,并重寫<code>satisfy</code>方法,這是一種典型的重複現象:重複型結構。

因<code>java</code>缺乏閉包的支援,程式員不得不承受這樣的煩惱,但此刻暫時不關心,繼續前進。

需求4:查找所有顔色為紅色或者綠色,并且重量小于10的産品

按照既有的代碼結構,往往易于設計出類似<code>colorandbelowweightspec</code>的實作。

存在兩個明顯的壞味道:

類名中包含<code>and</code>往往是違背單一職責的信号燈

<code>colorandbelowweightspec</code>的實作與<code>colorspec</code>,<code>belowweightspec</code>之間存在明顯的重複

此刻,需要尋找更本質的抽象來表達設計,引入<code>and/or</code>的語義模型。

composite spec: andspec, orspec

atomic spec:colorspec, beblowweightspec

精益程式設計:Write Lean Programs

可以通過<code>andspec</code>組合<code>colorspec, belowweightspec</code>來實作需求,簡單漂亮,并且富有表達力。

此時設計存在兩個嚴重的壞味道:

<code>andspec</code>與<code>orspec</code>存在明顯的代碼重複

大堆的<code>new</code>讓人眼花缭亂

先嘗試消除<code>andspec</code>與<code>orspec</code>存在的代碼重複,<code>oo</code>設計的第一個直覺就是通過抽取基類。

通過參數化配置,複用<code>combinablespec</code>的實作。

如何評判<code>boolean</code>接口的使用呢?在不損傷可了解性的前提下,為了消除重複的設計是值得推薦的。<code>boolean</code>接口的可了解性關鍵依賴于調用點與函數接口之間的距離,如果在同一個檔案,同一個類,并能在一個頁面顯示的,是完全可以接受的。

需求5:查找所有顔色為不是紅色的産品

<code>notspec</code>是一種修飾了的<code>productspec</code>,同時也使得使用者的接口也變得更加人性化了。

精益程式設計:Write Lean Programs

之前遺留了一個問題,一大堆眼花缭亂的<code>new</code>使得代碼失去了部分的可讀性。

可以引入<code>dsl</code>改善程式的可讀性,讓代碼更具表達力。

上述的dsl可以使用<code>static factory</code>的設計手段簡單實作。按照慣例,可以建立類似于<code>productspecs</code>的工具類,将這些工廠方法搬遷到工具類中去。

接口與對應工具類的對稱性設計在<code>java</code>社群中應用非常廣泛,例如标準庫中的<code>java.util.collection/java.util.collections</code>的設計。

此外,使用匿名内部類,可以得到意外的驚喜。通過有限地引入閉包的概念,進而避免了類似firth attempt/sixth attempt的設計中引入多餘的構造函數和成員變量的複雜度,進而消除了部分的結構性重複的壞味道。

當然,要讓這些<code>static factory</code>可見,需要<code>import static</code>導入這些方法。

使用<code>java8</code>可以将這些工廠方法直接搬遷到<code>productspec</code>的接口中去,這樣做至少得到兩個好處。

可以删除<code>productspecs</code>的工具類

使的接口和靜态方法(尤其靜态工廠方法)關系更加緊密

<code>java8</code>并沒有因為<code>comparing</code>等靜态工廠方法的增強而建立<code>comparators</code>的工具類,而是直接将它們內建在<code>comparator</code>的接口中,這是自<code>java8</code>之後思維的一個新的轉變(<code>comparator.comparing</code>的實作留作作業鞏固今天所學知識)。

對于本例,可以将<code>productspecs</code>删除,将所有靜态工廠方法搬遷到<code>productspec</code>中去。

需求6:無條件過濾掉或不過濾查找所有産品

至此,<code>productspec</code>存在如下一些類型:

composite specs: and, or

decorator specs: not

atomic specs: always, color, beblowweight

<code>java8</code>可以使用<code>lambda</code>表達式改善設計,增強表達力。

通過類型推演,可以進一步省略<code>labmda</code>表達式中參數的類型資訊。

當然,你可以通過提取<code>static factory</code>,構造dsl複用這些<code>lambda</code>表達式。

其中,<code>@functionalinterface</code>注解标注了<code>productspec</code>是一個函數式接口,其抽象方法<code>boolean satisfy(product p)</code>的原型描述了<code>lambda</code>表達式的<code>function descriptor</code>。

遺留了一個問題: 如何替換匿名内部類,使用<code>lambda</code>實作 <code>and/or/not/always</code>的語義?

這裡引入了<code>java8</code>一個重要的設計工具:<code>default method</code>,簡單漂亮,并巧妙地實作<code>dsl</code>的設計,使用者接口變得更加流暢、友好。

<code>java8</code>支援<code>default method</code>,擴充了<code>interface</code>原來的語義,進而隐式地支援了組合式設計,使的<code>oo</code>的設計更加完善和強大。

需求7:查找所有僞劣的産品

可以使用<code>method reference</code>進一步改善<code>lambda</code>的表達力。

泛化類型資訊,讓算法更具有通用性,并進一步增強代碼的可複用性。

這樣的實作存在一個明顯的問題:泛型參數缺乏型變的能力。通過對泛型參數實施無限定類型通配符的修飾,進而使的算法實作更加具有彈性和通用性。

<code>and, or, not, always</code>在代數系統中具有穩定的抽象,為此需要進一步重構,以便最大化代碼的可複用性。這樣當需要建立諸如<code>numberspec, fruitspec</code>時無需重複地再寫一遍<code>and, or, not, always</code>的實作。

為此,建立更為抽象的<code>predicate</code>的概念,并将通用的、抽象的<code>negate, and, or, always</code>搬遷到<code>predicate</code>中去,使其具有更大的可複用性。

同時,将領域内的<code>color, belowweight</code>等原子放回<code>productspecs</code>工具類中去(因為不存在<code>productspec</code>的接口了),讓領域内的<code>lambda</code>表達式具有更大的複用性。

至此,可複用的基礎設施便從領域中剝離出來,使其具有更高度的可重用性。

<code>java8</code>可以使用集合庫的<code>stream</code>複用代碼。

如果要支援并發,則可以建構<code>parallelstream</code>。

集合類通過<code>stream, parallelstream</code>工廠方法建立<code>stream</code>之後,其操作可分為<code>2</code>種基本類型:

transformation:其傳回值為<code>stream</code>類型

action:其傳回值不是<code>stream</code>類型

通過<code>stream</code>的機制,實作了集合類的惰性求值,直至<code>action</code>才真正地開始執行計算。<code>transformation</code>從某種意義上,可以看成是<code>stream</code>的<code>builder</code>,直至<code>action</code>啟動執行。

<code>scala</code>語言是一門跨越<code>oo</code>和<code>fp</code>的一個混血兒,可以友善地與<code>java</code>進行互操作。在<code>scala</code>中,函數作為一等公民,使用<code>lambda</code>是一個很自然的過程。當你熟悉了<code>scala</code>,我相信你絕對會放棄<code>java</code>,放棄<code>java8</code>,猶如作者本人一樣。

遺留了三個問題:

如何複用<code>lambda</code>表達式?

如何實作 <code>and/or/not</code>的語義?

如何實作 <code>always</code>的語義?

引入靜态工廠方法及其操作符重載的機制構造内部<code>dsl</code>。

如何替換實作<code>???</code>,并讓其具有<code>&amp;&amp;, ||, !</code>的語義呢?

<code>predicate</code>一個擴充匿名函數<code>a =&gt; boolean</code>的子類,其中,從面向對象的角度看,<code>a =&gt; boolean</code>的類型為<code>function[a, boolean]</code>。

其中<code>!</code>是一個一進制操作符。

<code>always</code>靜态工廠方法,可以搬遷到<code>predicate</code>的伴生對象中去。

<code>predicate</code>的設計既使用了<code>oo</code>的特性,又引入了<code>fp</code>的思維,<code>scala</code>使其兩者如此和諧、完美,簡直不可思議。

世界是多樣性的,計算機工業也不僅僅隻存在一種方法論。在我的哲學觀裡,oo和fp之間并不沖突,而是一個和諧的,互相補充的統一體。

除了c++語言之外,使得我最偏愛scala,多範式,一個問題存在多種解決方案等等思維習慣,給了程式員最靈活、最自由的空間。

以标準庫<code>collections.sort</code>,及其<code>comparator</code>在<code>java8</code>中的增強,及其<code>comparator.comparing</code>的泛型定義複習今天所學知識。

使用匿名内部類是<code>collectins.sort</code>最經典的使用方法之一。

可以通過<code>lambda</code>表達式替代匿名内部類,簡化設計。

通過類型推演,但依然得到編譯器類型安全的保護。

通過<code>comprator.compring</code>的靜态工廠方法,改善表達力。

通過<code>function reference</code>的機制,進一步改善表達力。

其中,<code>comprator.compring</code>的實作為: