途中でreturnの続き

http://d.hatena.ne.jp/Isoparametric/20090122/1232633359

あー、やっぱり。予想通り「ながら」処理の典型的なのが出てきました。「ながら」処理というのは複数の事を一度に実行しようとする実装です。

「ステータスを変更する必要があるかどうか?」を判断しながら「遷移先のステータスを決め」ながら「ステータスを変更する」という。わかりやすく言えば、一輪車に乗りながら頭にボールを乗せてバランスを取りながら焼きそばを食べるという必要性がほとんど説明できないコード。

困った事にC言語系だとこれが普通なんですが、こいつが多くのメンテナンス不能なコードとバグを生み出しているフリーザ級の悪の親玉。

リンク先のコードはエントロピーが低い状態だと確かに問題はないんだけど、ある程度の複雑さの増加が予想できる箇所では無視できない問題になります。

一つ目はSingleResponsibilityどころか三つの目的を同時に扱っているので、ほんのちょっとの修正によって修正量以上の複雑さが増すところ。つまり手に負えなくなりやすい。

二つ目がエントロピーが増えた状態でリファクタリングが困難になること。元々3つのロジックが入り乱れているので、それらを解きほぐすという苦痛に満ちた作業が必要になる。苦痛の果てに打ち倒せるんならいいほうで、苦痛の末に諦めることになったりすると、メンテナンスできなくなったコードの誕生に立ち会う事になる。一つ目の問題のせいでリファクタリングのタイミングを逃しやすくなっているので、このリスクは意外なほど高くて、前の例を使い回せば、焼きそばを食べながら一輪車から降りるのは相当に難しいと言うことです。

この辺はふつーのコードと比べると問題がわかりやすい。

public boolean action() {
  boolean result = false;
  if(needChange()) {
    some.set(getNextState());
    result = true;
  }
  return result;
}

上のコードはSingleResponsibilityに従っているので、仮にneedChange()とgetNextState()が複雑になってきても各メソッドは一つの問題を相手にしているので単純な状態を保ち易い。仮にリファクタリングすることになっても、複数のメソッドに展開したり、機能を別のクラスに追い出したりなど苦痛を伴わずに単純な作業で複雑さを分散できる。

さらにオブジェクト指向な人ならneedChange()を呼び出す責務はaction()メソッドのクライアント側にあるというのに気づくはず。なんでさらに修正すると、

public boolean needChange() {....}

public void changeState() {
  assert(needChange());
  some.set(getNextState());
}

こんな感じ。そんなわけで隠れていた責務が出てきました。焼きそばを食べるだけじゃ飽き足らなくて綱渡りもしていたことがわかったわけです。

と、ここまでが「ながら」処理が如何にアホらしいかの説明。
このまま終わっちゃうとタイトルと違ってしまうので、return問題にreturnすると。

何故に途中でreturnするのがいけないかというと、途中でreturnするということは何らかの状態が存在しているということなので、その時点でSingleResponsibilityに反していること。途中でのreturnを許すと明示化されているべき状態やシーケンスを実装に埋めて行くとことになるので「可能な限りコードでコードの説明をすべき」という、2つの良いソフトウェアを書くための基本的な法則を無視することになるから。

ちゃんとした理由があって無視するのならかまわないけど、たいがいはちゃんとした理由なんてないわけで、途中でreturnしたくなったらリファクタリングしておいた方が不幸にあう確率が減るわけです。

確実に言えることは、焼きそばは一輪車とボールでしこたま遊んだ後に食べた方が絶対においしいって事かな。

ちなみに「可能な限りコードでコードの説明をすべき」は今日はじめて書きました。「Explain by Code」とかしたらカッコいいかもだけど、英語的にあっているかどうかは不明...