Hello, Stupid World! -4ページ目

Hello, Stupid World!

いろいろとメモ代わりに書いていきます。

オブジェクトをインスタンス化する場合、newを使います。
しかし、別にstaticメソッド等を用意してnewを使わずに
インスタンス化したい場面があると思います。

そのような時にnewを抑止するにはどうしたら良いか。

JavaではコンストラクタをPrivateに指定する事で実現します。
Rubyでも同じようにすればできるのではないかと考えました。

Rubyでは他言語のコンストラクタに似たものに
initializeメソッドというものがあります。
その為、initializeメソッドをPrivateにしてみたのですが
newメソッドは変わらず呼べてしまいました。

調べた所、initializeメソッドはコンストラクタと似ているものの
微妙に異なるようです。

initializeメソッドは実はClass.newから呼ばれるものであり
initializeメソッド自体を外から呼ばれないようにしても
newメソッドの抑止はできません。

newメソッドを抑止するには以下のように直接newメソッドを
privateにする必要があります。

 class AAA
   private_class_method :new
 end

Rubyではメソッドをプライベートにする方法に以下の
ようにprivateというキーワードを指定したいメソッドの前に
書くというものがあります。
この方法しか知らなかったので最初、方法が分かりませんでした。
class Book
   
private
   def AAA
     ~
   end

   def BBB
     ~
   end
 end

このように指定するとAAAメソッドとBBBメソッドがprivateになります。
「拡張に対してオープンであり、修正に対してクローズド
でなければいけない」
というのがタイトルのオープン・クローズドの原則(以下、OCP)です。

拡張というのはオブジェクト指向言語で言えば継承や委譲ですね。
それらを可能(オープン)な状態にしておく事でメンバを増やしたり
メソッドの変更を行えるようにします。

修正というのは呼出し側の修正です。
修正(呼出し側)に対してクローズドで(修正の必要が無い)
なければいけないという事です。

今までも似たような事を書きましたが、クラスを拡張した事で
利用している側まで修正をしないといけない作りというのは
不具合が起きる可能性が高く、とても危険な状態です。

この原則に注意して設計する事で不具合が起きづらいものと
なります。

では、一例のソースを。
[Log.java]
 public class ConsoleLog {
   public void output(String str){
     System.out.println(str);
   }
}
[LogFactory]
> public class LogFactory {
   public static ConsoleLog getInstance(){
     return new ConsoleLog();
   }
 }
[OCPMain]
 public class OCPMain {
   public static void main(String[] args) {
     
ConsoleLog log = LogFactory.getInstance();
     
log.output("テスト");
   }
 }


ConsoleLogというクラスを使ってコンソールにログを
出力しています。
上記のような状態はOCPには当てはまりません。
拡張に対しては特に抑止していないのでオープンな状態ですが
修正に対してクローズドになっていません。

例えば、上記のプログラムでコンソールに出力していたログを
ファイルに出力するよう仕様が変わった場合に
呼出し側も合わせて変更が必要になります。

