kazasiki's blog

プログラミングとかVRゲームとか

Effective "if-else" ~分岐に関わる達人の思考~

経験豊かなプログラマの皆さんであればif文はご存知だと思います。
この記事では、様々な言語でのif文の機能や書き方の違いに触れつつ、より安全なif文の書き方を導きます。要点は、静的言語でコンパイルエラーになるような書き方は動的言語でもしないほうが良い、もしくは場所を選ぶということです。
言語によって文だったり、式だったり、関数だったりしますが、この記事では文という表現を主に使います。

機能の違い

if文は言語によって様々な文法が存在します。が、ここでは主にその機能の違いに触れます。

基本(分岐)

一番基本的な書き方は以下のようなものだと思います。rubyっぽい書き方です。

if 条件
  条件がtrueだったら何かする
end
if 条件
  trueの場合に実行する文
else
  falseの場合に実行する文
end

初期化

golangで実装されている機能です。http://golang.jp/effective_go#if

if err := file.Chmod(0664); err != nil {
    log.Print(err)
    return err
}

for文のように、if文の中だけで使う変数の初期化を行います。
これは非常に便利な手法なのですが、他の言語での良い代替手段はほぼありません。Cでよくある書き方は

if(  (fp = fopen( filne_name, "r" )) == NULL ){
  printf( "%sファイルが開けません¥n", filne_name );
  return -1;
}

ですが、あんまり綺麗ではありません。結局、変数(fp)のスコープも限定できません。
if文を全てメソッドに分離させれば同じことができますが、現実的ではありません。コンセプトは理解しておくといいでしょう。for文のiのスコープを限定でないとしたらゾッとしますね。(C言語からは目を背けながら)

戻り値

rubyではif文は正確には式であり、戻り値を返します。条件部分がtrueであれば、trueの場合に実行される部分の最後の文の戻り値が返されます。
それを利用して以下のような記述ができます。

# if式の結果を変数に入れる
result =
  if 条件
    trueの場合に実行する文
  else
    falseの場合に実行する文
  end

また、haskellでも同様に戻り値を返します。haskellは静的型言語であるため、戻り値の型が揃っていなければコンパイルエラーになります。

変数の代入検査

一部の言語においてif式が戻り値を返すことを説明しましたが、戻り値を返さない場合でも同じようなことはできます。例えば、Javaでは以下のようになります。

// if文の結果を変数に入れる
int result;
if (条件) {
  result = trueの場合に実行する文;
} else {
  result = falseの場合に実行する文;
}
System.out.print(result);

Javaで面白いのは、例えば以下の記述はコンパイルエラーになります。

int result;
if (条件) {
  result = trueの場合に実行する文;
} else {
  falseの場合に実行する文;
}
System.out.print(result);

これがコンパイルエラーになるのはelseの場合にresultに値が代入されないからです。このコンパイルエラーを回避する方法は大きく2つあります。
1つはresultの宣言時に初期値を入れることです。これはif文の条件がtrueになるのがあくまでも特殊なケースである場合に有効です。基本的には初期値のままで、特殊な場合にif文の中で値が設定されます。ただ、この方法は破壊的代入と言われ、あまり推奨されません。変数の状態が確認しにくくなるからです。
もう1つは元の通りelseの場合に値を入れる方法です。値は初期値と同じかもしれませんが、代入を複数回行わないため、変数の状態が確認しやすくなります。

条件の判定

if文において何がtrueで何がfalseか、です。私の知る範囲においてよくあるのは、0, null, falseがfalseで、それ以外は全てtrueというものです。golanghaskellではtrue/false以外を返す式を条件に入れるとコンパイルエラーになります。

シンタックスシュガー

if文には様々なシンタックスシュガー(書き換え)がありますが、有名どころはやはり三項演算子と後置if文でしょう。

三項演算子(?演算子

C言語Javaのようなif文が戻り値を返さない言語でも、1行で戻り値の代入までを明示することが出来ます。可読性が下がるという理由で嫌われがちですが、80~100文字程度で収まる場面なら使っていいと思ってます。ワンライナー御用達。

result = 条件 ? trueの場合に実行する文 : falseの場合に実行する文

後置if文

RubyCoffeeScriptにあるシンタックスシュガーです。以下のように書きます。

trueの場合に実行する文 if 条件

片方の条件の処理が長いときにif文を閉じるところまで読まずに済むというメリットが有ります。ネストも減ります。

# before
def do_something 
  if condition
    method0
  else
    method1
    method2
    method3
    method4
    method5
    method6
  end
end

# after
def do_something 
  return method0 if condition
  method1
  method2
  method3
  method4
  method5
  method6
end

テクニック

条件式を変数に入れて持ち回る

条件式を変数に入れて持ち回れば、例えばこんな書き換えが出来ます。
あと、条件式に尤もな名前を付けるのは常に良い習慣です。

# before
def do_something(a, b)
  if a > 0 && b > 0 && a > b
    c = a
    d = b
  else
    c = b
    d = a
  end
  puts "c:#{c}, d:#{d}"
end

# after
def do_something(a, b)
  condition = a > 0 && b > 0 && a > b
  c = condition ? a : b
  d = condition ? b : a
  puts "c:#{c}, d:#{d}"
end

等価演算子の変数を右側に書く

等価演算子の==と、代入の=を書き間違えたときにコンパイルエラーにしてくれるための技法です。C言語ではよく書いてました。
読みにくいので皆しなくなりました。条件式にboolean型しか許されない言語では無用な心配です。
ただ、現在においても==と=を間違えるのは比較的ポピュラーなバグであるというのは覚えておいたほうが良いです。

if (1==a) {
  do_something
}

まとめ

冒頭に書いたとおり、静的言語でコンパイルエラーになるような書き方は動的言語でもしないほうが良い、もしくは場所を選ぶの一言です。 それを学ぶためにも動的言語でプログラムを覚えた人は一度はモダンな静的言語のプログラムを覚えるのは良い刺激になると思います。
あと、if文のtrue/falseの時の実行内容を見比べやすくする意識を持つとよいです。

言い訳

検索などをせずに記憶をもとに書いたのでプログラムが間違ってるかもしれません。勿論動作確認もしてません。わかればいいのだ。