穿透型變數(pass-through variable):被一連串方法層層往下傳的變數。

範例#

某資料中心服務:

  • 命令列參數描述了用於安全通訊的憑證(cert)
  • 只有最底層的 m3 真正用到(呼叫函式庫開 socket)
  • 但 cert 出現在 main → m1 → m2 → m3 路徑上每個方法的簽章中

Figure 7.2: 處理穿透變數的四種做法——(a) 層層傳遞;(b) 存進共享物件;(c) 全域變數;(d) 集中於 context 物件

為什麼這樣有害#

  • 強迫所有中間方法都「意識到」這個變數的存在,即便它們根本沒用到
  • 一旦有新變數要加(例如系統初版沒有憑證,後來才加) → 必須修改大量介面與方法

消除穿透型變數的策略#

方法 1:找已經共享的物件#

如果上下層之間已經有共享物件(例如都能存取的某個與網路通訊相關的物件),就把資訊放進該物件,不用層層傳遞。

但這個物件本身可能就是個穿透變數——否則 m3 怎麼存取它?

方法 2:全域變數#

把資訊存成全域變數。但全域變數幾乎總是製造其他問題:

  • 不能在同一個 process 中建立兩個獨立實例(全域變數會打架)
  • 看似正式環境用不到雙實例,測試時卻很常用

方法 3:context 物件(推薦)#

作者最常用的解法:引入 context 物件。

  • 把所有原本可能成為穿透變數或全域變數的應用全域狀態,全部存進 context
  • 多數應用本來就有多個全域狀態(設定選項、共享子系統、效能計數器等)
  • 每個系統實例擁有一個自己的 context → 多實例可在同一 process 共存

怎麼讓 context 不變成另一個穿透變數#

  • 在系統的多數主要物件中,儲存對 context 的參考作為 instance 變數
  • 建立新物件時,由建立方法把自己的 context 參考傳入子物件的建構式
  • 這樣 context 就到處可用,但只在建構式中以明確參數出現

context 的好處#

  • 統一處理系統全域資訊
  • 新增變數只要動 context 的建構 / 解構
  • 全域狀態集中管理
  • 方便測試:測試碼可改 context 內欄位來改設定(用穿透變數很難做到這點)

context 的限制與注意事項#

context 不是理想解。

  • 仍具備全域變數的多數缺點:「為什麼有這個變數?」、「它在哪裡被用?」可能不顯而易見
  • 若沒有紀律,context 會膨脹成大雜燴,造成系統各處不顯而易見的依賴
  • 可能造成執行緒安全問題;最好的避險方式是讓 context 中的變數不可變(immutable)

作者目前還沒找到比 context 更好的解法