[OCPMain.java]
 public class OCPMain {
   public static void main(String[] args) {
     
FileLog log = LogFactory.getInstance();
     log.output("テスト");
 }

OCPMainのConsoleLogとしていた箇所がFileLogに変える
必要がありました。
今回は一か所ですが、実務では数百か所の修正が必要に
なる場合があります。

では、どのような状態がOCPに当てはまっているかというと
以下のような状態です。

[Log.java]
 public interface Log {
   public void output(String str);
 }

[ConsoleLog.java]
 public class ConsoleLog implements Log {
   @Override
   public void output(String str){

     System.out.println(str);
   }
 }

[LogFactory.java]
 public class LogFactory {
   public static
Log getInstance(){
     return new ConsoleLog();
   }
 }


[OCPMain.java]
 public class OCPMain {
   public static void main(String[] args) {
     
Log log = LogFactory.getInstance();
     log.output("テスト");
   }
 }


見ての通りインタフェースを使う事でOCPMainのソースから
実クラスの参照を排除しています。
この状態ならばConsoleLogから別のログ出力するクラスに
変更したい場合はLogFactory内のインスタンス化している
箇所だけを変更すれば他は変更が必要ありません。

このように使用している箇所に影響を与えずに使用クラスを
変えられる状態が、「修正に対してクローズド」な状態と言えます。

今までの原則もそうですが、必ず守る必要はありませんが
多く使用していたり、重要なものである場合に使っていくと
柔軟かつ変更に強いシステムにする事ができます。
前回に引き続き、設計の話です。

単一責任の原則とは
オブジェクトは1つの責任(役割)のみを持つという原則です。
Single Responsibility Principle、略称はSRP

どんな状態がその原則に当てはまっているかというと
そのクラスの変更理由が一つしかない場合です。

例えば、DBに書き込むクラスだったらDBに書き込む内容が
変わった場合に変更理由になります。

もし、DBへの出力、ファイルへの出力を両方行っているクラス
があったとしたら、それはDBへの出力という役割とファイルへの出力
という役割があります。

また、DBのレイアウトが変わった場合にもファイルレイアウトの変更
があった場合にも変更理由となります。
そのような状態は多くの問題を含んでいます。

この状態はDBに出力するメソッドとファイルに出力するメソッド
があり、メンバ変数が共有されているか、どちらかにしか関係無い
メンバ変数が存在するでしょう。


まずメンバ変数が共有されている場合をコードで書くと

 public class DbAndFile {
     private String data;

     public DbAndFile(String data){
         this.data = data;
     }

     public void dbWrite(){ ~ data }

     public void fileWrite(){ ~ data }
 }


すごい簡略化してるけど、こんな状態。
これで例えば、ファイルに書き込むデータに改行を後ろにつけようとかして

     public DbAndFile(String data){
         this.data = data + "\n";
     }

コンストラクタをこんな風に変えちゃうと
DBに書き込む方(dbWrite)にまで影響が出ちゃう。

例では小さいクラスだし名前から複数の役割持ってる事が
分かりやすいけど、失敗する時は名前も抽象的で
クラスも大きくて気づきずらかったりする。

クラス名やメソッド名が抽象的なのは危険信号。
役割毎に変数もメソッドも分かれている場合は下のような場合

 public class DbAndFile {
     private String file_data;
     private String db_data;

     public void setFileData(String file_data){ this.file_data = file_data; }
     public void setDbData(String db_data){ this.db_data = db_data; }

     public void dbWrite(){ ~ db_data }
     public void fileWrite(){ ~ file_data }
 }


役割毎に共有していないから確かにどちらかの役割の方を
変更しても、もう片方に影響が無い。

この場合の問題は

・修正時にいつ共有するように変更してしまうか分からない
(共有するように変更できてしまう)

・誤ったメソッドを使いやすい
(DBに書きたいのにsetFileDataとしてしまったり)

・修正を間違えても気づきずらい
(DBに書き込むメソッドを変更しようとしてfileWriteを変更した場合や
両方を変更してしまった場合)

・重複した機能を持つクラスを作りやすい。

等がある。

ここまで一つのクラス内に複数の役割をもったケースなので
オブジェクト指向でしか関係無いと思うかもしれないけど
手続き型言語でもファイルで置き換えて考えると同じ事なので
言語に問わず注意が必要です。
品質とは設計により作り上げるもの、品質の最大値が設計で決まり
テストではその最大値になっているかを確かめるものだと思います。

という事で何回か設計について、話をしようと思います。

今回はリスコフ置換原則。略称LSP。
「子クラスは親クラスと置換可能でないといけない」という原則です。

具体例で書くと・・

 public class Price {
   int val;
   public int getVal() {
     return val;
   }
   public void setVal(int val) {
     this.val = val;
   }
 }


こんな感じの商品の価格を取り扱うPriceクラスがあったとします。
そのクラスを使う、金額と単価から個数を計算するメソッドが
作られたとします。

 public static void outAmount(Price price, int val, int total){
   price.setVal(val);
   int amount = total / price.getVal();
   System.out.println("個数:" + amount);
 }


この時、Priceクラスの子クラスとして以下のクラスがあった場合

 public class BookPrice extends Price { }

outAmountにPriceの子であるBookPriceを渡しても
正常に動作する為、リスコフ置換原則に当てはまる状態です。

しかし、子クラスが以下の場合

 public class TaxPrice extends Price {
   @Override
   public void setVal(int val) {
     super.setVal((int)(val * 1.08));
   }
 }

消費税を計算する為に作られた子クラスですが
outAmountにこのTaxPriceを渡しても個数の表示が
正常に行われません。
しかも、上記例だと値によっては個数が正常に計算される
ケースもあり、バグに気付きづらいです。

何が問題かと言うと、子クラスを作った事でその親クラスを
使っていた関数(outAmount)が正常に動作しなくなる事です。

このような時にリスコフ置換原則に則っていないと言い
避けるべき状態です。

当然、クラスを作る人とそのクラスを使う人が別人である事は
よくあります。
クラスを作る方、使う方で会社が異なるかもしれませんし
作られたクラスはフレームワークかもしれません。

どのような場合に原則から外れてしまうのか?
それは、「子クラスが親クラスの挙動を変更した場合」です。

先ほどの例では親クラスの持つvalには税抜価格が
期待されていたのに子クラスが税込価格で入れたのが原因です。

対処方法は.関数だけでなく専用の属性を
用意すれば良かったのです。

 public class TaxPrice extends Price {
   int tax_val;
   public int getTax_val() {
     return tax_val;
   }
   @Override
   public void setVal(int val) {
     super.setVal(val);
     this.tax_val = (int)(val * 1.08);
   }
 }


このようにすれば、親で定義したvalには税抜価格が
tax_valには税込価格が入ります。
また、getValは親と同様に税抜価格で取得できます。
親の挙動を変更していない訳です。

abstract以外のメソッドをオーバーライドする時には
慎重に行う必要があります。

他の対処法としては継承を辞めて委譲を使うとかですね。
(継承しなければ親の挙動を変えてしまう事も無いし、
親クラスの代わりに使われてしまう事も無い)

継承はいらないメソッドや属性まで引き継いでしまうという問題も
ありますし継承より委譲をまず考えて委譲ではできない場合に
継承を使う事をお勧めします。
YARDとはRubyのコメントからAPIドキュメントを作ってくれるライブラリ
Java知ってる人なら、Javadocみたいのと言えば分るかな。

いつも通り、説明するより具体例を書いていっちゃいます。

インストールはいつものように「gem install yard」でOK

APIドキュメントを作るにはプロンプトから
「yardoc ファイル名」と指定します。



ドキュメントが終わると上記のように内訳が表示されます。
実行した場所配下に「doc」フォルダと大量のhtmlが作成されます。

トップレベルのhtmlを開くと



こんな感じでクラスやメソッドが一覧表示されてました。
YARDの構文に乗っ取ってコメントを書いておけば
パラメータや戻り値も明記されます。




各メソッドの左下にある「View source」リンクをクリックすれば
対応するソースコードもきちんと表示されます。



YARDのコメント書式は以下のように記述します。

 # 指定日付によりDAYテーブルからレコードを取得
 # @param [int] day 日付
 # @return [Hash] Dayテーブルのレコード。指定日付に該当するものが無い場合は新規に作った物が帰る。


通常のコメント(#)の後ろに@paramとか@returnと書いてあるのが
YARDの書式です。
@paramは引数を表し、@returnは戻り値を表します。

他にも色々ありますが、以下のものをよく使いそう。
@raise 例外を表す
@option ハッシュ引数を表す
@author 作者名
@example サンプルコード
@note 注釈
@todo TODO


RubyにはRDocという似たものも標準でついているそうですが
それを知る前にYARDを使ってましたし、YARDのが高機能ぽいので
こっち使ってます。
Ruby書くようになって、やはり専用のエディタが欲しい・・・
という事でよさそうなのを探しました。

恋に落ちるエディタとして有名な?Sublime Textを試してみました。

http://www.sublimetext.com/

恋に落ちるエディタって誰が言い出したのかと思ったら公式に書いてありました。

3のβ版がありましたが、とりあえず安定板の2をインストール。

インストール途中にコンテキストメニュー(右クリックで表示されるメニュー)に
追加するか設定できるので追加しておくと便利です。

シェアウェアですが評価版として無料で使えて機能制限はありません。
時々、保存時にメッセージが表示されるだけらしいです。

画面はこんな感じで結構、シンプルです。
予約語や文字列を自動で判断してくれているみたいで、色分け表示されて見やすいです。

簡単に機能を説明します。
数が多いので自分が使いそうなものを。


[Multiple Selections]
Ctrl押しながら、クリックしてくことで複数のカーソルを置いて同時編集できます。
また、他機能と組み合わせる事で全行の文末を同時編集したり
特定の単語を全て同時に編集したりできます。



32,34,35行目の先頭に同時に/をいれたとこです。


[Soft Undo]
カーソル位置を前に戻す。付いているとやっぱり便利。


[Minimap]
画面端にファイル内容がプレビュー画面として縮小表示されて
クリックした位置を表示できます。
何が書いてあるかまではプレビューじゃ読めないけど、前に編集していた所など
を形や色で意外と判断できる。



[Layout]
さまざまな形にレイアウトを変更できます。
複数のファイルを横に並べたり、縦に並べたり同時表示できます。
下はグリッド状に並べたところ。



[Hot Save]
編集すると自動的にファイルとは別に保存され、次に開いた時には
閉じた際のものが表示されます。
その為、編集中にエディタが落ちたりしても落ちる前の状態から続けられます。
保存状態に戻すのも「Revert File」というメニューを選択するだけ。


[Paste and Indent]
貼り付け時にインデントを合わせてくれます。


[Sort Lines]
エディタとしては珍しいソート機能。
エクセルに貼り付けソートとかしてなくて良くなるのでけっこう便利。


[Toggle Comment / Toggle Block Comment]
選択行(選択ブロック)をコメントにします。


[Permute Lines]
行の順番を反転したり、重複する行を削除できます。
これもエクセルでやらなくてよくなるので便利ー。


[Switch Header / Implementation]
C言語系で便利そうな、実装ファイルを開いている時に
関連するヘッダーファイルを開き、ヘッダーファイルを開いてる時には
実装ファイルを開く機能。


[Toggle Bookmark]
カーソル位置をブックマークします。
ブックマークしていれば簡単に移動可能。


[Snippets]
スニペットとして文字を登録しておくことで簡単に呼び出せます。



こんな感じで表示されるので、適したものを選択します。
例えば、「Snippet: #!/usr/bin/env」を選択すれば
#!/usr/bin/envとカーソル位置に出力されます。
よく使うものを登録しておけば忘れたときでも簡単に記述できます。


[Build]
エディタはプログラムを実行できない。そう思っていた時代がありました。



Rubyの簡単なテストプログラムを実行した所。
何も設定していないのに実行できてスゴイ・・・
他にもCやJava,Pythonなんかも実行できます。


[package]
パッケージ管理システムを入れ、様々なパッケージを追加する事で
色々な機能が使えます。

とりあえずSublimeCodeIntelというコード補完してくれるパッケージを
入れました。



上記のように入力途中の文字から候補が表示され、選択することで
補完されます。



iを入力後、ife~で補完した所。

このパッケージは他にも関数の定義元にジャンプすることもできます。
対応している言語はRuby, JavaScript, PHP, Perl, Python, CSS, XML, Node.js,
Tclなど24種類。
すごすぎて気持ち悪い。

パッケージは様々なものがあり、文法チェックしてくれるものや
ファイルの差分を比較するもの、簡易バージョン管理してくれるもの、
SVNやGitに連携してくれるもの等、すごい数があります。


Ruby用にインストールしましたが、他の言語やHTMLの編集時等
色々な場面で使えそうです。

前にrspecというBDDのフレームワークについて解説しましたが
今回はBDDやTDDの進め方について実践していきます。

TDDは求められるテストをまず記述する。
BDDは求めらる振る舞いをまず記述する。
二つは主体とすべきものが異なりますが手順的にはかなり似ています。

では、実際のソースコードを交えながら解説。
今回は勤怠システムで使うような出勤時間が休憩時間中だったら
休憩時間後に調整するadjustStartTimeという関数を作ります。


1. まずは失敗するコードを書く

初めての人はこの工程に疑問を持つかもしれませんが、理由は次に。

[テストコード]
 describe 'adjustStartTime Test' do
     context 'exist breaktime 22:00-22:30' do
         subject {adjustStartTime('22:05:00')}
         it {should eq '22:30:00'}
     end
 end


22:00~22:30が休憩時間の場合に22:05が出勤時間ならば22:30に
なるはずというテストコードです。

ここに対して失敗する実コードを書く。

[実コード]
 def adjustStartTime(starttime)
     return ''
 end


どんな引数を渡されても空文字を返す。
この状態でテストコードを動かし、エラーを確認したら
次のステップに移ります。


2. 正常となる仮の実装を行う。(仮実装)


 def adjustStartTime(starttime)
     return '22:30:00'
 end


求められる値を固定で返しているので正常となります。
これも当たり前と思いますよね。
この1,2の確認により、テストコード自体が正常かどうかが分かります。

TDD(BDD)のフレームワークが正常に動いているか。
テスト対象の関数を呼べているか。等

3. テストコードの追加(三角測量)


他のテストケースも追加していきます。

[テストコード]
 describe 'adjustStartTime Test' do
     context 'exist breaktime 22:00-22:30' do
         subject {adjustStartTime('22:05:00')}
         it {should eq '22:30:00'}
     end

     context 'out of breaktime 22:00-22:30' do
         subject {adjustStartTime('21:00:00')}
         it {should eq '21:00:00'}
     end

 end


休憩時間22:00~22:30に対して21:00に出勤したケースです。
この場合は休憩時間外なので出勤時間の調整は行なわず
そのままの値が返るべきです。

しかし、今の実コードでは固定値で22:30と返しているだけなので
エラーとなります。

4.関数を動作するよう作っていく(明白な実装)


ここからは関数を仕様通りに作っていきます。

[実コード]
 def adjustStartTime(starttime)
     breaktime = BREAKTIME.where('start_time <= ? AND ? <= end_time', starttime, starttime).first
     if breaktime then
         return breaktime[:end_time]
     else
         return starttime
     end

 end


DBから出勤時間が含まれる休憩時間を取得してあった場合には
休憩時間の終わりを返す、無ければ出勤時間をそのまま返すようにしました。
テストコードを動かした所、エラーとなりました。

原因を探ると、戻ってくる値としては文字列を期待しているのに
DBから取得した時間は型がtime型になっていた為です。
そこで以下のように修正。

[実コード]
 def adjustStartTime(starttime)
     breaktime = BREAKTIME.where('start_time <= ? AND ? <= end_time', starttime, starttime).first
     if breaktime then
         return breaktime[:end_time].strftime("%H:%M:%S")
     else
         return starttime
     end
 end


無事にテストコードもを動かしてもエラーが出なくなりました。


5. リファクタリング

ここまでで正常に動作するものは作れたのですが
可読性、保守性を高める為にリファクタリングをします。

ここではDB検索部分のプレースホルダを?とするより
名前付きプレースホルダを使い分かりやすくしました。

[実コード]
 def adjustStartTime(starttime)
     breaktime = BREAKTIME.where('start_time <= :starttime AND :starttime <= end_time', {starttime: starttime}).first
     if breaktime then
         return breaktime[:end_time].strftime("%H:%M:%S")
     else
         return starttime
     end
 end


最終的にこんな形になりました。
テストコードを動かす事でリファクタリング時のミスもすぐさま見つかります。


TDD/BDDまとめ

TDD/BDDは期待するテスト(振る舞い)を最初に書き、実装していく手法です。
面倒に感じるかもしれませんが、この行為を行っていくことで
実装したコードが正常に動作するものか着実に確認していくことができます。

TDD/BDD以外の方法として似たものにデバッガやアラートを使うものが
ありますが、一つのテストケースごとしかできない、挙動が変わってしまう
等の問題があります。

テストの理想形は1行変更したら、すぐテストする事です。
正常に動作していたものが1行変更してエラーが出れば
すぐさまその行が問題と分かります。
もし、数十行、数百行変更した後にテストするならば問題がある行を
特定するだけで多くの時間がかかるでしょう。

TDD/BDDにより上記の理想に近いテストが行えます。
また、最初からテストコードを書くことで、テストしやすいコードを書けます。
テストがしやすいコードというのは再利用しやすく、分かりやすいコードです。

是非ともマスターしてみて下さい。
Rubyではライブラリ(gem)使用時は環境内にある最新の物が使
われます。
しかし、gemの新しいバージョンを入れ、仕様が変わっていた
場合には動かなくなってしまう事があります。

そこで、プログラム内で使用するライブラリ(gem)のバージョンを
管理する必要があり、その為のツールがbundlerです。
gemは特にバージョンアップが頻繁なのでほぼ必須となっています。

インストールはいつもと同じように「gem install bundler」です。


bundlerで管理するライブラリを指定する為にGemfileという
設定ファイルが必要です。
bundle init コマンドでGemfileのテンプレートが作成されるので、これを
修正すると簡単。


c:\tmp\ruby\sinatra\TimeSheets>bundle init
DL is deprecated, please use Fiddle
Writing new Gemfile to c:/tmp/ruby/sinatra/TimeSheets/Gemfile

中身は

[Gemfile]
# A sample Gemfile
source "https://rubygems.org"

# gem "rails"


まあ、サンプルはこれだけなんだけど。

sourceにはライブラリを探しに行く先を記述します。
普段gem install ~で探しに行っている https://rubygems.orgを
書いておきましょう。

コメントになっている gem "rails"ってのがサンプルで
このように必要なライブラリはgem ~と指定します。
自分のプログラムに必要なライブラリをgem 文で追加しましょう。

使用するrubyのバージョンも「ruby 'バージョン'」と記述することで
指定可能です。

自分が作ったGmefileはこんな感じ。

[Gemfile]
source "https://rubygems.org"
ruby '2.0.0'

gem 'pg'
gem 'activerecord'
gem 'sinatra'
gem 'sinatra-contrib'



Gemfileができたら「bundle install」コマンドを実行します。


ちなみに自分の環境でbundle install を行った所、
以下のエラーが発生しました。

Gem::InstallError: The 'json' native gem requires installed build tools.

このエラーが発生した場合はDevKitというものを入れてください。
詳細は「http://yohshiy.blog.fc2.com/blog-entry-240.html」に
分かりやすく書いてありました。

bundle  installにより必要なライブラリがインストールされます。
同時にGemfile.lockというファイルが作成され、インストールした
ライブラリの依存関係が保存されます。

他環境で使う時にはこのlockファイルを配布したうえで
bundle installすれば 同じライブラリが使用できます。

インストールした場所は「bundle show ~」コマンドで表示されます。

また、アプリケーション内に require 'bundler/setup' を書くことで
以降のrequireで使われるバージョンがGemfile.lockの
ものになります。

アプリケーション実行時はbunle exec コマンドとします。
普通にrubyアプリケーションならばbundle exec ruby ~
rackアプリケーションならbundle exec rackup ~

このbundlerを使う事でライブラリのバージョンよるエラーを
なくせるので、使ってみて下さい。
 RSpecというRubyのフレームワークを使いながらBDDを解説していきます。

まず、BDDとは何かですが。
これはbehavior driven developmentの略称であり、日本語にすると
振る舞い駆動開発となります。
簡単に言ってしまえばTDD(test-driven development:テスト駆動開発)と
似たようなものです。

TDDはテストする為のコードを書いてから実コードを書いて
テストを繰り返しながら作っていくという開発スタイルです。
その為のフレームワークとして、JUnitとかCUnitとかのxUnitが有名です。
TDDの詳細はグーグル先生に聞いてみて下さい。
基本はBDDも同じです。

TDDとBDDの違いですが、TDDはあくまでテストが主体です。
それに対してBDDは振る舞いが主体であり、振る舞いを記述してから
振る舞いに対するテストを書いていきます。

例で言うと・・・
数字でない文字が渡された場合にエラーを返すクラスがあったとします。

これをTDDで記述した場合、いくつかのテストメソッドが作られます。
数字だけの場合のテストメソッド、英字だけの場合のテストメソッド、
数字と英字がまじった場合のテストメソッドなど。
しかし、それらのテストメソッドはそれぞれ関連性がありません。

BDDではまず振る舞いを記述します。
上記例で言うと、数値チェックを行うという振る舞いを記述し、その下に
各テストケースが記述されます。
つまり、各テストケースは数値チェックに対するものという関連性が
生まれます。

インストールはいつも通りプロンプトでgem install rspecでOKです。
Rubyインストールフォルダ配下のbinフォルダにもパスを通しておくと
わざわざフォルダ移動せずにコマンド実行できて便利です。

テスト対象は以下の簡単なクラス。

[num.rb]
 class Num
   def initialize(x)
     @x = x;
   end
   def add(y)
     @x + y;
   end
 end


クラスの説明は今までしていませんでしたがそれほど難しくないです。
classキーワードで定義する事と@変数でインスタンス変数が定義できる
ことだけとりあえず知ってもらえばいいかな。

次にrspecのテストコード。
色々な書き方ができるようですが、以下のように書いてみました。

[num_spec.rb]
 require '.\num'

 describe Num do

     context 'result is plus' do
         subject { Num.new(1).add(2) }
         it { should eq 3 }
         it { should_not eq -3 }
     end

     context 'result is minus' do
         subject { Num.new(1).add(-3) }
         it { should eq -2 }
         it { should_not eq 2 }
     end
 end


最初、見たときにすごいキレイに書けるんだなーと思いました。
何というかすごい文章的に書けるんですよね。構造的というか。
しかもそれが動作に直結している。
そこがBDDでよく言うテストコードがそのままドキュメントになる
っていう所なんでしょうね、きっと。

describeでまず全体として何の振る舞い対してのテストかを記述します。
そのクラスが存在しなければエラーとなります。
説明でもあり存在チェックにもなっている。

次にcontext。どんな振る舞いをテストするか概要を記述します。
subjectにはどんな振る舞いをテストするか実際に記述します。
このsubjectの結果がその後に使われます。

で、it~でsubjectの結果となる期待値を記述します。
期待値が異なればrspec実行時に分かる。

it should eq 3とかテストコードなのに、すごい英語の文章ぽい。
あと、変数とか使わずにit~、it~と連続して書けるのもいい感じ。

実行結果はこんな感じ。
あ、実行はrspec~で実行します。

[正常時]
 c:\tmp\ruby>rspec num_spec.rb
 ....

 Finished in 0.005 seconds (files took 0.34502 seconds to load)
 4 examples, 0 failures


[エラー存在時]
F...

Failures:

  1) Num result is plus should eq 2
     Failure/Error: it { should eq 2 }

       expected: 2
            got: 3

       (compared using ==)
     # ./num_spec.rb:7:in `block (3 levels) in <top (required)>'

Finished in 0.005 seconds (files took 0.35202 seconds to load)
4 examples, 1 failure

Failed examples:

rspec ./num_spec.rb:7 # Num result is plus should eq 2


テストコード自体が文章に近い形で、それらを組み合わせて
エラーメッセージが表示されるので、それもまた文章にかなり近くなります。
Failure/Error: it { should eq 2 }
とかね。

命令とか覚えたり、文章ぽくなるように考えたり
最初の敷居はある程度あるけど、うまくできるようになれば
そのままテストコードが英語ドキュメントとして使えるし、キレイにコードを
かけるようになりそうです。
前回はhtmlも書いてない簡単な例だったのでもう少し実践的なやつを。

テキストボックス二つに値を入れて送信ボタンを押すと合計が
下に出てくるという簡単な足し算アプリです。



ではソースコードを説明していきます。
まずはクライアント側。

[index.erb]

 <!DOCTYPE html>
 <html lang="ja">
     <head>
         <meta charset="utf-8">
         <title>Test</title>
     </head>
     <body>
         <form action="http://localhost:4567" method="post">
             <h4>足し算</h4>
             <input type="text" name="arg1">
             <input type="text" name="arg2">
             <input type="submit"><br/>
             <span><%= @result %></span>
         </form>
     </body>
 </html>


erbというファイルですが、JavaでのJSPと同じように使ってます。
erbは別にWebに限ったものでないので、テンプレート的に色々なものに
使えます。
メールライブラリと合わせて、本文のテンプレートにerbを使ったり。

次にサーバ側。

[main.rb]
 require 'sinatra'

 get '/' do
     erb :index
 end

 post '/' do
     @result = (params[:arg1].to_i + params[:arg2].to_i).to_s
     erb :index
 end


erb :indexという命令でviewsフォルダ配下にあるindex.erbが表示されます。
postメソッドが来た場合はerbを表示する前にパラメータを数値変換してから
合計してインスタンス変数(アットマークついているもの)にセットしています。

大きいシステムを作る場合ならばRailsとか使うのが良いでしょうけど
小さいものならシンプルで使いやすいSinatraいい感じです。