用“易於改編”原則,提升編程水平,寫出更好的代碼

提升編程水平,寫出更好的代碼
無論新手還是資深開發者都會經常問一個問題,“怎麼寫好的代碼?”,要知道怎麼寫好代碼,首先我們要知道怎麼樣纔是好的代碼。要有明確的目標,才能知道如何達成目標。在《程序員修煉之道》中提到的“ETC Principle” -- 易於改編原則。這個原則看似簡單,但是我們越是深入思考越是覺得“簡約而不簡單”。

這篇文章裏會詳細解刨在實際產品研發中“易於改編”的原因和怎麼做到“易於改編”, 從而讓我們編寫出更好的代碼。

三鑽分割線

「一」程序爲何需要“易於改編”?

程序爲何需要“易於改編”
爲何代碼必須要易於改編?因爲一個系統是會隨着一個產品的發展,每日有用戶增長就會有一直做不完的需求。只要公司一直在運營着這個產品,需求就會隨着公司的發展而改變。只要我們開發者一直與時並進專研新技術,我們就需要一直升級優化。

只有瞭解清楚一個系統在一個生命週期中,具體什麼會推動我們程序改變,從中我們纔會更深刻明白爲什麼我們的代碼需要”易於改編“。

需求會變

無論我們是研發任何系統,產品需求都是會一直變的。這個是永恆不變的命運。爲什麼呢?

  1. 產品方向 — 隨着產品的營銷,運營,發展會推動產品需求一直新增,修改,優化。
  2. 使用量 — 隨着產品的用戶量級,數據量級,併發量級也會推動程序的架構和策略上的變動。
  3. 技術升級優化 — 甚至是我們使用的語言,框架,依賴包等升級也會引起我們的代碼需要適應。
  4. 技術債 — 可能是因爲時間的限制,之前的代碼重於實現而質量不佳。

所以我們的代碼會隨着歲月的流逝一直在迭代升級優化。

“可快速更變”是一個軟件的核心

近幾年很多技術團隊啓用了敏捷迭代開發模式。什麼是敏捷迭代呢?

敏捷迭代就是把開發週期縮短到1-4周。小步快跑的迅速迭代交付功能上線。敏捷迭代的流程分別如下:

  1. 確定需求 - 與老闆和市場確認需求和流程
  2. 需求評審 - 與開發同頻需求裏面的功能點和業務流程
  3. 技術反講 - 開發與產品同頻需求,保證雙方理解無誤區,開發也需要評估開發難度和開發時間
  4. 研發週期 - 開發人員開始投入研發直接到功能和需求開發完畢,轉交給測試,在測試環境提測
  5. 測試周期 - 測試和開發人員開始排除缺陷,修復所有在開發過程產生的bug
  6. 驗收/預發佈週期 - 當測試在測試環境把所有bug排除掉後,當前迭代版本就會發布到預發佈環境讓市場和產品驗收功能
  7. 發佈正式 - 當驗收通過後,當前迭代版本就可以部署上線到正式環境
  8. 正式迴歸測試 - 發佈上線後,就會有正式迴歸測試,最後一道防線,保證系統加入的所有新功能都無問題
  9. 迭代總結 - 每一期迭代結束後都總結這次迭代遇到的問題,持續優化,提高效率

你想想如果一個APP或者系統,幾個月甚至一年才更新一次功能和升級。我們用起來其實很枯燥的,甚至我們會發現很多問題,還有很多功能可以便捷或者提升我們的使用體驗。但是這麼久才更新一次,我們還會對這個產品抱有希望嗎?(除了微信這種已經很成熟的應用,但是就算是微信也是有持續更新的)。

所以一個好的產品,是需要快速迭代,小步快跑的迅速迭代交付功能上線的。也是因爲這樣,功能就需要持續更新、升級和優化。自然我們研發的代碼就需要一直隨着產品的變化而改編。而且還是每1-4周就會升級優化一次。

🏆小總結一下:

  • 一個系統會隨着產品的發展和迭代,一直走在改變和更新的道路上。
  • 因爲系統一直在變,代碼就需要響應系統的變化,持續的快速迭代升級優化。
  • 既然代碼需要快速的更變和升級,那程序的“易於改編”性就必須要高。

三鑽分割線

「二」如何做到“易於改編”?

如何做到“易於改編
我們深刻懂得爲什麼系統會一直在改變,那我們就要知道怎麼寫代碼才能讓一個程序“易於改編”,然而在敏捷迭代中才能快速的響應需求的變化。如果想讓我們編寫的程序更容易的響應需求改變、業務改變和邏輯改變等,我們就要充分的給我們的程序解刨邏輯

