give IT a try

プログラミング、リモートワーク、田舎暮らし、音楽、etc.

JavaやC#の常識が通用しないRubyのprivateメソッド

衝撃を受けたできごと

最近Rubyを勉強しています。
JavaやC#でオブジェクト指向プログラミングの基本はマスターしてるから、Rubyもそのあたりは楽勝〜!・・・と思っていたら、JavaやC#の常識が全く通用しない振る舞いに遭遇してかなり衝撃を受けました。それは、


privateメソッドはサブクラスからも呼び出せる


・・・ということです!!がーん。


たとえば、JavaやC#だと自分のクラス内でprivateメソッドが使われていない場合、不要なメソッドとして削除できます。(リフレクションを使って呼び出される可能性はここでは無視ね)
しかし、Rubyでは誰かがサブクラスを作って呼び出している可能性があるので、privateメソッドを削除する場合は注意が必要です。メソッド名を変更する場合も同様ですね。


また、知らずに親クラスと同名のprivateメソッドを定義すると、予期せず親クラスの実装をオーバーライドしてしまうことにもなります。
なので、Rubyの教科書には継承を使う時は十分注意せよと書いてありました。


じゃあ、protectedメソッドはなんやねん!?ということになりますが、これはサブクラスがインスタンス経由で呼び出せる親クラスのメソッドを表します。
言い換えるとprivateメソッドの場合、サブクラスはインスタンス経由で親クラスのprivateメソッドを呼び出すことはできません。


だんだん話がややこしくなってきたので、コードで確認してみましょう。

サンプルコード

親クラスHogeとサブクラスPiyoを作って色々と実験してみました。
解説は実行結果の後に載せています。
ただ、ちょっと長いのでWebだと一画面に収まらないかもしれません。見にくくてごめんなさい。

親クラスの定義
class Hoge
  protected
  def a_protected_method
    "PROTECTED! - #{do_not_override_me}"
  end
 
  private
  def a_private_method
    "PRIVATE! - #{do_not_override_me}"
  end
 
  #May be overridden by sub class
  def do_not_override_me
    "I am Hoge."
  end
end
サブクラスの定義
class Piyo < Hoge
  def call_a_protected_method
    a_protected_method
  end
 
  def call_a_private_method
    a_private_method #OK!!
  end
 
  def call_a_protected_method_via(hoge_or_piyo)
    hoge_or_piyo.a_protected_method
  end
 
  def call_a_private_method_via(hoge_or_piyo)
    hoge_or_piyo.a_private_method #NG!!
  end
  
  private
  # Override a private method in super class and called from super class
  def do_not_override_me
    "I am Piyo."
  end
end
実行用コード
def puts_safe
  puts yield
rescue => e
  puts "ERROR!! - #{e}"
end

hoge = Hoge.new
piyo = Piyo.new

puts "<<Call a method in super class>>"
puts_safe { piyo.call_a_protected_method }
puts_safe { piyo.call_a_private_method } #OK!!
puts "\n"

puts "<<Call a method via the instance of super class>>"
puts_safe { piyo.call_a_protected_method_via hoge }
puts_safe { piyo.call_a_private_method_via hoge } #ERROR!!
puts "\n"

puts "<<Call a method via the instance of sub class>>"
piyo2 = Piyo.new
puts_safe { piyo.call_a_protected_method_via piyo2 }
puts_safe { piyo.call_a_private_method_via piyo2 } #ERROR!!
実行結果
<<Call a method in super class>>
PROTECTED! - I am Piyo.
PRIVATE! - I am Piyo.

<<Call a method via the instance of super class>>
PROTECTED! - I am Hoge.
ERROR!! - private method `a_private_method' called for #<Hoge:0x9c74814>

<<Call a method via the instance of sub class>>
PROTECTED! - I am Piyo.
ERROR!! - private method `a_private_method' called for #<Piyo:0x9c745d0>
解説

「a_private_method」というprivateメソッドが親クラスHogeにあって、そのメソッドをサブクラスPiyoから呼び出しています。
サブクラスPiyoでは単純に親のメソッドをコールする場合と、メソッドに渡されたインスタンス経由でコールする場合の2パターンを作りました。
実行結果を見ると、前者(call_a_private_method)は呼び出しに成功していますが、後者(call_a_private_method_via)は失敗しています。


