2009年08月11日

gotoの例

プログラマの宗教の話で、gotoの話が軽く盛り上がったので、別エントリにしてみた。miracleさんからリクエストのあった『goto使ったほうがきれいに書けるというシチュエーションってどんなの?』について、俺の作った自作アプリのソースの一部を見ながら、俺自身も検証してみようかと思う。画像が見にくかったらゴメンナサイ。保存して見てくだしあ。ちなみに、『Drag&Drop Delete Exif』で1つ、『コナミワイワイワールド エッチ005 アナライザ』で2つ使ってた。意外と使ってるなぁ。



1.例外(まぁエラーだ)が起こった際、以降の処理をかっ飛ばす。

goto.gif

すべて見えてはいないが、forループの中で、AssignメソッドやSaveToFileメソッドで例外処理が起きなければ、ProgressBar_StepIt関数で、プログレスバーを進める処理である。(全部で6個のtry〜catchがある。)AssignメソッドやSaveToFileメソッドが例外処理を吐き出したら、gotoで、かっ飛ばす。かっ飛ばす前に「エラーメッセージボックス処理(BcbMessage_Error関数とそれより上の行)」と「現在の進行度(theMessagePosへ代入してる行)」を格納する処理を経ている。かっ飛ばされた先では、例外処理の中で格納された「現在の進行度(theMessagePos)」をもとに、かっ飛ばされた分のプログレスバーを進める。(breakなしのswitch〜case文が見えるでしょ。)

if( 関数1() == 0 )  // 戻り値は0(正常)か10(例外)
{
  if( 関数2() == 0 )  // 戻り値は0(正常)か20(例外)
  {
    if( 関数3() == 0 )  // 戻り値は0(正常)か30(例外)
    {
      if( 関数4() == 0 )  // 戻り値は0(正常)か40(例外)
      {
        if( 関数5() == 0 )  // 戻り値は0(正常)か50(例外)
        {
          if( 関数6() == 0 )  // 戻り値は0(正常)か60(例外)
          {
          }
        }
      }
    }
  }
}
と、tryからProgressBar_StepItまで(例えば330行目から342行目まで)を関数にして、戻り値が0だったら次の関数へ・・・ともできるが、ネストがムダに深くなるのでやめた。戻り値が0以外だったらcontinueするという手もあるが、これまた個人的には美しさを感じない。上で強調したが、あくまで「forループの中でプログレスバーを進める処理」なので、プログレスバーを進める関数(ProgressBar_StepIt)が見えないというのは、ナシと判断した。また、「どのルートを辿っても、必ずProgressBar_StepItを6回通る」ことが一望できることを望んだその結果がgotoとも言える。なお、犠牲にしたのは、関数の長さ。一般的には長くても100行ぐらいに抑えるのが良いとされているが、この関数は200行ある。



2.同じ制御文が連続するのを嫌った。

goto_1.gif

先に答えを言ってしまうと、上記をgoto無しにすると、以下のようになる。

goto_2.gif

前者で言うところの468行目のif文、後者で言うところの481行目のif文は、ともに下のほうでelse文が待っている。ともかくtheIsErrorが真の際は、そのelse文へ飛ばしたい。後者のように、まったく同じif文が続くのはなんだか美しくないし、最適化されてくっつけられても困る。(どうなるかは分からない。)もし、後者が最適化されて2つのif文が合体してしまったとき、つまり前者の「goto IS_ERROR」の行だけが消えてしまうような状態に最適化されてしまうと、当然、意図する動作にならない。

過去に、後者のような書き方をした結果、最適化されてくっついてしまって意図しない動作となったことがあった。最適化されたバグを探し出すのは非常に面倒なので、それならばとgotoを使って戻すことにした。



3.forループの中のif文を完全に満たしたらエラー。

goto_3.gif

ワイワイワールドのパスワードが、基準パスワードかどうかを調べるくだり。14個のすべての値が基準パスワードと一致したらエラー。エラーの場合、2079行目にて、theReturnに-2をブチ込む。1つでも一致しなければ、theReturnの値は変わらない。これをgotoなしで書くと、

goto_4.gif

となる。もう個人的には、後者の2078行目のif( i == 13 ) ていう条件式を書くのがイヤでしょうがない(マジックナンバーのくだりは置いておく)。かといって、forの中に複数の条件を書くのもイヤだ。また、while文でも書けそうだが、先述のとおり条件式を2つ書くのは趣味じゃないし、そうすると
if( i == 13 )
{
  break;
}
となるが、これまた美しくない。その結果がgotoとなった。



あれー。アレックスさんの言うように
C言語なら、2重・3重ループから一気に抜ける場合
ぐらいでしか使ってないつもりだったけど、普通に使ってるなーwww

一応、自己フォローしておくと、
・gotoとラベルの距離が近い位置でしか使っていないこと
・複文の中へ入り込む位置にラベルを置いていないこと
・goto使った方がまだ美しいソース(主観)になること
は踏まえて使っているようです。

濫用するようなものではないけど、美しさや見やすさを犠牲にしてまで拒否するほどのものでもない、そんなカンジです。「miracleさんお分かりになりましたでしょうか〜。お分かりになりましたら今後の参考にして頂ければと思いま〜す(仁鶴師匠の声で)」



もっとイカす書き方があったら、ぜひ教えてください(他人任せ
posted by 小川 at 23:04| Comment(0) | TrackBack(0) | プログラム/マ | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前: [必須入力]

メールアドレス:

ホームページアドレス:

コメント: [必須入力]

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。

この記事へのトラックバック