goto
- プログラミング言語の構文のひとつ。その使用を巡って論争が絶えないことで有名である。本稿で解説する。
- 日本の経済政策「Go To キャンペーン」の略称。「Go To トラベル」、「Go To Eat」、「Go To イベント」、「Go To 商店街」で構成される。
概要
古典的にはgotoは無条件ジャンプを行うための構文であり、その名の通り「あそこの(あのラベルの)処理に飛べ!」という単純明解な機能である。 C言語のgotoを基準にすると、同一関数内である限り、ラベルが書ければどこにでもジャンプすることが出来、ジャンプ先から処理を継続する。ジャンプ元には特にgotoなどで指定されない限り戻ってこない。
gotoをサポートする言語にはFORTRAN, BASIC, C (当然C++も)と、メジャーな古参高級言語が名を連ねており、いかにも「由緒正しきスタンダードな機能です」感が漂うが、現在では実行速度を重視する分野などを除けば、無条件ジャンプができるgotoは好ましくない機能とする意見が主流を占めている。
なお、GoやD言語は"goto"を採用しているが、表記こそ"goto"であるものの、後述のように用途制限により実質的には"古典的gotoの代替"となっており、古典的gotoの議論とは無関係である。
なぜ批判されるか
gotoは関数呼び出しと違って呼び出した場所に戻って来る保証がないので、プログラムの構造をぶっちぎって構造化プログラミングを台無しにすることが出来てしまう。 ある意味構造化プログラミングの天敵ともいえる。
状況によっては特定の処理を飛ばす事も容易に行えてしまう。 たとえば、「ファイルを開き、データを読み込み、ファイルを閉じる」という処理があった時、「データを読み込む」処理にgotoが入っていて、gotoで飛んだ先から「ファイルを閉じる」処理に戻ってくるように書くのを忘れてしまうと、ファイルが開いたままになってしまう。
また、安易なgotoの濫用は絡まり合うような処理の流れを生み、本人ですら処理の流れを把握することが困難なソースコードを作り出す。 絡まり合っていることからスパゲティ・コードと呼ばれる。
歴史
gotoの誕生
gotoの誕生は最古の高級プログラミング言語FORTRANの登場(1957年)にまで遡る。現代のFortranは仕様改訂を経て現役のプログラミング言語として一部の分野では活躍を続けているが、最初期のFORTRANはサブルーチンすらなく(ただし、1958年のFORTRAN IIではサブルーチンと関数定義がサポートされている)フロー制御にgotoは必須であった。
もっとも、当時はプログラミング言語といえば機械語とアセンブラしかなく、メモリもソースコードの長さすら切り詰めねばならないほど少なく、できることも限られていたため、プログラムに構造をもたせる余裕も必要もなかったと言える。
gotoへの批判の誕生
その後、ハードウェアの進化と共にプログラムも複雑さを増していくつれて、前述のようなgotoの弊害が目立つようになり、ついには最短経路問題で有名なダイクストラらの1968年の論文「Go To Statement Considered Harmful (Go To構文は有害だと考えられる)」で批判されるまでになった。
なおC言語の誕生が1972年であり、上記論文はそれより早い。
goto擁護論の誕生により論争時代へ
gotoのある言語におけるgotoの擁護
構造化定理により、gotoを用いた処理を反復・分岐などの組み合わせに書き換えることは理論上可能であるが、その書き換えを行うことで性能や可読性について不利になるケースもある。
後述のようにC言語では例外処理がないために、goto構文に頼らざるを得ない局面が存在する。
また、gotoのある言語の多くは多重ループからの脱出する方法がgotoに頼らざるを得ない(gotoがあるから、多重ループ脱出のための仕組みがいらなかったとも言える)言語仕様になっている。
その後
gotoのある先発言語
C言語は仕様改訂でもgotoの性質は変化しなかったが、多くのプロジェクトがコーディング規約などでgotoの使用方法を制限するようになった。
C++はCとの互換性の関係からgotoが存在するが、例外処理機構を持っている(実装されたのは1990年よりも後の話だが)ため例外処理のためにgotoを使う局面はない。
後発言語
Javaを始めとするC言語より後のプログラミング言語の多くは、後述のように"古典的gotoの代替"を用意することで、無条件ジャンプとしてのgotoの採用は見合わせている。
余談だが、JavaはC言語の反省からgoto文を採用していないが、万が一後世でgotoの必要性が立証された時のためにgotoを予約語としてキープしている。goto議論の呪縛は根深い。
論争の本質
「gotoがないよりはあったほうがプログラムを書くときに便利だが、弊害が生じやすい」という点ではおそらく全員意見が一致している。
賛否が別れるのは、一つの問題点を、優先するものの違う2つの立場から捉えているからに過ぎない。すなわち、
という立場である。
もっとも、近年のプログラミング言語の傾向としては(優秀なプログラマーからそうでない者まで)大人数で安全な開発を行えるようにすることを重視するという考えが主流なので、今時の言語が古典的意味でのgotoを採用することはまずない。
議論がかみ合わない点
概要で述べたように、gotoはどこにでも飛ぶことが出来る一方で戻ってくることが保証されないために、注意しないと構造化プログラミングを破壊してしまうことが問題なのである。
逆にいうと、goto使用もプログラムの構造を破壊しない限りにおいては構造化プログラミング上の問題を生じない。gotoを使うことが「必ず」問題を引き起こすというわけではないのである。gotoの文字列を目にした途端に思考停止してアンチパターンだと糾弾するのはいいことではない。
一方で、gotoを否定する議論では、goto使用による弊害という「結果」のみを問題にしているわけではない。「gotoが存在すること」によって弊害のある使い方が「出来てしまう」こと自体が問題視されているのである。従って、弊害を起こさないように上手に使いさえすればよいというgoto肯定派の反論も的を射ていない。可能性を生み出しただけでアウトなんだよ。
また、gotoを適切に用いれば構造化プログラミング上は問題ないといっても、コード中にgotoが存在するだけで、そのgotoの適切性を検証しなければならないという管理上の問題が生じる。この場合、gotoを利用している単位での品質管理が重要で、継続してテストコードが書かれ検証されるようにすることであらかじめ問題を検知しておきたい。
そのような環境が整わないのであれば、やはり、gotoが使用可能な言語であっても使用しないほうがよいだろう。例外があるとしたら、コメントなどで適切性を簡潔に証明できる場合や、達人が自分一人でプログラミングする場合くらいであると思われる。
古典的gotoの代替
上記のようにgotoでジャンプすることは絶対悪ではなく、構造化プログラミングを妨げないことが保証されるならジャンプも有益でありえると言える。
この点に対して、C言語より後の比較的新しいプログラミング言語の多くは無条件ジャンプとしてのgotoではなく、より限定された範囲でのジャンプ制御命令(break, continue, 例外処理, etc.)を代替として提供していることが多い。そういった言語では無条件ジャンプとしてのgotoが必要になるケースはほぼない。
例外処理(try-catch-finally)
(tryで始まるブロックの中で)規定通りの動作ができないときに例外(Exception)を発生(throwする)させる。例外が発生すると、(tryブロックの直後にある)例外を捕捉(catch)する節までジャンプする。
例外処理は同一関数内という制限を受けずスタックを遡上して関数外に出ること(大域脱出)が可能という意味ではC言語のgotoよりも強力な機能である。その一方で、ジャンプ先はcatch節だけという限定を受けている上、「ファイルを閉じる」のような処理まではスキップしてしまわないように(catch節の直後にある)finally節で実行されることが保証されており、構造化プログラミングを破壊しないための安全機構が設けられている。
C++にはfinallyがないが、デストラクタなどを用いて実行の保証が可能である。
break, continue
for文から抜け出すbreak, continueもジャンプの一種であり、古典的gotoの代替と考えることもできる。ジャンプ先はループの末尾に限定されている。C言語でもサポートされているため、これらがないがためにgotoが必要になるケースは少ない。
初期のFORTRANはループの脱出にgotoを用いていたが、Fortran 90ではbreak, continueに相当する機能がサポートされた。
もっとも多重ループからの脱出には、Fortran 90でも、C言語でも、C++でも、gotoに頼るのが定石の一つとして認められている(なお例外処理があれば、gotoを使わずとも多重ループからの脱出は可能な気がする)。
Java以降の言語ではbreak, continueにラベルを使えるものが多いため、gotoがなくとも多重ループからの脱出は可能である。
C言語における"制限された"goto
C言語のように例外処理がない言語ではgoto文を用いずに高い可読性を実現出来ないことがあり、goto文を使用すべきとしているプロジェクトも存在する。
しかし、後述の事例に記したように、基本的には上記で述べた例外処理と同等のフローを実現するために使用されている場合がほとんどである(歴史的に考えれば、このエラー処理フローを言語レベルでサポートしたものが先述の例外処理であると言った方が適切かもしれない)。これらの場合、言語仕様上は制限されていなくても、コーディング規約としてgotoのジャンプ先に実質的制限が加えられていると見ることもできる。
制限の強まった"goto"
先述のように、新しい言語でも"goto"を採用している言語はある。 これらの言語のgotoは比較的C言語に近い機能を有しているが、使用するための制約を強くしたり無条件で処理をスキップしないようにするなど、C言語よりも自由度や機能を制限したものとなっている。
D
D言語のgotoはC言語と同様に同じ関数の内部のラベルしか参照出来ないようになっている。 それに加えてgotoによって変数の初期化をスキップするようなジャンプは禁止され、またジャンプする場合であってもfinally節の実行は回避されないようになっている。
Go
Goでもgoto構文は採用されており、やはりC言語と同様に同じ関数の内部のラベルしか参照出来ない。 加えてgotoする時点でスコープに入っていない変数がジャンプ先ではスコープに入っているような(i.e. 初期化をスキップする)ジャンプは禁止され、またより内側のブロックへのジャンプも出来ない仕様となっている。
GOTO 関連動画
GOTO 関連項目
参考: goto使用に関するC言語の事例
C言語によるオープンソースのプロジェクトでは、適した場所(例外処理など)に限るという前提ではあるがgotoを利用しているプロジェクトは非常に多い。
- Linuxカーネル: 関数内のエラー処理の集約のためにgotoが頻繁に利用されている(例えばRSA暗号の実装など)。
- スクリプト言語の処理系: 主にC言語で記述されることが多く、中でも主要なPython (CPython), PHP, Ruby (YARV)などは、gotoを用いた記述の適するパーサや有限オートマトンなどを実装していることが多いからという点も影響していると思われるが、いずれもgotoによる終了処理の集約が頻出している。
- vimやEmacs: ソースにgotoを利用する記述がある。
gotoが適すると思われるような例であってもgotoを利用しないという場合もなくはないです。
- GNU Scientific Libraryの常微分方程式ソルバのコード: (GNU Scientific Library全体として利用していないわけではなく、一部でgotoを用いない書き方がされている)。gotoを使用しない場合、同じ「後始末」のコードが反復して複数回出現している一方で、gotoを利用する場合は関数ブロック末尾にのみ「後始末」のコードの記述を行い、適切なラベル位置にジャンプ、すなわちgotoすることで可読性とメンテナンス性が確保されている。
- 6
- 0pt