本章探討使用變數時的通用原則,包括資料認知、宣告、初始化、作用範圍、持續性、綁定時間,以及單一用途原則。這些概念是撰寫高品質程式碼的基礎磚石。

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)——不需事先宣告即可使用變數。這是最危險的語言特性之一:你可能把 acctNoacctNum 當成同一個變數使用,卻因拼寫不同而產生難以察覺的錯誤。

要求明確宣告的語言能大幅降低這類問題,因為你必須犯兩次錯(同時在程式碼中使用兩個名稱,又同時宣告了它們),程式才會出問題。

應對隱式宣告的做法#

  • 關閉隱式宣告:例如 Visual Basic 中使用 Option Explicit
  • 主動宣告所有變數:即使編譯器不要求
  • 使用命名規範:統一縮寫(如 NumNo),避免同義變數
  • 檢查交叉參照表:利用編譯器產生的變數清單找出未使用或重複的變數

10.3 變數初始化原則 (Guidelines for Initializing Variables)#

不當的資料初始化是程式錯誤最肥沃的溫床之一。問題通常源自以下情境:

  • 變數從未被賦值,內容是記憶體中的隨機位元
  • 變數的值已經過時,不再有效
  • 變數只有部分被初始化(如物件中只設了部分成員)

初始化的關鍵守則#

  • 宣告時立即初始化:這是最廉價的防禦式程式設計
  • 在首次使用處附近初始化:遵循鄰近原則(Principle of Proximity),將相關操作放在一起
  • 盡可能在宣告的同時定義值(如 Java/C++ 支援 int accountIndex = 0;
  • 善用 final / const:防止變數在初始化後被意外修改
  • 特別注意計數器與累加器ijsumtotal 等容易忘記重設
  • 在建構函式中初始化成員資料:配合解構函式釋放記憶體
  • 檢查是否需要重新初始化:迴圈中使用的變數尤其需要留意

將所有宣告集中在開頭、初始化也集中在開頭、實際使用卻在遙遠的下方——這種風格會增加初始化錯誤的風險。當程式碼被修改(例如加入迴圈),遠離使用處的初始化很容易被遺忘。

其他防護技巧#

  • 利用編譯器警告訊息偵測未初始化的變數
  • 驗證輸入參數的合理性
  • 使用記憶體存取檢查器偵測不良指標
  • 在程式啟動時將工作記憶體填入已知值(如 0xCC0xDEADBEEF),以便在除錯時辨識未初始化的記憶體

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)**是變數與其值結合的時間點。綁定越晚,彈性越高,但複雜度也越高。

點擊展開:綁定時間的五個層級
  1. 撰寫程式碼時(Coding Time):直接寫死魔術數字,如 titleBar.color = 0xFF;——幾乎總是壞主意
  2. 編譯時(Compile Time):使用具名常數,如 TITLE_BAR_COLOR = COLOR_BLUE——可讀性與可維護性遠勝硬編碼
  3. 載入時(Load Time):從外部來源讀取值,如 Windows 登錄檔或 Java 屬性檔
  4. 物件實例化時(Object Instantiation Time):如每次建立視窗時讀取設定
  5. 即時(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 既存放判別式的計算結果、又拿來做陣列元素交換——這會讓讀者誤以為兩者有關聯。應該使用 discriminantoldRoot 等有意義的獨立變數。

避免隱藏含義 (Hidden Meanings)#

這種濫用的技術名稱是混合耦合(Hybrid Coupling)

  • pageCount 通常代表頁數,但值為 -1 時代表發生錯誤——整數兼差當布林值
  • customerId 超過 500,000 時需減去 500,000 才是逾期帳戶編號
  • bytesWritten 為負值時代表磁碟機編號

即使雙重用途對你自己很清楚,對其他人也不會清楚。使用兩個變數來承載兩種資訊所帶來的清晰度,會令你驚訝。

確保所有宣告的變數都有被使用#

研究顯示,未被引用的變數與較高的錯誤率存在相關性。養成檢查所有宣告變數是否都被使用的習慣,並善用編譯器或 lint 工具的警告。

要點#

  • 初始化容易出錯:使用本章描述的技巧,避免意外初始值造成的問題。
  • 最小化作用範圍:讓變數引用彼此靠近,限制在常式或類別內,避免全域資料。
  • 將操作相同變數的敘述盡量放在一起
  • 早期綁定限制彈性但降低複雜度;晚期綁定增加彈性但代價是更高的複雜度。
  • 每個變數只用於一個目的