OOBootcamp,全稱是Object-oriented Bootcamp,就是面向對象訓練營的意思,在12月份和1月份,我們整個項目組一些senior一些的同事一起接受了OOBootcamp的培訓,有這個培訓呢,還多虧了訓杰找駿總爭取到了這個機會,Tech leader做得好,關心組員的技術成長,哈哈。講師是袁慎建老師,intellij 的快捷鍵玩的出神入化,技術實力沒得說,又會說話還又會寫文章,關鍵是人還長得特別帥,這就讓人有點嫉妒了,附上他的簡書地址,歡迎大家前去騷擾. ??
前些天這次OOBootcamp圓滿結束,完結撒花??。領到了一張小獎狀
當然還有一些附帶的訓戰計劃,是需要在實際項目中去落實所學到的知識。所以之后還會有一些在實際項目中實踐這次學到的知識之后的感悟,好,廢話不多說了,下面就來回顧總結一下這次的OOBootcamp.
這次OOBootcamp, 主要分了三大塊的內容,一個是SOLID原則, Simple Design原則,貧血充血模型,二是TDD, 三是重構,主要用了Parking Lot 這個編程實例來給我們做練習,學以致用,先用例子實現一遍,再之后在真實項目中再操練一遍,幾板斧下來,保準你映像深刻,又有自己的體悟。
SOLID 原則 (面向對象的基石)
SOLID原則,我自己就不多現丑了,附上一篇袁老師的博文,供大家參考,雖然主要講的是里氏替換原則,但是其他原則附帶也有講,網上一搜索也是一大把。我下面就講一下我在OOBootcamp結束后對SOLID原則的理解,就不會講很多細節了。
先復制一波袁老師的一段文字??
SOLID由五大原則構成:
- Single Responsibility Principle【單一職責原則】
- Open Close Principle【開閉原則】
- Liskov Substitution Principle【里氏替換原則】
- Interface Segregation Principle【接口隔離原則】
-
Dependency Inversion Principle【依賴倒置原則】
并且請記住一句話,所有的原則其實都是為開閉原則服務的
注:袁老師說,你們知道了這幾個原則后,就把它忘掉,不過,我可能還沒到這種無招勝有招的境界,所以需要詳細記錄一下??
1. 單一職責原則
我理解的單一職責原則很簡單, 就是一個類,或者一個方法,它就只做一件事情,當然這句話說了等于沒說,你可能會說我知道單一職責是什么意思,但還是不會啊。對,我也不會,沒人敢說他會。因為單一職責的難點就在于這個一件事,到底是怎么個一件事,或者說怎么分。大到組裝一臺車,小到造一個螺絲釘,它都是一件事。怎么分?
其實這是一個很難的問題,這個問題是沒有一個標準的答案的,它跟你的業務相關,跟你具體的實踐相關。不是一個精準的度量而是一個感官度量。唯有以面向對象的方式多多練習,才能找到那個感覺。以我的感受來說,當你覺得你找出的那一件事情,分無可分,不存在二義性,那么我認為這就是單一職責的一件事。
2. 開閉原則
開閉原則的定義是對擴展開放,對修改關閉。這個真的咋一看云里霧里,要理解這個原則,我覺得我們首先得把這句話的作用域縮小一點,就針對java 面向對象來說,其實應該說的是,擴展功能可以增加新的類,但應該盡量避免修改已有的類。其實還是有點不好理解哈,也不知道怎么做,我覺得這時候可以去看下設計模式,全部的設計模式都遵循了開閉原則,簡直是最好的開閉原則示范。
3. 里氏替換原則
墻裂推薦袁老師的這篇博文 讓里氏替換原則為你效力,寫得很好,簡直寫到我心坎里去了。
我這里就只說說里氏替換原則的定義,任何基類可以出現的地方,子類一定可以出現,如果你覆寫了父類的方法,那么其實你是違背里氏替換原則的。至于為什么,都在袁老師的博文里,歡迎大家去圍觀,哈哈。
4. 接口隔離原則
接口隔離原則的定義是 客戶端不應該依賴它不需要的接口;類間的依賴關系應該建立在最小的接口上
這個原則,看定義就很好理解了,其實我認為它是對單一職責的一種補充,因為如果你單一職責做的非常好,那么你其實會很少碰到違背接口隔離的情況的,反之,如果真的出現了,那么一定是你單一職責做的還是不夠好。
5. 依賴倒置原則
這個原則的定義 是程序要依賴于抽象接口,不要依賴于具體實現。簡單的說就是要求對抽象進行編程,不要對實現進行編程,這樣就降低了耦合
這個其實也是有點繞的,面向抽象編程怎么就降低了耦合呢?其實我覺得還是很好理解的,比如你吃一個蘋果,如果寫實現,你寫一個蘋果類就完事兒了,但是如果面向抽象你需要定義一個蘋果接口,然后我吃這個接口就好,不關心是什么蘋果,然后再寫蘋果的實現,但是后面還有需求,說要吃一個美國的蘋果,因為美國蘋果又大又圓,面向實現的做法是,我改一下我的那個蘋果類改成美國蘋果就完了,面向抽象的做法是,我在寫一個類,美國蘋果類實現蘋果接口,其實對比兩種方式,你就能知道誰好誰壞。
Simple Design(簡單設計)
簡單設計原則 我還是推薦大家去讀袁老師的文章 簡單設計落地三板斧 其中鏈接了 簡單設計的價值觀和 簡單設計原則,哈哈,都是他寫的,十分高產。
簡單設計原則在我看來核心就是 保持簡單。對于程序開發來說,就是保持程序簡單,不要去過度設計,我們寫程序的時候常常會不由自主的為未來去考慮,比如一個好的程序員,他會有這樣個習慣,他不會單單只完成自己手上的工作,他會去思考未來會有什么需求進來,于是這些思考會常常體現在代碼上,形成過度設計。而如果你認同敏捷價值觀,那么這其實并不是一個好的習慣,因為你思考的需求,其實有時候并不會實際產生,于是代碼憑空復雜了很多,并且有一部分還是無用代碼。
Simple Design 就是需要你去認同這樣的價值觀 保持程序簡單,不要過度設計,你的程序實現應該是剛剛好滿足你現有的需求的。
那么怎么去落地Simple Design呢?請看袁老師講的Simple Design落地三板斧。TDD, 重構,clean code, 哈哈
培訓的時候映像比較深的是這樣一幅圖
這個是說的在實踐簡單設計過程中,當面臨沖突時,我們如何取舍,最重要的是通過所有測試,其次是消除重復和解釋意圖,優先級最低的是減少元素,當有沖突時,優先級越高的我們越應該關注。
貧血充血模型
期間由訓杰給我們分享了貧血和充血模型,現在一般Java 項目多數都會采用spring全家桶,而spring所推崇的mvc結構是一種典型的貧血模型,于是迅杰給我們分享了貧血模型和充血模型的區別(好處和壞處),以及充血模型的架構應該是怎么樣的
不了解貧血模型和充血模型的, 可以看看這篇文章 貧血模型與充血模型的對比
以下是摘抄自上面那篇文章
貧血模型的好處是:
1、每個貧血對象職責單一,所以模塊解藕程度很高,有利于錯誤的隔離。
2、非常重要的是,這種模型非常適合于軟件外包和大規模軟件團隊的協作。每個編程個體只需要負責單一職責的小對象模塊編寫,不會互相影響。
貧血模型的壞處是:
1、由于對象狀態和行為分離,所以一個完整的業務邏輯的描述不能夠在一個類當中完成,而是一組互相協作的類共同完成的。因此可復用的顆粒度比較 小,代碼量膨脹的很厲害,最重要的是業務邏輯的描述能力比較差,一個稍微復雜的業務邏輯,就需要太多類和太多代碼去表達(針對我們假定的這個簡單的工時管 理系統的業務邏輯實現,ruby使用了50行代碼,但Java至少要上千行代碼)。
2、對象協作依賴于外部容器的組裝,因此裸寫代碼是不可能的了,必須借助于外部的IoC容器。
對于Ruby來說,更加適合充血模型。因為ruby語言的表達能力非常強大,現在用ruby做企業應用的DSL是一個很熱門的領域,DSL說白了就是用來描述某個行業業務邏輯的專用語言。
充血模型的好處是:
1、對象自洽程度很高,表達能力很強,因此非常適合于復雜的企業業務邏輯的實現,以及可復用程度比較高。
2、不必依賴外部容器的組裝,所以RoR沒有IoC的概念。
充血模型的壞處是:
1、對象高度自洽的結果是不利于大規模團隊分工協作。一個編程個體至少要完成一個完整業務邏輯的功能。對于單個完整業務邏輯,無法再細分下去了。
2、隨著業務邏輯的變動,領域模型可能會處于比較頻繁的變動狀態中,領域模型不夠穩定也會帶來web層代碼頻繁變動。
TDD
上面的理論講完了之后,就是實踐了,用TDD 這個工具來實現一個比較經典的題目ParkingLot Management,在回顧TDD之前我要先回顧一下Tasking 思維管理工具
Tasking
Tasking 是一種思維管理工具, 它要求你對于一個比較復雜的問題,列出它的每一個完整子路徑,這樣進行Task分解之后,復雜的問題就變成了一個個簡單的容易實現的子問題了
Tasking 的經典格式是 Given When Then 格式,一個典型停車小弟的需求Tasking分解的例子是
Given: 我管理一個停車場,停車場有空位
When: 用戶委托我停一輛車
Then: 停車成功
可以看到,Tasking要求你分解出的Task要盡量的簡單并且可測試
一個錯誤的例子是
Given: 我管理N個停車場,停車場N個空位
When: 用戶委托我停N輛車
Then: 停車成功
這個Task其實是很難去測試的,N是多少?測試里面你難道要窮舉嗎?怎么窮舉?
TDD
TDD 測試驅動開發,是極限編程列出的12個團隊實踐之一,是其中重要的組成部分,TDD的具體理論和一些實踐,在網上都有,我這里只講我練寫TDD的感受,首先 TDD 是一個工具,是工具就有適用場景的,也就是說,不是所有的軟件開發過程都適合使用TDD,其次它是由三個單詞組成
- Task Driven Development
- Test Driven Development
- Test Driven Design
三個詞中都出現了Driven,所以,TDD的核心其實是那三個D, 而且從2,3兩個詞可以看出,TDD是必須Test First的
其實在課程開始之前,我對TDD的理解很粗淺,僅僅限于先寫測試后寫實現(當然,其實這樣理解也沒什么毛病),在實踐中也幾乎沒有去實踐,我們總是習慣于先寫實現,寫完之后再回過頭來補測試. 其實有時候就會有一種想法,覺得測試是一個可有可無的東西,你寫了更好,沒寫程序也不會出啥大的問題。就像是客戶要求我們測試覆蓋率90%以上,寫測試就是為了完成客戶的需求。
但是,TDD要求你重視測試,測試是一等公民, 你得先寫測試再去寫實現,你的測試得能表現你的業務,讓別人一看你的測試,就知道你的業務是啥,這就倒了過來,變成了實現是小兒子,哈哈
因為在TDD看來,好的能完整表現業務的測試,至少不會讓你漏了邊邊角角,而且這樣寫下來,理想情況下,測試的覆蓋率應該是百分之一百的. 你會對你寫的代碼無比有信心,因為在需求范圍內,你可以宣稱你寫的代碼是百分之百的滿足需求。
課程開始的時候,我們對TDD要求的 Test First是什么,是很明確的,就是先寫測試后寫實現,Task Driven Development 這個也是比較好理解的,但是后兩個確難到我們了,測試怎么去驅動開發,怎么去驅動你的設計,你的設計是測試驅動出來的嗎?以至于我們花了兩節課的時間去討論這些問題。下面我附上,我們討論的結論
- 我們寫測試之前,腦海里是有提前設計的,沒有提前設計,你測試都寫不了更不用說寫實現。
- 測試并不能驅動你的設計,只會讓你發現壞的設計(壞的設計測試很難寫)
下面是我們TDD的實踐步驟:
跟客戶確認當前階段需求,確認到什么程度呢?你覺得你能開始列Task了的時候,之后隨時保持與客戶的緊密溝通,需求有任何問題找客戶溝通
選擇視角(就是選擇看待問題的視角,比如是用戶視角還是停車場管理員視角),運用Tasking思維管理工具 列Task,Task是以業務場景為單位的,每一個Task都是要有交付價值,何為交付價值,就是客戶拿來能用的功能(能跑起來的程序), 代碼寫了一半,跑都跑不起來的,沒有任何價值. 先列簡單場景的Task 后列復雜場景的.
-
根據Task列表,從前到后一個一個的實現,由于我們是采取Pair的編程方式,所以是一個人寫測試,然后另一人寫實現,之后輪換
提前設計注意點:- 寫Task之前,是要有提前設計的
- 提前設計必須根據你當前已經做完和正要做的Task做提前設計,禁止根據全部的Task做提前設計
寫測試的注意點: - 測試必須能表現你當前Task的業務
- 抽象關系可以體現在測試里
寫實現的注意點: - 實現的代碼是基于你的提前設計的 (不要做重復的無用功)
- 實現的代碼要剛剛好滿足你的業務,何為剛剛好?就是刪除任意一行你的代碼,你的測試就會掛掉, 反之,就不是剛剛好
- 看到不爽的代碼就重構,什么是不爽的代碼?請看 clean code 這本書
全部Task完成之后就是向客戶交付產品了,由于我們的客戶是袁老師,所以給他review了代碼,保證所有的測試是綠的
TDD 這個東西,如果你單看字面,不去實踐,是無法領會它的精髓的,我覺得初學者如果要去實踐TDD, 可以不用去考慮它的后兩個單詞,只看Test First,先實踐一下Test First, 然后再慢慢去加上后兩個Driven,最終你會達到那種得心應手的境界,當然還可能是放棄,哈哈
據說如果熟練使用了TDD, 開發效率會比不使用TDD提升很多,這個我暫時也還沒體會到,看來是實踐的少了,之后會找機會多實踐TDD
重構
重構我就不多介紹了(要介紹也只能貼貼各種代碼示例),請大家移步 重構 這本書
我就只說說我的感受
- 當你看到不爽的點的時候,你就要重構你的代碼
- 不爽的點應該是違背團隊的代碼約定的,而不是違背你自己的小癖好
- 最好通讀一遍 clean code, 這樣你說別人代碼不好的時候也不至于詞窮(可以指著書本罵他??)
- 光看重構這本書,其實沒多大用處,你看過就會忘,你必須結合它來一場說寫就寫的實踐, clean code也一樣
- clean code, 重構這兩本書,請常備在桌面上,說不準啥時候就會用到,無論是指著書本罵人,還是適當的裝一下逼??
總結
OOBootcamp 培訓給我的進步還是很大的,以前我雖說是用著面向對象的語言,確寫著云里霧里,而這次培訓下來,結合理論和實踐,我深刻理解了面相對象的基本原則,簡單設計原則,以及Tasking TDD等做法,而且也使我對敏捷有了更深刻的認知,確實是猶如醍醐灌頂,受用無窮。我相信這些知識最終也能帶著我飛得更高。