把複雜度往下拉 (Pull Complexity Downwards)

★★★★Draft

FromA Philosophy of Software Design

📚 From the Books

把複雜度往下拉 (Pull Complexity Downwards)Complexity

開發模組時遇到一段無法避免的複雜性,只有兩條路:讓使用者扛,或讓模組內部自己扛。Ousterhout 的主張是——只要這複雜性和模組提供的功能有關,就該由模組內部吸收。介面簡單比實作簡單更重要;開發者多吃點苦,換使用者少吃苦,這筆帳幾乎永遠划算。

🧠 Why It's a Problem

IMPORTANT

判準很簡單:若複雜性與模組所提供的功能相關,通常應由模組內部處理。身為開發者,目標是讓使用者的生活盡可能輕鬆——即使這意味著你自己要多做點事。

⚖️ 設計對照(往上推 vs. 往下拉)

設定參數是最常見的「往上推」形式:表面上你給了使用者彈性,實際上你只是把「該用什麼值」這個你答不出來的問題,轉手給了每一個部署者。

往上推 — 用設定參數把決策丟給使用者
// 開發者不確定緩衝區該多大,於是開一個旋鈕丟出去
class FileReader {
  constructor(path, { bufferSize } = {}) {
    // 沒有預設;使用者不填就出事
    this.bufferSize = bufferSize;
    this.path = path;
  }
}

// 每一個呼叫端都被迫回答一個它其實不懂的問題
const r = new FileReader('/data/log', { bufferSize: 8192 });

「8192 好還是 65536 好?」這個問題你沒有比使用者更會答——但你比使用者更有機會去量測、去挑一個好預設。把它丟出去,等於讓每個部署現場各自猜一次。

往下拉 — 內部挑一個合理預設,介面收乾淨
// 模組自己吸收「該用多大緩衝」這個複雜性
class FileReader {
  constructor(path) {
    this.path = path;
    this.bufferSize = this.#pickBufferSize(path); // 依檔案大小自動決定
  }

  #pickBufferSize(path) {
    // 開發者做一次功課,換所有使用者不必做
    /* ...量測/啟發式,藏在門後... */
  }
}

// 呼叫端只需表達意圖
const r = new FileReader('/data/log');

使用者不再需要懂緩衝這件事。要調整策略、換啟發式,改的是模組內部,呼叫端一行都不用動。多出來的那點功課由開發者一次付清。

TIP

這不代表消滅所有設定參數。有些值真的只有部署現場才知道(例如外部服務位址)。問自己:這個決定,是我比使用者更有能力做,還是真的只有他們知道? 前者往下拉,後者才往上留。

WARNING

也別把這原則推過頭。往下拉只在複雜性與本模組功能相關、且你確實能替使用者做得更好時才成立。若你把不屬於這裡的複雜性硬吞進來,或吞下的東西使用者其實常常想自己控制,反而會讓模組變得又笨重又難用——痛苦沒有減少,只是搬錯了地方。

🔑 Takeaways

✍️ My Notes

No notes yet — jot your takeaways or Q&A here.

📖 Further Reading