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. 全ての不正な入力にエラーを返すべき
    3. ハードウェアの異常を心配する必要が無い
    4. システムソフトの異常を心配する必要が無い
    5. エラーを発見したら終了してよい
  • 下三つを仮定しないソフトウェアは本書の範囲外
    • そりゃそーだ
  • 1,2を厳守しようとする姿勢こそがプロフェッショナルの証。あくまで理想ではあるが常に目指すべき
  • エラーは避けられない。どう付き合うか
    • まともなソフトウェア開発の9割はエラーとの闘いだ(と思われ)
  • 3つのアプローチ
    1. ソフトウェアの組織化
    2. デバッグ&テスト
    3. 残ったエラーが深刻でないことを確認
  • 全アプローチ使うべし
  • 信頼性の高いプログラムを開発する技=すなわちエラーの許容量に関する嗅覚のよさ、というのは、多分に経験の勝負になる
    • (ということは要するにまだ誰も工学として扱えるほどエラーをよーく考えていない、ということなのかもね)
5.2 Source of errors

source of errors は僕自身><

  1. 仕様の不備 : 仕様上明文化されていない「危険な角」ってのは大抵チェックされない。
  2. 不完全なプログラム : 開発中、作りかけ、抜けがあるのは当たり前。重要なのは全カバーの「いつ」を知ること。
  3. 予期できない引数 : 例えば sqrt(-1.2)とか(ちなみにlibcの場合はNaNですね)。
  4. 予期していない入力 : 数値期待しているところで文字列入力されるとか。5.6.3でやるらしい。
  5. 予想外の状態 : データ構造に抜けがあった場合とか? よくわからない
  6. 論理的バグ : そもそも式が間違ってるとか、そういうの。
  • はじめっから上記の事くらいは考えに入れて開発を始めたほうが良い。そうじゃないと「はいフロムスクラッチ」ということになりかねない。
5.3 Compile-time Error
  • プログラムのエラーに対する第一防衛ライン:コンパイラ
  • ここで見つかるエラーのほとんどはいわゆる「しょぼい間違い」(silly errors)
  • その他、プログラムの各部の相互作用がおかしい、という場合もあり。
  • 前の章でも出てきたけど、このステージでのコンパイラは、頼もしくも親しい友人。特に型システム、「あれは・・・いいものだ!!」
5.3.1 Syntax errors

まずは

  int area(int length, int width);

のような呼び出しを考える。

  • いきなりダサイみすオンパレード。 ')'抜けとか、';'抜けとか、大文字小文字間違いとか、余計な’とか。
    • しかしこういった間違いってのは見つけてみると「なんでこんな馬鹿な事を!!!」となることが多い(あるある)
      • というかTYPOの嵐のわたくしが偉そうな事は言えませぬ
  • コンパイラのレポートはしばしば「わけわからん」(あるある)しかも全然違う場所を示していることもある(あるあるあるある)
    • つうわけでこの手のエラーがでたら前の方も疑ってみてね、とのこと
  • これというのもコンパイラが真にあなた(プログラマ)の意図を汲み取っているわけではなく、書いてあることを忠実に守っているから。
  • 例えば Int s3 = area(7); と書いてしまった場合、
    • 「それ intのまちがいちゃうん? 大文字じゃダメぜよ」とは言ってくれない
    • 大抵は "Syntax error: missing ';' before identifier 's3'" とか "'s3' missing storage-class or type identifiers" とか "'Int' missing storage-class or type identifiers" とか(烈しく!あるあるあるある)
      • でも慣れてくると脳内で自動変換してるよねw
      • 大阪弁はどうでもいいけど、ツッコミいれてくる賢いコンパイラほしいな
TRY THIS

というかareaの定義か,間違い例のどちらかを(後者がいいんだろうなあ)変えたほうが良いかも。パラメタ足りねエラーが先に出てしまう。それにしても、

  • gccはかなり優秀。
    • それでもわけわかめなところも。慣れるとKYしてしまいますが。

まず後ろの括弧抜け

// 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
  • 文法違反を取り除いたら次、型エラーです
  • 宣言した型と、実際に使われている型の不整合をチェックします。関数とかのプロトタイプ宣言も含む。
  1. 未定義関数 : int x0 = arena(7); これはarena(int)とかが定義されてたりするとdisasterですね。
  2. 引数の数が合わない : int x1 = area(7);  デフォルト引数がある場合、、とか思ったけど、その場合は「おそらくイイ」値が入るか。
  3. 引数の型が合わない : int x2 = area("seven", 2); "seven" = 7って汲んでよ! って無茶。型によっては暗黙変換されることもあるけれど、実は評判悪かったり。
TRY THIS

もういや。やんない

5.3.3 non-errors
  • 慣れてくるといろいろ我侭が・・・・
    • 「空気読んでよ!それエラーじゃないし!」ということも。まあこれは自然
    • しかし「空気読んでよ!それエラーじゃんか!」ということもある。びっくり
  • 意味的に変なやつ(負の面積とか ・・・ 正直これは unsigned を付ければ良い気が)と、暗黙の変換系ですね。
    • 特に小さくなる方向 e.g. int -> char etc..
      • gccでも -Wconversion付けないと検出してくれません。-Wextraくらいには付けてくれてもいいのにね
  • 更に慣れてくると、コンパイラのクセとかそういったものを自分の物に出来るようになってくる
    • 「ここまではコンパイラが確実にはじいてくれるから、あと僕が考えなきゃいけないのはここだ」という感じかな。
  • そして、、、
  • さーてコンパイル通った、、、と行ってもまだまだ安心すること無かれ。ここからが本番です。
5.4 Link-time errors
  • プログラムはいくつかの部分=翻訳単位に分けられてコンパイルされる
    • つなぎあわせなきゃ。で、この中では関数の宣言はプログラム中で一貫している必要がある。
    • これをヘッダファイルに書いた宣言にて実現している
  • 未定義関数も当然リンクタイムエラー
  • 引数や戻り値の型も合っている必要がある。
  • 普通はタイプミスした関数はコンパイルを通らない(リンクエラーまで行かない)。これはC++ではプロトタイプ宣言が無い関数はエラーになるため。
  • 関数についてのみ、説明したけど、当然のように変数とか型についても成り立つ話。
    • 要するに議会で全会一致でないと変なことがおこる訳ですな。
5.5 Run-time errors
  • コンパイルエラー、リンクエラーを乗り越えるとついにプログラムは走り始めます!
  • 実行時のエラーは簡単じゃないこともしばしば
  • 例では例によって長方形の面積の話。マイナスだったらどうだとか、あまつさえ0除算になってしまったらどうだとか。
  • こっから「実行時エラーを検出する(レポートする)のは誰の責任?」という話になる
    • そりゃ最も知ってる人にまかすのが・・・(つまり関数だ・・・)というのはやっぱりKY
  • というわけでその前に、2つの戦略を吟味する
    1. area()の呼び出しもとがなんとかする
    2. area()自身がなんとかする

だーかったりー

5.5.1 The caller deals with error
  • しかしまあ確かにライブラリとかでオープンソースじゃない場合はこれしかない。
  • 予想通り、この方法には沢山の弱点がある
    1. まず各部の内部情報を関数利用者側が知っていなきゃいけない。これは明らかに設計的間違い
    2. 関数側に何か変更があったらカオス。全コードをフルサーチして呼び出しを見つけ出し、全てに変更を入れなければ逝けない。絶対にどっか孤児コードができる
    3. 今回のように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
  • 元の単純さからしてみても何かがおかしい。