質問

ディクストラのことは誰もが知っています 編集者への手紙:有害と考えられるステートメントに移動 (また ここ .html トランスクリプトと ここ .pdf) であり、それ以来、可能な限り goto ステートメントを回避するという強力な推進が行われてきました。goto を使用して保守不能で無秩序に広がるコードを生成することは可能ですが、それでも、 最新のプログラミング言語. 。上級者でも 継続 Scheme の制御構造は、洗練された goto として説明できます。

goto の使用が正当化されるのはどのような状況ですか?いつ避けるのが最善ですか?

追加の質問として:C には、setjmp と longjmp という 1 組の関数が用意されており、現在のスタック フレーム内だけでなく、呼び出しフレーム内でも移動できるようになります。これらはgotoと同じくらい危険だと考えるべきでしょうか?もっと危ない?


ディクストラ自身はその称号に責任がなかったことを後悔していた。の終わりに EWD1308 (また ここ .pdf)彼はこう書きました:

最後に記録用の短編小説を。1968年、ACMの通信は、タイトルの下で私のテキストを公開しました」GOTOの声明は有害と見なされました「後年には最も頻繁に参照されるだろうが、残念ながら、しばしばそのタイトルよりもそれを見た著者によって、テンプレートになることで私の名声の礎となった著者によって:「xは有害と見なされる」というタイトルの「xは有害と見なされる」というタイトルのタイトル「x xと考えられている」というタイトルの下で、あらゆる種類の記事を見るでしょう。しかし、何が起こったのですか?タイトルの下で論文を提出しました」GOTO声明に対する訴訟「それは、その出版物をスピードアップするために、編集者が「編集者への手紙」に変更し、その過程で彼自身の発明の新しいタイトルを与えました!編集者はニクラウス・ワースでした。

ダイクストラの論文に匹敵する、このトピックに関するよく考え抜かれた古典的な論文は次のとおりです。 go to ステートメントを使用した構造化プログラミング, 、ドナルド E.クヌート。両方を読むと、文脈と主題の非独断的な理解を再確立するのに役立ちます。この論文では、この事件に関するダイクストラの意見が報告されており、さらに強力です。

ドナルド E.クヌース: 彼が最近書いたので、そのような見解を提示することで、私は実際にはディクストラのアイデアに鋭く反対していないと信じています。「私が[声明に出かける]についてひどく独断的であると信じているというtrapに陥らないでください。 私は、他の人が宗教を作っているという不快な感覚を持っています。まるでプログラミングの概念的な問題は、単一のトリックで、単純な形式のコーディング規律によって解決できるかのように!"

役に立ちましたか?

解決

以下の記述は一般化したものです。例外を主張することはいつでも可能ですが、通常は(私の経験と謙虚な意見では)リスクを冒す価値はありません。

  1. メモリ アドレス (GOTO または生のポインタ) を制約なく使用すると、簡単に回避できる間違いを犯す機会が多すぎます。
  2. コード内の特定の「位置」に到達する方法が多ければ多いほど、その時点でのシステムの状態がどのようなものであるかについて確信が持てなくなります。(以下を参照してください。)
  3. 私の意見では、構造化プログラミングは「GOTO を避ける」というよりも、コードの構造をデータの構造と一致させることに重点を置いています。たとえば、繰り返しデータ構造 (例:配列、シーケンシャル ファイルなど)は、コードの繰り返し単位によって自然に処理されます。組み込みの構造を持つ(例:while、for、until、for-each など) を使用すると、プログラマは同じ決まりきったコード パターンを繰り返す退屈な作業を回避できます。
  4. GOTO が低レベルの実装詳細であっても (常にそうとは限りません!)、それはプログラマが考えるべきレベルを下回っています。生のバイナリで小切手帳の残高を計算しているプログラマーが何人いるでしょうか?データベース エンジンにキーを提供するだけでなく、ディスク上のどのセクターに特定のレコードが含まれているかを心配するプログラマーがどれだけいるでしょうか (また、実際にすべてのプログラムを物理ディスク セクターの観点から作成した場合、問題が発生する可能性は何通りあるでしょうか)。

上記の脚注:

ポイント 2 に関しては、次のコードを考えてみましょう。

a = b + 1
/* do something with a */

コードの「何かをする」時点では、高い自信を持って次のように言えます。 a より大きい b. 。(はい、トラップされていない整数オーバーフローの可能性は無視しています。単純な例で行き詰まってしまうのはやめましょう。)

