2010年7月11日日曜日

三項演算子を使うべき理由

三項演算子を使うべきでないとする規約や現場って、結構多い。

それに対する「使う」派の主張もたまに見かけるが、
  • 「控えめに使うなら許されるべき」なんて消極派だったり、
  • 「三項演算子すら理解できない奴はクズ」なんて議論拒絶派だったり、或いは
  • 「三項演算子の方が格好良くて好き」的な頭脳労働苦手派であったり
する。

自分は使用肯定派だけど、なるべく合理的に三項演算子について考えてみたい。日曜なのに暇だしな。

最初にまず、「if 文ではなく三項演算子を使うべき」状況を考えて、その後で、良くある三項演算子否定論への異議を述べてみる。(言語は C# とか Javaを想定)

■■ if 文を避けて三項演算子を使うべき理由

★ 変数の宣言と定義を同時にできる
一時変数への値の設定について、宣言と同時なのと宣言から離れているのとで、どちらが良いコードかという問題だが、後者の「離れて」が正解なんて言う人は聞いたことが無いので、説明は割愛。(一応手持ちの資料を調べると、『Code Complete 上巻』の変数の部に詳述されているので、理屈を確認したければこの辺りを読めばいい)
public int Foo(bool f)// if 文版
{
  int i;
  if (f)
  {
    i = 100;//← 宣言した行から離れている
  }
  else
  {
    i = 200;//← 宣言した行から離れている
  }
  // i を 使う
}

public int Foo(bool f)// 三項演算子版
{
  int i = f ? 100: 200;//← 宣言した行で値を設定している
  // i を 使う
}
Javaならば更に final を付けて、より低リスクな安心コードにできる。(残念ながら、C# の const では、同様の安全策ができそうでできない。)
public int foo(boolean f) {
  final int i = f ? 100: 200;← 誤変更のリスクが解消
  //ここで i を変更しようとするとコンパイルエラー
  // i を 使う
}
また、上のサンプルコードでも明らかなように、「一時変数の宣言箇所は、使用する箇所のギリギリにまで近づけるべき」と言うプラクティスについても、三項演算子の方がより準拠している。

★ 一時変数が少なくて済む
三項演算子はもちろん演算子だから、式を書ける所ならば普通に使えるので、わざわざ作業用の一時変数を使わないで済む場合がある。
※一時変数の使用と不使用とでどちらが望ましいかなんて解説は省略。
※『リファクタリング』に「説明用変数の導入」という一時変数を増やす事になる小技が載っているが、それとは別の話
public void Foo(bool f)// if 文版
{
  string arg;
  if (f)
  {
    arg = "one"; 
  }
  else
  {
    arg = "two"; 
  }
  Bar(arg);//作業用の一時変数にわざわざ入れる羽目になる。
}
public void Foo(bool f)// 三項演算子版
{
    Bar(f ? "a" : "b");//式なので普通に使える。一時変数不要。
}

★ 重複コードを解消できる
上の どちらのFoo()も、条件分岐/条件演算を 別のメソッド Baz() に移して値をすぐに return させるようにすると、共にBar(Baz(f))という形に置き換えることができるが、それでも、まだ微妙な重複が残る。
public void Foo(bool f)
{
    Bar(Baz(f));
}
public string Baz(bool f)// if 文版
{
  if (f)
  {
    return "a";
  }
  else
  {
    return "b";
  }
}
public string Baz(bool f)// 三項演算子版
{
  return f ? "a" : "b";
}
実務上は、このreturn の重複についてまでも、Code Smell と捉えて、厳しく除去するなんて事はあまり無いが、例えば Baz()から値を返す前に何かの書式化を施すような変更を想定すると、これだって立派な重複コードだと言う事が分かる。目くじらを立てる事もないほどではないが、無ければ無いに越した事は無い。

★ 不要なブロックを減らせる
ブロックが多い(深い)のと少ない(浅い)のでは、少ない方に決まっているという普通の話。(自明のように見えるが、実はプログラマの中にはブロックをどんどん深くして悦に入る人もたまにいる。これ以上くどい説明もアレなので省略するが・・・)
コードブロックに関して突き詰めていくと、ブロックそのものが、長過ぎるメソッド/一時変数の濫用/重複コード/スパゲティコード/オブジェクト指向を無視した手続き型ロジックやらの温床でもあり元凶でもあるように見えてくる。他のトレードオフも考慮した上で、できるだけ式かメソッド呼び出しに変えてしまった方が、将来的にも安全なコードになる。

三項演算子を「使うべき」理由は、こんなところか・・・
続いて、よくある三項演算子反対の意見に反論してみる。

== == == == == ==
■ 三項演算子否定論への異議

★三項演算子は難しいから禁止すべき
そもそも「難しいか否か」なんて主観的判断が、合理的評価基準として妥当なのか疑問だが、差し当たりこれを認めるとして、それならば他の言語要素に関しても三項演算子より難しいものは否定してしまうのかという話になる。LINQ はどうか?ラムダ式はどうか?ジェネリクスは簡単か?

何かの言語要素を難しいと感じた誰かさんが(難しさを認識する事自体は望ましい事だが)、それを一律に禁じてしまうような事で、言語という道具と、それを操るプロフェッショナルから、投下したコストに対する効用を最大限に引き出す事が本当に可能なのか疑わしい。ちゃんと分かって、禁止しているのだろうか?むしろ、その禁則さえなければ得られたはずの利益を捨ててしまっていないか?

また「平均的プログラマの技術水準」なんて怪しい設定を持ち出して、三項演算子は無理なんて主張する人も多いが、どこに根拠あるんだろう?というか、
(isNegative ? -1 : 1) * absoluteValue
の意味が分からない人が、更に高度な他の文法要素を使いこなしたり、あるいはテスト駆動開発でモックテストを書いたりできる訳がない。

「三項演算子ができないレベルで妥協した方が、人材調達が容易」なんて言い分もあるようだが、それって「我々のスタイルは、基礎的な文法要素も使えない、最底辺PGによる人海戦術です」なんて言うのと同義なわけだけど、マジでそんなので良いのかな。三項演算子どころではない大問題だと思うんだが・・・

★ 三項演算子は読みにくいから禁止
三項演算子の方が読みやすいと言う人もいるし、どっちでも変わらないという人もいて、結局、単なる主観なわけで、フォーマルな規約の根拠としては元々ふさわしくない。

あと、読みにくさの話になると、ことさら読みにくくした三項演算子コードのサンプルだけを引き合いに出して「ほら、こんなに読みづらい」なんて言う人が必ず出て来るけど、そういうのはスルー。

★ 三項演算子はミスを生じやすいから禁止
自然な対策としては、作業ミスを誘発しやすい箇所を識別した上で、括弧付けの規約などで可読性を確保すればいいのであって、三項演算子そのものの禁止にはならないはず。これについても、数ある言語要素の中でなぜ三項演算子だけが槍玉に挙げられるのか分からんが。
Sun のJava規約みたいに 2項演算子を含む条件部を括弧でくくるなど

★ 三項演算子が嫌いだから禁止
三項演算子を使う理由を「好きだから」という情緒によってしか説明できない人と同じくらい、知的作業に向いていない。よって、この手の輩はコーディング規約策定には関与すべきでない。


なーんて、なぜか書いているうちにエキサイトしてきて、文章にトゲが入ってくる・・・
やはり三項演算子談義には、何か人の攻撃性を刺激するような魔物が住んでいるのか・・・

0 件のコメント:

コメントを投稿