本節透過軟體設計課程中學生實作 HTTP 協定的設計決策,來看資訊隱藏的好例子與壞例子。
HTTP 背景#
- HTTP 是瀏覽器與 Web 伺服器溝通的協定
- 使用者點連結或送出表單時,瀏覽器透過 HTTP 把請求送到伺服器
- 伺服器處理後回傳回應(通常是新的網頁)
- HTTP 協定規定請求與回應的格式(皆為文字格式)
學生被要求實作一個(或多個)類別,讓 Web 伺服器能方便接收 HTTP 請求並回傳回應。

Figure 5.1: HTTP 協定中的 POST 請求樣本——首行(請求類型、URL、參數、版本)、若干 header(如 Content-Length)、空行、可選 body
POST 請求的構造:第一行(含請求類型、URL、可選參數、HTTP 版本)、若干 header、空行、可選的 body。
例如 header 中的
Content-Length、body 中的comment、priority等表單欄位。
反例 1:類別太多#
最常見的錯誤:把程式拆成大量淺類別,造成類別之間資訊洩漏。
某團隊做法:
- 用兩個類別接收 HTTP 請求
- 第一個類別把請求從網路連線讀進字串
- 第二個類別解析該字串
這是典型的時序分解(「先讀再解析」),結果:
- HTTP 請求不解析就讀不完整——例如
Content-Lengthheader 指定 body 長度,必須先解析 header 才能知道整個請求多長 - 因此兩個類別都得理解 HTTP 請求結構,解析程式碼出現重複
- 呼叫端要照特定順序呼叫兩個方法 → 介面更複雜
較好的做法:合併成單一類別同時負責讀取與解析。
軟體設計的一個普遍主題:有時讓類別「稍微大一點」,反而能改善資訊隱藏。
兩個原因:
- 把與某個能力相關的程式碼通通放在一起(例如把所有解析 HTTP 請求的程式碼放在同一個類別)
- 提高介面層次:把多步驟拆成的多個方法合併成單一方法
合併後的類別比原來的類別都更深。當然這也不能無限上綱(極端就是整個應用一個類別);第 9 章會討論何時該拆。
反例 2:HTTP 參數的處理#
伺服器收到請求後,往往需要存取請求中的某些參數(例如圖中的 photo_id)。
學生做對的兩件事#
- 隱藏參數位置:合併首行參數與 body 參數,呼叫端不需要關心參數從哪裡來
- 隱藏 URL encoding:解析器返回前就解碼,所以呼叫端拿到的是
"What a cute baby!",而不是"What+a+cute+baby%21"
但取參數的介面太淺#
多數學生這樣寫:
public Map<String, String> getParams() {
return this.params;
}問題:
- 不是回傳單一參數,而是把內部用來存所有參數的
Map整個吐出來 - 內部資料結構的選擇被暴露在介面上:未來改實作(例如為了效能換資料結構),介面就得改,所有呼叫端都得跟著改
- 呼叫端要先呼叫
getParams,再從Map取參數 → 多走一步 - 呼叫端必須知道「不能修改回傳的
Map」(否則會影響HTTPRequest內部狀態)
較好的設計#
public String getParameter(String name) { ... }
public int getIntParameter(String name) { ... }getParameter直接以字串回傳指定參數值,隱藏內部表示getIntParameter直接做字串到整數的轉換,把轉換機制也封裝起來- 必要時可加
getDoubleParameter等其他資料型別版本 - 找不到參數或無法轉型時拋出例外(範例略過例外宣告)
反例 3:HTTP 回應的預設值#
學生在這部分最常犯的錯:預設值給得不夠。
不好的做法#
某團隊要求呼叫端建立回應時明確指定 HTTP 版本。但:
- 回應版本必須對應到請求版本
- 而且建立回應時就會把請求物件當參數傳入
- 呼叫端通常不知道要填什麼版本,強行讓呼叫端填,反而造成 HTTP 函式庫與呼叫端之間的資訊洩漏
Date header 也類似:函式庫應自動填合理的預設值。
設計原則:讓常見情況最簡單#
介面設計的核心:讓常見情況盡可能簡單。
預設值是「部分資訊隱藏」的範例:
- 一般情況下,呼叫端不需要意識到該欄位存在
- 少數需要覆寫預設值的情況才需要知道,並透過特殊方法修改
能自動「做對的事」的類別,就不該逼使用者明確要求。
Java I/O 的反例(須明確
BufferedInputStream)就是負面教材:緩衝幾乎人人都需要,沒人應該需要明確要求或意識到它存在。最好的功能是「你連它存在都不知道,卻已經受惠於它」。
紅旗:過度暴露(Red Flag: Overexposure)#
如果某個常用功能的 API 強迫使用者先學會其他極少用到的功能,便提高了不需要這些功能的使用者的認知負擔。