作者 | 袁慎建
歡歡:“你看我的代碼用了策略模式和狀態模式,假如后面客戶會有這樣的需求,可以無縫擴展,多么健壯!” 清揚一臉狐疑,心中念叨了數遍 :“哼,過度設計!”,只見她欲言又止,好幾次話到嘴邊又被自己咽回去了。
這種關于設計的討論,袁帥最近一周不是第一次聽到了,就在昨天他還看到清揚和正義的一次口水仗。最近清揚有點仕途不順,幾次被結對的搭檔懟得無言以對。袁帥想為他的Buddy清揚“討回公道”,但不是直接出面幫清揚懟回去。
設計的標準在哪里?
周五下午,公司內部的敏捷工程實踐指導手冊上醒目的三條價值觀的「簡單性」讓袁帥陷入了沉思:
簡單性:我們重視剛剛夠用的設計。只為當下設計,不為未來可能出現的需求做設計。但是,我們做出的決策應當允許軟件快速變更,能夠快速響應需求變化。
「簡單性」,看起來也似乎明白在講什么,可什么是剛剛夠用的設計呢?這句話讓他想起來當年他在一次面向對象訓練營的課后跟某個學員說過的一句話:“設計猶如西紅柿炒雞蛋,鹽要恰到好處?!?這句話是如此的正確卻又無比空洞。
設計的好壞本身沒有一個標準的答案,這么多年,袁帥也在跟著軟件界各路神仙學習設計原則,仍然處在似懂非懂的狀態。他也深知每個人心中都有一桿秤。什么是好的設計?便成了公說公有理,婆說婆有理的問題,誰也難以說服誰。
這一次他不太想提那些空空如也的東西,為了能夠讓清揚快速掌握要點,他嘗試將范圍縮小到敏捷團隊程序員交付用戶故事卡時的編碼設計,避開架構設計。從變量、常量、方法、類、類與類之間的關系、對象的交互開始。
重溫舊文:簡單設計
周六,袁帥很早就鉆到書房,點燃背景音樂《稻香》,打開博客主頁,發現了一篇多年前寫的文章《簡單設計》,仔細通讀一遍之后,他覺得還不錯,可以作為入門,發給清揚閱讀,約下周一大一早去公司討論。
他花了近一個小時將文字潤色,也基于最近對設計的體會調整了部分內容,保留了文章的整體脈絡。
用具體的詞匯表達設計
抽象的設計問題大大提升了初學者的學習門檻,想得太多怕被說過度設計,吃飽撐著沒事找事。想少了,又怕被人認為能力不足,無腦編碼。到底怎么辦,怎么樣才能做出好的設計?SOLID、GoF的23種設計模式、STUPID、GRASP這些原則學會了就可以了嗎?No,統統忘掉這些抽象不接地氣的設計原則。
起步,盡量別為難自己。極限編程領域的大師程序員Kent Beck很早前就提出了4條相對容易理解的參考原則:
原則一:通過測試(信仰)
「通過測試」 通常會被一概地理解為通過自己在項目中的各種測試(自動化 + 手工),這么理解,也沒有什么問題,但是需要滿足兩個前提條件:
- 測試覆蓋率達到100%
- 所有測試都是有效的
如果你的項目中沒法滿足這兩點,當然,99%的項目是做不到的(還有1%存在傳說中)。此時你需要換一個角度去理解 通過測試。
你為什么寫測試?測試在測什么?不就是為了增強你對系統功能是否滿足了業務需求的信心嗎?所以「通過測試 」廣義理解為要滿足業務需求,不論是自動化測試還是手工測試,你需要做的是滿足業務需求,只不過我們提倡盡可能編寫足夠的自動化回歸測試。
原則二:消除重復(職責)
重復乃萬惡之源——Kent Beck 沒有說過
重復意味著低內聚、高耦合,導致的后果是難以修改(霰彈式修改),必然降低系統對變化的響應力。響應力的降低勢必會造成維護工作量的提升,我的簡單設計價值觀 一文中的 懶惰 將驅使我盡我所能消除這些重復,從而減少修改時的工作量,提升軟件的響應力。
原則三:揭示意圖(初衷)
揭示意圖,聽起來是一個不可言說的概念,怎樣表示揭示意圖了呢?對于這一條,我們很難有一個標準且完美的答案,做不到完美,但不妨礙我們努力嘗試趨近完美。
你可以在編碼過程中,不斷問自己:代碼容易理解嗎?它有沒有偏離它的初衷(業務需求)?緊接著,進一步探索這背后暴露的行為信號 -- 「解釋」:
- 新人了解了業務需求后,能夠第一時間清晰地從代碼中找到對應的代碼嗎?
- 你需要額外對一個新人解釋代碼的含義嗎?如果要,你要解釋到什么程度?
這幾個問題會讓你不斷反思你的代碼能夠體現業務初衷嗎?變量、方法以及類的命名等,你時刻都保持警惕的是:賦予它一個更加準確表達業務的名字,一個不要額外解釋的名字。從而讓讀者能夠在深入細節之前就能夠在較高層次上快速理解代碼的意圖。
原則四:最少元素(精髓)
既然說的是代碼,那么充斥在你的代碼庫中的任何東西都可以理解是元素。當然,我們還是焦點聚焦在與代碼相關的元素,比如,變量、常量、注釋、注解、關鍵字、包。
「最少元素」 的核心思想是:在不必要的時候,盡可能減少代碼元素來降低代碼復雜度,保持簡潔,貫徹less is more的思想,它道出了簡單設計的精髓。
原則五:前四條優先級依次降低(靈魂)
簡單設計前四條原則給設計決策提供了指導,在實際運用過程中,當面臨沖突時,我們如何取舍,Kent Beck也提出一個優先級:通過測試 > 消除重復 >= 揭示意圖 > 最少元素。
以上四條優先級依次降低,這就話有點類似敏捷宣言中的最后一句:也就是說,盡管右項有其價值,我們更重視左項的價值。
- 通過測試
- 消除重復
- 揭示意圖
- 最少元素
- 以上四條優先級依次降低?
優先級幫助揭開迷霧
周末過的飛快,清揚讀完了袁帥發給他的《簡單設計》,而且讀了很多遍,一腦子的疑問等著要找袁帥探討。她比往日提前了一個小時到了辦公室,只見袁帥已經在工位,一副就等她來戰的態勢。
還沒等清揚開口,袁帥遞給她四張紅色卡片,是他的手寫筆記,清揚見字跡工整,貌似她從未見過袁帥如此認真寫過字,頗感意外和感動,格外認真地閱讀起來。
“清揚,考你一個腦筋急轉彎 -- 在工作中你的領導的領導的領導的領導(4個人),當他們給你的指令有沖突時,你該聽誰的?”?!爱斎皇锹牳呒夘I導的指令??!”清揚條件反射式快速回答到。
袁帥見狀會心一笑,清揚也撓撓頭貌似明白袁帥的意思?!翱墒?,我還是......” 還沒等清揚說完,袁帥示意她靠近來看他早早準備好的代碼。
Talk is cheap
示例一:
袁帥快速出招:“這段代碼在做什么?用簡單設計框架怎么解讀?”。清揚敏捷地接招:“抽取公共方法,為了「消除重復」而違背「最少元素」?!?/p>
示例二:
“常量代替魔鬼數字,為了「揭示意圖」而違背「最少元素」?!边€沒等袁帥發問,清揚搶先回答,當然也贏了袁帥的大拇指(向上的)。
一晃就08:55了,袁帥見Jeany朝他走來,搭載著一副即將要開會的眼神,便起身準備去會議室跟她商量晚上OOBootcamp最后一次課的安排。
“喂,我還有一個疑問...” “桌上還有一張藍色卡片,你看能不能解答你的疑問?!?袁帥扭著頭得意地給清揚一個微笑,就跟Jeany進入了會議室。
清揚拿起卡片開始閱讀:
清揚很是驚訝袁帥竟然如此懂她,不愧是優秀的Buddy,一大早開啟了美好的工作節奏。她讀完卡片,繼續閱讀袁帥留給她的幾個代碼示例,15分鐘過去了,她對簡單設計算是有點體會了,拿起一張綠色的卡片認真寫下了:
- 通過測試(信仰,不可動搖)
- 消除重復(職責,盡職盡責)
- 揭示意圖(初衷,不忘初心)
- 最少元素(精髓,精煉簡潔)
- 以上四條優先級依次降低(靈魂,賦予生命)
簡單設計遐想
跟Jeany開完會,袁帥回到工位上,看到清揚留下的卡片,深感欣慰,他清楚清揚已經入門了,日后Code Review不再會被懟得無言以對,而他幫清揚“討回公道”的小心愿很快就要實現。
此時,他坐下來喝了口水,發出了感慨 -- Kent Beck 提出的簡單設計原則更多關注的是代碼設計,簡單設計思想其實也能運用在架構設計、溝通協作上。
架構設計:
- 我們應該最先考慮的是滿足業務的系統架構(通過測試:性能、穩定性等)。
- 借助DDD來合理的劃分微服務(揭示意圖:明確限界上下文)。
- 提取公共服務組件來分離關注點(消除重復:API Gateway、BFF等)。
- 最后,我們在滿足了前三點的前提下盡可能簡化系統架構中的組件(最少元素)。
溝通協作:
- 在與客戶正式場合的溝通中,我們始終應該明確溝通主題,確定目標(通過測試 )。
- 通過加強結構思考力來提升表達的結構性和清晰度,從而達到言簡意賅(消除重復,揭示意圖 )。
- 最后,我們達到了前面三點之后盡量不說多余的廢話(最少元素)。
簡單不僅如此
簡單設計五原則中,測試要確保通過(滿足需求)、重復應該被消除、元素沒必要就不要存在,這幾條看起來相對具體,而且能見字如意。但揭示意圖這樣一個每個人持有不一樣標準的概念,它代表了代碼的可理解性,可理解性的參考則要回到業務源頭,是否準確表達了業務概念。最后,優先級原則是萬萬不可忽略的,否則這個框架就失去了靈魂和生命力。
袁帥做了多年的軟件開發和培訓,他心里很清楚,那個完美的答案可能不存在。軟件開發是一種知識工作,設計又是仁者見仁智者見智,簡單設計五原則能在一定程度上幫助程序員少走彎路。
除了這些,在團隊社交活動發生探討是一個非常有效的途徑。這也是他如此重視在工作坊中引入社交活動的原因。代碼是否易讀懂,除了自我審視,還需要多幾個大腦,比如:Code Review、結對編程。