web-dev-qa-db-ja.com

シュレディンバグとは何ですか?

これは wikiページ が伝えます:

Schrödinbugは、誰かがソースコードを読んだり、通常とは異なる方法でプログラムを使用したりして、そもそも動作するはずがないことに気づき、その時点でプログラムが修正されるまで全員の動作をすぐに停止した後に現れるバグです。ジャーゴンファイルはこう付け加えています。

話されていることは非常にあいまいです。

誰かが(架空の/現実の状況のような)シュレーディンバグの例を提供できますか?

53
Pacerier

私の経験では、パターンは次のとおりです。

  • システムは、何年にもわたって機能する
  • エラーが報告された
  • 開発者はエラーを調査し、完全に欠陥があると思われるコードのビットを見つけ、それが「機能するはずがなかった」と宣言します。
  • バグが修正され、機能しなかった(ただし何年も機能しなかった)コードの凡例が拡大する

ここで論理的にしましょう。機能しなかった可能性のあるコード... 機能しなかった可能性があるdidが機能する場合、ステートメントはfalseです。

したがって、説明されているバグは正確に(欠陥のあるコードが動作を停止していることを確認している)は、まったくナンセンスであると言います。

実際には、次の2つのいずれかが発生しています。

1)開発者はコードを完全に理解していません。この場合、コードは通常混乱しており、コードのどこかに、ある外部条件に対して重要ではあるが自明ではない感度があります(特定のOSバージョンまたは特定のOSバージョンまたは構成によって、一部の機能が重要だが重要な方法で機能する方法を制御します)。この外部条件が変更され(たとえば、サーバーのアップグレードや無関係であると思われる変更によって)、そうすることでコードが破損します。

次に、開発者はコードを調べ、履歴コンテキストを理解したり、あらゆる依存関係やシナリオをたどったりする時間がないため、機能しない可能性があることを宣言し、コードを書き直しました。

この状況で、ここで理解する必要があるのは、「うまくいかなかった」という考えはおそらく間違いだったということです(実際に機能したため)。

それが悪いことであると言っているわけではありません-多くの場合はそうではありませんが、多くの場合、何が間違っているのかを正確に知るのに時間がかかり、コードのセクションを書き換える方が高速であり、修正したことを確認できます。

2)実際には機能しなかった、だれも気づかなかっただけ。これは、特に大規模なシステムでは、驚くほど一般的です。この例では、誰かが新しいものを開始し、誰も以前に見たことのない方法で物事を見始めたり、ビジネスプロセスが変更されて、以前はマイナーだったEdgeケースをメインプロセスにもたらしたり、実際には機能しなかった(または一部では機能しなかった)時間)が見つかり、報告されます。

開発者はそれを見て「うまくいかなかった」と宣言しますが、ユーザーは「ナンセンス、私たちは何年も使ってきました」と言っており、それらはある程度正しいが無関係であると考えています(通常、開発者は、「ああ、そうですします今や以前に行ったことがありません)」が変更された時点での正確な状態を見つけました。

ここで開発者は正しいです-それはうまくいったことができなかったし、決してうまくいかなかった。

ただし、どちらの場合も、次の2つのいずれかが当てはまります。

  • 「うまくいかなかった」という主張は真実であり、うまくいったことはありません。
  • それは機能し、「それは決して機能することができなかった」という文は誤りであり、コードとその依存関係に対する(通常は妥当な)理解の欠如に帰着します。
83
Jon Hopkins

誰もが機能してはならないコードについて言及しているため、8年前に.netに変換されていた瀕死のVB3プロジェクトで私が遭遇した例を紹介します。残念ながら、.netバージョンが完成するまでプロジェクトを最新の状態に保つ必要があり、VB3をリモートで理解できるのは私だけでした。

計算ごとに何百回も呼び出される非常に重要な関数が1つありました。長期年金制度の月利を計算しました。面白い部分を再現します。

