語法書 / AA 競程語法書 下冊 / 第十五單元 / 查錯的心法:找出會錯的測資

15.1 查錯的心法:找出會錯的測資

先把整個單元最重要的三句話講完。

心法一:不要只盯著程式碼看,要去尋找會錯的測試資料

「程式錯了」永遠是對某一組輸入錯了。找到一組「餵進去、輸出就是不對」的測試資料,你手上就有了鐵證:錯誤從抽象的「總覺得哪裡怪怪的」變成具體的「輸入這個,它就答錯」。反過來說,找不到會錯的測資,你連「程式真的有錯」都還不確定——說不定是你看錯了題目(見心法三)。

心法二:拿著錯的測資,模擬自己的程式

有了會錯的測資,下一步是模擬程式碼:順著這組輸入,一步一步走過你的程式——測資小就拿紙筆手動跑,步驟太多就讓程式把中間變數印出來(15.5 的主題)。目標只有一個:找到第一個「實際結果和預期不符」的步驟。錯誤一定藏在那一步;找到它,bug 就無所遁形。

心法三:適時重讀題目

新手 WA 的兩大宗其實跟程式碼無關:理解錯題意看錯測試資料的範圍。輸出格式看漏一個換行、範圍把 10^9 看成 10^5、「求最大」看成「求最小」——程式寫得再對也是白搭。卡關超過十分鐘,先回去把題目逐字重讀一遍,特別是輸入輸出格式和範圍那幾行。

標準查錯流程

三句心法串起來,就是一套可以照表操課的流程。之後每次 WA,照著走:

  1. 重讀題目:確認題意、輸入輸出格式、資料範圍,一項一項對。
  2. 快掃檢查清單:用 15.3 的常見錯誤清單掃一輪程式碼——慣犯就那幾個,先抓最常見的。
  3. 尋找會錯的測資:範例測資 → 自己手造的極端測資(15.6)→ 還找不到就上對拍(15.7)。
  4. 模擬程式:拿著錯的測資,手動跑或印變數(15.5),找到第一個和預期不符的步驟。
  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)——試一組全部是正數的:輸入 33 5 7,答案應該是 7 - 3 = 4

執行結果(輸入 33 5 7):

7

抓到了!接著模擬(步驟 4):mn0 出發,第一圈 x = 33 < 0 不成立,mn 沒更新;後面兩圈也一樣——mn 從頭到尾停在 0,可是輸入裡根本沒有 0。第一個不符的步驟找到了:mn 的初始值。範例會過純屬僥倖——範例裡恰好有負數,mn 有機會被更新成 -2。修法:把 mxmn 都初始化成第一個輸入的數(或比照上冊 6.4 找最值的寫法),再把兩組測資都重跑(步驟 5),通過才提交。

動手試試看:把上面的錯誤程式親手打進電腦,照流程走一遍:先確認範例會過,再造出讓它出錯的測資,最後修好它——修好的定義是:範例、全正數、全負數三組測資全部答對。