PPPUC++#7.5

尻切れになってしまったので改めてきりなおし

5.5.2 The callee deals with errors

  • と言う訳でsanity checkは関数(呼び出され側=callee)の中でやろうず
  • いちいちテストコードを分散させなくても良い。変更も一カ所で済む。
    • 何よりも「そこに全ての必要な情報がそろっている」のが重要だと思うす。
  • この方法、単純なのに、なぜ常にそうしないのか?
    • もちろん、エラー処理にあまり気を使ってないとか、手抜き、というのもあるだろうけれども・・・
  • いくつか致し方ない場合もある
    1. コードがいじれない: プロプライエタリだけでなく、更新が頻繁で、直しても直しても、、って場合も。
    2. エラーを検出することは出来ても、じゃあどうしたらいいのか不明: エラーの結果どうしたらいいのかは大抵呼び出し側にしか判らない。下請けには委託元の事情まではわかりません。
    3. ライブラリルーチンは自分がどこから呼び出されたかわかんない: つまりはエラー粒度が細かすぎても、報告された側がわかんないという上のと同じような話?
    4. 性能: 僕はこれが一番大きいと思っていました。配列だってそうだものね。
  • で、結局じゃあどうすりゃいいのよ、というと、「やるべきだと判った時は常にやるべきだ」ということ。可能な限り呼び出され側で対処すべしです。
5.5.3 Error reporting
  • 今度は、 さてエラーチェックでエラーを見つけた→  で、どうしたらいい? という話
    • エラー値を返す。。。 -1 とか NULLとか
    • システムコールや標準Cライブラリでよく見る方式ですね
      • しかしこれだと常に2箇所(ライブラリルーチン&呼び出し元)両方でテストしなきゃいけない
      • こういうのってcheck忘れる
      • そもそもエラーを示す値が空いていない状況のほうが多い。 -1 に意味があったら? NULL(=0)に意味あったら?
  • つうわけで例外使おうね。という話
5.6 Exceptions
  • 例外は, エラー検出部とエラー処理部を絶縁するための機構
  • エラーを見つけたらreturnするんじゃなくthrowで例外を投げる
    • throwされた例外はcatchされるまで上に上げられる。だれもcatchしないと強制終了。
  • Chapter 19でもっと高度な例外の使い方を学ぶ。ここではさわりだけ。
5.6.1 Bad arguments
  • Bad_area classをこさえてそれをthrowする例
    • これは他のどの例外とも独立しているので混線の心配は無いのです
  • コード中、ライブラリコードもmainコードもエラーに関して相互に非依存になっている
    • といっているんだけど、こんな風にエラー例外クラスを定義する場合、そこに依存している気もする。というわけで本番はここからか
5.6.2 Range errors
  • 大抵の実アプリは多数のデータの塊を扱う
    • テーブルだとかリストだとか、c++的にはいわゆるコンテナ
    • vectorもそう
  • vectorなどは[0:size()) の要素があるわけだけど、範囲超えたらどうなる?
  • で、良くある < と <= を勘違いした例
    • こーゆーの真夜中とかで疲れてるときとか慌ててるとよくやるとかw(あるあるある)
      • まあ、ここまであからさまなのは手に染み込んでるので有り得なさそうですが。
  • off-by-one とか range error とか bound error 呼ばれているらしい(最初の知らんかった。「一歩はみだしちゃった」エラーって感じかな?)
  • で、しれっと"the vector we are using does(it check the range)" とカイテルんだけど、しかしここで不思議に思い始める...
    • vector がout_of_range 出すのってat()の時だけで、operator[]って確かrange checkしてなかったんじゃ?
    • まあ確かに op[] が、とは明示的に書いてない。うーむ。と思って、19.4章をプレビューしてみると、ここではちゃんと僕の認識通りのことが書いてある。あれれれれ? 僕の英語力もうボロボロ?
      • gccで調べてみるとやはり記憶通り。at()はout_of_range出すけど []では無い。つまり本書のサンプルコードは例外出さない
      • と、もひとつ気になったことが。。。
      • 1章のドリルに掲載されてたstd_lib_facilities.hには iostream, string, vector, algorithmくらいしか入ってないんだけど、どれかにstdexcept入ってたっけ?
      • やってみるとはやりout_of_rangeは未定義。あれれ。とそこで気がつく。サポートサイト!!!
      • http://www.stroustrup.com/Programming/std_lib_facilities.h 中読んでみたら、、、、あった><
      • #define vector Vector とかやってはいけないことをーーーー ひどいよMr.S-- 。基本的にダーティハックが気にならないタイプなのね。。。そんなだから言語自身もそ(ゴニョゴニョ)、
  • むう、気を取り直して。。。(皆さんは必ずサポートサイトから拾うようにしてください)
