Python 程式設計

第 4 章    除錯

∗ 如何成為一個成功的程式設計師

▸ 程式開發過程:分析問題、設計解決方案、實作、測試

✶ 每一個步驟都會歷經掙扎、壓力、挫折
✶ 成功完成程式那一刻:滿心歡喜,腦部會釋放快樂的化學物質
✶ 策略:讓腦部常常釋放快樂的物質
✶ 作法:從小程式開始練習,累積許多完成程式的喜悅

∗ 完成程式的一大挑戰:除錯 (Debug)

▸ 策略:與其事後除錯,最好一開始就避免出錯

▸ 作法:

✶ 從小處開始:應將大問題分成小問題,從小問題開始解決才容易成功
✶ 經常測試:等到寫了許多程式才開始測試,如果有錯誤會很難釐清問題所在
✶ 累積式加入程式:完成並測試完一部分程式後,下一步就是想辦法再加入一小部份程式,漸漸往目標推進。 只加一小部份程式就要測試,這樣如果有錯,也比較可能是發生在新加入的程式,除錯較容易

(1) 除錯範例

∗ 文件的送件及收件時間

▸ 請使用者輸入送件時間及文件處理時間,然後印出收件時間

▸ 開始分析,然後實作,從簡單開始:請使用者輸入資料,再列印資料,確定資料正確

ch4/receiveTime.py (首先建立 python/ch4 目錄)
sendTime = input('請輸入送件時間(小時):')
processTime = input('請輸入處理時間(小時):')

print(sendTime)
print(processTime)

▸ 測試:輸入 10 及 8,印出 10 及 8,看來沒問題

▸ 下一步:將輸入的數值相加再印出來

...
print(processTime)

receiveTime = sendTime + processTime
print(receiveTime)

▸ 測試:輸入 10 及 8,最後印出 108,有問題!!

▸ 除錯:問題癥結何在?如何解決?

✶ 蛛絲馬跡:答案看來是將字串 10 與 8 串成了 108,顯然沒將輸入轉成數值!!
修正:
sendTime = input('請輸入送件時間(小時):')
processTime = input('請輸入處理時間(小時):')

print(sendTime)
print(processTime)
sendTime = int(sendTime)
processTime = int(processTime)

receiveTime = sendTime + processTime
print(receiveTime)
✶ 測試:輸入 10 及 8,最後印出 18,數值相加問題解決囉!

▸ 程式沒問題了?如果輸入 20 及 8 呢?有 28 點鐘的說法??

▸ 測試程式的另一個重要作業:需測試邊界條件 (Boundary condition)

✶ 程式最常在邊界數值發生問題 (初始值、負數、0、很大數、沒有數字、...),因為程式設計師經常沒考慮到

▸ 假設在此例中,我們只處理收件時間大於 24 的條件,修正:

sendTime = input('請輸入送件時間(小時):')
processTime = input('請輸入處理時間(小時):')

sendTime = int(sendTime)
processTime = int(processTime)

receiveTime = sendTime + processTime
print(receiveTime)
receiveDays = (sendTime + processTime) // 24
receiveHours = (sendTime + processTime) % 24
print(f'收件時間:{receiveDays} 天 {receiveHours} 小時')

(2) 錯誤類別

∗ 除錯和撰寫程式的思考模式不同

▸ 除錯的過程猶如偵探一般,要仔細探索每一個可能出錯的地方

▸ 主要線索:

✶ 仔細檢視錯誤訊息,了解問題的癥結
✶ 列印相關變數資料,看看其值是否如預期

∗ 錯誤類別

▸ 依照發生頻率統計,SyntaxError, TypeError, NameError, ValueError 四種錯誤佔了所有錯誤近 90%:

錯誤訊息比例說明
SyntaxError54.74%語法錯誤
TypeError14.29%型態錯誤 (例如:函式參數數量不對)
NameError11.05%名稱錯誤 (例如:未定義的變數)
ValueError9.78%值錯誤 (例如:應該是數值,卻給了字串)
TokenError2.67%字元錯誤 (例如:括號不對稱或輸入了不允許的字元)
IndentationError0.31%縮排錯誤
AttributeError0.30%屬性錯誤 (例如:物件無此屬性)
ImportError0.18%匯入錯誤 (例如:匯入的套件不存在)
IndexError0.07%索引錯誤 (例如:陣列元素不存在)

(3) 常見錯誤及除錯策略

∗ SyntaxError:程式語法錯誤

▸ 這是最常見的錯誤

▸ 範例 1:ch4/syntaxError.py

