PPPUC++#7
5. Errors
"I realized that from now on a large part of my life would be spent finding and correcting my own mistakes." フイタ。まさに喜劇。問題を作るのは一瞬だけど、解決には多量の時間がかかる、ってわけかな。人生ってそんなもんかもしれない。みんな何もせずに滅びれば一番楽チンなんだけど、そういうふうに僕たちは作られていないのだよね。
ここで学ぶのは「プログラマのような視点」でモノを見る方法らしい。つまりはpessimisticにanalysticに、ちゅう目かな。
5.1 Introduction
- エラーは開発においては避けられない。しかし最終的には(少なくとも「ま、これくらいならいっか」というものを除いて)エラーは抹殺しなければならない。
- エラーの分類
- Compile-time errors: Syntax or Type error。言語ルールに則ってないというやつ
- Link-tie errors:シンボル無いとか
- Run-time errors:SEGVとか(ハードウェアによる検出)libraryや自前コードのsanity checkとか(ソフトウェアによる検出)
- Logic errors:結果から判るエラー。もっともやっかい
- 全てのエラーをなくすのが理想だが、能力の問題以上に、「全てのエラー」というものは定義次第で変わってしまうので、そもそも「全て」を抹殺するのが無理
- たとえば、「電源コードに足引っ掛けちゃったら文書消えたぞ!どうしてくれる」とかモンスターなことでも、対象が医療とか通信回線でのシステムなら、何らかの対処をすべきとなる。
- そこまで行くとプログラマの問題というより、アーキテクトの問題だけれどもね・・・
- たとえば、「電源コードに足引っ掛けちゃったら文書消えたぞ!どうしてくれる」とかモンスターなことでも、対象が医療とか通信回線でのシステムなら、何らかの対処をすべきとなる。
- ここでのエラーフリーという概念での仮定は以下の通り
- 全ての正規の入力に期待される結果を返すべき
- 全ての不正な入力にエラーを返すべき
- ハードウェアの異常を心配する必要が無い
- システムソフトの異常を心配する必要が無い
- エラーを発見したら終了してよい
- 下三つを仮定しないソフトウェアは本書の範囲外
- そりゃそーだ
- 1,2を厳守しようとする姿勢こそがプロフェッショナルの証。あくまで理想ではあるが常に目指すべき
- エラーは避けられない。どう付き合うか
- まともなソフトウェア開発の9割はエラーとの闘いだ(と思われ)
- 3つのアプローチ
- ソフトウェアの組織化
- デバッグ&テスト
- 残ったエラーが深刻でないことを確認
- 全アプローチ使うべし
- 信頼性の高いプログラムを開発する技=すなわちエラーの許容量に関する嗅覚のよさ、というのは、多分に経験の勝負になる
- (ということは要するにまだ誰も工学として扱えるほどエラーをよーく考えていない、ということなのかもね)
5.2 Source of errors
source of errors は僕自身><
- 仕様の不備 : 仕様上明文化されていない「危険な角」ってのは大抵チェックされない。
- 不完全なプログラム : 開発中、作りかけ、抜けがあるのは当たり前。重要なのは全カバーの「いつ」を知ること。
- 予期できない引数 : 例えば sqrt(-1.2)とか(ちなみにlibcの場合はNaNですね)。
- 予期していない入力 : 数値期待しているところで文字列入力されるとか。5.6.3でやるらしい。
- 予想外の状態 : データ構造に抜けがあった場合とか? よくわからない
- 論理的バグ : そもそも式が間違ってるとか、そういうの。
- はじめっから上記の事くらいは考えに入れて開発を始めたほうが良い。そうじゃないと「はいフロムスクラッチ」ということになりかねない。
5.3 Compile-time Error
5.3.1 Syntax errors
まずは
int area(int length, int width);
のような呼び出しを考える。
- いきなりダサイみすオンパレード。 ')'抜けとか、';'抜けとか、大文字小文字間違いとか、余計な’とか。
- しかしこういった間違いってのは見つけてみると「なんでこんな馬鹿な事を!!!」となることが多い(あるある)
- というかTYPOの嵐のわたくしが偉そうな事は言えませぬ
- しかしこういった間違いってのは見つけてみると「なんでこんな馬鹿な事を!!!」となることが多い(あるある)
- コンパイラのレポートはしばしば「わけわからん」(あるある)しかも全然違う場所を示していることもある(あるあるあるある)
- つうわけでこの手のエラーがでたら前の方も疑ってみてね、とのこと
- これというのもコンパイラが真にあなた(プログラマ)の意図を汲み取っているわけではなく、書いてあることを忠実に守っているから。
- 例えば Int s3 = area(7); と書いてしまった場合、
TRY THIS
というかareaの定義か,間違い例のどちらかを(後者がいいんだろうなあ)変えたほうが良いかも。パラメタ足りねエラーが先に出てしまう。それにしても、
まず後ろの括弧抜け
// int s1 = area(7;
voo.cpp:7: error: expected ‘)’ before ‘;’ token
すばらしい。次にセミコロン抜け。
// int s2 = area(7)
は実はムリ。次の行にかかる部分ででる。
voo.cpp:9: error: expected ‘,’ or ‘;’ before ‘Int’
結果としてこのままだと int -> Int のバグがでない。紹介する順番も変えたほうがよかったと思うよ> Mr.S。で、場所を変えると
// Int s3 = area(7);
voo.cpp:7: error: ‘Int’ was not declared in this scope
voo.cpp:7: error: expected ‘;’ before ‘s3’
さすがだ。最後に余分な'が入って文字コンテキストが終わらない奴
// int s4 = area('7);
voo.cpp:10: warning: missing terminating ' character
voo.cpp:10: error: missing terminating ' character
すばらしい。ていうかこれ " 二重引用符でもちゃんと出る。すばらしいx2
この場合、その後に'が無い限りはプログラムの最後まで解析してしまうので、最後の行にまで出てしまう。
voo.cpp:12: error: expected primary-expression before ‘}’ token
voo.cpp:12: error: expected ‘,’ or ‘;’ before ‘}’ token
:
:
- というわけで正しい順序は s3, s1, s2, s4ですね。これだと一発で全部見れてお得です。
voo.cpp:10: warning: missing terminating " character
voo.cpp:10: error: missing terminating " character
voo.cpp: In function ‘int main()’:
voo.cpp:7: error: ‘Int’ was not declared in this scope
voo.cpp:7: error: expected ‘;’ before ‘s3’
voo.cpp:8: error: expected ‘)’ before ‘;’ token
voo.cpp:10: error: expected ‘,’ or ‘;’ before ‘int’
5.3.2 Type Errors
- 文法違反を取り除いたら次、型エラーです
- 宣言した型と、実際に使われている型の不整合をチェックします。関数とかのプロトタイプ宣言も含む。
- 未定義関数 : int x0 = arena(7); これはarena(int)とかが定義されてたりするとdisasterですね。
- 引数の数が合わない : int x1 = area(7); デフォルト引数がある場合、、とか思ったけど、その場合は「おそらくイイ」値が入るか。
- 引数の型が合わない : int x2 = area("seven", 2); "seven" = 7って汲んでよ! って無茶。型によっては暗黙変換されることもあるけれど、実は評判悪かったり。
TRY THIS
もういや。やんない
5.3.3 non-errors
- 慣れてくるといろいろ我侭が・・・・
- 「空気読んでよ!それエラーじゃないし!」ということも。まあこれは自然
- しかし「空気読んでよ!それエラーじゃんか!」ということもある。びっくり
- 意味的に変なやつ(負の面積とか ・・・ 正直これは unsigned を付ければ良い気が)と、暗黙の変換系ですね。
- 特に小さくなる方向 e.g. int -> char etc..
- gccでも -Wconversion付けないと検出してくれません。-Wextraくらいには付けてくれてもいいのにね
- 特に小さくなる方向 e.g. int -> char etc..
- 更に慣れてくると、コンパイラのクセとかそういったものを自分の物に出来るようになってくる
- 「ここまではコンパイラが確実にはじいてくれるから、あと僕が考えなきゃいけないのはここだ」という感じかな。
- そして、、、
- さーてコンパイル通った、、、と行ってもまだまだ安心すること無かれ。ここからが本番です。
5.4 Link-time errors
5.5 Run-time errors
- コンパイルエラー、リンクエラーを乗り越えるとついにプログラムは走り始めます!
- 実行時のエラーは簡単じゃないこともしばしば
- 例では例によって長方形の面積の話。マイナスだったらどうだとか、あまつさえ0除算になってしまったらどうだとか。
- こっから「実行時エラーを検出する(レポートする)のは誰の責任?」という話になる
- そりゃ最も知ってる人にまかすのが・・・(つまり関数だ・・・)というのはやっぱりKY
- というわけでその前に、2つの戦略を吟味する
- area()の呼び出しもとがなんとかする
- area()自身がなんとかする
だーかったりー
5.5.1 The caller deals with error
- しかしまあ確かにライブラリとかでオープンソースじゃない場合はこれしかない。
- 予想通り、この方法には沢山の弱点がある
- まず各部の内部情報を関数利用者側が知っていなきゃいけない。これは明らかに設計的間違い
- 関数側に何か変更があったらカオス。全コードをフルサーチして呼び出しを見つけ出し、全てに変更を入れなければ逝けない。絶対にどっか孤児コードができる
- 今回のようにframed_area()関数がarea()関数に依存して・・・とかなってる場合、原種をいじると変種全てに対応する必要がある。情報爆発
- こういうコードは "brittle"と呼ぶらしい。多分これのこと。すぐボロボロになって脆いと
- ちょっとだけマシにしようと"Avoid magic constants!"をやってみるが
const int frame_width =2; : : if (1 - frame_width <= 0 || z - frame_width <= 0) error("non-positive argument for area() called by framed_area()"); int area2 = framed_area(1,z0; if (y - frame_width <= 0 || z - frame_width <= 0) error("non-positive argument for area() called by framed_area()"); int area3 = framed_area(y,z)
- ........................なんぞこれ
- 何ツー汚い。つーかあってるかどうかパッと見わかんねー
- そもそも繰り返しが何度もあってコピペ日和なのが既に超危険信号。抽象化というのは同じ事の繰り返しをさけるためにあるんだZE
- 元の単純さからしてみても何かがおかしい。