作者: Yさ
日時: 2005/4/1(03:13)
 [続き]
 次に、'百'(100)を扱えるように拡張してみましょう。
 テストケースを追加し、'百'(100)を返すようロジックを追加します。
-----
  eval("#03: 100",cnvJNum("100"),"==","百");
-----

----- ['百'ロジック追加途中]
function cnvJNum_(num,  name,wk,buf){
  if(length(num)==1){
    split("一,二,三,四,五,六,七,八,九",name,","); name[0]="";
    if(1<=num+0 && num+0<=9) return name[num+0];
    return name[0];
  }
  wk=substr(num,1,1);
  buf=(wk=="1")?"":cnvJNum_(wk);
  if(length(num)==3) return buf "百" cnvJNum_(substr(num,2));
  return buf "十" cnvJNum_(substr(num,2));
}
-----

=====
 :
err: #03: 100=[百十] == 百

... でもあんたのテスト、失敗しちゃってますから! 残念!!
18個のテストで、失敗1個、斬り!
=====

 まだ失敗しています。
 どうやら変換する際に0かどうかを意識しないとダメなようです。

-----
 :
  wk=substr(num,1,1);
  buf=(wk=="1")?"":cnvJNum_(wk);
  if(length(num)==3) return buf "百" cnvJNum_(substr(num,2));
  return buf ((wk=="0")?"":"十") cnvJNum_(substr(num,2));
}
-----

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

 続けて、重複の除去=リファクタリングします。

-----  [リファクタリング途中]
 :
  if(length(num)==3){
    wk=substr(num,1,1);
    buf=(wk=="1")?"":cnvJNum_(wk);
    return buf ((wk=="0")?"":"百") cnvJNum_(substr(num,2));
  }
  if(length(num)==2){
    wk=substr(num,1,1);
    buf=(wk=="1")?"":cnvJNum_(wk);
    return buf ((wk=="0")?"":"十") cnvJNum_(substr(num,2));
  }
  return "";
}
-----
 ↓
-----  [ループで重複除去(テーブル導入)、他]
 :
  split("十,百",unitName,",");
  n=split("2,3",unitPos,",");
  for( ; n; --n){
    if(length(num)==unitPos[n]){
      wk=substr(num,1,1);
      buf=((wk=="1")?"":cnvJNum_(wk)) ((wk=="0")?"":unitName[n]);
      return buf cnvJNum_(substr(num,2));
    }
  }
  return "";
}
-----

 (省略しますが)リファクタリング途中で、テストが失敗しないか都度確認します。

 では、101以上のテストケースを追加してみましょう。
-----
 :
  eval("   : 101",cnvJNum("101"),"==","百一");
  eval("   : 110",cnvJNum("110"),"==","百十");
  eval("   : 111",cnvJNum("111"),"==","百十一");
  eval("   : 123",cnvJNum("123"),"==","百二十三");
  eval("   : 678",cnvJNum("678"),"==","六百七十八");
  eval("   : 999",cnvJNum("999"),"==","九百九十九");
-----

 (省略しますが)そのままでテストは成功しました。

 そんな時もあります。


 では、どんどん続けます。
 まず1000以上のテストケースを追加します。
-----
 :
  eval("#04: 1000",cnvJNum("1000"),"==","千");
  eval("   : 1001",cnvJNum("1001"),"==","千一");
  eval("   : 1011",cnvJNum("1011"),"==","千十一");
  eval("   : 1111",cnvJNum("1111"),"==","千百十一");
  eval("   : 1234",cnvJNum("1234"),"==","千二百三十四");
  eval("   : 5678",cnvJNum("5678"),"==","五千六百七十八");
  eval("   : 9999",cnvJNum("9999"),"==","九千九百九十九");
-----

 テーブルに'千'(1000)を追加します。
-----
 :
  split("十,百,千",unitName,",");
  n=split("2,3,4",unitPos,",");
 :
-----

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

 続けて、10000以上のテストケースを追加します。
