作者: Yさ
日時: 2004/3/31(13:52)
(1.2 続き)
 ここで 1,5,10 の並びのパターンが重複している事に気がつきました。
 リファクタリングしましょう。

----- [テーブル_val[],_name[]導入、変数base除去]
function cnvRoman(n,  base,p){
  if(_size<1){
    _size=split("1 5 10",_val);
    split("I V X",_name);
  }
  for(p=1; p<=_size; ++p){
    if(n==_val[p]) return _name[p];
    if(n<_val[p]) break;
  }
  if(p<=_size && n==_val[p]-1) return cnvRoman(1) cnvRoman(n+1);
  return _name[p-1] cnvRoman(n-_val[p-1]);
}
-----

 (省略しますが)テストは成功します。

 次に、'L'(50)を扱えるように拡張してみましょう。
 まずテストケースを追加します。
-----
  eval("#02:39",cnvRoman(39),"XXXIX");
  eval("   :40",cnvRoman(40),"XL");
  eval("   :43",cnvRoman(43),"XLIII");
  eval("   :44",cnvRoman(44),"XLIV");
  eval("   :45",cnvRoman(45),"XLV");
  eval("   :46",cnvRoman(46),"XLVI");
  eval("   :49",cnvRoman(49),"XLIX");
  eval("   :50",cnvRoman(50),"L");
-----

 (省略しますが)テストが失敗します。
 'L'(50)をテーブルに追加します。
-----
function cnvRoman(n,  base,p){
  if(_size<1){
    _size=split("1 5 10 50",_val);
    split("I V X L",_name);
  }
  for(p=1; p<=_size; ++p){
    if(n==_val[p]) return _name[p];
    if(n<_val[p]) break;
  }
  if(p<=_size && n==_val[p]-1) return cnvRoman(1) cnvRoman(n+1);
  return _name[p-1] cnvRoman(n-_val[p-1]);
}
-----

-----
 :
OK : #02:39=XXXIX == XXXIX
err:    :40=XXXX == XL
err:    :43=XXXXIII == XLIII
err:    :44=XXXXIV == XLIV
err:    :45=XXXXV == XLV
err:    :46=XXXXVI == XLVI
err:    :49=IL == XLIX
OK :    :50=L == L
-----

 とりあえず40のテストで失敗しています。
 'V','X'からは1引きますが、'L'からは10引かないといけません。

----- [テーブル_dec[]を追加]
function cnvRoman(n,  base,p){
  if(_size<1){
    _size=split("1 5 10 50",_val);
    split("I V X L",_name);
    split("0 1 1 10",_dec);
  }
  for(p=1; p<=_size; ++p){
    if(n==_val[p]) return _name[p];
    if(n<_val[p]) break;
  }
  if(p<=_size && n==_val[p]-_dec[p])
    return cnvRoman(_dec[p]) cnvRoman(n+_dec[p]);
  return _name[p-1] cnvRoman(n-_val[p-1]);
}
-----

 40のテストは成功します。が、まだ40〜50の間で失敗します。
 どうやら条件が不足しているようです。

 :
  if(p<=_size && n==_val[p]-_dec[p])
    return cnvRoman(_dec[p]) cnvRoman(n+_dec[p]);
 :

 ここを↓のように修正しましょう。

 :
  if(p<=_size && n>=_val[p]-_dec[p])
    return cnvRoman(_dec[p]) cnvRoman(n+_dec[p]);
 :

 (省略しますが)テストは成功します。

 続けて、'C'(100)を扱えるように拡張しましょう。
 まずテストを追加します。
-----
  eval("#03:90",cnvRoman(90),"XC");
  eval("   :99",cnvRoman(99),"XCIX");
  eval("   :100",cnvRoman(100),"C");
  eval("   :130",cnvRoman(130),"CXXX");
  eval("   :140",cnvRoman(140),"CXL");
  eval("   :150",cnvRoman(150),"CL");
  eval("   :160",cnvRoman(160),"CLX");
  eval("   :200",cnvRoman(200),"CC");
  eval("   :399",cnvRoman(399),"CCCXCIX");
-----

 テストが成功するように、テーブルに'C'(100)を追加します。

 :
  _size=split("1 5 10 50",_val);
  split("I V X L",_name);
  split("0 1 1 10",_dec);
 :

 ここを↓のように修正。

 :
  _size=split("1 5 10 50 100",_val);
  split("I V X L C",_name);
  split("0 1 1 10 10",_dec);
 :

 (省略しますが)テストは成功します。
 さらに大きな数も扱えるように拡張できそうですが、この辺で終わりにします。


1.3 まとめ
 今までのステップをまとめると次のようになります。

  1. Red
    テストを書いて失敗させる。
  2. Green
    テストを成功させる。Fake Itも許される。
  3. Refactor
    テストが成功する状態を保ちながら、リファクタリングを進める。

 こう言ってもいいでしょう。

  1. Fail It
  2. Fake It
  3. Make It
  4. Refactor It

 プログラムが間違いなくプログラマの意図するとおりに動作しているか、執拗に確認
しながら進んでいくTDDのやり方がわかっていただけましたでしょうか?

 それでは、また。