一方、コードが次のように書かれていたとします。

...
goto 10
...
a = b + 1
10: /* do something with a */
...
goto 10
...

ラベル 10 に到達する方法は多数あるため、ラベル 10 とラベル 10 の間の関係に自信を持つためには、さらに努力する必要があります。 a そして b その時点で。(実際、一般的なケースではそれを決定することはできません!)

ポイント 4 に関しては、コード内の「どこかに行く」という概念全体が単なる比喩にすぎません。電子と光子 (廃熱用) を除いて、CPU 内部には実際には何も「移動」しません。時々、私たちは別の、より有用な比喩をあきらめることがあります。私は(数十年前に!)次のような言語に出会ったことを思い出します。

if (some condition) {
  action-1
} else {
  action-2
}

action-1 と action-2 をアウトオブラインのパラメーターなしルーチンとしてコンパイルし、条件のブール値を使用してどちらかを呼び出す単一の 2 引数 VM オペコードを使用することで、仮想マシンに実装されました。コンセプトは、「ここに行くかあそこに行く」ではなく、単に「今何を呼び出すかを選択する」というものでした。もう一度言いますが、比喩を変えただけです。

他のヒント

XKCD's GOTO Comic

私の同僚は、GOTO を使用する唯一の理由は、自分自身を追い詰められてしまい、それが唯一の脱出方法であるとプログラムしている場合だ、と言いました。言い換えれば、事前に適切な設計をしておけば、後で GOTO を使用する必要がなくなります。

このコミックは、「プログラムの流れを再構築するか、代わりに1つの小さな「go」を使用することができる」と美しく示していると思いました。 GOTOは、デザインが弱いときに弱い方法です。 ヴェロキラプトルは弱い者を捕食する.

単一関数内で例外処理の代わりに GOTO を使用することが有効な場合があります。

if (f() == false) goto err_cleanup;
if (g() == false) goto err_cleanup;
if (h() == false) goto err_cleanup;

return;

err_cleanup:
...

COM コードはかなり頻繁にこのパターンに当てはまるようです。

gotoを一度だけ使った記憶があります。一連の 5 つのネストされたカウント ループがあり、特定の条件に基づいて内部から構造全体を早期に抜け出すことができる必要がありました。

for{
  for{
    for{
      for{
        for{
          if(stuff){
            GOTO ENDOFLOOPS;
          }
        }
      }
    }
  }
}

ENDOFLOOPS:

ブール値のブレーク変数を簡単に宣言して、それを各ループの条件の一部として使用することもできましたが、この例では、GOTO が同様に実用的で読みやすいと判断しました。

ヴェロキラプトルは私を攻撃しませんでした。

これはすでに持っていました 議論 そして私はスタンバイしています 私のポイント.

さらに、高レベルの言語構造を次のように説明する人々にはうんざりしています。goto 彼らは明らかに要点を理解していないからです まったく. 。例えば:

Scheme の高度な継続制御構造も、高度な goto と言えます。

それはまったくのナンセンスです。 制御構造は次のように実装できます。 goto しかし、この観察はまったく取るに足らないものであり、役に立ちません。 goto はプラスの影響があるため有害とは見なされませんが、マイナスの結果があるため、これらは構造化プログラミングによって排除されています。

同様に、「GOTO はツールであり、他のツールと同様に、使用したり悪用したりすることができます」というのは完全に的外れです。現代の建設労働者は岩を使用して、それが「ツールである」と主張することはありません。岩はハンマーに置き換えられました。 goto は制御構造に置き換えられました。建設作業員がハンマーを持たずに野外で立ち往生した場合、当然、代わりに石を使用するでしょう。プログラマーが機能 X を持たない劣ったプログラミング言語を使用しなければならない場合、もちろん、彼女は使用する必要があるかもしれません。 goto その代わり。しかし、適切な言語機能の代わりに他の場所でそれを使用する場合、彼女は明らかにその言語を適切に理解しておらず、間違って使用しています。それは本当に簡単です。

後藤は、私のプログラムに含めるべきもののリストの中で非常に下位にあります。それが受け入れられないという意味ではありません。

Goto はステートマシンにとって便利です。ループ内の switch ステートメントは次のとおりです (一般的な重要性の順に)。(a) 実際には制御フローを表していない、(b) 醜い、(c) 言語とコンパイラによっては非効率である可能性があります。したがって、状態ごとに1つの関数を作成し、「next_stateを返す」などのことを行うことになります。それはgotoのようにさえ見えます。