Function CalculateMonthlyInterest([...], IsYearlyInterestMode As Boolean, [...]) As Double
    [about 30 lines of code]
    If IsYearlyInterestMode Then
        [about 30 lines of code]
        If Not IsYearlyInterestMode Then
            [about 30 lines of code (*)]
        End If
    End If
End Function

星印が付いている部分が最も重要なコードでした。実際の計算を行ったのはそれだけでした。明らかにこれはうまくいかなかったはずですよね?

多くのデバッグが必要でしたが、最終的に原因を見つけました:IsYearlyInterestModeTrueであり、Not IsYearlyInterestModeもtrueでした。それは、誰かがラインに沿って整数にキャストした後、それをtrueに設定することになっている関数でインクリメントされたためです(Falseが0の場合、1に設定されますVB Trueなので、ロジックを確認できます)、それをブール値にキャストします。そして、私は決して起こり得ない状態であり、それでも常に起こります。

54
configurator

実際の例はわかりませんが、例の状況でそれを単純化するために:

  • バグが発生する原因となる状況下ではアプリケーションがコードを実行しないため、しばらくはバグに気づきません。
  • 誰かが通常の使用以外で何かをする(またはソースを検査する)ことによってそれに気づきます。
  • バグに気付いたので、アプリケーションは通常の状態になるまで、そしてバグが修正されるまで失敗します。

これは、バグによってアプリケーションの一部の状態が破損し、以前の通常の状態で障害が発生するために発生する可能性があります。

16
StuperUser

実際の例。コードを表示することはできませんが、ほとんどの人はこれに関係があります。

私たちが作業するユーティリティ関数の大きな内部ライブラリがあります。ある日、特定のことを行うための関数を探していて、Frobnicate()を使ってみてください。ええと、Frobnicate()は常にエラーコードを返すことがわかりました。

実装を掘り下げてみると、Frobnicate()にいくつかの基本的な論理エラーがあり、常に失敗します。ソース管理では、関数が作成されてから変更されていないことがわかります。つまり、関数にはneverが意図したとおりに機能していることがわかります。なぜ誰もがこれに気づいていないのですか?残りのソース登録を検索したところ、Frobnicate()の既存の呼び出し元がすべて戻り値を無視していることがわかりました(そのため、独自の微妙なバグが含まれています)。これらの関数を変更して、戻り値をチェックするようにすると、それらも失敗し始めます。

これはジョンホプキンスが彼の回答で述べた条件#2の一般的なケースであり、大規模な内部ライブラリでは憂鬱なほど一般的です。

13
JSBձոգչ

これが私がいくつかのシステムコードで見た本物のシュレーディンバグです。ルートデーモンはカーネルモジュールと通信する必要があります。したがって、カーネルコードはいくつかのファイル記述子を作成します。

_int pipeFDs[1];
_

次に、名前付きパイプに接続されるパイプを介して通信をセットアップします。

_int pipeResult = pipe(pipeFDs);
_

これはすべきではない機能します。 pipe()は、2つのファイル記述子を配列に書き込みますが、1つのスペースしかありません。しかし、約7年間はdid機能します。配列は、たまたまメモリ内の未使用の領域の前にあり、ファイル記述子として選択されました。

その後、ある日、コードを新しいアーキテクチャに移植する必要がありました。動作が停止し、動作するはずのないバグが発見されました。

10
user4051

Schrödinbugの当然の結果は Heisenbug -で、バグの調査や修正を試みたときに消える(またはときどき現れる)バグを表しています。

Heisenbugsは、デバッガーが読み込まれると実行されて非表示になる神話的な賢い小さなブレーターですが、監視をやめると木工から出てきます。

実際には、これらは通常、次のいずれかによって引き起こされているようです。

  • 最適化の影響。コードは-DDEBUGはリリースビルドとは異なるレベルに最適化されています
  • シミュレーションされた「完全な」ダミーロードと微妙に異なる実際の通信バスまたは割り込みによるわずかなタイミングの違い