5.6.3 Bad input
  • istreamとかで意図しない文字が来た場合(数値期待しているところに文字が来た、くらいしか無いような気が)
    • あ、コネクションが切れるとかあるか
  • 詳細は10.6で。ここでは検出した後どうするかに絞って。
  • cin とかを評価するとエラーが出ているかどうかが分かるのでこれでif 分岐
  • 開発初期段階では、まだエラー時にどういうリカバリをしたら良いかとか分からないことも多い。そういう場合はとりあえずメッセージ出して終了、というのが良いことも
    • と言うワケでerror()ルーチンを自分で作ってみるコーナー(完成品はstd_lib_facilities.hにあります)
    • error()の仕様 1) 渡された文字列を表示する 2) 終了する 3) 終了前にキーボード待ったりとか、ちょっとなんかできる。 2のため、4)戻り値は使えない
    • runtime_error はメッセージを託すことが出来るので、これはイイ!  だれもcatchしなければ2)終了するし、catchで3)インタラプトできるし。
  • cerr の話
  • 標準エラーは大抵 exceptionの派生クラス。exceptionクラスはwhat()で説明文字列を取得できるので、exceptionで総合受付して、説明表示するのが便利。
  • catchしなかった場合はシステムハンドラが引き取る。runtimeのmessageも表示される
TRY THIS

catchしなかった場合を見ろとのこと。