確かに、ステート マシンを理解しやすい方法でコーディングするのは困難です。ただし、その困難は goto の使用に関係するものではなく、代替の制御構造を使用しても軽減することはできません。ただし、言語に「ステート マシン」構造がある場合は別です。私の場合はそうではありません。

まれに、より具体的な制御フロー (ループ、条件分岐など) ではなく、許容される一連の遷移 (goto) によって接続された一連のノード (状態) を通るパスという点でアルゴリズムが最も理解しやすい場合があります。 )、それをコード内で明示する必要があります。そして、きれいな図を描く必要があります。

setjmp/longjmp は、例外または例外のような動作を実装するのに適しています。一概に称賛されるわけではありませんが、例外は一般に「有効な」制御構造であると考えられています。

setjmp/longjmp は、分かりやすくても、正しく使用するのが難しいという意味で、goto よりも「危険」です。

悪いコードを書くのが最も難しい言語でもありませんでした。-- ドナルド・クヌース。

C から goto を削除しても、C で優れたコードを書くのはそれほど簡単にはなりません。実際、C が次のとおりであるという点をむしろ見逃してしまいます。 想定 洗練されたアセンブラー言語として機能できるようにするためです。

次に「有害と考えられるポインタ」、その次が「有害と考えられるダックタイピング」になります。それでは、彼らがあなたの危険なプログラミング構造を持ち去ろうとしたとき、誰があなたを守ることができるでしょうか?え?

Linux:カーネルコードでの goto の使用 Kernel Trap では、Linux コードでの GOTO の使用について、Linus Torvalds と「新人」とのディスカッションがあります。そこには非常に良い点がいくつかあり、ライナスはいつもの傲慢さを装いました:)

いくつかの一節:

ライナス:「いいえ、あなたはニクラウス・ワースが実際に彼が話していることを知っていると思ったCSの人々に洗脳されました。彼はそうしなかった。彼には凍りつく手がかりがありません。」

-

ライナス:「GoToは大丈夫だと思うし、多くの場合、大量のくぼみよりも読みやすいことが多い。」

-

ライナス:「もちろん、ラベルが記述できないパスカルのような愚かな言語では、gotoは悪いことがあります。」

Cでは、 goto 現在の関数のスコープ内でのみ動作するため、潜在的なバグが特定される傾向があります。 setjmp そして longjmp 非ローカルで複雑で実装に依存するため、はるかに危険です。しかし、実際には、あまりにも曖昧で一般的ではないため、多くの問題が発生します。

私は次のような危険があると信じています goto C ではかなり誇張されています。オリジナルであることを思い出してください goto この議論は昔ながらの BASIC のような言語の時代に遡り、初心者は次のようなスパゲッティ コードを作成していました。

3420 IF A > 2 THEN GOTO 1430

ここで Linus は、 goto: http://www.kernel.org/doc/Documentation/CodingStyle (第7章)。

今日、それに関する重大な問題を理解するのは難しい GOTO なぜなら、「構造化プログラミング」の人々がほとんどの議論で勝利し、今日の言語には回避するのに十分な制御フロー構造があるからです。 GOTO.

の数を数えてください goto最新の C プログラムでは。次に、次の数を追加します。 break, continue, 、 そして return 発言。さらに使用回数も加算 if, else, while, switch または case. 。それくらいの数です GOTOダイクストラが手紙を書いた 1968 年に、FORTRAN または BASIC で作成していれば、プログラムはこうなっていただろう。

当時のプログラミング言語には制御フローが不足していました。たとえば、オリジナルのダートマス BASIC では次のようになります。

  • IF ステートメントには何もありませんでした ELSE. 。必要な場合は、次のように記述する必要があります。

    100 IF NOT condition THEN GOTO 200
    ...stuff to do if condition is true...
    190 GOTO 300
    200 REM else
    ...stuff to do if condition is false...
    300 REM end if
    
  • たとえあなたの IF ステートメントには必要ありませんでした ELSE, 、通常は 1 行に制限されていましたが、 GOTO.

  • ありませんでした DO...LOOP 声明。非FOR ループの場合、明示的にループを終了する必要がありました。 GOTO または IF...GOTO 最初に戻ります。

  • ありませんでした SELECT CASE. 。使用する必要がありました ON...GOTO.

それで、最終的には 多くGOTOプログラム内にあります。そして、あなたはその制限に依存することができませんでした GOTO単一のサブルーチン内に (なぜなら GOSUB...RETURN サブルーチンの概念が非常に弱いものでした)。 GOTO行けるかもしれない どこでも. 。明らかに、これにより制御フローを追跡するのが困難になりました。

