前言
很久沒有寫心得,一來是懶了,二來是難,早就很想寫一點有關業務邏輯和模式、演算法的關系,可以找到很多理論,但卻很少有理論實際相結合的文章,以至于許多人認為服務端代碼就是CRUD,演算法無用,設計模式無用,還有一種人他們認為設計模式已經融入自己日常的開發中,不需要特別去在意,這兩種情況我都經歷過,在寫了這么多年代碼后才意識到,自己思考的還是太淺,這篇文章就算是拋磚引玉吧,
抽象
抽象是從眾多的事物中抽取出共同的、本質性的特征,而舍棄其非本質的特征的程序,
在編碼作業程序中,我們或多或少、有意無意的會進行一些抽象,其中最常見的,就是將事物轉為變數,一個簡單的變數就可以完成大量的抽象,對于一個書店,設計一個“book”變數,就可以代表所有的書,我們換一下它的名字改為“goods”,它就能代表任何可以銷售的商品,而不僅僅是書,這一切看起來非常簡單,太簡單了,那我們怎么會在最后要重構代碼,技術迭代趕不上業務發展,業務越來越好,代碼越寫越苦?
本質
在寫代碼的時候,尤其是互聯網常見的服務端業務代碼,通常是將現實行為做數字化的程序,例如在超市結算的時候,交錢,拿貨走人,在淘寶上要復現這個行為,就得將錢和商品數字化,可以被代碼表達,那么,萬一有人要插隊怎么辦?萬一網銀突然無法支付怎么辦?萬一商品中有贈品怎么判斷,有活動商品怎么判斷,臨時有商品因為碼掃不出(類似缺貨)怎么辦,全部都需要考慮,而這些現實中可能發生的問題,在線上也幾乎都會發生,代碼就要考慮,
無窮大的問題
那么理論上有多少問題需要考慮呢?這讓我回想起第一節概率論的課上,老師問的一個問題:2個人約好10點見面,10點同時到達的概率是多少?因為到達的情況是無窮大,所以同時到達概率是0,這個問題也一樣,現實中有無窮多種問題需要考慮,而這正是我們重構噩夢的開始,看過《銀河系漫游指南》的同學應該知道有個“沉思”的超級電腦,它為了計算出宇宙的究極問題,設計了“地球”這個更加強大的電腦,事情就是這樣,你要用程式表達這個世界,你需要的資源就是這個世界本身,除非你能從更高的緯度空間來設計,
妥協
既然我們不可能表達完整的世界,我們就需要想辦法去表達部分,
骨架
現代科學可以根據恐龍的骨骼化石來估計肌肉的走向和大小,從而還原它的外形,我們不能表達整個世界,但可以想辦法表達它的骨架,選擇性的表達是最常見的手段,它的重點在于:骨架要在它準確的位置上,不要因為肌肉的缺失而偏離,例如錢,無論在數字錢包的時代還是紙幣的時代,它在生產生活中的價值是不變的,我們就認為我們找對了骨架:價值,
我們可以用 y = ax + b表達任意一條實數域內的直線,但是現實中沒有哪條線是直的,但”直“這件事作為骨架是對的,
程式就是這個世界的“骨架”,
邊界
很多問題在有限的邊界內是是有限的,比如人際關系,在一代直系血親中,只會出現父母和子女,但是擴展到整個人類社會,會出現父親的叔叔,父親的叔叔的爺爺,父親的叔叔的爺爺的老婆的娘家,,,通過邊界約束問題范圍,正是很多DDD設計和微服務架構建設的目的,只不過微服務是一種“死緩”的做法,而DDD只是用于幫助思考,不直接解決問題,為什么說微服務是死緩?很多微服務在一開始是“微”服務,隨著業務復雜,就變成“腫服務”,拆分?不好意思,業務等不起,
邊界的合理性
當我們給問題畫上了邊界,怎么才知道邊界是合理的?合理的邊界應該是:
- 邊界內元素可以被完整描述,也就是全集,
- 邊界內元素是互斥的,
完整的描述有2種方法
列舉
比如:邊界 = 性別,值 = [男,女,男改女,女改男,男改女又改男...],每次提到性別這個屬性,總有那么幾個壞小子要懟我,雖然不是惡意,但是也說明這個邊界有點問題,
邊界改 = 當前性別,值 = [男,女],
看,通過“當前”2個字修飾的性別,就好多了,
使用公式
之前直線上的點已經說明了這種方法,而公式描述又可以通過分段函式、取樣函式進一步將邊界合理化,比如年齡,只有正整數,
互斥
被描述的內容在邊界的約束下,元素是唯一的,例如年齡,1歲和2歲就是互斥,如果要描述一群人的年齡,往往會有同歲的,那么邊界約束就要增加一個:某人,人在人類現實社會的邊界內,是不會重復的,而一個人的當前年齡只有一個,所以一群人中某個人的年齡是唯一的,年齡 + 人 共同約束了一個邊界范圍,就像分段函式有多個約束條件,你可以看到,慢慢的我們發現在使用數學方法來描述問題,而這些數學方法的可以通過“演算法”最終實作,這就是演算法的作用,
妥協后的抽象
有點經驗的研發很快就會發現,上面的例子反應在關系型資料庫中,就是唯一索引和復合唯一索引,這沒什么稀奇的,接下來我要舉一個栗子來說它到底怎么毀了我們幸福的coding生活,有一個專案,要研究不同的食草動物吃什么植物,研發團隊根據OOP思想,抽象了動物,動物行為,植物,又把植物和動物都抽象為“生物”以描述共性,完美,開始寫代碼:
動物->吃(植物);//return 要死 or 健康;
好了,一切都很正常,微生物要吃什么,食肉動物要怎么吃都沒有問題,還有點擴展性,不錯,
同時,在隔壁的一個部門,接了另一個需求,他們要研究植物,以及他們可以作為那些動物的食物,于是他們寫了另一種代碼:
植物->被(動物)->吃();//return 可以 or 不可以;
老板覺得兩個部門做的有點重復,你們合并吧,反正都是吃,為啥要寫兩個?兩邊研發團隊當晚打了一架,要求對方放棄自己的方案,最終動物研究部門因為體力比較好,植物研究部門放棄了自己的方案,第二天老板走進辦公室說:我們今天研究一種植物叫豬籠草,它吃動物,,,動物部門一咬牙,行,我還有辦法,凡是生物它都有吃,他們可以相互吃,,,
新的抽象
生物->吃(生物);//return 可以 or 不可以;
到這里,我們才看到了真正的骨架,研發團隊發現之前的邊界都是有缺陷的,動物、植物都只是生物圈的一部分,這還沒囊括微生物,重構問題的發生,往往是我們沒有找到骨架,沒有摸清邊界,在我們研發的程序當中,太多人喜歡寫“動物->吃(植物);”這樣的代碼,很簡單,也滿足業務當前需求,如果抽象到“生物->吃(生物);//return 可以 or 不可以;”便可能被戴上“過度設計”的帽子,久而久之,大家就怕了,
沿著骨架重構是容易的
如果你在紙上畫了一條短一點的直線,如果需要一條長直線,延長便可,這很容易,如果一開始畫的是弧線,要得到一條長的直線,你就得擦干凈重畫(重構),或者換個地方(重寫),以生物行為研究為例,如果一開始就把吃的行為賦予“生物”這個基礎類,然后只實作動物的“吃”,而不關心植物的吃,比如豬籠草,這就降低了研發壓力,后來出現豬籠草的需求,在植物類中覆寫掉吃方法,植物就有不同的吃法了,微生物也一樣,被吃是一個道理,食物鏈頂端也會被微生物吃掉,同樣可以放在生物類中,這樣我們的代碼就是循序漸進的,可不斷疊加,
設計模式
終于說到設計模式,幾乎所有的設計模式,都在幫助我們解決豬籠草問題,只是方式不同罷了,但是他們不能解決邊界劃分和骨架尋找問題,這就是為什么很多人覺得,用了設計模式,擴展性也不是很好,那么說DDD,DDD其實是在提醒你,你要去尋找邊界和骨架,它也不是告訴你如何去找,之前有個研究DDD的大牛來分享怎么劃分邊界,有一個簡化版工序,大概有19道吧,反正我是記不得,總之不是一件簡單的事情,
總結
“業務“分析來得到抽象,分析的方法就是畫邊界和抽骨架的程序,方法我也就那么點都寫在上面了,
而”抽象“需要數學來描述,”數學”需要演算法來編碼,”編碼“需要模式來避免重構,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/254370.html
標籤:其他
