用「易於改編」原則,提高編程水平,寫出更高質量的代碼

不管新手仍是資深開發者都會常常問一個問題,「怎麼寫好的代碼?」,要知道怎麼寫好代碼,首先咱們要知道怎麼樣纔是好的代碼。要有明確的目標,才能知道如何達成目標。在《程序員修煉之道》中提到的「ETC Principle」 -- 易於改編原則。這個原則看似簡單,可是咱們越是深刻思考越是以爲「簡約而不簡單」。php

這篇文章裏會詳細解刨在實際產品研發中「易於改編」的緣由和怎麼作到「易於改編」, 從而讓咱們編寫出更好的代碼。前端

「一」程序爲什麼須要「易於改編」?

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

只有瞭解清楚一個系統在一個生命週期中,具體什麼會推進咱們程序改變,從中咱們纔會更深入明白爲何咱們的代碼須要」易於改編「。算法

需求會變

不管咱們是研發任何系統,產品需求都是會一直變的。這個是永恆不變的命運。爲何呢?編程

  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」

服務定義:

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

理解服務

  • 假設是一個控制器,如今拿到了一個衣服對象參數,而後人擁有一個洗衣服方法
  • 如今人須要洗衣服,可是手洗效率過低了,因此咱們寫了一個多功能的洗衣機服務給到人去使用
  • 洗衣機這個服務裏面有不少不一樣洗衣服的方法,可是其實具體洗衣機裏面的每個清洗方法人是不知道怎麼實現的,人都是直接按照提供的功能直接使用。
  • 因此服務裏面的全部方法都是解耦在服務裏面,服務要提供的方法是能夠方便人使用的。

這樣說是否是很好理解了?因此最簡單的理解就是:

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


編寫思路

錯誤例子

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

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

⭕️ 正確例子

洗衣機服務實現了不少不一樣的經常使用洗衣服的模式, 好比快速清洗,毛衣清洗,地毯清洗,風乾,甩乾等等。都是一些經常使用的功能。 每一個功能方法裏面其實調用了不少洗衣機封裝好的流程和方法。因此當人使用洗衣機時,根本就不須要知道這些功能是怎麼實現的,只要知道本身要幹嗎,洗衣機恰好也有這個模式,直接用就完事兒了。

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

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

總結

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