ここがアンチの場所ですGOTO 動きはから来ました。

Go To は、特定の場合に「実際の」例外処理の一種の代役を提供できます。考慮する:

ptr = malloc(size);
if (!ptr) goto label_fail;
bytes_in = read(f_in,ptr,size);
if (bytes_in=<0) goto label_fail;
bytes_out = write(f_out,ptr,bytes_in);
if (bytes_out != bytes_in) goto label_fail;

明らかに、このコードは占有スペースを減らすために簡略化されているため、詳細にはあまりこだわらないでください。しかし、私が何度も見た代替案を考えてみましょう 生産 コーダーによるコードは、goto の使用を避けるために、不条理な長さにすることになります。

success=false;
do {
    ptr = malloc(size);
    if (!ptr) break;
    bytes_in = read(f_in,ptr,size);
    if (count=<0) break;
    bytes_out = write(f_out,ptr,bytes_in);
    if (bytes_out != bytes_in) break;
    success = true;
} while (false);

機能的には、このコードはまったく同じことを行います。実際、コンパイラによって生成されるコードはほぼ同じです。しかし、プログラマはなだめようとする熱意で、 野琴 (学術的叱責の恐ろしい神)、このプログラマーは、 while ループはコードの可読性を表す実数を表します。 これは良くありません。

したがって、この話の教訓は、goto の使用を回避するために本当に愚かなことに頼っていることに気付いた場合は、使用しないでください、ということです。

ドナルド E.クヌースは、1992 CSLI の書籍「Literate Programming」の中でこの質問に答えました。p.17 エッセイがあります」goto ステートメントを使用した構造化プログラミング」(PDF)。この記事は他の本にも掲載されているかもしれないと思います。

この記事では、ダイクストラ氏の提案について説明し、これが有効となる状況について説明します。しかし、彼はまた、構造化されたループだけを使用して簡単に再現できない多くの反例 (問題とアルゴリズム) も示しています。

この記事には、問題、歴史、例、反例の完全な説明が含まれています。

Jay Ballou が回答を追加していることに惹かれたので、0.02 ポンド追加します。Bruno Ranschaert がまだそうしていなかったら、私は Knuth の「GOTO ステートメントを使用した構造化プログラミング」の記事について言及したでしょう。

議論されているのを見たことがないものの 1 つは、必ずしも一般的ではないものの、Fortran の教科書で教えられている種類のコードです。DO ループの拡張範囲やオープンコーディングされたサブルーチンなどです (これは Fortran II、Fortran IV、または Fortran 66 であり、Fortran 77 や 90 ではないことを思い出してください)。構文の詳細が不正確である可能性は少なくともありますが、概念は十分に正確である必要があります。それぞれの場合のスニペットは 1 つの関数内にあります。

優れているが時代遅れの(そして絶版の)本『プログラミング スタイルの要素、第 2 版Kernighan & Plauger 著には、当時 (70 年代後半) のプログラミング教科書に載っていた GOTO の悪用の実例がいくつか含まれています。ただし、以下の資料はその本からのものではありません。

DOループの拡張範囲

       do 10 i = 1,30
           ...blah...
           ...blah...
           if (k.gt.4) goto 37
91         ...blah...
           ...blah...
10     continue
       ...blah...
       return
37     ...some computation...
       goto 91

このようなナンセンスの原因の 1 つは、古き良き時代のパンチカードでした。ラベル (正規のスタイルなので、順序が正しくありません!) が 1 列目にあり (実際には、ラベルは 1 ~ 5 列目にある必要がありました)、コードは 7 ~ 72 列目にあります (列 6 が続きでした)。マーカー列)。列 73 ~ 80 にはシーケンス番号が与えられ、パンチカードデッキをシーケンス番号順に並べ替える機械がありました。シーケンスされたカード上にプログラムがあり、ループの途中に数枚のカード (行) を追加する必要がある場合、それらの追加行の後にすべてを再パンチする必要があります。ただし、1 枚のカードを GOTO のものに置き換えると、すべてのカードの順序を変更する必要がなくなります。新しいカードをルーチンの最後に新しいシーケンス番号を付けて挿入するだけです。これは、「グリーン コンピューティング」、つまりパンチ カードの節約 (より具体的には、再入力の労力の節約、および結果として生じるキー再入力エラーの節約) の最初の試みであると考えてください。

