初日から「未知の未知の」バグに備えて設計とコードを準備する最良の方法は何ですか?


8

私はただ疑問に思っています。「未知の未知の」バグ、特に最後の数分に頻繁に発生する厄介でランダムなバグを回避するための実用的な方法やテクニック、さらにはこれらのことを最小限のレベルに保つためのトリックはありますか。新しいプラットフォームで作業するとき、または特定の新しいテクノロジーを初めて使用するとき、設計とコードが十分に堅牢であることをどのように正当化するのですか?またはこれらのことは時間と間違いによってのみ学ぶことができますか?

(ほとんどの作業時間にC ++を使用しています)

ありがとう!

回答:


5

「いいえバグ:CおよびCでお届けするエラーフリーのコード++」ほぼ20年前、私はデビッドThielenの優秀な本から本への洞察の多くを得たとして利用できるようになりました、無料のPDF

彼は私に2つの素晴らしいアイデアを教えてくれました...

バグはどこからも発生しません。私たちプログラマーは皆、座って自分の指でコードに書き込みます。

「バグ」は、外部の機関がプログラムにバグを蔓延させることを決定したこと、およびクリーンな生活を送って、コンピュータの足元で小さな毛皮で覆われた動物を犠牲にすると、彼らは消え去ることを意味します...この概念は重要ですコードをデバッグするためのアプローチ。間違いを「バグ」と見なした場合、間違いが見つからないことを望みます。(あなたは、良い妖精がやって来て、ピクシーダストを振りかけ、バグが残っていることを望みます。)

バグはバグと呼ばれるのではなく、Massive Fuck-Ups [MFU]と呼ばれるべきです。MFUは、プログラムが人によって書かれ、人がミスをするために存在します... MFUを作成します。あなたは座って、先見の明の完全な悪意を持ってMFUをコードに入れます。それについて考えてください-あなたはそこにバグを置いているのがあなただということを知っています。したがって、コードに腰を下ろすと、いくつかのバグが挿入されます。

バグを書くのはすべてプログラマーの避けられない運命なので、バグを検出したときにジャンプし、悲鳴を上げ、赤旗を振るようなものを含めて、防御的にコーディングする必要があります。

90年代初頭に書かれたため、ティーレンの本の詳細はかなり古くなっています。たとえば、LinuxおよびMac OS Xでは、C ++ new演算子用の独自のラッパーを作成する必要がなくなりました。そのためにvalgrindを使用できます。

しかし、C / C ++ / ObjCに対して日常的に行うことはいくつかあります。

  1. 可能であれば、コンパイラの[警告はエラーです]オプションをオンにして、すべて修正します。(私はこれらすべてを一度に修正するのに数週間かかるという1つのレガシープロジェクトを維持しています。そのため、数週間ごとにファイルを修正するだけで、数年後にはそのオプションをオンにすることができます。)
  2. GimpelのPC-LintまたはAppleのXcodeに現在組み込まれている非常に気の利いたツールなどの静的コード分析ツールを使用します。コベリティはさらに優れていますが、コストは大企業の場合であり、単なる人間ではありません。
  3. valgrindなどの動的分析ツールを使用して、メモリの問題やリークなどをチェックします。
  4. Thielenが言うように(そしてこの章はまだ読む価値があります):Assert The World。もちろん、馬鹿以外は誰もnilポインタであなたの関数を呼び出さないでしょう-そしてそれは誰かがどこかでそれを行う馬鹿であることを意味します。あなたが今日やっていたことがぼんやりした3年後のあなたかもしれません。そのため、関数の先頭にアサートを追加して、そのポインター引数を検証します-入力に3秒かかり、リリース実行可能ファイルではなくなります。
  5. C ++では、RTTIはあなたの友達です。繰り返しになりますが、馬鹿以外の誰も間違った種類のオブジェクトへのポインターを使用して関数を呼び出すことはありません。つまり、必然的に、馬鹿はそうします-そして、それを防御するためのコストはごくわずかです。GObjectから派生したCベースのコードでは、防御動的キャストマクロを使用して同じことを行うことができます。
  6. 自動化された単体テストと回帰テストは、私のレパートリーの重要な部分になりました。1つのプロジェクトでは、これらはリリースビルドシステムの不可欠な部分であり、すべてが合格しない限り、ビルドは完了しません。
  7. もう1つの重要な部分は、実行時の環境変数などによって有効にできるデバッグ実行可能ファイルとリリース実行可能ファイルの両方でコードをロギングすることです。
  8. 防御的なテストを作成して、デバッグ実行可能ファイルを実行しているプログラマが失敗しても、それらを無視できないようにします。コンソールへのランタイムメッセージは無視できます。アサートでクラッシュするプログラムは無視できません。
  9. 設計するときは、パブリックAPIと、外部コードでは取得できないプライベート実装を提供します。そうすれば、リファクタリングする必要がある場合、魔法の内部状態変数などに依存する人はいません。C ++クラスでは、私は保護されたプライベートの大ファンです。また、プロキシクラスは素晴らしいと思いますが、実際には使用しません。

