為什麼SOLID原則仍然是現代軟體架構的基礎?- StackOverflow

banq發表於2021-11-22

儘管自構思 SOLID 原則以來的 20 年來計算髮生了很大變化,但它們仍然是設計軟體的最佳實踐。

SOLID 原則是經過時間考驗的用於建立高質量軟體的準則。但在多正規化程式設計和雲端計算的世界裡,它們還能疊加嗎?我將探索 SOLID 代表什麼(字面上和比喻上),解釋為什麼它仍然有意義,並分享一些關於它如何適用於現代計算的例子。

SOLID是一組從 Robert C. Martin(鮑勃大叔) 在 2000 年代初期的著作中提煉出來的原則。它被提議作為一種專門考慮物件導向 (OO) 程式設計質量的方式。總體而言,SOLID 原則對如何拆分程式碼、哪些部分應該是內部的或公開的以及程式碼應該如何使用其他程式碼提出了爭論。我將深入研究下面的每個字母並解釋其原始含義,以及可應用於 OO 程式設計之外的擴充套件含義。

 

發生了什麼變化?

在 2000 年代初期,Java 和 C++ 是王者。當然,在我的大學課程中,Java 是我們選擇的語言,我們的大部分練習和課程都使用它。Java 的流行催生了書籍、會議、課程和其他材料的家庭手工業,以幫助人們從編寫程式碼到編寫好的程式碼。

從那時起,軟體行業發生了深刻的變化。幾個值得注意的:

  • 動態型別語言,如 Python、Ruby,尤其是 JavaScript,已經變得和 Java 一樣流行——在某些行業和型別的公司中超過了它。
  • 非物件導向正規化,尤其是函數語言程式設計 (FP),在這些新語言中也更為常見。甚至 Java 本身也引入了 lambdas!超程式設計(新增和更改物件的方法和特徵)等技術也很受歡迎。還有“更軟”的物件導向風格,例如 Go,它具有靜態型別但沒有繼承。所有這些都意味著類和繼承在現代軟體中不如過去重要。
  • 開源軟體已經激增。早先,最常見的做法是編寫供客戶使用的閉源編譯軟體,而現在,您的依賴項是開源的更為常見。因此,在編寫庫時曾經必不可少的那種邏輯和資料隱藏不再那麼重要。
  • 微服務和軟體即服務迅速湧現。與其將應用程式部署為將其所有依賴項鍊接在一起的大型可執行檔案,不如部署一個與其他服務(您自己的或由第三方提供支援的服務)對話的小型服務。

總的來說,SOLID 真正關心的許多事情——比如類和介面、資料隱藏和多型——已經不再是程式設計師每天要處理的事情。

 

什麼沒變?

這個行業現在在很多方面都不同了,但有些事情沒有改變,也可能不會改變。這些包括:

  • 程式碼是由人編寫和修改的。程式碼編寫一次,閱讀多次。無論是內部的還是外部的,總是需要記錄良好的程式碼,尤其是記錄良好的 API。
  • 程式碼被組織成模組。在某些語言中,這些是類。在其他情況下,它們可能是單獨的原始檔。在 JavaScript 中,它們可能是匯出物件。無論如何,存在某種方法可以將程式碼分離和組織成不同的、有界的單元。因此,總是需要決定如何最好地將程式碼組合在一起。
  • 程式碼可以是內部的,也可以是外部的。有些程式碼是為您自己或您的團隊編寫的。編寫其他程式碼供其他團隊甚至外部客戶使用(通過 API)。這意味著需要某種方式來決定哪些程式碼是“可見的”,哪些是“隱藏的”。

 

現代SOLID

  • 單一職責原則:

“一個類改變的理由永遠不應該超過一個。”

新定義:“每個模組都應該做一件事並做好。”

這個原則與高內聚的話題密切相關。本質上,您的程式碼不應將多個角色或用途混合在一起。

這也適用於微服務設計;如果您有一個服務來處理所有這三個功能,那麼它就試圖做太多事情。

  • 開閉原則

原始定義:“軟體實體應該對擴充套件開放,對修改關閉。”

新定義:“您應該能夠在不重寫模組的情況下使用和新增模組。”

這在物件導向的領域是免費的。在 FP 世界中,您的程式碼必須定義明確的“掛鉤點”以允許修改。

  • 里氏替換原則

原始定義:“如果 S 是T的子型別,那麼型別 T 的物件可以替換為型別 S 的物件,而不會改變程式的任何理想屬性。”

新定義: 如果宣告這些事物的行為方式相同,您應該能夠用一種事物替換另一種事物。

在動態語言中,重要的是,如果你的程式“承諾”做某事(例如實現一個介面或一個函式),你需要遵守你的承諾,不要讓你的客戶感到驚訝。

許多動態語言使用鴨子duck型別來實現這一點。本質上,您的函式正式或非正式地宣告它期望其輸入以特定方式執行並根據該假設進行。

下面是一個使用 Ruby 的例子:

# @param input [to_s]
def split_lines(input)
 input.to_s.split("\n")
end

在這種情況下,函式並不關心input是什麼型別——只關心它有一個to_s函式,它的行為方式與所有to_s函式的行為方式相同,即將輸入轉換為字串。許多動態語言沒有辦法強制這種行為,所以這更像是一個紀律問題,而不是一種形式化的技術。

這是一個使用 TypeScript 的 FP 示例。在這種情況下,高階函式接受一個過濾器函式,它需要一個數字輸入並返回一個布林值:

const isEven = (x: number) : boolean => x % 2 == 0;
const isOdd = (x: number) : boolean => x % 2 == 1;
 
const printFiltered = (arr: number[], filterFunc: (int) => boolean) => {
 arr.forEach((item) => {
   if (filterFunc(item)) {
     console.log(item);
   }
 })
}
 
const array = [1, 2, 3, 4, 5, 6];
printFiltered(array, isEven);
printFiltered(array, isOdd);

  • 介面隔離原則

原始定義:“許多特定於客戶端的介面比一個通用介面要好。”

新定義: “不要向客戶展示超出他們需要看到的內容”。

只記錄您的客戶需要知道的內容。這可能意味著使用文件生成器只輸出“公共”函式或路由,而讓“私有”函式或路由不發出。

在微服務世界中,您可以使用文件或真正的分離來增強清晰度。例如,您的外部客戶可能只能以使用者身份登入,但您的內部服務可能需要獲取使用者列表或其他屬性。您可以建立一個單獨的“僅限外部”使用者服務來呼叫您的主服務,或者您可以只為隱藏內部路由的外部使用者輸出特定文件。 

  • 依賴倒置原則

原始定義:“取決於抽象,而不是具體。”

新定義: “取決於抽象,而不是具體。”

保持定義不變!在可能的情況下保持事物抽象的想法仍然很重要,即使現代程式碼中的抽象機制不如嚴格的物件導向世界中那麼強大。

 

結論

再次重申“現代 SOLID”:

  • 不要讓閱讀你程式碼的人感到驚訝。
  • 不要讓使用你的程式碼的人感到驚訝。
  • 不要壓倒閱讀您程式碼的人。
  • 為您的程式碼使用合理的邊界。
  • 使用正確的耦合級別——將屬於在一起的事物放在一起,如果它們分開,則將它們分開。

好的程式碼就是好的程式碼——這不會改變,而 SOLID 是實踐這一點的堅實基礎!

 

相關文章