ああ、私が叫んでいるわけではなく不正行為をしていることに注意してください。Fortran IV は通常、すべて大文字で書かれていました。

オープンコード化されたサブルーチン

       ...blah...
       i = 1
       goto 76
123    ...blah...
       ...blah...
       i = 2
       goto 76
79     ...blah...
       ...blah...
       goto 54
       ...blah...
12     continue
       return
76     ...calculate something...
       ...blah...
       goto (123, 79) i
54     ...more calculation...
       goto 12

ラベル 76 と 54 の間の GOTO は、計算された goto のバージョンです。変数 i の値が 1 の場合、リストの最初のラベルに移動します (123)。値が 2 の場合は 2 番目に進み、以下同様です。76 から計算された goto までのフラグメントは、オープンコード化されたサブルーチンです。これはサブルーチンのように実行されるコードですが、関数の本体に書き出されていました。(Fortran にはステートメント関数もありました。これは 1 行に収まる埋め込み関数でした。)

計算された goto よりも悪い構造がありました。ラベルを変数に割り当ててから、割り当てられた goto を使用することができました。グーグル検索 割り当てられたgoto Fortran 95 から削除されたことがわかります。構造化プログラミング革命は、ダイクストラ氏の「GOTO は有害であると考えられる」という手紙または記事から公の場で始まったと言っても過言ではありません。

Fortran (および他の言語でも、そのほとんどは当然のことながら道端に落ちています) で行われた種類の知識がなければ、私たち初心者にとって、ダイクストラが扱っていた問題の範囲を理解するのは困難です。なんと、私がプログラミングを始めたのは、その手紙が出版されてから 10 年も経ってからでした (しかし、不運にもしばらく Fortran IV でプログラミングすることになりました)。

後藤氏は役立つと考えた。

私がプログラミングを始めたのは 1975 年です。1970 年代のプログラマーにとって、「goto は有害であると考えられている」という言葉は、多かれ少なかれ、最新の制御構造を備えた新しいプログラミング言語を試す価値があることを示していました。私たちは新しい言語を試してみました。私たちはすぐに改宗しました。私たちは二度と戻りませんでした。

私たちは二度と行きませんでしたが、もしあなたが若いなら、そもそもそこに行ったことがないでしょう。

さて、古代のプログラミング言語の背景は、プログラマの年齢を示す指標として以外にはあまり役に立たないかもしれません。それにもかかわらず、若いプログラマーにはこのような背景がないため、「goto は有害であると考えられている」というスローガンが伝えるメッセージを理解できなくなっています。 導入時に対象としていた視聴者に向けて。

理解できないスローガンはあまり啓発的ではありません。そのようなスローガンは忘れたほうがよいでしょう。そのようなスローガンは役に立ちません。

しかし、この特定のスローガン「後藤は有害であると考えられた」は、それ自体がアンデッドの命を帯びています。

gotoは悪用できないのか?答え:確かにそうだけど、だから何?ほぼすべてのプログラミング要素 できる 虐待される。謙虚な人 bool たとえば、私たちの一部が信じたいよりも頻繁に虐待されています。

対照的に、私は1990年以降、GoTo乱用の実際の事例に一度も会った記憶がありません。

goto の最大の問題はおそらく技術的なものではなく、社会的なものです。あまり知識のないプログラマーは、goto を非推奨にすることで賢く聞こえると感じることがあるようです。時々、そのようなプログラマを満足させる必要があるかもしれません。それが人生だ。

今のgotoの一番悪いところは、それが十分に活用されていないことだ。

というようなことはありません GOTOは有害と考えられる.

GOTO はツールであり、すべてのツールと同様に使用できます。 虐待された.

ただし、プログラミングの世界には、次のような傾向があるツールがたくさんあります。 虐待された 存在以上のもの 使用済み, 、GOTOもその1つです。の デルフィの声明は別のものです。

個人的にはどちらも使いません 典型的なコードでは, 、しかし私は両方の奇妙な用法を持っていました 後藤 そして それは保証されており、代替ソリューションにはさらに多くのコードが含まれていたでしょう。

最善の解決策は、コンパイラがキーワードが次のとおりであることを警告することです。 汚れた, 警告を取り除くには、ステートメントの周りにいくつかのプラグマ ディレクティブを詰め込む必要があります。

自分の子供たちにこう言っているようなものだ ハサミで走らないこと. 。ハサミが悪いわけではありませんが、ハサミの使用方法によっては、健康を維持するための最良の方法とは言えない場合があります。