もちろん、新しい言語やテクノロジーに対して何をするかは、細部で異なります。しかし、バグがバグであり、自分の指で書いた大規模な性交であるという考えを心に留めると、あなたのコードは馬鹿の軍隊から絶え間なく攻撃され、頭に将軍がいるので、きっと適切な防御技術を理解します。


14

まあ、あなたがそれを知っているなら、それらは「既知の未知のバグ」のカテゴリーに陥ります(つまり、「この」性質の何かが発生することを知っています)。単体テストの量は、それらをキャッチするつもりはありません。それらは、既知のケースでのみ本当に役立ちます。

これに対処する方法は、実行中のアプリケーションにエラーログサービスを配置し、エラー発生時にベースに報告し、起動したらそれを処理することです。それ以外の場合は、何年も費やしても何もカバーできません...ある時点で、現実の世界で何が起こっているかを見て、急速に進化するのを待つだけです。

設計面では、重要な要素の1つとして保守性を考慮して設計します。

  • 機能する学習パターンと回避すべきパターン。特定の種類の問題が発生する、または発生しないということは、より一貫性があり、まとまりのあるパターンであるほど快適になります。
  • 物事を明確にする。不明瞭は混乱を招き、バグを引き起こします。
  • ずっと強い命名規則。物事に適切で一貫性のある名前を付ければ、物事を変更したり、そこに説明したりするときに大きなメリットがあります... Factoryと呼ばれるすべての物がXを実行します。
  • 完全にスペルアウトします。最近ではオートコンプリートがあり、完全な単語で混乱を取り除く場合は頭字語を使用しません。
  • レイヤーまたはアブストレーションに分離します。特定のスタイルの問題が「どこか」ではなく特定のレイヤーで発生します。
  • 問題のクラスをレイヤーとアスペクトに分離します。一般に、コードの別の部分との関係が少ない方が優れています。似たようなものを2回書き出すのに少し時間がかかっても。

そして重要なのは...以前にすべてのミスを犯したメンターを見つけるか、何が機能し何が機能しないかを見つけるまで、いくつかのミスを台無しにします。


1
「メンテナンス性を重視した設計」が好きです。特に良いのは、問題最終的に発生するときに迅速に方向転換することです。これは、優れた包括的な単体テスト、優れた展開戦略、および優れたバグ追跡/ QAプロセスを意味します。
ディーンハーディング

4

上記のすべてが良い点です。しかし、言及されていないものがあります。あなたはあなたのモジュールと関数を偏執狂にする必要があります。すべての関数パラメーターの範囲テスト。先頭または末尾が空白の文字列、または文字列が短すぎたり長すぎたりしないように注意してください。偽ではなく真であるブール値に注意してください。PHPのような型なし言語では、予期しない変数の型に注意してください。NULLに注意してください。

多くの場合、この偏執狂的なコードは、プロダクションビルドで無効にしてスピードアップできるアサートとしてエンコードされます。しかし、それは間違いなく直前のパニックバグを防ぎます。


ブール値はどのように真でも偽でもないのですか?
Zhehao Mao

@Zhehao Mao:ブール値がデータベースの列の場合、True、False、またはNULLになります。
マイクシェリル「キャットリコール」、

私がGIだったとき、私たちは格言をしました。「誰もが本当にするときであるあなたを得るために出て、妄想はちょうど良い、健全な思考です。」一部の当局はこれを防御的プログラミングと呼んでいます
マイクシェリル「キャットリコール」、

ああなるほど。SQLの奇妙さ。
Zhehao Mao

3

ユニットテストは未知のバグからあなたを救わないとロブは正しいですが、ユニットテストは未知のバグを修正するときにバグを導入することからあなた助け、誤って古いバグを再導入することからあなたを救います。TDDはまた、最初からテスト可能なようにソフトウェアを設計することを余儀なくさせ、それは大きな継続的な価値をもたらします。


ユニットテストのこの側面は最も誤解されているようです。ユニットテストでコードの正確さを証明せず、次の変更の正確さを改ざんします。しかし、単体テストされたコードのバグが見つかる
たびに

次に、欠陥を再現するためのテストを追加します。欠陥を修正すると、テストスイートを実行するたびにそのエラーを常にテストすることになります...
mcottle

それは私がやろうとしていることですが、それはしばしば「ええ、今では手遅れで、バグはすでに起こっています」につながります。バグが再び導入されないという事実はしばしば監視されます
ケプラ

