このWikiページは次のことを示しています。
schrödinbugは、ソースコードを読んだり、異常な方法でプログラムを使用したりした場合に最初に動作するはずのないことに気づいた後にのみ現れるバグです。Jargonファイルには、「…これは不可能に思えますが、起こります。一部のプログラムは潜在的なシュレディンバグを長年にわたって隠していました。」
話されていることは非常にあいまいです。
誰かがschrödinbugがどのようなものであるかの例を提供できますか(架空/現実の状況のように)?
このWikiページは次のことを示しています。
schrödinbugは、ソースコードを読んだり、異常な方法でプログラムを使用したりした場合に最初に動作するはずのないことに気づいた後にのみ現れるバグです。Jargonファイルには、「…これは不可能に思えますが、起こります。一部のプログラムは潜在的なシュレディンバグを長年にわたって隠していました。」
話されていることは非常にあいまいです。
誰かがschrödinbugがどのようなものであるかの例を提供できますか(架空/現実の状況のように)?
回答:
私の経験では、パターンは次のとおりです。
ここで論理的にしましょう。働いたことがない可能性があり、コードは... 働いたことがない可能性が。それは場合はやった仕事を、その後の文はfalseです。
したがって、説明したとおりのバグ(欠陥のあるコードが動作を停止するのを観察しているバグ)は、明らかにナンセンスです。
現実には、次の2つのいずれかが発生しました。
1)開発者はコードを完全には理解していません。この場合、コードは通常混乱であり、そのどこかに何らかの外部条件に対する重大だが非自明な感度があります(たとえば、特定のOSバージョンまたは機能がマイナーではあるが重要な方法で動作する方法を制御します)。この外部条件は変更され(たとえば、サーバーのアップグレードまたは無関係と思われる変更によって)、そうすることでコードが破損します。
その後、開発者はコードを見て、履歴コンテキストを理解せず、考えられるすべての依存関係とシナリオをトレースする時間がないため、機能しなかったと宣言して書き直します。
この状況で、ここで理解するべきことは、「それは機能しなかった」という考えは間違いなく間違っているということです。
それは、それを書き換えることが悪いことではないということではありません-多くの場合、そうではありませんが、多くの場合、時間がかかり、コードのセクションを書き換えることは多くの場合、より速く、あなたが物事を修正したことを確認することができます。
2)実際には機能せず、誰も気づいていない。これは、特に大規模システムでは驚くほど一般的です。この例では、誰かが新しいものを開始し、これまで誰もしなかった方法で物事を検討し始めます。または、ビジネスプロセスが変更されて、以前はマイナーなエッジケースがメインプロセスに持ち込まれ、実際には機能しなかった(または一部ではなく一部が機能した)時間)が見つかり、報告されます。
開発者はそれを見て、「それは機能しなかったはずだ」と宣言しますが、ユーザーは「ナンセンス、私たちは何年もそれを使用しています」と言います。開発者は、彼らが「ああ、私たちが行うのです行く、その時点での正確な条件見つかったことを)今までになかった」に変更しました。
ここで開発者は正しい-それは決して機能しなかったかもしれないし、機能しなかった。
ただし、いずれの場合も、次の2つのいずれかが当てはまります。
誰もが機能するはずのないコードに言及しているため、約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
星印の付いた部分には最も重要なコードがありました。実際の計算を行ったのはそれだけです。明らかに、これはうまくいかないはずですよね?
多くのデバッグIsYearlyInterestMode
が必要でしたがTrue
、最終的には原因が判明しましたNot IsYearlyInterestMode
。それは、誰かが線に沿って整数にキャストし、それを真に設定することになっている関数でそれをインクリメントしたためです(0のFalse
場合は1に設定され、VB True
なので、ロジックを見ることができますそこで)、それをブール値にキャストバックします。そして、私は決して起こり得ない状態でありながら、常に起こる状態を残されました。
IsYearlyInterestMode
真と非真の両方を評価するつもりはありませんでした。1を含む数行(追加元の開発者if
sが、実際にそれがどのように動作するかを理解していなかった-それはちょうど仕事に起こった、それは良い十分だったので。
実世界の例を知らないが、例の状況でそれを簡素化する:
これは、バグがアプリケーションの一部の状態を破壊し、以前の通常の状態で障害を引き起こすために発生する可能性があります。
実際の例。コードを表示することはできませんが、ほとんどの人はこれに関係します。
私が働いているユーティリティ関数の大きな内部ライブラリがあります。ある日、特定のことを行う関数を探していますが、Frobnicate()
それを使用してみてください。ええと:Frobnicate()
常にエラーコードが返されることがわかりました。
実装を掘り下げてみるFrobnicate()
と、常に失敗するという基本的な論理エラーがいくつか見つかりました。ソース管理では、関数が記述されてから変更されていないことがわかります。つまり、関数が意図したとおりに動作したことはありません。誰もこれに気付いていないのはなぜですか?残りのソース登録を検索すると、既存のすべての呼び出し元Frobnicate()
が戻り値を無視していることがわかります(したがって、独自の微妙なバグが含まれています)。これらの関数を変更して戻り値をチェックする必要がある場合、それらも失敗し始めます。
これは、ジョン・ホプキンスが答えで言及した条件#2の一般的なケースであり、大規模な内部ライブラリでは憂鬱なほど一般的です。
printf()
いつですか?)
これは、いくつかのシステムコードで見た本物のSchrödinbugです。ルートデーモンはカーネルモジュールと通信する必要があります。そのため、カーネルコードはいくつかのファイル記述子を作成します。
int pipeFDs[1];
次に、名前付きパイプに接続されるパイプを介して通信をセットアップします。
int pipeResult = pipe(pipeFDs);
これは機能しません。pipe()
2つのファイル記述子を配列に書き込みますが、1つだけのスペースがあります。しかし、約7年間は機能しました。配列は、メモリ内でファイル記述子として採用される未使用の領域の前にありました。
その後、ある日、コードを新しいアーキテクチャに移植しなければなりませんでした。動作しなくなり、動作するはずのないバグが発見されました。
Schrödinbugの帰結はHeisenbugです -これは調査または修正、あるいはその両方を試みると消える(または時々現れる)バグを記述しています。
ハイゼンバグは、デバッガーがロードされたときに実行および非表示になる神話上の賢い小さなブライターですが、視聴を停止すると木工から出てきます。
実際には、これらは通常、次のいずれかによって引き起こされるようです。
-DDEBUG
はリリースビルドとは異なるレベルに最適化されます。どちらも、リリース機器でリリースコードをテストすることの重要性と、エミュレータを使用したユニット/モジュール/システムテストを強調しています。
私はいくつかのシェーディンバグを見てきましたが、いつも同じ理由で:
会社の方針では、誰もがプログラムを使用することになっています。
誰もそれを実際に使用しませんでした(主にトレーニングがなかったためです)。
しかし、彼らはこれを経営者に伝えることができませんでした。そのため、誰もが「このプログラムを2年間使用しており、今日までこのバグに遭遇したことはありません」と言わなければなりませんでした。
少数のユーザー(それを書いた開発者を含む)を除いて、プログラムは実際には機能しませんでした。
あるケースでは、プログラムは十分なテストを受けていましたが、実際のデータベースではテストされていませんでした(機密性が高すぎると見なされたため、偽のバージョンが使用されました)。
私自身の歴史の例がありますが、これは約25年前のことです。私はTurbo Pascalで初歩的なグラフィックプログラミングをしている子供でした。TPには、画面の領域をポインターベースのメモリブロックにコピーし、それを他の場所にblitできる関数を含むBGIというライブラリがありました。白黒画面でのxor-blittingと組み合わせると、単純なアニメーションを実行するために使用できます。
さらに一歩踏み込んでスプライトを作りたかった。大きなブロックとコントロールを描画して色を付けるプログラムを作成しましたが、それらをピクセルとして再現し、簡単な描画プログラムを作成してスプライトを作成し、それをメモリにコピーしました。問題が1つだけありました。これらのブリットスプライトを使用するには、他のプログラムが読み取れるようにファイルに保存する必要があります。しかし、TPには、ポインターベースのメモリ割り当てをシリアル化する方法がありませんでした。マニュアルには、ファイルに書き込めないと書かれていました。
私は、ファイルへの書き込みに成功したコードを思い付きました。そして、ゲームを作成する途中で、バックグラウンドで描画プログラムからスプライトをブリットするテストプログラムの作成を開始しました。そして、それは美しく働きました。しかし翌日、それは機能しなくなりました。それは文字化けした混乱以外何も見せませんでした。二度と機能しなかった。私は新しいスプライトを作成しましたが、完全に機能しました。機能しないまで、そして再び文字化けしました。
長い時間がかかりましたが、最終的には何が起こっているのかがわかりました。私が思ったように、描画プログラムはコピーしたピクセルデータをファイルに保存しませんでした-ポインター自体を保存していました。次のプログラムがファイルを読み取ると、最後のプログラムがそこに書き込んだものがまだ含まれているメモリの同じブロックへのポインタで終わりました(これはMS-DOS上にあり、メモリ管理は存在しませんでした)。しかし、それは機能しました...再起動するか、同じメモリ領域を再利用したものを実行するまで、ビデオメモリブロックにまったく無関係なデータを大量に送信していたため、文字化けしました。
動作することはないはずで、動作するように見えることさえなかったはずです(そして実際のOSでは動作しません)、それでも動作し、一度壊れると、壊れたままになります。
これは、人々がデバッガを使用するときに常に発生します。
デバッグ環境は、実際の(デバッガーなしの)実稼働環境とは異なります。
デバッガーで実行すると、デバッガーのスタックフレームがバグをマスクするため、スタックオーバーフローなどをマスクする場合があります。