本章探討使用變數時的通用原則,包括資料認知、宣告、初始化、作用範圍、持續性、綁定時間,以及單一用途原則。這些概念是撰寫高品質程式碼的基礎磚石。
10.1 資料認知 (Data Literacy)#
有效使用資料的第一步,是了解你手邊有哪些資料型別可用。豐富的資料型別知識是程式設計師工具箱的關鍵組成。
書中列出了一份「資料認知測驗」,包含約 30 個術語(如 abstract data type、B-tree、enumerated type、linked list、pointer、stack 等),讓讀者自我評估對資料型別的掌握程度。
測驗中刻意混入了三個虛構術語(elongated stream、retroactive synapse、value chain)。若你為這些術語打了分,作者建議你閱讀第 33 章的「智識誠實」一節。
10.2 輕鬆掌握變數宣告 (Making Variable Declarations Easy)#
隱式宣告的風險#
某些語言(如早期 Visual Basic)允許隱式宣告(Implicit Declaration)——不需事先宣告即可使用變數。這是最危險的語言特性之一:你可能把 acctNo 和 acctNum 當成同一個變數使用,卻因拼寫不同而產生難以察覺的錯誤。
要求明確宣告的語言能大幅降低這類問題,因為你必須犯兩次錯(同時在程式碼中使用兩個名稱,又同時宣告了它們),程式才會出問題。
應對隱式宣告的做法#
- 關閉隱式宣告:例如 Visual Basic 中使用
Option Explicit - 主動宣告所有變數:即使編譯器不要求
- 使用命名規範:統一縮寫(如
Num或No),避免同義變數 - 檢查交叉參照表:利用編譯器產生的變數清單找出未使用或重複的變數
10.3 變數初始化原則 (Guidelines for Initializing Variables)#
不當的資料初始化是程式錯誤最肥沃的溫床之一。問題通常源自以下情境:
- 變數從未被賦值,內容是記憶體中的隨機位元
- 變數的值已經過時,不再有效
- 變數只有部分被初始化(如物件中只設了部分成員)
初始化的關鍵守則#
- 宣告時立即初始化:這是最廉價的防禦式程式設計
- 在首次使用處附近初始化:遵循鄰近原則(Principle of Proximity),將相關操作放在一起
- 盡可能在宣告的同時定義值(如 Java/C++ 支援
int accountIndex = 0;) - 善用
final/const:防止變數在初始化後被意外修改 - 特別注意計數器與累加器:
i、j、sum、total等容易忘記重設 - 在建構函式中初始化成員資料:配合解構函式釋放記憶體
- 檢查是否需要重新初始化:迴圈中使用的變數尤其需要留意
將所有宣告集中在開頭、初始化也集中在開頭、實際使用卻在遙遠的下方——這種風格會增加初始化錯誤的風險。當程式碼被修改(例如加入迴圈),遠離使用處的初始化很容易被遺忘。
其他防護技巧#
- 利用編譯器警告訊息偵測未初始化的變數
- 驗證輸入參數的合理性
- 使用記憶體存取檢查器偵測不良指標
- 在程式啟動時將工作記憶體填入已知值(如
0xCC或0xDEADBEEF),以便在除錯時辨識未初始化的記憶體
10.4 作用範圍 (Scope)#
**作用範圍(Scope)**指的是變數在程式中被認識和引用的範圍。範圍小的變數只在一小段程式碼中可見(如迴圈索引);範圍大的變數則在許多地方可見(如全域的員工資料表)。
跨距 (Span)#
**跨距(Span)**衡量同一個變數的兩次引用之間隔了多少行程式碼。例如:
a = 0; // a 的第一次引用
b = 0;
c = 0;
a = b + c; // a 的第二次引用,跨距 = 2平均跨距越低,代表對該變數的引用越集中,程式碼可讀性越高。
存活時間 (Live Time)#
**存活時間(Live Time)**是從變數第一次被引用到最後一次被引用之間的總行數。與跨距不同,存活時間不受中間引用次數影響。
短的存活時間有多項好處:降低初始化錯誤風險、提升可讀性、減少同時需要追蹤的變數數量,並有利於將大型常式拆分為小型常式。
最小化作用範圍的具體指引#
- 在迴圈正前方初始化迴圈變數,而非在常式開頭
- 延後賦值:在真正需要值之前才賦值
- 分組相關敘述:將操作同一組變數的程式碼放在一起,減少同時追蹤的變數數量
- 拆分為獨立常式:較短的常式自然帶來較小的跨距與存活時間
- 從最嚴格的可見性開始:先用區域變數,需要時再擴大為類別私有、受保護、套件、最後才是全域
flowchart TB
A["區域變數\n(最小範圍)"] --> B["類別私有\n(private)"]
B --> C["受保護\n(protected)"]
C --> D["套件\n(package)"]
D --> E["全域\n(global,最大範圍)"]
style A fill:#d5f9d5,stroke:#27ae60
style E fill:#f9d5d5,stroke:#c0392b「方便性」與「智識可管理性」的取捨:全域變數用起來方便,但程式在閱讀、除錯和修改時會變得困難——因為你必須同時理解所有共享該全域資料的常式。最佳策略是將變數限制在最小的可見範圍。
10.5 持續性 (Persistence)#
**持續性(Persistence)**描述資料的生命長度,有幾種形式:
| 持續性形式 | 說明 |
|---|---|
| 區塊或常式的生命期 | 如 C++/Java 中 for 迴圈內宣告的變數 |
| 由程式設計師控制 | 如 Java 的 new(垃圾回收前存活)、C++ 的 new(直到 delete) |
| 程式的生命期 | 全域變數、static 變數 |
| 永久 | 儲存於資料庫或檔案中,跨越程式執行期間 |
主要問題在於假設變數的持續性比實際更長。應對方法:
- 使用除錯碼或斷言檢查關鍵變數的合理值
- 用完變數後將其設為「不合理的值」(如將指標設為
null) - 撰寫程式碼時假設資料不會跨次保留
- 養成在使用前立即宣告並初始化的習慣
10.6 綁定時間 (Binding Time)#
**綁定時間(Binding Time)**是變數與其值結合的時間點。綁定越晚,彈性越高,但複雜度也越高。
點擊展開:綁定時間的五個層級
- 撰寫程式碼時(Coding Time):直接寫死魔術數字,如
titleBar.color = 0xFF;——幾乎總是壞主意 - 編譯時(Compile Time):使用具名常數,如
TITLE_BAR_COLOR = COLOR_BLUE——可讀性與可維護性遠勝硬編碼 - 載入時(Load Time):從外部來源讀取值,如 Windows 登錄檔或 Java 屬性檔
- 物件實例化時(Object Instantiation Time):如每次建立視窗時讀取設定
- 即時(Just in Time):如每次繪製視窗時才讀取值
flowchart LR
A["撰寫程式碼時"] --> B["編譯時"]
B --> C["載入時"]
C --> D["物件實例化時"]
D --> E["即時"]
A -. "彈性最低\n複雜度最低" .-> A
E -. "彈性最高\n複雜度最高" .-> E
style A fill:#f9d5d5,stroke:#c0392b
style E fill:#d5f9d5,stroke:#27ae60成功的程式設計取決於最小化複雜度。熟練的程式設計師會依據需求建構足夠的彈性,但不會增添超出需求的彈性與複雜度。
10.7 資料類型和控制結構之間的關係#
Michael Jackson(英國電腦科學家)描述了三種資料與對應控制結構的對應關係:
- 循序資料(Sequential Data) → 循序敘述:一組以特定順序處理的資料,如依序讀取員工姓名、地址、電話
- 選擇性資料(Selective Data) →
if/case敘述:同一時間只使用其中一個資料項目,如依員工薪資類型(時薪或月薪)分別處理 - 迭代資料(Iterative Data) →
for/while/repeat迴圈:同一種資料重複多次,如一串社會安全號碼的陣列
flowchart LR
subgraph 資料類型
S["循序資料"]
Se["選擇性資料"]
I["迭代資料"]
end
subgraph 控制結構
SS["循序敘述"]
IF["if / case"]
LP["for / while / repeat"]
end
S --> SS
Se --> IF
I --> LP實際的資料通常是這三種基本型態的組合。
10.8 為變數指定單一用途 (Using Each Variable for Exactly One Purpose)#
一個變數只做一件事#
避免將同一個變數用於兩個不同的用途。例如用同一個 temp 既存放判別式的計算結果、又拿來做陣列元素交換——這會讓讀者誤以為兩者有關聯。應該使用 discriminant 和 oldRoot 等有意義的獨立變數。
避免隱藏含義 (Hidden Meanings)#
這種濫用的技術名稱是混合耦合(Hybrid Coupling):
pageCount通常代表頁數,但值為-1時代表發生錯誤——整數兼差當布林值customerId超過 500,000 時需減去 500,000 才是逾期帳戶編號bytesWritten為負值時代表磁碟機編號
即使雙重用途對你自己很清楚,對其他人也不會清楚。使用兩個變數來承載兩種資訊所帶來的清晰度,會令你驚訝。
確保所有宣告的變數都有被使用#
研究顯示,未被引用的變數與較高的錯誤率存在相關性。養成檢查所有宣告變數是否都被使用的習慣,並善用編譯器或 lint 工具的警告。
要點#
- 初始化容易出錯:使用本章描述的技巧,避免意外初始值造成的問題。
- 最小化作用範圍:讓變數引用彼此靠近,限制在常式或類別內,避免全域資料。
- 將操作相同變數的敘述盡量放在一起。
- 早期綁定限制彈性但降低複雜度;晚期綁定增加彈性但代價是更高的複雜度。
- 每個變數只用於一個目的。