Linux カーネルでいくつかのことを行うようになって以来、goto は以前ほど気にならなくなりました。最初、彼ら (カーネル担当者) が私のコードに goto を追加したのを見て、ある意味恐怖を感じました。それ以来、私は限られたコンテキストでの goto の使用に慣れてきており、今では自分でも時々使用するようになりました。通常、これは関数内の複数の場所で同じクリーンアップと救済を複製するのではなく、関数の最後にジャンプして何らかのクリーンアップと救済を行う goto です。そして通常、それは別の関数に引き渡すほど大きなものではありません。ローカルで (k)malloc された変数の一部を解放するのが典型的なケースです。

setjmp/longjmp を使用するコードを一度だけ書いたことがあります。それはMIDIドラムシーケンサープログラムの中にありました。再生はすべてのユーザー操作とは別のプロセスで行われ、再生プロセスは UI プロセスと共有メモリを使用して、再生に必要な限られた情報を取得しました。ユーザーが再生を停止したい場合、再生プロセスは、ユーザーが停止したいときに実行されていた場所を複雑に巻き戻すのではなく、longjmp を「最初に戻して」開始するだけでした。それは素晴らしく機能し、シンプルでした。その際、それに関連する問題やバグは一度もありませんでした。

setjmp/longjmp にはそれなりの場所があります。しかし、その場所は、あまり頻繁に訪れることはなく、非常に長い間一度しか訪れない場所です。

編集:コードを見ただけです。実際に私が使用したのは、longjmp ではなく siglongjmp() でした (大したことではありませんが、siglongjmp が存在することさえ忘れていました)。

自分で考えることができる限り、決してそうではありませんでした。

C で VM を作成している場合、(gcc の) goto を使用すると次のように計算されることがわかります。

char run(char *pc) {
    void *opcodes[3] = {&&op_inc, &&op_lda_direct, &&op_hlt};
    #define NEXT_INSTR(stride) goto *(opcodes[*(pc += stride)])
    NEXT_INSTR(0);
    op_inc:
    ++acc;
    NEXT_INSTR(1);
    op_lda_direct:
    acc = ram[++pc];
    NEXT_INSTR(1);
    op_hlt:
    return acc;
}

ループ内の従来のスイッチよりもはるかに高速に動作します。

なぜなら goto 混乱を招くメタプログラミングに使用できる

Goto どちらも 上級 そして 低レベルの 式を制御するため、その結果、ほとんどの問題に適した適切な設計パターンが存在しません。

その 低レベルの goto は、次のようなより高度なものを実装する原始的な操作であるという意味で、 while または foreach か何か。

その 上級 特定の方法で使用すると、構造化されたループを除いて、明確なシーケンスで中断のない方法で実行されるコードが取り込まれ、それが十分な機能を備えたロジックの部分に変更されます。 gotos、動的に再構築されるロジックのグラブバッグ。

そこで、 平凡な 側に goto.

平凡な側面 つまり、上向きの goto は完全に合理的なループを実装でき、下向きの goto は完全に合理的なループを実行できます。 break または return. 。もちろん、実際の while, break, 、 または return 可哀想な人間がその効果をシミュレートする必要がないので、はるかに読みやすくなるでしょう。 goto 全体像を把握するために。したがって、一般的には悪い考えです。

悪の側 while、break、return に goto を使用せず、いわゆる呼び出しに対して goto を使用するルーチンが含まれます。 スパゲッティロジック. 。この場合、goto に満足している開発者は、goto の迷路からコードの断片を構築していますが、それを理解する唯一の方法は、全体を頭の中でシミュレーションすることですが、goto が多い場合は非常に疲れる作業です。つまり、コードを評価する際の困難を想像してみてください。 else 正確には逆ではありません if, 、ネストされた場所 if外部によって拒否されたいくつかのものを許可する可能性があります if, 、などなど。

最後に、この主題を実際にカバーするために、Algol を除く基本的にすべての初期言語は、当初、そのバージョンの対象となる単一のステートメントのみを作成していたことに注意する必要があります。 if-then-else. 。したがって、条件付きブロックを実行する唯一の方法は次のとおりです。 goto 逆条件を使用してそれを囲みます。非常識なことはわかっていますが、古い仕様書をいくつか読んだことがあります。最初のコンピューターはバイナリ マシン コードでプログラムされていたことを思い出してください。そのため、あらゆる種類の HLL が命の恩人だったと思います。彼らは、具体的にどのような HLL 機能を搭載しているかについてはあまりこだわりがなかったのだと思います。