また、親クラスHogeで「do_not_override_me(いいか、絶対俺をオーバーライドするなよ)」というprivateメソッドを作ったにも関わらず、サブクラスPiyoではバッチリオーバーライドできています。(出力結果で"I am Hoge."と"I am Piyo."が状況によって切り替わっている点に注目)

今回の件で学んだこと

というわけで、どの言語でもたぶん同じだろうとタカをくくっていると、意外な落とし穴にはまってしまいます。
新しい言語を学ぶ時はそれまでの常識が通用しない部分に十分注意する必要があるなあと反省しました。
みなさんも注意してくださいね。

追記(2012.03.15 09:15)

思い切ってRubyのパパこと、Matz先生に設計思想を聞いてみたら回答してもらえました〜!うれしいです。

なるほど〜。Smalltalkの影響ですか。
確かにRubyが誕生したころはJavaも知られてなかったですもんね。
やはり歴史や思想を確認すると、より理解が深まります。
どうもご回答ありがとうございました!

参考書籍

プログラミング言語 Ruby

プログラミング言語 Ruby

  • 作者: まつもとゆきひろ,David Flanagan,卜部昌平(監訳),長尾高弘
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2009/01/26
  • メディア: 大型本
  • 購入: 21人 クリック: 356回
  • この商品を含むブログ (129件) を見る

あわせて読みたい

英語ブログを書いてredditに投稿してみた - give IT a try
このエントリを英語ブログに翻訳した時の一部始終を紹介しています。


Rubyのクラスメソッドは同じクラスのprotectedメソッドやprivateメソッドにアクセスできない - give IT a try
これもC#/Javaプログラマが意外に思うようなRubyの言語仕様です。

レガシープログラマさんと一緒にリファクタリングをする、の巻

前回のエントリではレガシープログラマの判断項目について、書きました。
その日、仕事でレガシープログラマさんの一人が書いたプログラムを一緒にリファクタリングしました。
レガシープログラマさんと言っても、おいらより年下の女性エンジニアです。
今回のエントリではそのやりとりについて書いてみたいと思います。

元のプログラムはどんなプログラム?

そのプログラムは以下の判断項目に該当していました。

  • 複数のデータをまとめて扱う際は毎回配列を使う。配列の上限数はありえなさそうな数を指定する(1000とか)。
  • 基本データ型(stringやint)と配列だけでデータ構造を表現しようとする。
  • クラスのフィールド変数をグローバル変数のように利用する。


言語はC#2.0で、CSVを読み込んでメールを送信するプログラムです。
ただし、同じFromとToの組み合わせに対しては一通のメール内の複数のコンテンツを含めて送信します。
イメージ的にはこんな感じです。

csvの中身
MailFrom MailTo Content
Aさん Xさん おはよう
Aさん Xさん こんにちは
Aさん Yさん おはよう
Bさん Xさん こんばんは


このCSVの場合、3通のメールが送信されます。
1件目と2件目はFromとToが同じなので、一つのメールに「おはよう」と「こんにちは」を含めて送信します。


次にプログラムの改善前と改善後を示します。
ちなみにこの案件は新規開発ではなく、既存プログラムの機能追加です。
これもイメージです。実際はもっとややこしい感じで、コメントもほとんどありません。

改善前のプログラム
public class MailSender
{
    // メール送信情報テーブル
    // 10は送信先の最大件数(運用上、10通以上同時に送ることはないだろうという前提)
    // 3はCSVのカラムが3列だから3
    private string [,] mailInfoTable = new string[10, 3];
    
    private const string Separator = "|";
    
    public void SendMailFromCsvLines(string[] csvLines)
    {
        this.ReadCsvLines(csvLines);
        this.SendMailFromMailInfoTable();
    }
    
    private void ReadCsvLines(string[] csvLines)
    {
        // CSVの行数分ループをまわす
        foreach (string csvLine in csvLines)
        {
            // カラムを分割する
            string[] columns = csvLine.Split(',');
            
            // メール送信情報テーブルに取得した情報を格納する(最大10回ループする)
            for (int i = 0; i < this.mailInfoTable.GetLength(0); i++)
            {
                // 新しい行であればtrue
                if (string.IsNullOrEmpty(this.mailInfoTable[i, 0]))
                {
                    // MailFromとMailToを保存
                    this.mailInfoTable[i, 0] = columns[0];
                    this.mailInfoTable[i, 1] = columns[1];
                }
                
                // 既存行のMailFromとMailToが同じならtrue
                if (this.mailInfoTable[i, 0] == columns[0] &&
                    this.mailInfoTable[i, 1] == columns[1])
                {
                    // コンテンツをセパレータ文字列で区切って追加
                    this.mailInfoTable[i, 2] += Separator + columns[2];
                    break;
                }
            }
        }
    }
    
