ギルドワークスの増田です。
以前に書いたリファクタリングのエッセンスの続編です。
場合ごとのロジックの書き分け(条件分岐)は、プログラミングの基本ですね。
if-then-else 構文は、良く使われる「場合分け」の記述方法です。 今回は、この「場合分け」の書き方のバリエーションを考えてみます。
「場合分け」の書き方の違いは、ソフトウェアの変更コストに大きく影響します。
※注意:この記事は2014年10月14日にGuildWorks Blogで公開したエントリをリライトしたものです。
if-then-elseを使った書き方の例
double getPayAmount(){
double result;
if(isDead()){
result = deadAmount();
} else if(isRetired()){
result = retiredAmount();
} else {
result = normalAmount();
}
return result;
}
給付金(payAmount)の金額を、「死亡時」「退職時」「通常時」で、場合分けする処理を、if-then-else 構文で記述した例です。
早期リターン
前の例では、一時変数 result を使いました。 この例の場合は、条件に一致すれば金額は確定します。 ですので一時変数は使わず、条件文の中からただちにreturnできます。
double getPayAmount(){
if(isDead()){
return deadAmount();
} else if(isRetired()){
return retiredAmount();
} else {
return normalAmount();
}
}
こちらのほうが、シンプルでわかりやすそうですね。これが「早期リターン」という書き方です。
elseを使わない書き方
「早期リターン」で書くと、実はelse句も書く必要はありません。
double getPayAmount(){
if(isDead()) return deadAmount();
if(isRetired()) return retiredAmount();
return normalAmount();
}
「死亡した時」は、ただちに「死亡時の金額」を返す。「退職した時」は、ただちに「退職時の金額」を返す。それ以外の一般的な場合は、「通常の金額」を返す。ずいぶんとすっきりしたコードになりました。
最初のif-then-else形式の書き方と比較してみてください。
特殊な場合をif文で判定し、else句を使わずに早期リターンするこの書き方を「ガード節」と呼びます。
「リファクタリング」本の「条件記述の単純化」で紹介されているテクニックです。
if-then-else構文を見つけたら、「早期リターン」や「ガード節」に書き換えることを検討してみましょう。
ちょっとした書き方の変更ですが、「場合分け」のコードがすっきりし、変更時にバグが紛れ込む可能性を減らせます。
複文より単文
「ガード節」と「早期リターン」を適用した最後の例は独立した三つの「文」をならべた、シンプルな構造です。
if-then-else 構文を使った最初の2つの例は、if文の(else句の)中にif文を書いています。つまり文の中に文を書く「複文」構造です。
「複文」構造がわかりにくいのは、自然言語でも、プログラミング言語でも同じです。
最後の例がすっきりしてわかりやすのは、文の中に文を書いた「複文」ではなく、「単文」を並べたシンプルな構造だからです。
else-if のような「複文」構造を見つけたら、「早期リターン」+「ガード節」で「単文」構造にできないか検討してみましょう。
「単文」構造にできれば、コードは読みやすく変更しやすくなります。
処理の独立性
単文構造にした最後の例は、処理(文)の独立性も高くなっています。
「複文」は、「文」と「文」の関係があきらかに「密結合」ですね。「単文」を並べる方式は、それぞれの「文」の結合度(影響度合い)が下がります。
たとえば「離婚した時の給付金額」を追加してみましょう。 単文を並べた方式は、別の単文を追加するだけです。簡単でまちがいが減ります。
double getPayAmount(){
if(isDead()) return deadAmount();
if(isRetired()) return retiredAmount();
if(isSeparated()) return separatedAmount();
return normalAmount();
}
この三つのif文は、順番を入れ替えても何も問題がおきません。これも処理どうしの関係が「疎結合」になっている良い点です。
オブジェクト指向らしい場合分け
今までの例は、if文を使って、処理の場合分けを書きました。
Javaなどオブジェクト指向の仕組みを取り入れた言語では、「場合分け」は、もっと別の書き方もできます。
発想は単純です。
場合ごとに別のクラスにしてしまう
場合分けを、単文方式でif文を並べると、場合ごとの処理(if文とif文)の関係が疎結合になりました。これを発展させて、場合ごとのロジックを別々のクラスに分けて書く、という発想です。
class DeadAmount { double getAmount(){...}; }
class RetiredAmount { double getAmount(){...}; }
class NormalAmount { double getAmount(){...};}
こうすれば、それぞれの「場合」のロジックをどこに書くかが明確です。
「死亡時」の金額の計算方法を変更するために、DeadAmountクラスを修正しても、他のクラスに影響しません。場合ごとの変更を分離し独立させて扱うことが、この書き方の狙いであり、メリットです。
もちろん、場合ごとにクラスを分けると、複数のクラスを使い分けるのはたいへんになります。
そこで三つのクラスの違いを意識せずに同じように扱う仕組みを導入します。それが「インタフェース宣言」です。
interface PayAmount {
dboule getAmount();
}
//それぞれのクラスでインタフェースを実装する
class DeadAmount implements PayAmount {...}
class RetiredAmount implements PayAmount {...}
class NormalAmount implements PayAmount {...}
「給付金額」を知りたいクライアント側(使う側)のクラスでは、こんな書き方になります。
class Client {
private PayAmount amount ;
double getAmount() {
return amount.getAmount();
}
}
クラス図で描くと、こんな感じです。
クライアントクラスは、PayAmountという「型」を意識しているだけで、「死亡時」「退職時」「通常時」の場合分けは、意識していません。
これが「多態」とか「ポリモーフィズム」と呼ぶ、オブジェクト指向らしい場合分けの書き方の例です。
場合ごとのオブジェクトをif文なしに生成する
さきほどの例では、Clientクラスには場合分けは登場しません。しかし、インスタンス変数で宣言したPayAmount型で参照する実装クラスのオブジェクトを生成する時に、どこかにif文が必要になりそうです。Javaの場合は、Enumという便利な仕組みがあって、実装クラスの生成も、if文なしに記述できます。 たとえばこんなEnumを宣言します。
enum PaymentType{
dead( new DeadAmount() ),
retired( new RetiredAmount() ),
normal( new NormalAMount() );
private PayAmount amount;
private PaymentType( Payamount amount ) {
this.amount = amount;
}
double getAmount() {
return amount.getAmount();
}
}
こうすると、PayAmountで参照するオブジェクトは、タイプ名の文字列から、生成できます。
PaymentType type = Payamount.valueOf("dead");
...
double amount = type.getAmount();
Enum#valueOf() メソッドは、if文を使わずに、場合ごとに異なるオブジェクトを生成できる便利でわかりやすい方法です。
Enumオブジェクトの永続化は、フレームワークを使えば簡単にできます。
たとえば私たちが使っているmyBatisというO-Rマッピングのフレームワークだと、「区分」を表す文字列と、オブジェクトの変換を自動でやってくれます。PaymentType.normalオブジェクトを、データベースのpayment_typeカラムの文字列に"normal"として格納したり、データベース上の"normal"文字列を、PaymentType.normal オブジェクトとして復元することは、フレームワークまかせで、開発者が意識する必要はありません。
画面のラジオボタンなどで選択されたvalue "normal"を,PaymentType.normalマッピングすることも、valueOf()メソッドを使えば、if文は不要ですね。
「場合分け」の書き方いろいろ
場合分けのいろいろな書き方を紹介してみました。
- if-then-else
- 早期リターン
- ガード節
- インタフェースと実装クラス(多態)
- Enum宣言
どの書き方が良いかは、時と場合によります。 経験的に、以下のような場合、「if-then-else」より「多態」や「Enum」を使ったほうが、変更が楽で安全になります。
- 場合分けの追加をしたい
- 場合ごとのロジックが複雑になる
- 場合ごとのロジックに修正が入ることが多い
業務アプリケーションでは「区分」や「種別」ごとのビジネスルールが複雑になりがちです。
また、「区分」や「種別」は変更が必要になることも多く、変更ミスやバグの温床になります。
「区分」や「種別」を使ったビジネスルールを記述するプログラミングでは、if-then-elseを使った最初の書き方と、最後に紹介した「多態」と「Enum」を使った書き方では、ソフトウェアの変更コストがだいぶ違ってきそうです。
「プログラムを動かす」だけなら、どの書き方でも同じです。 しかし「変更コスト」を考えると、if-then-elseを使った「複文」構造は、できるだけ避けたい書き方です。
現場で、コードレベルで、実際に
ギルドワークスでは、こういう設計の考え方ややり方を学ぶワークショップやセミナーを開催しています。
また、みなさんの現場に出かけて実際のコードを題材にやってみるオンサイトのワークショップにも力を入れています。
ご興味を持たれた方はギルドワークスのホームページからお気軽にお問い合わせください。
1
取り消す
この記事に共感したら、何度でも押してこの記事のポイントをみんなでアップしよう。