說到邏輯與業務的分解,首先要根據需求和功能深入思考分析,然後對其進行一個架構的設計。最常用的方式就是把系統模塊化,組件化等的系統架構設計。

模塊設計 —「Modular Design」

模塊設計,就是以功能塊爲單位進行程序設計,實現其求解算法的方法稱爲模塊化。模塊化的目的是爲了降低程序複雜度,使程序設計、調試和維護等操作簡單化。

不論是前端開發還是後端開發,我們都有模塊化和組件設計模式。使用模塊設計來分解我們的功能和邏輯,目的是爲了降低程序的複雜度、利於調試、維護、修改和新增功能。

比如現在我們要做個CMS(內容管理系統),我們一起來嘗試使用模塊設計來分解這個系統的功能。


設計思路

首先我們要理解一個內容管理系統有哪些功能,然後把每個功能劃入各個模塊裏。但是很多童鞋一開始接觸一個系統,然後開始瓜分模塊會覺得無從入手,可能花了半天坐在電腦前思考🤔,但是半天都吐不出一個所以然來。接下來讓我們一起來學習一套邏輯思維,讓我們以後更輕鬆架構一套模塊設計吧!


一開始先思考這個系統的目的和使用場景,這個系統是用來做什麼的?

一個內容管理系統,一般來說都是用來發發文章,新聞,或者是一個官方網站的內容管理。那必定就有文章。那管理文章內容,需要什麼功能呢?

文章模塊 「Article 模塊」

  • 增刪查改文章
  • 文章草稿
  • 文章置頂

文章子模塊 — 分類 「Article Category 模塊」

  • 增刪查改分類
  • 文章圖片

那這些與文章相關的功能是不是可以統一放在“Article”模塊中統一管理,然後文章的模塊中還有一個文章分類的子模塊叫做“Category”


有文章了必定就需要有作者,那作者在系統中其實是一個用戶。那我們就需要有用戶模塊了。 加上一個管理系統,必定就有管理員,作者,甚至是會員。走一波這個邏輯我們就發現應該要有以下的功能點。

用戶模塊 「User 模塊」

  • 用戶增刪查改
  • 用戶身份管理
  • 用戶權限管理
  • 會員等級管理

這麼一來我們就可以建立一個單獨的User模塊。這個模塊主要是管理用戶相關的信息和功能。


看到這裏我們應該對一個系統的模塊構思有一點的概念了。這個時候產品經理過來給我們提了一個需求,“我們現在要在這個系統添加一個標籤體系,專門用來管理文章標籤的。”

那童鞋們,你們覺得這個需求應該放入那個模塊呢?🤔

你們答對了!🎉這個是屬於文章的一個子模塊,Tag模塊 — 專門管理文章的標籤,然後和每一篇文章有多對多關係的。所以標籤模塊歸納入文章模塊中。如果我們的內容管理系統做的很大,裏面有視頻內容,圖文文章等等。我們可以在一開始就把這些統一歸納入“內容模塊”,也就是Content模塊中。


前端模塊設計

說到了這裏前端的童鞋估計要舉手咯🙋‍♂️,前端的我們求關注呀!“前端是以頁面和交互爲單位,不可能和後端一樣按功能邏輯來分解模塊吧?” — 這個童鞋說的在理哈。其實前端和後端的設計上是有稍微的不一樣的。

後端會以業務邏輯來分解模塊,但是前端有頁面和數據邏輯兩塊的代碼。所以前端相對比後端就要分開兩種模塊分解思路了。

頁 (排) 面 (版) 的模塊設計

  • 前端的頁面模塊與產品定義的系統模塊會更加貼切一些。前端分解的模塊會跟用戶所看到的操作功能分組。
  • 簡單的模塊分解,可以利用產品童鞋給到我們的導航來分解,這樣會更合理的規整我們的頁面模塊。
  • 如果在頁面功能上再想細分,那就可以用組件設計來分解了。

前端邏輯模塊設計

  • 幾年前的前端就是個“切圖仔”,基本不用考慮什麼業務邏輯,數據邏輯,數據交互這些技術領域。但是因爲前後端分離現在已經變成大多數公司的研發策略。慢慢前後端都各自分攤了業務邏輯和數據交互等處理。

  • 因爲前端也有大量的業務邏輯和交互邏輯,所以在我們封裝和解耦的時候,也會遇到需要分解模塊來處理。現在最典型的例子就是在使用Vue的狀態管理Vuex的時候,需要用到模塊管理來分解邏輯,使後面維護和修改更容易。

  • 其實前端也是用後端同一套思維模式來分解業務就可以了,以功能爲單位來分解你們的模塊就可以了。