どちらも、リリース機器でリリースコードをテストすることの重要性、およびエミュレータを使用したユニット/モジュール/システムテストの重要性を強調しています。

5
Andrew

私はいくつかのシェーディンバグを見てきましたが、いつも同じ理由で:

会社の方針では、全員がプログラムを使用することになっています。
誰も実際にはそれを使用していませんでした(主にそれに対するトレーニングがなかったためです)。
しかし、彼らは経営者にこれを伝えることができませんでした。したがって、「このプログラムを2年間使用していて、今日までこのバグに遭遇したことがない」と誰もが言わなければなりませんでした。
少数のユーザー(作成した開発者を含む)を除いて、プログラムは実際には機能しませんでした。

あるケースでは、プログラムは多くのテストの対象となっていましたが、実際のデータベースにはありませんでした(機密性が高すぎると見なされたため、偽のバージョンが使用されました)。

2
finnw

私自身の歴史の例があります。これは約25年前のことです。私はTurbo Pascalで初歩的なグラフィックプログラミングをしている子供でした。 TPには、BGIと呼ばれるライブラリがあり、画面の領域をポインタベースのメモリブロックにコピーして、別の場所に書き込むことができるいくつかの関数が含まれていました。白黒画面でのxor-blittingと組み合わせると、単純なアニメーションを実行するために使用できます。

一歩踏み込んでスプライトを作りたかった。大きなブロックとコントロールを描画してそれらを色付けするプログラムを作成しました。これは、これらをピクセルとして再現し、スプライトを作成する単純な描画プログラムを作成して、メモリにコピーできるようにしました。問題が1つだけありました。これらの分割されたスプライトを使用するには、他のプログラムが読み取れるようにファイルに保存する必要があります。しかし、TPには、ポインタベースのメモリ割り当てをシリアル化する方法がありませんでした。マニュアルには、ファイルに書き込むことができないと書かれていた。

ファイルへの書き込みに成功したコードを思いつきました。そして、ゲームを作成する途中で、バックグラウンドで描画プログラムからスプライトを破壊するテストプログラムを書き始めました。そして、それは美しく機能しました。しかし翌日、それは機能しなくなりました。それは文字化けした混乱しか示しませんでした。それは二度と機能しませんでした。私は新しいSpriteを作成しましたが、完全に機能しました。それができず、再び文字化けした混乱になるまでです。

長い時間がかかりましたが、最終的に何が起こっているのかが分かりました。描画プログラムは、思ったように、コピーしたピクセルデータをファイルに保存するのではなく、ポインタ自体を保存するものでした。次のプログラムがファイルを読み取ると、最後のプログラムがそこに書き込んだものをまだ含んでいる同じメモリブロックへのポインタで終了しました(これはMS-DOS上にあり、メモリ管理は存在しませんでした)。しかし、それは機能しました...再起動するか、同じメモリ領域を再利用する何かを実行するまで、完全に無関係なデータの束をビデオメモリブロックにブリットしていたため、文字化けしました。

動作するはずがなく、動作するように見えるはずもありません(実際のOSでは動作しません)が、動作しました。壊れた場合は、壊れたままでした。

1
silentcoder

これは、人々がデバッガを使用するときに常に起こります。

デバッグ環境は、実際のデバッガーなしの実稼働環境とは異なります。

デバッガーのスタックフレームがバグをマスクするため、デバッガーで実行すると、スタックオーバーフローなどがマスクされる場合があります。

0
S.Lott

私は本当のシュロディンバグを見たことがないので、それらが存在する可能性はないと思います。

むしろ、何年にもわたって潜んでいるバグを露わにする何かが変わった。何が変更されても変更されるため、誰かがバグを発見すると同時にバグが現れ続けます。

0
Loren Pechtel