Sway 參考手冊/除錯
在目前階段,您應該能夠編寫一些複雜的 Sway 程式。此外,我確信您也引入了某些複雜的錯誤。這正是程式設計師的生活!
程式設計中通常會遇到兩種型別的錯誤:語法錯誤,原始碼存在問題,無法編譯和執行;以及語義錯誤,程式碼(以某種方式)執行,然後過早終止或產生錯誤的結果。
人類語言也存在語法和語義錯誤。以下是一些示例
The box mxyskd the water.
由於標記“mxyskd”不是一個單詞,因此此句子不是有效的英語。這是一個語法錯誤。
The box drank water the
此句子也不是有效的英語,因為冠詞“the”放錯了位置,並且沒有標點符號表示句子的結束。這些也是語法錯誤。
The box drank the water.
此句子在語法上是正確的,但在語義上沒有意義:盒子通常不喝酒。
程式語言也有類似的錯誤,我們將在後續章節中探討如何檢測和修復它們。
在學習這些技巧時,請記住,除錯的第一規則是儘早捕獲錯誤。也就是說,找到對程式碼的最小改進,使您更接近最終版本,然後實現、測試和除錯該步驟。然後重複。
對於嘗試在測試之前編寫整個專案的同學來說,真是苦不堪言!
語法錯誤總是看起來像這樣
SYNTAX ERROR: :syntaxError file prog1.s,line 113 expecting OPEN_PARENTHESIS, found function instead error occurred prior to: ;[END OF LINE]
錯誤報告會給出檔名和行號。通常,這正是問題所在,但請意識到,這是檢測到錯誤的位置;問題可能在檔案中的更早位置開始。
許多不明顯的錯誤涉及在錯誤的位置放置分號或在需要的地方省略分號。請記住,分號不跟在命名函式定義和某些函式呼叫(如if和while)之後。在下一章中,您將學習哪些函式呼叫需要後跟分號,哪些不需要。
如果您遇到一個無法追蹤的奇怪語法錯誤,請嘗試註釋掉程式碼片段,直到錯誤消失。錯誤可能就在剛註釋掉的程式碼中。
Sway 有三種類型的註釋。第一種是行尾註釋。雙斜槓(兩個連續的正斜槓)後面的行上的任何內容都將被忽略。
//removing the next function
//function id(x) { x; }
第二種是三斜槓。這會導致檔案其餘部分被忽略,對於除錯語法非常有用。
///removing the rest of the file
function id(x) { x; }
要使用三斜槓,請在檔案的最頂部以“///”開頭,以便忽略整個檔案。這應該會消除語法錯誤。然後繼續將“///”向下移動到檔案中,直到語法錯誤出現。新發現的程式碼部分很可能包含錯誤。
最後一種註釋是塊註釋。出現在斜槓星號和星號斜槓之間的任何內容都將被忽略
/* removing this function
function id(x) { x; }
*/
請注意,塊註釋不能包含另一個塊註釋。
語法錯誤是在 Sway 直譯器讀取程式碼時發現的,而語義錯誤是在直譯器評估程式碼時發現的。語義錯誤報告看起來像這樣
EVALUATION ERROR: :mathError file prog1.s,line 3: division: cannot divide by zero
CALL TRACE:
initial call:
prog1.s,line 5: inspect(f(3));
in <function f(x)>...
prog1.s,line 3: x / 0;
會給出檔名和行號;與語法錯誤一樣,這是檢測到語義錯誤的位置。在錯誤描述之後是呼叫跟蹤。呼叫跟蹤從上到下讀取。在示例中,問題始於第 5 行,當時呼叫了inspect函式。此呼叫觸發了對函式f的呼叫,在該函式中,第 3 行嘗試進行除以零操作。呼叫跟蹤對於追蹤問題非常有用。
語義錯誤通常發生在某個重要變數最終具有與您預期不同的值時。因此,在除錯程式碼時檢視變數的值非常重要。
您應該養成能夠“視覺化”程式狀態的習慣。例如,假設您希望在程式中的多個不同點檢視名為x的變數的值。最簡單的視覺化方法是使用以下形式的列印語句
println("x is ",x);
作為一名老師,我必須評論一下,學生們很少使用這個簡單的工具。您應該大量使用列印語句來除錯語義錯誤。
但是,新增這種列印語句相當繁瑣,因為您只需在解決問題後刪除這些行。需要一種更快的新增列印語句的方法。一種這樣的方法是inspect函式。例如,以下兩個函式呼叫是等價的
println("x is ",x);
inspect(x);
這兩個呼叫的輸出完全相同。如果x的值為5,則這兩個呼叫的輸出為
x is 5
現在,在這種情況下,使用inspect節省的時間並不多(節省了鍵入 8 個字元),但當要檢查的表示式變得複雜時,節省的時間就會增加。考慮這些呼叫
println("alpha . beta(gama,delta) is ",alpha . beta(gama,delta));
inspect(alpha . beta(gama,delta));
同樣,每個輸出都相同,但在使用inspect時節省的時間有所增加。
最後,如果即使inspect也寫起來太多,定義一個變數,例如vv,表示“檢視變數”
var vv = inspect;
並將其用於inspect代替
vv(x);
有時跟蹤函式呼叫的執行(逐行)很有用。假設您希望呼叫一個名為f的函式並跟蹤函式執行時的每一行。只需將呼叫包裝在設定函式的filter元件的語句中即可。
f . filter = trace*; y = f(x); f . filter = :null;
或者更簡單地說,使用包裝函式trace:
y = trace(f(x));
將過濾器設定為trace*(trace的作用)將開啟被呼叫函式的跟蹤;將過濾器設定為null將關閉跟蹤。您需要在檔案頂部包含debug庫。例如,此程式
include("debug");
function f(x)
{
var a = x + 1;
var b = x - 1;
a * b;
}
var result = trace(f(5));
inspect(result);
產生以下輸出
prog.s,line 5: var a = x + 1; prog.s,line 6: var b = x - 1; prog.s,line 7: a * b; result is 24
如果將過濾器設定為trace,則將顯示被跟蹤函式主體中的每一行。您需要按<Enter>鍵才能繼續執行函式中的下一行。
如果將函式的過濾器設定為trace*,則程式將在打印出被跟蹤語句後暫停,就像trace一樣。不同之處在於,如果您鍵入任何非空格字元並按回車鍵,您將進入一個微型 Sway 直譯器。在這裡,您可以執行在互動式 Sway 直譯器中執行的幾乎任何操作。唯一的區別是,要執行的每個定義或表示式都必須在一行上輸入。
單步執行函式類似於跟蹤,但您的程式將在執行每個語句之前暫停。如果只是按<Enter>,則當前語句將被執行,下一條語句將被顯示(如果存在),程式將再次暫停。
但是,如果您在暫停期間鍵入其他任何內容,則您的輸入將被評估為 Sway 表示式並顯示結果。此評估是在函式主體評估期間有效的環境下執行的。換句話說,您可以在函式呼叫期間檢查(和修改)作用域內的變數。此迷你直譯器將一直執行,直到您停止輸入要評估的表示式。當您停止輸入表示式時,程式將前進到函式呼叫中的下一行。
設定單步執行函式與跟蹤類似
f . filter = step*; y = f(x); f . filter = :null;
或者更簡單地說,使用包裝函式step:
y = step(f(x));
在互動中(如上所述,但使用step),檢查值a並修改b
prog.s,line 5: var a = x + 1;> prog.s,line 6: var b = x - 1;> a; INTEGER: 6 > prog.s,line 6: var b = x - 1;> prog.s,line 7: a * b;> b = 10; INTEGER: 10 > b; INTEGER: 10 > prog.s,line 7: a * b;> result is 60
請注意,在任何給定點顯示的行尚未執行。因此,我們必須等到宣告變數a後才能檢查它。與跟蹤一樣,您必須包含debug庫才能使用單步執行。
迷你直譯器和 Sway 直譯器之間的主要區別在於,傳遞給迷你直譯器的表示式必須全部在一行上輸入。
您可以透過輸入 <Ctrl-d> 或輸入空行來退出迷你直譯器。
有時,您確切地知道希望在函式中的哪個位置暫停並檢查變數。與其使用step,您可以使用sway函式直接呼叫迷你直譯器。
include("debug");
function f(x)
{
var a = x + 1;
var b = x - 1;
println("breakpoint!");
sway(); //call the mini-interpreter
println("done.");
a * b;
}
var result = f(5);
inspect(result);
執行此程式將產生
breakpoint! sway> a; INTEGER: 6 sway> b = 10; INTEGER: 10; sway> done. result is 60
與之前一樣,必須在一行上輸入迷你直譯器的輸入。
sway函式是內建的;因此,您無需包含debug。
您必須處理兩種型別的錯誤。第一種是您的程式使用者提供了錯誤的輸入。這些型別的錯誤稱為外部錯誤。“防彈”您的程式碼意味著新增處理外部錯誤的邏輯。
第二種錯誤源於程式碼本身的錯誤。這些稱為內部錯誤。優秀的計算機科學家會預料到無論程式設計師多麼優秀,這些錯誤都會發生,並且會使用斷言儘早發現這些錯誤。例如,假設您“知道”函式的輸入始終是非負整數。您可以使用斷言來檢測與該約束的偏差
function f(x)
{
assert(x >= 0);
...
}
在程式碼開發期間,斷言確保您沒有不適當地呼叫此類函式。
完成程式碼後,您可以註釋掉您的斷言。