解耦 - 「Decoupling」

解耦,就是把複雜繁瑣的邏輯拆分成更小的邏輯塊。從而讓複雜的邏輯分解成小的邏輯處理,使得邏輯變得更簡化,更易於調試和維護。

在一個功能衆多、業務複雜和系統模塊繁多的系統中,每一個模塊裏面的代碼也會開始變得臃腫,越來越難調試、維護和管理。其實模塊化和解耦是一致的。模塊化也是爲了解耦你的程序。這裏我們重點講的是模塊之間和邏輯之間的解耦(Decouping)。

我分享一個經歷讓大家深刻認知到解耦的重要性。我遇到過最誇張的有一段邏輯處理寫了上5000行代碼的童鞋,然而更可怕的是,在相同功能的地方那5000行代碼被複制粘貼過來了。😱我滴乖乖,這位童鞋在研發小組中有個花名叫“複製兄”。不過得到大家的幫忙和提點下,後面他也成爲了這個小組中的一名優秀的程序員。


如果我們不懂得解耦代碼,編寫的代碼會給我們後面帶來很重的“技術債”。假設一下,你的5000行處理邏輯,在上數十個地方使用了。我們要改一下這段邏輯就難過登天了。就算是這段邏輯沒有複用性,但當你需要回頭去修改這段邏輯也是會讓你頭皮發麻,無從入手。修改一點這個邏輯都可能會導致出現10個bug的後果。

我們深刻知道解耦的重要性,那麼我們應該怎麼去高效解耦代碼呢?

在《程序員修煉之道》中的 Design by Contract 裏提到我們編寫“害羞”的代碼是很有益處的。“害羞”有兩個含義:“不要把自己暴露給別人”和“不要與過多的人相互影響”。 這個是什麼意思?我們用書中的例子來理解一下。

在一個龐大的間諜組織中,特工們會分到各個小組,每個小組內部的特工基本都互相認識,但是各個小組之間的特工就都互不相識。假設某個特工被俘虜了,一個小組可能會被摧毀,但是其他小組的特工是不會被暴露被影響的。因爲各個小組之間的關係都是絕對隔離的。但是在任務中,各個小組之間都是會有合作和互相幫助,但是都互不相識。所以這麼龐大的間諜組織才能長期安全存活下來。

這個種隔離模式用在編程中是非常好的。把我們的代碼解耦到相對獨立的模塊和方法中,讓它們之間的關聯性和影響性降到最低。如果一個模塊或者邏輯方法出了問題,我們可以獨立重構或者修復,而不會給其他模塊帶來巨大的影響。只要最終的結果是一致的,就可以完美優化升級或者修復了。

在程序中,我們需要一個Service (服務)給我們處理一個Object(對象),或者請求一個服務獲得一個Object,我們希望這個服務給到我們需要的結果,但是不需要我們去操心它是怎麼處理與獲得這個Object的。這個服務或者方法是獨立運行的,裏面的邏輯和代碼是與我們寫的代碼絕對隔離的。我們只需要在獲得結果的時候驗證這個結果的可用性就可以了,如果結果與我們需要的不一致,那我們就可以拋出錯誤。只要這個服務做對應的修正,就可以繼續運行了。

理論我們解說的差不多了,現在我們來個實戰例子吧:

案例:
假設現在我們需要寫一個獲取天氣預報數據的類,獲取天氣預報數據首先你需要提供Geolocation 定位信息參數。Geolocation對象中含有一個地址對象。裏面有經緯度,省市區等數據。我們需要獲取到地址中的經緯度才能得到精準定點的天氣預告信息。我們的代碼會這麼寫:

/**
* 獲取天氣方法
*/
public function getWeather(Geolocation $geolocation) {
	// 假設我們已經封裝了一個獲取定位的天氣的方法叫getWeatherByGeo()
	return $this->getWeatherByGeo($geolocation->getLocation()->getLat());
}
  • 我們通過getLocation方法獲取到定位對象裏面的地址對象
  • 然後通過getLat()方法獲取到定位地址的經緯度信息

以上例子中,因爲我們需要在geolocation對象中取到經緯度,所以我們需要先經過獲取地址對象,然後再通過這個對象獲取到經緯度。其實這裏面有不需要的關聯關係。無論是寫服務,還是寫對象方法,我們都不要讓使用這個服務/對象的開發者去過度的理解和使用你關聯性很強的內部方法。這樣會導致如果我們那天改變了這個關聯性,多處都需要修改代碼。