    private void SendMailFromMailInfoTable()
    {
        int i = 0;
        
        // メール送信情報テーブルを順番に読みこみ、なくなったら処理中止
        while (!string.IsNullOrEmpty(this.mailInfoTable[i, 0]))
        {
            // コンテンツはセパレータ文字列で区切られているので、分解してListに格納
            ArrayList contents = new ArrayList();
            foreach (string content in 
                this.mailInfoTable[i, 2].TrimStart(Separator).Split(Separator))
            {
                contents.Add(content);
            }
            
            // 既存のメール送信メソッドに送信処理を依頼する
            this.SendMail(this.mailInfoTable[i, 0],     // MailFrom
                            this.mailInfoTable[i, 1],     // MailTo
                            contents);                    // Contents
            i++;
        }
    }
    
    private void SendMail(string mailFrom, string mailTo, ArrayList contents)
    {
        // 既存メソッドなので実装は省略
    }
}


mailInfoTableというフィールド変数が

  • 複数のデータをまとめて扱う際は毎回配列を使う。配列の上限数はありえなさそうな数を指定する(1000とか)。
  • 基本データ型(stringやint)と配列だけでデータ構造を表現しようとする。
  • クラスのフィールド変数をグローバル変数のように利用する。

というアンチパターンをすべて兼ね備えてしまっています。
そのため、ReadCsvLinesメソッドやSendMailFromMailInfoTableメソッドの実装が分かりにくいものになってしまっています。


「this.mailInfoTable[i, 2] += Separator + columns[2];」というように複数のコンテンツを文字列で区切って格納しているところも強引というか、涙ぐましい工夫を凝らしたロジックになっています。(T T)


また、ReadCsvLinesメソッド内で同一のMailFromとMailToを探すロジックも、毎回配列をループで回しながら検索するので非効率です。


これを大体一時間ぐらいかけて下のようにリファクタリングしてみました。

改善後のプログラム
public class MailSender
{
    // メール送信情報を格納するDTOクラス
    private class MailInfo
    {
        public string MailFrom;
        public string MailTo;
        public ArrayList Contents = new ArrayList();
    }
    
    public void SendMailFromCsvLines(string[] csvLines)
    {
        // メール送信情報は戻り値で受け取る
        Dictionary<string, MailInfo> mailInfoTable = this.ReadCsvLines(csvLines);

        // メール送信情報は引数で渡す
        this.SendMailFromMailInfoTable(mailInfoTable);
    }
    
    private Dictionary<string, MailInfo> ReadCsvLines(string[] csvLines)
    {
        Dictionary<string, MailInfo> mailInfoTable = new Dictionary<string, MailInfo>();
        
        // CSVの行数分ループをまわす
        foreach (string csvLine in csvLines)
        {
            // カラムを分割する
            string[] columns = csvLine.Split(',');
            
            // CSVの値を変数に保存
            string mailFrom = columns[0];
            string mailTo = columns[1];
            string content = columns[2];
            
            // ディクショナリ用のキーを作成
            string key = mailFrom + mailTo;
            
            MailInfo mailInfo = null;
            if (mailInfoTable.ContainsKey(key))
            {
                // すでに同一のキーが存在していればディクショナリから取得
                mailInfo = mailInfoTable[key];
            }
            else
            {
                // 同一のキーが存在しなければMailInfoオブジェクトを新規に作成
                mailInfo = new MailInfo();
                mailInfo.MailFrom = mailFrom;
                mailInfo.MailTo = mailTo;
                
                // ディクショナリに追加する
                mailInfoTable.Add(key, mailInfo);
            }
            
            // コンテンツ情報を追加する
            mailInfo.Contents.Add(content);
        }
        
        return mailInfoTable;
    }
    
