2013年10月31日 星期四

Round 8: Exception - fail fast

Backlog中仍有SP2與SP7,這一 round 我們選SP7:建立vector與matrix超限功能與測試。

U步驟:

超限測試是vector::operator [] 與 matrix::at極為重要的面向。以vector::operator []為例,由於當初我選擇以operator [] 取用向量的分量,一開始向量v的第一分量為v[0],後來認錯修正為v[1],這樣的歷程即便是你自己都有可能記錯。於是,你有可能仍用v[0]表達向量v的第一分量 -- 超限了。

簡單的說起來,我們需要在取用向量的分量之前檢查operator []的傳入參數是否超限。如是,則應避免傳值回去。這種情況類似於先前計算innerProduct的函數,


bool innerProduct(double &product, const vector &u, const vector &v);

所以,一種解法是仿照innerProduct的函數的做法,傳回bool並引進一個參數作為回傳結果。

問題來了,由於operator []是C++ 內建運算子,只能接受一個輸入參數,且返回型態在這裡必須是 double:


double & operator [](int i) const;

所以無法套用innerProduct的函數的做法!我們得另想辦法。

幸運的是C++及其他較新的程式語言如Java、Python、Scala等,均提供表達與處理例外狀況的機制(exception handling mechanism)如果將超限視為一種例外狀況,則 operator []可檢查(detect)是否超限,如是則拋出例外(throw an exception),表示超限已發生, 並終止正常函數執行:

double & vector::operator [](int i) const {
if (i <=0 || i > _dim )
throw std::string("Index to vector out of bound!");
return _v[i-1];
}

C++ 提供一個exception class,用來表達例外。我們可以利用這個型態,但是代價為較為複雜些。幸好,C++容許拋出任意型態的例外,例如int、const char* (C string)、string等。這裡,vector::operator[]可拋出的例外型態為string。

呼叫operator []的敘述,則可放置在 try 敘述 (try statement)try block之中,並由緊跟在後的catch block加以捕捉:

try {
double component = u[0];
}
catch (string s) {
CHECK(string("Index to vector out of bound!") == s);
}

注意 catch敘述是以型態比對的方式捕捉例外。

如超限例外未被捕捉(例如,呼叫operator []的敘述未被放置於try statement或無對應的catch block加以捕捉),則該例外將繼續上傳至前一個做出呼叫的的函數,依此類推,直到到達main。如main未加以捕捉,則C++ runtime將會終結程式。

D步驟:

我們將以string作為operator []超限例外。工作列出如下

T31:寫vector::operator []超限測試。
T32:vector::operator []超限檢查,及拋出例外
T33:決定如何處理vector::operator []超限例外,做必要的程式修改
T34:寫matrix:at超限測試。
T35:matrix:at超限檢查,及拋出例外
T36:決定如何處理matrix:at超限例外,做必要的程式修改

C步驟:

T31:operator []超限測試。

增加兩個超限測試,期望超限發生時,拋出的例外物件為string("Index to vector out of bound!")。

TEST (index_outofbound_low, vector){
vector u(2);
try {
double ccomponent = u[0];
}
catch (string s) {
CHECK(string("Index to vector out of bound!") == s);
}
}

TEST (index_outofbound_high, vector){
vector u(2);
try {
double ccomponent = u[3];
}
catch (string s) {
CHECK(string("Index to vector out of bound!") == s);
}

}


T32:operator []超限檢查,及拋出例外

double & vector::operator [](int i) const {
if (i <=0 || i > _dim )
throw std::string("Index to vector out of bound!");
return _v[i-1];

}

注意到 throw std::string(...)了嗎?如果只寫 throw string("...")則發生compilation error,原因是vector有一個同名的函數 string(),故以std::string(...)加以區別。另一種做法是將vector::string()改名,例如改為vector::str()。

T33:決定如何處理超限例外,做必要的程式修改
operator []變更,影響innerProduct.exe 與 linearTransform.exe。我們需要決定當vector index超限發生時,該如何處理?

首先,我們先問,使用者會不會引發超限例外?如果會,則超限例外處理的方式,需要顯考慮使用者。

以innerProduct.exe為例,程式並不會讓使用者有直接引發這個例外的機會

接著我們問,使用vector::operator []的programmer會不會所引發超限例外?注意,這裡programmer可能是你,或者其他使用你寫的vector class的programmer。同時,這個超限例外極容易發生:由於後來從1開始的使用方法顯然與operator [] 的從0開始慣用法不合,發生超限的機率就更大了。

Programmer引發這個超限例外時,我們可以說這個例外使因為程式的bug造成的。有bug的程式不該給客戶用。所以,這個決定很容易做:提示programmer這個情形後終結程式,讓programmer修復造成超限的code。

決定了超限例外處理方針之後,下一個決定是由哪一個函數來提示programmer這個情形後終結程式被賦予責任處理這個例外的函數,它的code將因增加try statement而變複雜。根據前面關於例外處理的描述,最簡單的作法是不要做任何事:
超限例外發生時,任由C++ runtime 異常終止 main函數。

以下在main中使用者輸入第一個向量之後,故意讓超限發生,程式執行不正常終止的畫面:




我寫的程式提供你參考。畫面中告訴我們程式在拋出 'std::string' 之後異常終結,並請我們連繫開發者。不論對使用者或programmer,以此方式處置一個bug,應屬恰當。

T34、T35、T36的工作請你試試看。

L步驟:

1. vector::operator [] index 由1而非0開始,有違慣例,對programmer而言,vector::operator []的使用已成bug的溫床。請將vector::operator []改名,例如vector::component(int i)或vector::at(int i)。請你試試看。

2. 前述超限例外,如果我們讓使用者輸入特定維度,為它取得向量在該維度分量,則使用者將有直接引發這個例外的機會,如果這種情形發生,程式應適度提示使用者,使他有機會更正此項錯誤,並繼續正常執行。同理,innerProduct程式有哪些地方會有此類使用者引發的錯誤?這些地方都可以改以例外的方式處理。 

© Y C Cheng, 2013. All rights reserved.

沒有留言:

張貼留言