反模式:神仙大类和黄金大锤
數學中有正數和負數
物理學有『物質』和『反物質』的存在
武俠小說中有九陽神功也有九陰真經
生活中有婚姻也有出軌
......
事物總是充滿這種相互矛盾而統一的有趣現象。
對于GoF提出的23種設計模式,是否也有反模式呢?答案是顯而易見的。
一個設計模式在特定的場合下是積極并且顯現優勢的,但是在偏離最佳適合場景下,它本身就會轉變為了一個反模式,從而導致不良的影響,就像現實世界中沒有所謂的純粹的好人或者壞人一樣。
反模式
Andrew Koenig在1995年首創了anti-pattern這個詞,三年后anti-pattern因《AntiPatterns》這本書而獲得普及,而它的使用也從軟件設計領域逐漸擴展到了日常的社會互動中。
反模式其實體現的是一種積極反思的行為,通過對不斷出現,糟糕透頂的解決方案反思之后的深刻總結。讓我們能夠從錯誤或者失敗中學習提高,避免犯相同的或者類似的問題,提升效率。理解了反模式,有助于我們在實際工作中預防或者改正它們。
有一篇文章,講到用了5種設計模式來完成一個Hello Word的打印,使用關鍵字「write Hello Word with design patterns」谷歌一下就能找到(請使用正確的姿勢訪問谷歌),甚是有趣,然而有趣的背后反思,卻是『為了設計模式而設計模式』,成了一種典型的反模式「為了XX而XX」。
從圖書網站上,也可以搜索到一些反模式的書籍,比如測試反模式,SQL反模式,Python反模式等,連最近很火的微服務也有了反模式,這些書中都總結了一些最差實踐,讓我們學習并避免之。
反模式有很多種,我這里想給大家介紹其中的兩種,分別是神仙大類和黃金大錘,我這里把他們比喻成倚天劍和屠龍刀,意思是這種大殺器一般要謹慎使用,因為威力太大,用得不好的話,就極有可能傷及自身,或者沒有傷到自身,傷到邊上無辜的花花草草也是不好的。
神仙大類
神仙大類,又稱God Class, Blob Class等,就是一個類擁有太多屬性和方法(比如超過20個),它能處理的事務涉及方方面面(比如員工類,涉及工資計算、稅務計算、入職離職、數據庫讀寫、請假報銷等等),它所占用的代碼行數從數百到上萬行。
「神仙大類」是KISS原則和SRP原則的反模式。
我自己雖然未曾有幸見識過上萬行的大類(倒是聽說過不少),但是確實親自見證過超過2000行的只有一個main函數的大類。
針對這種大類,我是該獻上我的膝蓋還是我的口水呢,我想剛開始是膝蓋,因為不覺明歷,后來鐵定就是WTF口水了,因為我不幸接手了并負責維護它,搞得每次只要一改它,在上線的時候我都會心驚膽戰,晚上睡不好覺。
當然,對于一錘子買賣,不再需要維護的代碼,或者需求絕對不會變更的代碼,這種神仙大類就讓它逍遙去吧。
唯一的不足之處就是,不能為下一個項目提供可復用的單元,只能看著年齡在增長,技能和效率卻沒有什么提升了。
普通程序員只是年復一年地完成日常的業務需求,沒什么代碼復用可言,好的程序員卻可以在完成日常業務開發的同時,不斷地總結并豐富自己的代碼工具箱,代碼復用率很高,真正需要寫的業務代碼也寫得非常少,有時候只需要做一下配置,就可以完成類似的事情。
有一種聲音,就是神仙大類的擁躉說,你看,我一個大功能就一個大類搞定了,一個大類就一個文件,如果按照你的那套所謂的SRP/KISS,至少要不下20個類的小文件了,我不也算符合簡單化原則了啊,一個文件還不算簡單嘛。
乍一聽,我竟無言以對,如果你一個類文件里面也是分了各種層次,做了各種不同抽象設計的話,好像也不無道理。只是,這種情況下,一個文件里面那么多功能,如果想重用其中的一個,咋個辦呢,是不是得把整個大類照單全收,還是把要重用的那個小函數拷貝一份出來?
只是同一段代碼一旦重復拷貝,就違反了DRY(Don’t Repeat Yourself )干燥不慘水原則,而被 WET(Write Everything Twice)濕漉漉反模式給恨恨地砸臉了。
神仙大類,本質上就是一個『集大成』的大胖子,在這個以瘦為美以減肥為時尚的今天,確實不受歡迎,感情好的時候說胖子是潛力股還親切地喊小胖胖,感情破裂了轉口就罵人家死胖子了。
你雖然不能像林丹那樣擁有擁有8塊完美腹肌,但是你可以讓你的代碼做到啊,只需要遠離神仙大類,或者使用『人擋殺人佛擋殺佛』的重構「拆」字訣把遇到的神仙大類就地拆成大約8個各司其職的小類就可以了。
黃金大錘
黃金大錘,Golden Hammer,指使用相同的工具、產品或技術,解決幾乎所有的問題。
如果你只有一個關系型數據庫,那么任何問題都看上去是其中的一張關系表。或者我們學習了設計模式,然后就開始肆無忌憚地到處用設計模式,就連最簡單的打印一個Hello World的入門程序也都能用上幾個設計模式的話,那就是把設計模式當成黃金大錘了。
有一種『面向接口編程』濫用的反模式,就是『一個服務一個接口』。這種常見的就是所有的服務類都有一個所謂的XxxService及XxxServiceImpl,前者是一個接口,后者是對應的惟一實現。
問題的關鍵在于,這種XxxService和XxxServiceImpl竟然一一對應,也就是說一個XxxService其實只有一個XxxServiceImpl與其對應。
這如果不是對『面向接口編程』的一種曲解與濫用,那就是『夸夸其談的未來性(Speculative Generality)』的代碼壞味。
『接口』在面向對象的設計中,是屬于抽象層面的東西,那什么時候需要抽象呢?一定是兩種及以上事物擁有共同的一些特征時,才能形成抽象(自底向上),或者從高層定義一些抽象特征,由兩種或以上事物來體現這個特州,這種抽象才有意義(自頂向下)。
比如光喊我一個人吃飯,你根本不需要抽象,喊我的名字我就來了(我的思路是,吃飯不積極肯定有問題?),但是我跟很多男的在一起的時候,你喊『IT男們,走啰』,我們就一起過來了。
那么這種『IT男們』就是一種抽象,只當有兩個以上的實體的時候,這種抽象才有必要和更有意義。
在數據類型使用方面往往也有類似的錘子問題。比如涉及到List的統統都是ArrayList,涉及到Map那就都是HashMap了,其它類型那就統統String,好像其它的都不存在了一樣,這就相當于把String, ArrayList和HashMap當成了處理全部數據類型的黃金大錘了。
我曾見識過一個根據電話號碼段查找歸屬地的實現,可以說是堪稱經典。
原始實現是,將數據庫包含起始號碼、結束號碼以及歸屬地市幾個字段的表中所有記錄,按起始號碼排序后一次性地讀入到程序內存中,然后每次查找特定號碼的歸屬地時,在數據結構中順序查找比對,使用的數據結構是ArrayList<HashMap<String, String>>的模式。
這個在數據量較小時,好像不是什么問題,因此也在線上歡快地運行了那么若干年。直到表記錄達到十萬多條時,經常出現程序加載緩慢導致發布經常性失敗,或者查找歸屬地十分緩慢的性能問題時,深藏了那么多年的實現問題才浮出水面。
有一個反模式叫做『過早優化』,還有一個兄弟反模式叫做『過晚優化』。這個案例,不只是『黃金大錘』的反模式,還是『過晚優化』的反模式。
這個實現我后來Review了一下,首先是十多萬條記錄有太多零星的單個號碼成一個號段的記錄,實際上可以歸并到三萬多條記錄左右,其次是電話號碼不要使用String轉用Long來表示,再次是可以使用TreeMap來完成快速定位查找而不是List順序查找,具體實現可以參考https://gist.github.com/bingoohuang/5916691。
在閱讀代碼時,還經常會看到這種函數的入參和返回的類型都是Map,OMG感覺就是一個黑洞,沒有任何語義,只知道是一個大麻袋,里面是什么,深不可測。
比如下面這個activeVcher函數,短短幾行代碼中里面就出現了4處Map,我想寫這個代碼的人一定是丐幫中的四袋弟子。
當然這也怪我當初剛入道JAVA時留下的一個『債務』,沒想到6年以后,債務依然還在向前滾動。
當年我還不知道代碼可讀性,更不知道POJO的意義,以及語義化的涵義,只是感覺Map好靈活好喜歡,只是今日回頭一看,竟然有種在濃濃的霧霾中看不清前路的感覺,看到Map好懵逼,渾然不覺里面到底兜了些什么,套句流行的話說『霧是String的濃,霾是Map的厚』。
總結
入了設計模式的門,又摸了一下反模式的皮毛,到此我們終于大概摸清了模式這頭大象比較完整的輪廓了,大家可以在嘗試騎象遠行了。
在日常工作中,我們也可以嘗試把一些常見的寫法進行歸納總結,好的命名成XXX模式,不好的就命名成YYY反模式。只要堅持不懈,最終我們可以超越模式本身,進一步提升認識,跨越到『碼可碼、非常碼,道可道、非常道』的境界之中了。
總結
以上是生活随笔為你收集整理的反模式:神仙大类和黄金大锤的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux通俗图解
- 下一篇: 一位 83 岁独立开发者教会我的道理