    private void SendMailFromMailInfoTable(Dictionary<string, MailInfo> mailInfoTable)
    {
        foreach (MailInfo mailInfo in mailInfoTable.Values)
        {
            // 既存のメール送信メソッドに送信処理を依頼する
            this.SendMail(mailInfo.MailFrom, mailInfo.MailTo, mailInfo.Contents);
        }
    }
    
    private void SendMail(string mailFrom, string mailTo, ArrayList contents)
    {
        // 既存メソッドなので実装は省略
    }
}

まあこの状態でも色々とツッコミどころはありますが、改善前よりかは可読性や堅牢性が増したはずです。
ポイントは以下の通りです。

  • MailInfoというクラスを作成し、メール送信情報のデータ構造を表現した点
  • フィールド変数を使わず、メソッドの戻り値と引数でメール送信情報を引き渡すようにした点
  • 複数のデータを格納するのに配列ではなくDictionaryクラスを使い、10件というシステム都合の上限数をなくした点
  • 同一のFromとToを検索するのに、ディクショナリのキーを利用した点

レガシープログラマさんの反応は?

リファクタリングはペアプログラミング方式というか、おいらが横について説明し、プログラムの入力はレガシープログラマさんにやってもらいました。
一通りコーディングと動作確認を行ったあとに、最後にまとめてロジックの説明しました。


自分でクラスを作ってデータを格納するようなプログラムはおそらくそれまで作ったことがないと思うので、理解してもらえるかどうか不安でしたが、大丈夫だったようです。
紙の上に図を書いたりして丁寧に説明したのが良かったのかもしれません。


そして、最後にぽつりと「感動しました」と言われました。


おいらも昔、すごい先輩の技術を目の当たりにして感動した事が何度かあります。
そうした体験が技術力を向上させたいと思う大きな原動力になりました。
おいらもそういう先輩のように誰かの手本になりたいと思いながら勉強してたりするので、非常に嬉しかったです。

2種類のレガシープログラマ

レガシープログラマの中にも昔のやり方に固執しようとするタイプの人と、単にモダンなプログラミングスタイルを知らないだけの人がいると思います。
前者の人とはぶつかりやすいかもしれませんが、後者の人はまだまだ伸びしろが大きいと思うので、こういった機会をもっと増やしていきたいですね。

レガシープログラマかどうかを判断する10項目

※2011.3.30追記
11個目の判断項目を追加しました。
また、「昔はね...」の補足説明を各項目に追加しました。

レガシープログラマ = モダンな言語のおいしい機能をうまく使いこなせていないプログラマ

おいらは時々社内システムのコードレビューなんかをやっているのですが、「なんかちょっと前時代的だな〜」とか「ちょっと修正したらC言語でもコンパイルできそうだな〜」って思うことがよくあります。


おいらがレビューする言語は主にC#です。C#やJavaのような比較的モダンな言語のおいしい機能をうまく使いこなせていないプログラマを、ここでは「レガシープログラマ」と呼ぶことにします*1


そこで、おいらがこれまでに見てきたコードの中から「これはレガシープログラマっぽい」と思った典型的な症例を10個11個挙げてみます。

レガシープログラマの判断項目

  1. 使われるローカル変数をすべてメソッドの最初に宣言する。
  2. ローカル変数の宣言時に空文字("")や新しいオブジェクト(new Xxx())で初期化する。その後にすぐ別の値をセットする。
  3. メソッドの戻り値がすべて成功・失敗を表す 0 か -1 になっている。
  4. 複数のデータをまとめて扱う際は毎回配列を使う。配列の上限数はありえなさそうな数を指定する(1000とか)。
  5. 基本データ型(stringやint)と配列だけでデータ構造を表現しようとする。
  6. 変数の命名規則にハンガリアン記法*2を使う。
  7. クラスのフィールド変数をグローバル変数のように利用する。
  8. 配列やリストを毎回forループで処理する(例: for (int i = 0; i < array.Length; i++))。
  9. クラスやクラスメンバの可視性を意識していない(privateメソッドがpublicになっている等)。
  10. 変更履歴をコード中にコメントとして残す (ADDやDELみたいなコメントがたくさん付いている)。
  11. 変数名やメソッド名を何かと略したがる。

判断項目に従って採点してみる

いかがでしょうか?自分の書いたプログラムを見て採点してみてください。


