15.1 查錯的心法:找出會錯的測資
先把整個單元最重要的三句話講完。
¶心法一:不要只盯著程式碼看,要去尋找會錯的測試資料
「程式錯了」永遠是對某一組輸入錯了。找到一組「餵進去、輸出就是不對」的測試資料,你手上就有了鐵證:錯誤從抽象的「總覺得哪裡怪怪的」變成具體的「輸入這個,它就答錯」。反過來說,找不到會錯的測資,你連「程式真的有錯」都還不確定——說不定是你看錯了題目(見心法三)。
¶心法二:拿著錯的測資,模擬自己的程式
有了會錯的測資,下一步是模擬程式碼:順著這組輸入,一步一步走過你的程式——測資小就拿紙筆手動跑,步驟太多就讓程式把中間變數印出來(15.5 的主題)。目標只有一個:找到第一個「實際結果和預期不符」的步驟。錯誤一定藏在那一步;找到它,bug 就無所遁形。
¶心法三:適時重讀題目
新手 WA 的兩大宗其實跟程式碼無關:理解錯題意、看錯測試資料的範圍。輸出格式看漏一個換行、範圍把 10^9 看成 10^5、「求最大」看成「求最小」——程式寫得再對也是白搭。卡關超過十分鐘,先回去把題目逐字重讀一遍,特別是輸入輸出格式和範圍那幾行。
¶標準查錯流程
三句心法串起來,就是一套可以照表操課的流程。之後每次 WA,照著走:
- 重讀題目:確認題意、輸入輸出格式、資料範圍,一項一項對。
- 快掃檢查清單:用 15.3 的常見錯誤清單掃一輪程式碼——慣犯就那幾個,先抓最常見的。
- 尋找會錯的測資:範例測資 → 自己手造的極端測資(15.6)→ 還找不到就上對拍(15.7)。
- 模擬程式:拿著錯的測資,手動跑或印變數(15.5),找到第一個和預期不符的步驟。
- 修正並重新驗證:改完後,原本會錯的測資、範例測資、其他造過的測資全部重跑一遍,都過了才重新提交。
¶實際走一遍
用一個小例子感受整套流程。題意:讀入 n 個整數(可能有負數),輸出最大值減最小值。範例測資:輸入 3 與 -2 0 7,輸出 9。有人寫出:
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int mx = 0, mn = 0; // 最大、最小都從 0 出發……嗎?
for (int i = 0; i < n; i++) {
int x;
cin >> x;
if (x > mx) mx = x;
if (x < mn) mn = x;
}
cout << mx - mn << '\n';
return 0;
}
執行結果(輸入範例 3 與 -2 0 7):
9
範例過了,提交——WA。開始辦案:題目沒讀錯(步驟 1);清單掃過去,「忘記初始化或初始化成錯的值」有嫌疑但一時看不出哪裡錯(步驟 2);那就造測資(步驟 3)——試一組全部是正數的:輸入 3 與 3 5 7,答案應該是 7 - 3 = 4:
執行結果(輸入 3 與 3 5 7):
7
抓到了!接著模擬(步驟 4):mn 從 0 出發,第一圈 x = 3,3 < 0 不成立,mn 沒更新;後面兩圈也一樣——mn 從頭到尾停在 0,可是輸入裡根本沒有 0。第一個不符的步驟找到了:mn 的初始值。範例會過純屬僥倖——範例裡恰好有負數,mn 有機會被更新成 -2。修法:把 mx、mn 都初始化成第一個輸入的數(或比照上冊 6.4 找最值的寫法),再把兩組測資都重跑(步驟 5),通過才提交。
動手試試看:把上面的錯誤程式親手打進電腦,照流程走一遍:先確認範例會過,再造出讓它出錯的測資,最後修好它——修好的定義是:範例、全正數、全負數三組測資全部答對。