そうは言っても、私は以前は1つを貼り付けていました goto 私が書いたすべてのプログラムに 「純粋主義者を困らせるためだけに」.

プログラマーに対して GOTO ステートメントの使用を拒否することは、大工に釘を打つときに壁を傷つける可能性があるのでハンマーを使用しないでくださいと言うようなものです。本物のプログラマは、GOTO をいつどのように使用するかを知っています。私は、GOTO の使用を避けるためだけに、いわゆる「構造化プログラム」のいくつかを追跡し、プログラマーを射殺しかねないほど恐ろしいコードを目にしました。わかりました。相手側を弁護するために、私も実際のスパゲッティ コードをいくつか見ましたが、それらのプログラマーも射殺されるべきです。

ここに私が見つけたコードのほんの一例を示します。

  YORN = ''
  LOOP
  UNTIL YORN = 'Y' OR YORN = 'N' DO
     CRT 'Is this correct? (Y/N) : ':
     INPUT YORN
  REPEAT
  IF YORN = 'N' THEN
     CRT 'Aborted!'
     STOP
  END

- - - - - - - - - - - -または - - - - - - - - - - -

10:  CRT 'Is this Correct (Y)es/(N)o ':

     INPUT YORN

     IF YORN='N' THEN
        CRT 'Aborted!'
        STOP
     ENDIF
     IF YORN<>'Y' THEN GOTO 10

「このリンクで http://kerneltrap.org/node/553/2131"

皮肉なことに、goto を削除するとバグが発生します。スピンロック呼び出しは省略されました。

元の論文は「無条件のGOTOは有害であると考えられる」と考えるべきです。特に、条件付き (if) と反復 (while) は、初期のコードに一般的なテストとジャンプではなく、構築します。 goto 適切な制御構造が存在しない一部の言語や環境では依然として役立ちます。

唯一同意したところについては後藤 できた エラーに対処する必要がある場合に使用され、エラーが発生する特定のポイントごとに特別な処理が必要になります。

たとえば、リソースを取得してセマフォまたはミューテックスを使用している場合、それらを順番に取得し、常に逆の方法で解放する必要があります。

一部のコードでは、これらのリソースの取得に非常に奇妙なパターンが必要であり、デッドロックを回避するために、これらのリソースの取得と解放の両方を正しく処理するための、保守が容易で理解できる制御構造を単に作成することはできません。

goto を使用しなくても正しく実行することは常に可能ですが、このケースや他のいくつかのケースでは、実際には、主に読みやすさと保守性の観点からは Goto の方が優れたソリューションです。

-アダム

最新の GOTO 使用法の 1 つは、C# コンパイラーによって、yield return で定義された列挙子のステート マシンを作成することです。

GOTO はプログラマではなくコンパイラが使用する必要があります。

C と C++ (他の原因の中でも特に) が中断と続行のラベルを付けるまで、goto は役割を持ち続けます。

GOTO 自体が悪であるとすれば、コンパイラは JMP を生成するため、悪となるでしょう。コードのブロックにジャンプすること、特にポインターに従うことが本質的に悪であるとすれば、RETurn 命令も悪となるでしょう。むしろ、悪は悪用の可能性を秘めています。

時々、イベントに応じて各オブジェクトが複雑な状態シーケンスに従う必要がある多数のオブジェクトを追跡する必要があるアプリを作成しなければならなかったことがありますが、全体は間違いなくシングルスレッドでした。典型的な状態シーケンスを疑似コードで表すと、次のようになります。

request something
wait for it to be done
while some condition
    request something
    wait for it
    if one response
        while another condition
            request something
            wait for it
            do something
        endwhile
        request one more thing
        wait for it
    else if some other response
        ... some other similar sequence ...
    ... etc, etc.
endwhile

これは新しいことではないと思いますが、C(++) でこれを処理する方法は、いくつかのマクロを定義することでした。

#define WAIT(n) do{state=(n); enque(this); return; L##n:;}while(0)
#define DONE state = -1

#define DISPATCH0 if state < 0) return;
#define DISPATCH1 if(state==1) goto L1; DISPATCH0
#define DISPATCH2 if(state==2) goto L2; DISPATCH1
#define DISPATCH3 if(state==3) goto L3; DISPATCH2
#define DISPATCH4 if(state==4) goto L4; DISPATCH3
... as needed ...