すべて当てはまるようであれば、あなたは立派な「レガシープログラマ」です。
残念ですがJavaやC#を使えているつもりでも、実はほとんど使いこなせていません。
今すぐ中級者〜上級者向けの本を読んで勉強しましょう。


また、該当項目が少なかったといって安心しないでください。
1つでも該当しているようなら、あなたのどこかにレガシープログラマの血が流れています・・・。
下の解説を読んでこの先自分が書くコードを改善していって下さい。


2015.10.26追記:この記事を書いた頃の自分の職場について
この記事を読むと人によっては「xxxみたいな環境だったら今でもこう書く」とか「こういう制約があったらレガシーとは限らない」みたいな異論が出てくると思います。
もちろんそういった異論が出てくるのは理解できるのですが、この記事を書いた時の職場には特に制約はなく、自由にC#のコードが書ける環境でした。

レガシープログラマの処方箋

1. 使われるローカル変数をすべてメソッドの最初に宣言する。
問題点
変数の寿命が長くなり、可読性が低下する。
改善方法
変数は必要になったタイミングで宣言する*3。できるだけ変数の寿命が短くなるようにする。
昔はね...
C言語では変数は関数の最初に宣言しなくてはいけなかった。VBも変数の宣言と代入が同時にできなかったので、最初にまとめて宣言されることが多かった。
2. ローカル変数の宣言時に空文字("")や新しいオブジェクト(new Xxx())で初期化する。その後にすぐ別の値をセットする。
問題点
無駄なオブジェクトが作成され、メモリを無駄遣いする。そのコードを書いたプログラマはオブジェクト参照の概念を理解できていない可能性が高い。
改善方法
変数は宣言と同時に必要な値をセットする。オブジェクト参照の概念を理解する。
昔はね...
C言語では変数の初期値が決まっていなかったので、必ず初期化する必要があった。
3. メソッドの戻り値がすべて成功・失敗を表す 0 か -1 になっている。
問題点
戻り値が無視され、予期せぬ不具合を生む。処理結果が引数やフィールド変数に格納され、可読性が低下する。
改善方法
エラー処理のベストプラクティスを導入する*4
昔はね...
C言語では例外機構がなかったので、処理の成功と失敗を戻り値で返すことが多かった。
4. 複数のデータをまとめて扱う際は毎回配列を使う。配列の上限数はありえなさそうな数を指定する(1000とか)。
問題点
上限が決まってしまうためにプログラムの柔軟性がなくなる。
改善方法
Listクラスなど、大きさを動的に変更できるコレクションクラスを利用する。
昔はね...
C言語やVBでは配列の大きさを簡単に変更する方法がなかった。
5. 基本データ型(stringやint)と配列だけでデータ構造を表現しようとする。
問題点
複雑なデータ構造を表現するのが困難になる。無理矢理表現しようとすると複雑怪奇なデータ構造ができあがる。
改善方法
注文クラスや社員クラスのようなデータをまとめて保持するクラスを作成する。
昔はね...
この問題についてはC言語でも構造体を使えば同じようなことができたはずだが。。。(構造体がネストしたり、構造体に配列を組み込んだりするとポインタの処理がややこしくなったとか??)
6. 変数の命名規則にハンガリアン記法を使う。
問題点
変数の型が変更されたら変数名の変更も必要になり、保守性が低下する。基本データ型以外の型が登場すると意味不明なPrefixが生まれる(「Order ordOrder = new Order()」みたいな)。
改善方法
ハンガリアン記法*5を廃止する。基本データ型以外の型やクラスが数多く登場することを理解する。
昔はね...
Microsoftが(誤解に基づいたまま)ハンガリアン記法*6を推奨していた。
7. クラスのフィールド変数をグローバル変数のように利用する。
問題点
処理の前後にコンテキスト(前提条件)が発生し、可読性が低下する。また予期せぬ不具合が発生しやすくなる。
改善方法
必要なデータのやりとりは引数と戻り値だけで完結するようにメソッドを設計する。データの数が多い場合は、データをまとめて保持するクラス*7を作成する。
昔はね...
項目3で説明した通り、戻り値が成功・失敗を表すことが多かったので、戻り値以外の方法(たとえばグローバル変数)でデータをやりとりしていた。
8. 配列やリストを毎回forループで処理する(例: for (int i = 0; i < array.Length; i++))。
問題点
終了条件を間違えることによって、予期せぬ不具合が発生する可能性がある。
改善方法
イテレータやforeach文など、ループカウンタを使わない処理方法に変更する(「foreach (string val in array)」等)。
昔はね...
C言語ではループ処理を実現する構文と言えばforループとwhileループぐらいしかなかった。
9. クラスやクラスメンバの可視性を意識していない(privateメソッドがpublicになっている等)。
問題点
カプセル化が不完全になり、コードの変更に弱くなる。また、使われていないクラスメンバを見つけにくくなる。
改善方法
可視性を正しく設定する。最低でもpublicとprivateの区別はつける。
昔はね...
C言語ではpublicやprivateといった可視性をコントロールする機能がなかった。
10. 変更履歴をコード中にコメントとして残す (ADDやDELみたいなコメントがたくさん付いている)。
問題点
コードがコメントだらけになり、可読性が下がる。
改善方法
変更履歴をコメントとして残さない。履歴はバージョン管理システムとDiffツールで確認する。
昔はね...
バージョン管理システムが今ほど広く普及しておらず、コード中に変更履歴を残すのが最善とされていた。(個人的な推測)
11. 変数名やメソッド名を何かと略したがる。
問題点
第三者には理解できない変数名やメソッド名であふれかえり、可読性が下がる。
改善方法
多少長くなっても第三者が理解しやすい名前をつける。どうしても省略したい場合は開発チーム内でルールを決める。
昔はね...
プログラムサイズをできるだけ小さくする必要があった。言語や環境によっては識別子の長さに制約があった。変数名や関数名を自動補完するIDEやエディタがあまり普及していなかった。プログラムが複数の開発者で共有されることが少なかった。(などと推測)