sendTime = input('請輸入送件時間(小時):')
processTime = input('請輸入處理時間(小時):'

sendTime = int(sendTime)
processTime = int(processTime)

receiveTime = (sendTime + processTime) % 24
print(receiveTime)
✶ 錯誤訊息:
  File <...>, line 4
    sendTime = int(sendTime)
           ^
SyntaxError: invalid syntax
✶ 除錯:
# 仔細檢查語法的錯誤,大多數是漏了標點符號,例如括號、引號、或逗點等
# 可以將發生錯誤訊息註解掉,看看是否還有錯誤,如果還有,可能是上下行的問題,以本例:
* 註解掉第 4 行,第 5 行卻發生錯誤,同樣是 SyntaxError
* 自問:程式能力真有這麼差?連續錯兩行?
* 乾脆將之後的程式全部註解掉,再次執行出現以下錯誤訊息:
Could not run code because it is incomplete.
(或 SyntaxError: unexpected EOF while parsing)
--> Python 在解析程式時,原本預期要讀到某個符號,但卻意外讀到檔案結束,程式根本沒完成。 仔細找錯誤:發現第 2 行少了右括號

▸ 範例 2:ch4/syntaxError2.py

sendTime = input('請輸入送件時間('小時'):')
processTime = input('請輸入處理時間(小時):')

sendTime = int(sendTime)
processTime = int(processTime)

receiveTime = (sendTime + processTime) % 24
print(receiveTime)
✶ 錯誤訊息:
  File <...>, line 1
    sendTime = input('請輸入送件時間('小時'):')
                                ^
SyntaxError: invalid syntax
✶ 註解掉第 1 行,輸入資料後,第 4 行卻發生另外一個錯誤 NameError
    sendTime = int(sendTime)
NameError: name 'sendTime' is not defined
# 變數 sendTime 並未定義!
✶ 仔細分析:其實這是因為我們拿掉第一行所造成的問題
# 把第一行註解拿掉,重新執行,還是得到 SyntaxError
# 嘗試:既然該行錯誤,乾脆將該行註解掉,然後改成一個非常簡單的指令,例如將 sentTime 設為一個常數試試看,如下:
# sendTime = input('請輸入送件時間('小時'):')
sentTime = '10'
✶ 重新執行,成功了!
✶ 結論:input() 函式裡一定有問題! --> 仔細檢視,發現兩組單引號發生衝突,修正:
sendTime = input("請輸入送件時間('小時'):")

∗ TypeError:變數型態錯誤

▸ 範例:ch4/typeError.py

sendTime = input('請輸入送件時間(小時):')
processTime = input('請輸入處理時間(小時):')
int(sendTime)
int(processTime)
hours = processTime//24
remainingHours = processTime%24
print (hours, ' 天 ', remainingHours, ' 小時')
receiveTime = sendTime + remainingHours
print (f'收件時間:{receiveTime} 點')
✶ 執行:
請輸入現在時間(小時):5
請輸入處理文件的時間 (小時):36
✶ 錯誤訊息:
  File <...>, line 5, in <module>
    hours = processTime//24
TypeError: unsupported operand type(s) for //: 'str' and 'int'
# 型態錯誤:// 運算不支援字串與整數
processTime 是字串?第四行不是轉成整數了嗎?
✶ 加上一行列印 processTime 變數的型態:
...
int(processTime)
print(processTime, type(processTime))
hours = processTime//24
... 
# 訊息回覆:36 <class 'str'>processTime 是字串沒錯!
✶ 轉成整數並沒有成功!原來是忘了指派回來了,更正第三行:
...
int(sendTime)
processTime = int(processTime)
print(processTime, type(processTime))
...
✶ 刪除 print(processTime, type(processTime)) 指令,再次執行,又發生錯誤:
1 天, 12 小時
  File <...>, line 8, in <module>>
    receiveTime = sendTime + remainingHours
TypeError: must be str, not int
# 型態錯誤:應該是字串,不是整數
✶ 第 7 行指令 print(hours, ' 天 ', remainingHours, ' 小時') 印出資料沒有問題, 所以 remainingHours 變數沒問題,那就是變數 sendTime 發生問題
✶ 往上找 sendTime,發現也是相同問題,亦即轉換值沒有指派回來,修正:
...
processTime = input('請輸入處理時間(小時):')
sendTime = int(sendTime)
processTime = int(processTime)
...
✶ 最後執行:成功

∗ NameError:變數名稱錯誤

▸ 範例:ch4/nameError.py

sendTime = input('請輸入送件時間(小時):')
processTime = input('請輸入處理時間(小時):')

sendTime = int(sendTime)
processTime = int(processTime)

receivTime = (sendTime + processTime) % 24
print(receiveTime)
✶ 錯誤訊息:
File <...>, line 8, in <module>
    print(receiveTime)
NameError: name 'receiveTime' is not defined
# 變數 receiveTime 沒有定義 (亦即:還沒指派值,就先使用了)
✶ 利用搜尋找找看「receiveTime」,結果只在第 8 行找到
✶ 第 7 行怎麼回事?原來是字打錯了,少了一個 e,修正:
receiveTime = (sendTime + processTime) % 24

∗ ValueError:參數值錯誤 (當傳參數給某個函式時,參數的型態錯誤)

▸ 範例:ch4/valueError.py

sendTime = input('請輸入送件時間(小時):')
processTime = input('請輸入處理時間(小時):')

sendTime = int(sendTime)
processTime = int(processTime)

receiveTime = (sendTime + processTime) % 24
print(receiveTime)

▸ 執行時,輸入任何字串 (或不輸入資料),而不要輸入數字

✶ 錯誤訊息:
  File <...>, line 4, in <module>
    sendTime = int(sendTime)
ValueError: invalid literal for int() with base 10: '...'
# 值錯誤:以 10 為基底的 int() 給了錯誤文字
✶ 錯誤訊息的意思是「無法轉換成整數」,而往前看發現 sendTime 的值是由使用者輸入,因此並非程式錯誤,而是操作錯誤

∗ 結論:最常用的除錯法是利用 print() 函式將有問題的變數值列印出來,看看是否符合期待

上一章       下一章