次に (状態が最初は 0 であると仮定して)、上記の構造化ステート マシンは構造化コードに変わります。

{
    DISPATCH4; // or as high a number as needed
    request something;
    WAIT(1); // each WAIT has a different number
    while (some condition){
        request something;
        WAIT(2);
        if (one response){
            while (another condition){
                request something;
                WAIT(3);
                do something;
            }
            request one more thing;
            WAIT(4);
        }
        else if (some other response){
            ... some other similar sequence ...
        }
        ... etc, etc.
    }
    DONE;
}

これのバリエーションとして、CALL と RETURN を使用できるため、一部のステート マシンが他のステート マシンのサブルーチンのように動作できます。

珍しいですか?はい。メンテナー側にある程度の学習が必要ですか?はい。その学習は報われますか?そう思います。ブロックにジャンプする GOTO なしで実行できますか?いいえ。

同僚やマネージャーがコードレビューの際、または偶然このコードを見つけたときに、間違いなくこのコードの使用に疑問を呈するため、私はこれを避けています。これには用途があると思いますが (たとえば、エラー処理の場合)、これに関して何らかの問題を抱えている他の開発者と衝突することになるでしょう。

それはそれだけの価値はありません。

実際、このコードを書くより良い (より速い) 方法が文字通り思いつかなかったため、goto を使用せざるを得なくなったことがわかりました。

複雑なオブジェクトがあり、それに対して何らかの操作を行う必要がありました。オブジェクトが 1 つの状態にある場合は、操作の迅速なバージョンを実行できますが、そうでない場合は、操作の遅いバージョンを実行する必要があります。問題は、場合によっては、遅い操作の途中で、これは速い操作で実行できたかもしれないと認識することができたということです。

SomeObject someObject;    

if (someObject.IsComplex())    // this test is trivial
{
    // begin slow calculations here
    if (result of calculations)
    {
        // just discovered that I could use the fast calculation !
        goto Fast_Calculations;
    }
    // do the rest of the slow calculations here
    return;
}

if (someObject.IsmediumComplex())    // this test is slightly less trivial
{
    Fast_Calculations:
    // Do fast calculations
    return;
}

// object is simple, no calculations needed.

これはリアルタイム UI コードの速度が重要な部分であったため、ここでは GOTO が正当化されたと正直に思います。

ヒューゴ

goto を使用できるほぼすべての状況で、他の構造を使用して同じことを実行できます。いずれにせよ、Goto はコンパイラによって使用されます。

私自身は明示的に使用したことはありませんし、使用する必要もありません。

私が見ていないことが一つあります どれでも ここでの答えは、「goto」ソリューションが多くの場合、 もっと効率的 よく言われる構造化プログラミング ソリューションの 1 つよりも優れています。

多数のネストされたループの場合を考えてみましょう。ここでは、一連のループの代わりに 'goto' を使用します。 if(breakVariable) セクションの方が明らかに効率的です。「ループを関数に入れて return を使用する」という解決策は、多くの場合まったく不合理です。ループでローカル変数が使用されている可能性が高い場合は、関数パラメーターを介してそれらをすべて渡す必要があり、そこから生じる余分な問題の負荷を処理する可能性があります。

ここで、クリーンアップのケースを考えてみましょう。これは私自身も頻繁に使用しており、多くの言語で使用できない try{} catch {} 構造の原因であると考えられるほど一般的です。同じことを達成するために必要なチェックと追加の変数の数は、ジャンプするための 1 つまたは 2 つの命令よりもはるかに悪く、また、追加の関数による解決策はまったく解決策ではありません。それがより管理しやすく、より読みやすいとは言えません。

コード スペース、スタック使用量、および実行時間は、多くのプログラマにとって多くの状況ではあまり重要ではないかもしれませんが、作業するコード スペースが 2KB しかない組み込み環境では、明確に定義された命令を避けるために 50 バイトの追加命令が必要になります。 「goto」はただ笑えるだけであり、これは多くの高レベルのプログラマーが信じているほど珍しい状況ではありません。

「goto は有害である」という発言は、たとえそれが常に過剰な一般化であったとしても、構造化プログラミングに移行する上で非常に役立ちました。現時点では、私たちは皆、この言葉を十分に聞いているので、それを使用することには慎重になります(当然です)。それが明らかに仕事に適したツールである場合、それを恐れる必要はありません。

これは、深くネストされたループから抜け出すために使用できますが、ほとんどの場合、深くネストされたループがなくても、コードをよりクリーンになるようにリファクタリングできます。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top