該当項目がゼロでも安心しない

該当項目がゼロだった人も安心してはいけません。
「10年以上前の技術の成功体験は実はあまり役立っていないどころか、綺麗なプログラムを書く邪魔をしている時すらある。」という意見もあります*8
JavaやC#もほぼ10年選手です。つまり、JavaやC#の知識が徐々にレガシー化してきている可能性もあるわけです。


たとえば上の改善方法でforループをforeach文に変更する例を挙げましたが、最近のC#だったらLINQで処理する方法もありますし、Rubyならeachメソッドとブロックで処理する方法もあります。
こうした方法は10年前に存在しなかったり、あまり知られていなかったりする方法です。
JavaやC#を使いこなしているプログラマがレガシープログラマを笑っていても、ぼーっとしてたら10年後には自分がレガシープログラマと笑われている可能性もあります。


過去の成功体験に満足せず、これまでの成功体験をぶち壊しながらでも新しい技術を学んでいくのが真のモダンプログラマですよね?

あわせて読みたい

レガシープログラマさんと一緒にリファクタリングをする、の巻 - give IT a try
レガシープログラマさんと一緒に実務で使うコードをリファクタリングしてみました。


Javaプログラマが知るべき9のこと - @katzchang.contexts
視点は若干違いますが、ダメコードを実例とともに紹介してくれています。いくつかの項目は参考にさせてもらっています。


C#プログラマのための理解度チェックリスト - give IT a try
C#らしいプログラムを書くためのチェックリストです。レガシープログラマの判断基準に重なる部分もあります。


CODE COMPLETE 第2版 上 完全なプログラミングを目指して

CODE COMPLETE 第2版 上 完全なプログラミングを目指して

参考文献は色々あるけど、まずはここから。

*1:最初は「C言語病」って呼んでたんですが、C言語に限定されないネタもあったのでやめました

*2:2010.02.18 追記 ここではシステムハンガリアン記法を指します。二つのハンガリアン記法があるとは知りませんでした。勉強不足でごめんなさい。Wikipediaの解説 http://fwd4.me/vyW

*3:ただし、JavaScriptはブロックスコープを持たないので当てはまらない

*4:長くなるので割愛しますが、C#ならこのサイトが参考になります: http://blogs.msdn.com/b/nakama/archive/2008/12/29/net-part-1.aspx

*5:システムハンガリアン記法の方です

*6:くどいですがシステムハンガリアン記法の方です

*7:Data Transfer Object = DTOクラスと呼ばれることもある

*8:http://forza.cocolog-nifty.com/blog/2011/02/xp2011agileagil.html