# 第 8 章 下沉複雜性

本章介紹了有關如何建立更深的類的另一種思考方式。假設您正在開發一個新模組，並且發現了一個不可避免的複雜性。那麼，是應該讓模組的使用者處理複雜性，還是應該在模組內部處理複雜性？如果複雜性與模組提供的功能有關，則第二個答案通常是正確的答案。大多數模組的使用者會多於其開發人員，因此麻煩開發人員比麻煩使用者更好。作為模組開發人員，您應該努力使模組使用者的生活儘可能輕鬆，即使這對您來說意味著額外的工作。表達此想法的另一種方式是，**讓模組的介面簡單比讓其實現簡單更為重要**。

作為開發人員，很容易以相反的方式行事：解決簡單的問題，然後將困難的問題推給其他人。如果出現不確定如何處理的情況，最簡單的方法是丟擲異常並讓呼叫者處理它。如果不確定要實施什麼策略，則可以定義一些配置引數來控制該策略，然後由系統管理員自行確定最佳策略。

這樣的方法短期內會使您的生活更輕鬆，但它們會加劇複雜性，因為許多人都必須處理一個問題，而不僅僅是一個人。例如，如果一個類丟擲異常，則該類的每個呼叫者都必須處理該異常。如果一個類暴露配置引數，則每個系統管理員在每次安裝中都必須學習如何設定它們。

## 8.1 示例：編輯器文字類

考慮為影像介面文字編輯器管理檔案文字的類，這在 [第 6 章](ch06.md) 和 [第 7 章](ch07.md) 中討論過。該類提供了將檔案從磁碟讀入記憶體、查詢和修改檔案在記憶體中的副本以及將修改後的版本寫回磁碟的方法。當學生們要實現這個類時，許多人選擇了面向行的介面，該介面具有讀取、插入和刪除整行文字的方法。這導致了類實現起來很簡單，但也為更高層級的軟體帶來了複雜性。在使用者介面的層級，很少涉及整行操作。例如，擊鍵會導致在現有行中插入單個字元；複製或刪除選擇的區域可能同時修改幾個不同的行。使用面向行的文字介面，更高層級的使用者介面在實現時必須自行拆分和連線行。

面向字元的介面（如 [第 6.3 節](ch06.md) 中所述）下沉了複雜性。使用者介面軟體現在可以插入和刪除任意範圍的文字，而無需拆分和連線行，因此變得更加簡單。但是文字類的實現可能會變得更加複雜：如果內部將文字表示為行的集合，則必須拆分和連線行以實現面向字元的操作。但這種方法更好，因為它在文字類中封裝了拆分和連線的複雜性，從而降低了系統的整體複雜性。

## 8.2 示例：配置引數

配置引數是上升複雜性而不是下沉複雜性的一個示例。類可以暴露一些控制其行為的引數，而不是在內部確定特定的行為，例如快取記憶體的大小或在放棄之前重試請求的次數。該類的使用者必須為引數指定適當的值。在當今的系統中，配置引數已變得非常流行，有些系統有數百個配置引數。

配置引數的擁護者認為配置引數不錯，因為它們允許使用者根據他們的特定要求和工作負載來調整系統。在某些情況下，低層級的基礎結構程式碼很難知道要應用的最佳策略，而使用者則對其領域更加熟悉。例如，使用者可能知道某些請求比其他請求更緊迫，因此使用者為這些請求指定更高的優先順序是有意義的。在這種情況下，配置引數可以在更廣泛的領域中帶來更好的效能。

但是，暴露配置引數還提供了“偷懶的機會”：將引數該如何配置的重要問題傳遞給它的使用者。在多數情況下，使用者或管理員很難或無法確定正確的引數值。在其他情況下，可以透過在系統實現中進行一些額外的工作來自動確定正確的值。設想一個必須處理丟失資料包的網路協議。如果它傳送了請求但在一定時間內未收到響應，則重新發送該請求。確定重試間隔的一種方法是引入配置引數。但是，傳輸協議可以透過測量成功請求的響應時間，並將該響應時間的倍數用於重試間隔，自己計算出一個合理的值。這種方法下沉了複雜性，使其使用者不必自行找出合適的重試間隔。它還具有動態計算重試間隔的優點，那麼，當操作條件發生變化時，它將自動調整引數值。相反，配置引數很容易就過時了。

因此，您應儘可能避免使用配置引數。在暴露配置引數之前，請問自己：“使用者（或更高層級的模組）能比我們確定一個更好的引數值嗎？” 當您建立配置引數時，請確認是否可以提供合理的預設值，以便使用者僅需在特殊情況下提供這個值。理想情況下，每個模組都應當徹底解決問題，而配置引數使得解決方案不完整，從而增加了系統複雜性。

## 8.3 做過頭了

下沉複雜性時要謹慎處理；這個想法很容易做過頭。一種極端的方法是將整個應用程式的所有功能歸為一個類，這顯然沒有意義。如果（a）被下沉的複雜性與該類的現有功能密切相關，（b）下沉複雜性將導致應用程式中其他地方的簡化，(c) 下沉複雜性將簡化類的介面，則下沉複雜性最有意義。請記住，目標是最大程度地降低整體的系統複雜性。

[第 6 章](ch06.md)介紹了學生們如何在文字類中定義一些反映使用者介面的方法，例如實現退格鍵功能的方法。這似乎很好，因為它可以下沉複雜性。但是，將使用者介面的知識新增到文字類中並不會大大簡化高層級的程式碼，並且使用者介面的知識與文字類的核心功能無關。在這種情況下，下沉複雜性只會導致資訊洩露。

## 8.4 結論

在開發模組時，為了減少使用者的痛苦，要找機會給自己多吃一點苦。