-----
 :
  eval("#05: 10000",   cnvJNum(   "10000"),"==","一万");
  eval("   : 100000",  cnvJNum(  "100000"),"==","十万");
  eval("   : 1000000", cnvJNum( "1000000"),"==","百万");
  eval("   : 11111111",cnvJNum("11111111"),"==","千百十一万千百十一");
  eval("   : 54321987",cnvJNum("54321987"),"==","五千四百三十二万千九百八十七");
  eval("   : 99999999",cnvJNum("99999999"),"==","九千九百九十九万九千九百九十九");
-----

 テーブルに'万'(10000)を追加します。
 10000からは"一"万とするようにロジックを変更します。
 '千万'の桁まで変換を考慮するロジックを追加します。

----- [変換する桁の範囲指定、他]
function cnvJNum_(num,  name,wk,buf,unitName,unitPos,n){
  if(length(num)==1){
    split("一,二,三,四,五,六,七,八,九",name,","); name[0]="";
    if(1<=num+0 && num+0<=9) return name[num+0];
    return name[0];
  }
  split("十,百,千,万,",unitName,",");
  n=split("2,3, 4, 5, 9",unitPos,",");
  for(--n ; n; --n){
    if(unitPos[n]<=length(num) && length(num)<unitPos[n+1]){
      wk=substr(num,1,(length(num)-unitPos[n]+1));
      buf=((wk=="1" && unitPos[n]<=4)?"":cnvJNum_(wk)) ((wk=="0")?"":unitName[n]);
      return buf cnvJNum_(substr(num,(length(num)-unitPos[n]+1)+1));
    }
  }
  return "";
}
-----

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

 続けて100000000以上のテストケースを追加します。
-----
 :
  eval("#06: 100000000",cnvJNum("100000000"),"==","一億");
  eval("   : 100100000",cnvJNum("100100000"),"==","一億十万");
  eval("   : 123456789012",cnvJNum("123456789012"),"==","千二百三十四億五千六百七十八万九千十二");

  eval("#07: 1000000000000",cnvJNum("1000000000000"),"==","一兆");
  eval("   : 1010000000000",cnvJNum("1010000000000"),"==","一兆百億");
  eval("   : 9876543210987654",cnvJNum("9876543210987654"),"==","九千八百七十六兆五千四百三十二億千九十八万七千六百五十四");
-----

 テーブルに'億','兆'を追加します。
-----
 :
  split("十,百,千,万,億,兆,",unitName,",");
  n=split("2,3, 4, 5, 9,13,17",unitPos,",");
 :
-----

=====
 :
err: #06: 100000000=[一億万] == 一億
err: #07: 1000000000000=[一兆億万] == 一兆

... でもあんたのテスト、失敗しちゃってますから! 残念!!
43個のテストで、失敗2個、斬り!
=====

 まだテストに失敗します。
 どうやら条件が不足しているようです。

-----
 :
    if(unitPos[n]<=length(num) && length(num)<unitPos[n+1]){
      wk=substr(num,1,(length(num)-unitPos[n]+1));
      buf=((wk=="1" && unitPos[n]<=4)?"":cnvJNum_(wk)) ((wk=="0")?"":unitName[n]);
      return buf cnvJNum_(substr(num,(length(num)-unitPos[n]+1)+1));
    }
 :
-----
 ↓ここを修正します
-----
 :
    if(unitPos[n]<=length(num) && length(num)<unitPos[n+1]){
      wk=substr(num,1,(length(num)-unitPos[n]+1));
      buf=((wk=="1" && unitPos[n]<=4)?"":cnvJNum_(wk)) ((wk==substr("0000",1,length(wk)))?"":unitName[n]);
      return buf cnvJNum_(substr(num,(length(num)-unitPos[n]+1)+1));
    }
 :
-----

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

 さらに大きな数も扱えるように拡張できそうですが、この辺で終わりにします。


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

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

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

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

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