第五章:錯誤處理(40%)

Handle發表於2012-07-23

我們總是希望讓編寫的程式總是如預期的那樣執行。但是,如何保證程式在不可預測的條件仍能正確執行,這將是我們所面對的挑戰。

讓程式執行失敗的原因可以分為兩類:程式缺陷和真實的錯誤。第一類問題的一個典型就是程式設計師忘了向一個方法傳遞一個必要引數。而第二種型別則是,如果程式需要使用者輸入一個名字,然而使用者卻給了一個空的字串,這種情況是程式設計師無法避免的。

一般而言,程式缺陷通過修改程式可以得到解決,對於真實的錯誤而言,需要在程式碼中新增一些檢查,並真對這些檢查結果執行一些相應的動作(例如,要求使用者重新輸入名字),或者至少讓程式能夠以一種比較能溫和的方式結束。


重要的是如何判斷是那種原因導致程式執行失敗。例如,回憶一下我們之前的power方法:

function power(base, exponent) {
  var result = 1;
  for (var count = 0; count < exponent; count++)
    result *= base;
  return result;
}

當試圖呼叫power('Rabbit',4),明顯會產生一個程式錯誤,但是power(9,0.5)這樣呼叫呢?"power"方法無法處理過小的指數,但是,從數學上來講,這樣的指數又是有意義的(Math.pow能處理這樣的問題)。這個方法的問題在於沒有明確指明何種引數是可接受的,一個最佳實踐就是用註釋的方式表明何種引數是合法的。

當一個方法遭遇到自己無法處理的問題是,應該怎麼做呢?在第四章我們寫過一個方法between:

function between(string, start, end) {
  var startAt = string.indexOf(start) + start.length;
  var endAt = string.indexOf(end, startAt);
  return string.slice(startAt, endAt);
}

startend沒有出現在string中時,indexOf會返回-1並且between方法將會返回一個奇怪的結果:between("Your mother!", "{-", "-}"將會返回"our mother"。

當程式執行時,如果一個方法向上面一樣去呼叫between方法,那麼它將會得到一個String,然後用這個String接下去繼續處理,但是這個String是錯誤的,所以不管如何執行到最後結果總是錯誤的。如果你不走運,這個錯誤的結果可能在你執行了另外20個方法之後才導致新的錯誤的發生。如果真是這樣,找到出錯的源頭是一件非常困難的事情。

在某些情況下,你可能不需要關心當一個方法使用了錯誤的引數而導致了錯誤的行為。例如,如果你知道一個方法將會在哪些地方被呼叫,並且你能保證呼叫這些方法時給出的是正確的引數,一般而言,這種情況下不值得修改程式碼而導致程式碼的臃腫和醜陋,所以說這是看情況而定的。

但是大多數情況下,如果方法不是以顯示的”失敗“執行,那麼它將是難以使用甚至是危險的。如何能夠知道呼叫了between方法並且該方法正確執行了呢?當前的版本無法告訴我們這些內容,除非重新執行一遍所有"betwee*方法做的事情,並且逐一檢查其結果。這樣做真是太糟糕了。一個解決方法就是,讓between方法執行錯誤的時候返回一個特殊值例如false或者undefined

function between(string, start, end) {
  var startAt = string.indexOf(start);
  if (startAt == -1)
    return undefined;
  startAt += start.length;
  var endAt = string.indexOf(end, startAt);
  if (endAt == -1)
    return undefined;

  return string.slice(startAt, endAt);
}

可以看到當方法加入了錯誤檢查以後看起來就不那麼美觀了。但是現在這個程式碼能夠像下面那樣呼叫:

var input = prompt("Tell me something", "");
var parenthesized = between(input, "(", ")");
if (parenthesized != undefined)
  print("You parenthesized '", parenthesized, "'.");

在很多情況下當錯誤發生後返回一個特殊的值是一個不錯的解決辦法。但是這種方法同樣也有缺點。第一點,返回何種型別的值才能表明出現了錯誤。例如,讓我們看如下程式,該程式返回陣列的最後一個元素

function lastElement(array) {
  if (array.length > 0)
    return array[array.length - 1];
  else
    return undefined;
}

show(lastElement([1, 2, undefined]));

這個陣列有最後一個元素嗎?再看看lastElement返回的值。所以這個錯誤返回值是很難界定的。

第二種情況則是,一個特殊的錯誤返回值有時候會導致整個程式碼的混亂。如果有一段程式碼呼叫了*between*10次,那麼它每次都需要判斷between的返回值是否為undefined。另外,如果一個方法呼叫了between而其本身則沒有從錯誤中恢復的機制。那邊它就不得不檢查between的返回值,如果between返回值是undefined,那麼這個方法可以將undefined或者其他什麼特殊的值返回給它的呼叫者,那麼它的呼叫者也需要對返回值來做檢查。

有時候,當意外發生時,明智的做法是,立即停止當前正在處理的任務,並且跳轉到知道如何處理此類錯誤的程式中去。

幸運的是,很多程式都提供了類似的功能,通常我們稱其為“錯誤處理”

相關文章