5.6.4 Narrowing errors
  • 3.9.2 で出てきたまずい暗黙変換。小さい方向への変換で値が変わってしまう場合
  • この場合のエラー処理も例外とテンプレートを使うと簡単にできる
  • と言うワケでnarrow_cast<>()を自分で作ってみるコーナー(完成品(ry
  • と、思ったら説明まではまだしてくれないのか。先走ってみる
// run-time checked narrowing cast (type conversion):
template<class R, class A> R narrow_cast(const A& a)
{
    R r = R(a);
    if (A(r)!=a) error(string("info loss"));
    return r;
}
  • おお、単純。キャスト先の Rで a を Cキャスト(良いのですかエヴァンジェリストさま)したものを、もう一回 キャスト元の Aにキャストし直して、同じ値ならOKと。
  • ああああああああああああ、ダーティ(ry
5.7 Logic errors
  • この最終防衛線はもっとも手薄。コンパイラ、リンカ、ランタイム、どれも助けてくれない
    • 普通はテストプログラムとかCIツールに頼ることになるのかな
  • 例として出ているのは最大。最小、平均値を出すもの
main()
{
    vector<double> temps;  // temperatures

    double temp = 0;
    double sum = 0;
    double high_temp = 0;
    double low_temp = 0;

    while (cin >> temp) // read and put into temps
        temps.push_back(temp);

    for (int i = 0; i < temps.size(); ++i)
    {
        if (temps[i] > high_temp) high_temp = temps[i];// find high
        if (temps[i] < low_temp) low_temp = temps[i];  // find low
        sum += temps[i];
    }

    cout << "hiehg temperature: " << high_temp << endl;
    cout << "Low temperature: " << low_temp << endl;
    cout << "Average temperature: " << sum/temps.size() << endl;
}
    • 経験値高い人なら大抵はこういうのって max, min の初期値で失敗するのを知っているから、反射的に分かるかも
  • この場合、超寒冷地や赤道直下のデータだと困るわけですね。全データが <0 ならmaxがホゲって, >0ならminが・・・
    • ここでの解法は -1000, 1000を入れるというもの。まあ、実用上は問題ないけど、いいのか。そもそも Avoid magic (ry
      • pedantic にいくならnumeric_limits<>.max, minとかやるべきなのかも。意味は無いけど。
      • あるいは 第一要素は無条件にmin, max に入力するのもありですね。ロジックはちと汚くなるけど
5.8 Estimation
  • 答えが出てきたとして、それが正しいというのをどうやって判断するのか?
    • 例えば六角形の面積を出そうってのに -34.56 とかわかりやすく負値になってくれたりしたら一発だけれども、中途半端に正しそうな21.65685とか出てきたらどうやって正しいと判断すれば良いのか?
    • (六角形程度なら解析的にもわかるけど)この場合だと四角形に近似するとか言うのがありそう。それで、それっぽい値であることを確認する。
  • そう「それっぽい」重要
    1. この問題に対して、この答えはそれらしいか?
    2. それっぽい結果、というのをどう判断するか?
    • 「厳密な正解」を求めてる訳でないことに注意。複雑な問題の場合、それが欲しいからプログラム書いているワケでまさに鶏と卵。
    • こういった問題の場合は、それっぽいで十分。先に進んでOK。
      • 後から戻ってくるカモだけど
  • 推測にはいくらかの常識とちょっとした演算の組み合わせが必要になる。
    • 何かに書いてみると良い。個人的には無意味に図、というか落書きを書いたりとか。混乱しがちな問題の場合は有効
  • guestimation !! lol!! nice!!
  • フェルミ推定っぽい頭の体操が重要なわけですね。
    • そーゆーの大好き。
  • 今の時代だとweb でクイックにサーチでもおk。
TRY THIS

何かに書いてみると良い、のターゲットは原書ではback of an envelope (or a napkin).ナフキンに走り書きする科学者の逸話はよく聞くけれどもw
この話は本文に出てきた、修正版の六角形面積 10.3923が果たしてあっているのか?というのを、「その六角形は各辺が2cmの正六角形だった」という情報から正しいかどうか考えろ、というもの。正六角形であれば 6個の正三角形となって、したがって3個の菱形、つまり横6cm 高さ 2*sqrt(3)/2の平行四辺形、ってことなので、手元のEmacsでevalしてみると

(* 6 (sqrt 3))
10.392304845413264

ふむ。「それっぽい」

5.9 Debugging
  • プログラムかけたー -> かならずバグがある。
  • よほど単純なトイプログラムでもない限り、一発で動いたら「何かがオカシイ」と思うべき(確かに)
    • もしあったら友達と記念パーティーやんなさい。毎年あることじゃない
  • まあそんなわけで普通はdebugging必須です。
    1. プログラムをコンパイルします
    2. プログラムをリンクします
    3. プログラムを意図した通りに動かします
  • これを何度も何度も繰り返す作業。
    • debuggingはくだんねー時間の無駄、設計もっとしっかりやっとけよ とMr.Sは申しております。
    • 別の人はデバッグを「楽しくってクセになるぜ!」と言っていますが、「俺も経験上わかる、請け負うぜ!」とMr.Sは申しております
      • どっちなんだ!!!

while (プログラムが正しく動かない) {
でたらめに「なんか変」なコードを探す
いい感じに見えるように直す
}

  • こんなん↑デバッグじゃねー
  • debugging の基本は 「プログラムがまともに動いているか」をどうやって知るか? という問いかけ。
    • これに答えられないようじゃあまともなデバッグとは言えねーな、とMr.Sは申しております。
  • バグが隠れる余地を無くす設計。これも大事。
5.9.1 Practical debug advice
  • コードを書く前にデバッグについて考え始めよう。
    • たくさんコードを書いた後ではデバッグを単純にするには遅すぎる
  • エラーのレポート方法を決定する。
    • error()使って main()のcatchでとらえる , ってのが本書のここまでの知識でのデフォルト。
  1. コメントを適切に。大量にではない。コードにかけないことを書く。
    1. プログラム名
    2. プログラムの目的
    3. 誰がいつ書いたコード?
    4. バージョン
    5. 複雑なコード片:何をしようとしているか?
    6. 全般的な設計方針
    7. ソースコードの組織化方法
    8. どんな入力を想定しているか?
    9. 未実装のコードは何か? まだ実装されていない機能はなにか?
  2. 意味ある名前を
    1. 長い名前ではない
  3. レイアウトを統一せよ
    1. IDE が支援してくれる。しかし、全部ではないので、ちゃんと考えること
    2. 本書のコーディングスタイルを出発点にするのは妥当なところ
  4. 小さな関数にコードを分ける。分割単位は1論理動作
    1. 短くすることに気を遣う。1、2頁を越えるようなら分けてみる
  5. ややこしいコードを避ける
    1. 深いネストのループとかif節とか、ややっこしい条件文とか。ややこしいコード、というのはバグの良い隠れ家になる。(見たくないものね)
  6. ライブラリを活用する。なんでもDIYしない
    1. ライブラリはよく考えられて、よくテストされている。
  • ある言語のこの文法うざっ、いらねっ、死ねば? とかは滅多にない。 "A poor craftsman curses his tools." !!!!
    • 言語設計者がそれを言うか ^^;
  • コンパイラのお小言集
    1. 文字列リテラルは終端してる?
    2. 文字リテラルは(ry
    3. ブロックは(ry
    4. 括弧は対応してる? (大抵はだいぶ先まで読まないとコンパイラは問題に気づけない)
    5. 全ての名前は宣言されている?
      • ヘッダ全部読んだ?
      • 使用前に全部宣言されている?
      • スペルミスない?
    6. セミコロンの忘れない?
  • drill でもっとやるらしい。えーーー
  • 次はロジカルエラーの方
  • コンピュータだ、コンピュータになりきるんだ!
    • 多くの場合、コードをいくら見てもバグが見つからないのはあなたが自分が思っているようにコードを見てしまうから
      • ネストループの変数がかぶるってアホかって思うけどよくやってしまう・・・
    • さらに、OKな結果から次の結果までの論理が長すぎる(多すぎる)場合もバグは見つかりにくい
    • 常に成り立つ関係(invariants)でassertを立てるのも良い(チェックする)
    • assertでコードの2分探索を行う。「ここにはバグない」と確信しながら進める
  • デバッグは十人十色でいろんな方法がある。分野でも違うし、それぞれの考え方の違いである場合もある。
  • しかしどんな場合でも言えることが一つ。汚いコードというのは必ずバグの温床になる。
    • Keep It Simple Stupid!!!
5.10 Pre- and post-condition
  • 関数を切り口にしてデバッグってのはなかなかいい方法。論理的に分割するために関数に切ったわけだものね。
    • 関数の引数に与える制約条件を pre-conditionと呼んでいる。 関数が正常動作するために必要な条件。
    • で、違反があったら具体的にどうするか?
    1. 無視(全ての呼び出し元は正しい引数を出してくるはず、と仮定する)
    2. チェック(レポートする)
  • こういう発想で見ると型チェックと言うのはpre-condition第一関門でもある。
  • コメントにpre-conditionを書いておくのがオススメ
  • チェックをサボる3条件
    1. 間違った引数を出す者がいない
    2. 性能が落ちるのがいや
    3. チェックが複雑でいや