これは本当ですが、それまでに既知の不明に移行しました:)
Robin Vessey '25

2

ステータス/「副作用」は可能な限り避けてください。コンピュータは確定的であり、同じ入力に対して同じ出力を提供しますが、入力に関する概要は常に不完全です。悲しいことに、ほとんどの場合、それがどれほど不完全であるを理解していません。

Webアプリケーション、データベース全体、現在のリクエスト、ユーザーのセッション、インストールされているサードパーティのライブラリなどについて話すときは、入力の一部です。スレッドについて言えば、それはさらに悪いことです。オペレーティングシステム全体が同じスケジューラによって管理されている他のすべてのプロセスは「入力の一部」です。

バグは、入力の処理方法を誤って判断したり、入力を誤って判断したりすることによって発生します。後者は、私の経験では難しいものです。「ライブ」でしか観察できず、多くの場合、もう入力がありません。

新しいテクノロジー、インフラストラクチャなどを学ぶときは、概要、どのコンポーネントが入力に寄与しているかを把握し、それらのできるだけ多くを回避することをお勧めします


+1:副作用は、SOLIDの原則を適用し、アトミックメソッドを作成することで回避できることがよくあります。また、コードはアサートでカバーする必要があります。
ファルコン

0

ソフトウェアが複雑になるにつれて、いくつかのバグが発生することは避けられません。それを完全に回避する唯一の方法は、ささいなソフトウェアを開発することです-そしてそれでも、あなたは時々重大な間違いを犯すことになります。

あなたができる唯一の実用的なことは、不必要な複雑さを回避することです-ソフトウェアをできるだけ単純にすることですが、それ以上に単純ではありません。

それが基本的に、より具体的な設計原則とパターンのすべてです。物事をできるだけシンプルにすることです。問題は、「どのように単純であるか」が主観的である可能性があることです。つまり、現在の要件に対して最も単純な設計を意味するのか、将来の要件に合わせて変更するのが単純なのかを意味します。そして、そのための原則もあります。

ユニットテストはこの不確実性の例です。一方で、これらは不必要な複雑さです。コードは開発と保守が必要ですが、それでは仕事が完了しません。一方、これらはテストを自動化する簡単な方法であり、実行する必要のあるはるかに困難な手動テストの量を減らします。

どれほど多くの設計理論を学び、どれだけ多くの経験を積んでも、最優先の原則(そして、場合によっては唯一のガイド)は単純さを目指すことです。


0

ソフトウェアのバグについてはランダムなことは何もありません。根本的な原因は本質的に完全に決定論的であり、コンピュータに対する誤った指示です。

スレッド化のバグは実行の動作が非決定的である可能性がありますが、根本的な原因ではランダムではありません。

それらはまったく同じ理由で一見予測できない瞬間に発生しますが、根本的な原因がわかったら、それらがいつ発生するかを決定論的に予測できれば、それらがランダム明らかに予測不可能になるわけではありません。

私はどうやら言って、理由で区別をしました。ランダムとは、方法や意識的な決定なしに作られる、行われる、起こる、または選択されるという1つのことを意味します。つまり、コンピューター側で独立した意思決定が行われることを意味します。いくつかの非常に確定的なケースでは正しいことをするように言わなかっただけです。

言葉の意味論には理由があります。ランダムは、誰かが誤ってそれを使用したからといって、何か違うことを意味するのではなく、常に同じことを意味します。より適切な用語は、意図的ではない、または明白ではない論理エラーです。

バグをランダムであると見なすことは、コンピューターへの入力とは独立して動作することが完全には理解されていない、理解できない力が働いていることを受け入れるのとほとんど同じであり、あまり科学的ではありません。つまり、神々は怒っていて、気まぐれにあなたのアプリケーションを送信していますか?


有効なポイントの場合は+1、ニッチピッキングの場合は-1。+/-0。質問を読むほとんどの人は、完全に文字通りの意味で「ランダム」をとったとは思いません(「ランダム」は実際に何を意味し、極端に解釈したのでしょうか)ではなく、「私はこの動作がどのように忍び込んできたのか、またはソフトウェアがなぜこのことを間違っているのか理解できません。」
CVn

@Micheal-それが私が明らかに言って区別した理由です。そこにはありません極端ランダムな、それは一つのこと、意味の方法や意識的な決定せずに起こって、メイドが、行っては、または選択した、言葉の意味が理由であるランダムは、ただで-正しく誰かが使用しているため異なる平均何かをしませんそれは常に同じことを意味します。彼らがおそらく私が答えで私の説明で述べた理由のために、彼らがおそらく意味したことは意図的ではなかった。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.