私の教授は、「堅牢な」コードについて話すとき、このJavaの例を参照し続けます。
if (var == true) {
...
} else if (var == false) {
...
} else {
...
}
彼は、「堅牢なコード」とは、プログラムがすべての可能性を考慮に入れており、エラーなどは発生しないことを意味します。すべての状況はコードによって処理され、有効な状態、つまり「else」になります。
しかし、私は疑わしいです。変数がブール値の場合、3番目の状態が論理的に不可能であるときに3番目の状態をチェックするポイントは何ですか?
「エラーのようなものがない」もばかげているようです。 Googleアプリケーションでさえ、ユーザーにエラーを表示するのではなく、メッセージを黙って飲み込むのではなく、何らかの方法で有効な状態と見なします。そして、それは良いことです-私は何かがうまくいかないときを知るのが好きです。そして、アプリケーションにエラーが発生することは決してないと言うのはかなりの主張のようです。
では、「堅牢なコード」のactual定義とは何でしょうか。
3番目の状態が論理的に不可能であるときに3番目の状態をチェックする意味は何ですか?
Boolean?
これは、真でも偽でもないNULL
状態を可能にします。ソフトウェアは何をすべきでしょうか?一部のソフトウェアは、ペースメーカーのように非常に耐衝撃性でなければなりません。誰かがBoolean
であったデータベースに列を追加し、現在のデータを最初にNULL
に初期化するのを見たことがありますか?私はそれを見たことがあります。
ここでは、ソフトウェアの面で堅牢であることの意味を説明するリンクをいくつか紹介します。
ここで「ロバスト」の定義について普遍的に合意されているものがあると思うなら、頑張ってください。爆弾防止やばか防止などの同義語が存在する場合があります。 ダクトテーププログラマー は、少なくとも私の用語の理解において、通常は堅牢なコードを書く人の例です。
私の議論のために、ブールは2つの状態、真または偽を持つことができます。それ以外はプログラミング言語仕様に準拠していません。ツールチェーンがその仕様に準拠していない場合は、何をしても関係ありません。開発者が2つ以上の状態を持つBoolのタイプを作成した場合、それは彼が私のコードベースでこれまで行う最後のことです。
オプションA。
if (var == true) {
...
} else if (var == false) {
...
} else {
...
}
オプションB
if (var == true) {
...
} else {
...
}
オプションBの方が堅牢だと思います。
どんなツイットでも、予期しないエラーを処理するように指示できます。それらは通常、一度考えると簡単に検出できます。あなたの教授が示した例は起こり得るものではないので、非常に悪い例です。
Aは、複雑なテストハーネスなしではテストできません。作成できない場合、どのようにテストしますか?コードをテストしていない場合、どのようにしてコードが機能することを確認できますか?動作することがわからない場合は、堅牢なソフトウェアを作成していません。彼らは今でもCatch22と呼んでいると思います(素晴らしい映画、いつか見てください)。
オプションBのテストは簡単です。
次の問題、教授にこの質問をしてください。「ブール値がTrueでもFalseでもない場合、それについて何をしたいですか?」それは非常に興味深い議論につながるはずです.....
ほとんどの場合、コアダンプは適切ですが、最悪の場合、ユーザーを困らせるか、多額の費用がかかります。たとえば、モジュールがスペースシャトルのリアルタイム再突入計算システムである場合はどうなりますか?どのように答えても、どれほど不正確であっても、ユーザーを殺すアボートよりも悪いことはありません。答えが間違っている可能性があることがわかっている場合はどうすればよいか、50/50に進むか、中止して100%の失敗に進みます。もし私が乗組員だったら、私は50/50を取ります。
オプションAは私を殺しますオプションBは私に生存のチャンスを与えます。
しかし、待ってください-それはスペースシャトルの再突入のシミュレーションです-それから何ですか?あなたはそれについて知っているので中止します。いいアイデアですね。 -しない-出荷する予定のコードでテストする必要があるため。
オプションAはシミュレーションには適していますが、展開できません。それは役に立たないオプションBはデプロイされたコードなので、シミュレーションはライブシステムと同じように実行されます。
これが正当な懸念事項だったとしましょう。より良い解決策は、エラー処理をアプリケーションロジックから分離することです。
if (var != true || var != false) {
errorReport("Hell just froze over, var must be true or false")
}
......
if (var == true){
....
} else {
....
}
参考文献 -Therac-25 Xrayマシン、Ariane 5 Rocketの障害など(リンクには多くの壊れたリンクがありますが、Googleが役立つ十分な情報があります)
実際、コードはより堅牢ではありませんが、より堅牢です。最後のelse
は、テストできないデッドコードです。
宇宙船などの重要なソフトウェアでは、デッドコード、より一般的にはテストされていないコードは禁止されています。宇宙線が単一のイベントの混乱を引き起こし、デッドコードがアクティブになる場合は、何でも可能です。 SEUが堅牢なコードの一部をアクティブ化する場合、(予期しない)動作は制御されたままになります。
教授は「間違い」と「バグ」を混同しているのではないかと思います。確かに、堅牢なコードにはバグがほとんど/まったくないはずです。堅牢なコードは、悪意のある環境では、優れたエラー管理が必要になる場合があります(例外処理や厳密な戻りステータステストなど)。
教授のコード例はばかげていますが、私のようにばかげていません。
// Assign 3 to x
var x = 3;
x = 3; // again, just for sure
while (x < 3 or x > 3) { x = 3; } // being robust
if (x != 3) { ... } // this got to be an error!
ロバストなコードの定義に同意はありません、プログラミングの多くの事柄は多かれ少なかれ主観的です...
教授の例は言語によって異なります。
Boolean
はTrue
またはFalse
のいずれかであり、3番目のオプションはありません。bool
はtrue
、false
の場合があります。または(残念ながら)不明なケースにある疑わしいキャストからのものです...これする必要があります発生しませんが、前のエラーの結果として発生する可能性があります。ただし、教授が助言しているものは、コアプログラムの途中でshould-not-happenイベントに無関係なロジックを導入することでコードを覆い隠すので、私は代わりに、 Defensive Programming に向けてください。
大学の場合、Design By Contract戦略を採用することでそれをさらに強化することができます。
size
はdata
リスト内の項目の数です)a
が10
より小さい場合にのみ呼び出すことができます)例:
class List:
def __init__(self, items):
self.__size = len(items)
self.__data = items
def __invariant(self):
assert self.__size == len(self.__data)
def size(self):
self.__invariant()
return self.__size
def at(self, index):
"""index should be in [0,size)"""
self.__invariant()
assert index >= 0 and index < self.__size
return self.__data[index]
def pushback(self, item):
"""the subsequent list is one item longer
the item can be retrieved by self.at(self.size()-1)"""
self.__invariant()
self.__data.append(item)
self.__size += 1
self.__invariant()
assert self.at(self.size()-1) == item
あなたの教授のアプローチは完全に間違っています。
関数、またはほんの少しのコードには、可能な入力をすべてカバーする、その関数が行うことを示す仕様が必要です。そして、その振る舞いが仕様に一致することが保証されるようにコードを書く必要があります。この例では、次のように非常に単純な仕様を記述します。
Spec: If var is false then the function does "this", otherwise it does "that".
次に、関数を記述します。
if (var == false) dothis; else dothat;
そして、コードは仕様を満たしています。だからあなたの教授は言う:var == 42の場合はどうなりますか?仕様を見てください:関数は「それ」を行うべきだと言っています。コードを見てください:関数は「それ」を行います。関数は仕様を満たしています。
教授のコードが完全に不安定なのは、彼のアプローチでは、varがtrueでもfalseでもない場合、以前に呼び出されたことのないコードが実行され、まったくテストされていないため、まったく予測できない結果になるという事実です。
@ gnasher729の意見に同意します:教授のアプローチは完全に誤っています。
堅牢性とは、ほとんどの仮定を行わず、分離されているため、破損/障害に対して耐性があることを意味します。自己完結型で、自己定義型で、移植可能です。また、変化する要件に適応できることも含まれます。 Wordでは、コードはdurableです。
これは一般に、具体的な実装コードを含む関数ではなく、呼び出し元から渡されたパラメーターからデータを取得する短い関数に変換され、コンシューマーのパブリックインターフェイス(抽象メソッド、ラッパー、間接参照、COMスタイルインターフェイスなど)が使用されます。
堅牢なコードは、障害を適切に処理する単純なコードです。それ以上でもそれ以下でもありません。
失敗には、不正なコード、不完全なコード、予期しない値、予期しない状態、例外、リソース不足など、多くの種類があります。堅牢なコードは、これらを適切に処理します。
私はあなたが防御的プログラミングの例として提供したコードを検討します(少なくとも私はこの用語を使用しています)。防御的プログラミングの一部は、システムの他の部分の動作についてなされた仮定を最小化する選択を行うことです。たとえば、次のうちどれが優れていますか。
_for (int i = 0; i != sequence.length(); ++i) {
// do something with sequence[i]
}
_
または:
_for (int i = 0; i < sequence.length(); ++i) {
// do something with sequence[i]
}
_
(違いを確認できない場合は、ループテストを確認してください。最初のテストでは_!=
_を使用し、2番目のテストでは_<
_を使用しています)。
これで、ほとんどの状況で、2つのループはまったく同じように動作します。ただし、最初の(_!=
_と比較して)は、i
が反復ごとに1回だけインクリメントされると仮定しています。値sequence.length()
をスキップすると、ループがシーケンスの境界を超えて続行され、エラーが発生する可能性があります。
したがって、2番目の実装の方がより堅牢であるという主張をすることができます。それは、ループ本体がi
を変更するかどうかに関する仮定に依存しません(注:実際には、i
は決して負ではないと仮定しています)。
なぜそのような仮定をしたくないのかという動機を与えるために、ループが文字列をスキャンしてテキスト処理を行っていると想像してください。あなたはループを書き、すべてがうまくいきます。要件が変更され、テキスト文字列でエスケープ文字をサポートする必要があると判断したため、ループ本体を変更して、エスケープ文字(たとえば、バックスラッシュ)を検出した場合、i
をインクリメントして文字をスキップします脱出直後。テキストの最後の文字がバックスラッシュの場合、ループ本体はi
をインクリメントし、ループはシーケンスの終わりを超えて続くため、最初のループにバグがあります。