如果那天劉某改了Geolocation對象,裏面不再含有Location對象,而且也沒有了getLocation()方法,經緯度可以直接在Geolocation對象中直接取得。這個時候所有之前運用這個對象的其他人都需要修改代碼了。很多時候開發者很難修改代碼,或者一改動就會傷筋動骨的,其實就是因爲這種過多過度的關聯性關係導致而爲的。

所以作爲Geolocation對象的封裝者,我們應該直接給到一個方法getLat(),讓調用這個對象的開發者直接能拿到所需要的信息:

/**
* 獲取天氣方法
*/
public function getWeather(Geolocation $geolocation) {
	// 假設我們已經封裝了一個獲取定位的天氣的方法叫getWeatherByGeo()
	return $this->getWeatherByGeo($geolocation->getLat());
}

這樣就剪斷了剛剛對象中的強關聯關係的缺陷。


服務化 — 「Service」

服務定義:

角色:服務是系統架構裏面的業務處理層。
作用:主要是爲了高度解耦和封裝不同場景的業務和功能到對應的服務,然而達到高度中心化的業務代碼。

理解服務

  • 假設是一個控制器,現在拿到了一個衣服對象參數,然後人擁有一個洗衣服方法
  • 現在人需要洗衣服,但是手洗效率太低了,所以我們寫了一個多功能的洗衣機服務給到人去使用
  • 洗衣機這個服務裏面有很多不同洗衣服的方法,但是其實具體洗衣機裏面的每一個清洗方法人是不知道怎麼實現的,人都是直接按照提供的功能直接使用。
  • 所以服務裏面的所有方法都是解耦在服務裏面,服務要提供的方法是可以方便人使用的。

這樣說是不是很好理解了?所以最簡單的理解就是:

服務是用來封裝業務邏輯代碼,是一個獨立的邏輯層,高度封裝解耦後提供給控制器或者其他需要用到這個服務的地方使用的。


編寫思路

錯誤例子

把所有洗衣機的方法提供給人使用,那就等同於讓人來決定所有洗衣機的參數和清洗步驟。當人放衣服到洗衣機後,要選擇先加水,加多少水,然後清洗開始,清洗多久,再甩乾等等。

光想想,洗個衣服還那麼多的選項,還要想怎麼樣的洗衣順序纔是正確的! 我太難了!洗個雞腿哦!(ノ`□ ´)ノ⌒┻━┻

⭕️ 正確例子

洗衣機服務實現了很多不同的常用洗衣服的模式, 比如快速清洗,毛衣清洗,地毯清洗,風乾,甩乾等等。都是一些常用的功能。
每個功能方法裏面其實調用了很多洗衣機封裝好的流程和方法。所以當人使用洗衣機時,根本就不需要知道這些功能是怎麼實現的,只要知道自己要幹嘛,洗衣機剛好也有這個模式,直接用就完事兒了。

(✧ᗜ✧)👍哇! 介麼人性化的麼!這種洗衣機給我來一打謝謝!

我寫過一篇詳細關於編寫服務的文章《你真的懂怎麼寫服務層嗎?》,有興趣的童鞋可以前往查看哦。這裏我就不詳細解說了。

三鑽分割線

總結

學懂編程第一法則助你寫出更好的代碼
這篇文章已經到達尾聲了,到了這裏我們已經深刻知道何爲易於改編原則,更懂得如何編寫易於改編的代碼。其實在開發的過程中,我們還是需要先思考,後設計,再編寫。根據所拿到的的功能需求,做好程序的架構設計,從而寫出易於改編的程序。只有這樣我們編寫的代碼才能越來越好,走上技術巔峯!

推薦閱讀

推薦閱讀以下幾篇文章,可以讓助你成爲出色的開發者。

  • 《5大法則助你 成爲更出色的開發者》 — 這篇文章傳授5大法則助我們成爲更出色的開發者,在衆多開發者中脫穎而出的訣竅,也會在我們的技術職業生涯中給我們很多的幫助。
  • 《如何高效學習編程》 — 編程確實不是一件容易的事情,除了要有較強的邏輯思維,還需要花大量的時間和集中力來提升或者維持一定的高度。
  • 《你真的懂怎麼寫服務層嗎?》 — 其實很多系統架構裏面都有服務層,但是服務對很多開發人員來說都有很多不同的定義和寫法。甚至在我待過的公司裏都有不同的寫法和編寫模式。每個人每個團隊每個項目都有對服務不同的理解。那到底什麼是服務,怎麼理解纔是對的呢?

三鑽分割線
和你一起終身學習

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章