PPPUC++#9

7 Completing a Program

"It ain't over till the fat lady sings." オペラの格言
僕ら日本人的には小林幸子さんか!(そこまで太ってないけど。。。)
プログラムを書く、ということは一発で鋳造する、というよりは少しずつ盛ったり削ったりして積み重ねていく感じ。この章ではそれをやります

7.1 Introduction

  • プログラムは「なんとか動くようになった」時点で富士山五合目。
    • 大規模プログラムや信頼性の求められるプロジェクトなら、まだまだ五合目は「ほど遠い」
  • でもここからがオタノシミノハジマリ
    • ここからは様々な実験ができるようになる
  • プロフェッショナルのカイゼン術というのを見せてやるぜ! とMr.Sは申しております
  • 要求と制約の元で、如何にして改善していくか、の芸術を見せてやるぜ!

7.2 Input and output

  • あ、そういえば最初の予定ではプロンプトに"Expression: " と、結果出力に "Result:"をつける予定だった!
    • 忘れてたのかよ!
      • 僕はちゃんとつけたよ!w
  • まあでもこういう事は、デスマーチ開発中にはよくあること
    • 人間と言うのはいっぺんにたくさんの事を考えられるようにはできていないのだ
  • 旧態依然とした「仕様の変更は許しません」なんてーのはろくでもない結果にしかならぬ! 仕様なんてのは状況に応じて変えるべき!(スミマセン強くtkuroの私見が入ってしまいました)
    • というわけでいろいろ試してみよう
  • プロンプトほんとにそうした方が良いんだろうか?
    • 考えてたって仕方ない。やってみるべし
  • 現状のコードでは例えば

2+3;5*7; 2+9;
=5
=35
=11

  • 変更してみると

Expression: 2+3;5*7; 2+9;
Result: 5
Expression: Result: 35
Expression: Result: 11
Expression:

    • (いやなにこれダサイ)
  • 好みは十人十色なので、カスタマイズ可能にすべきかもだけど、この小さな例では「俺が法律だ!」で十分
  • とりあえずクドいだろこれ(いや、あんたが決めたんだロ ^^;)
  • と言う訳でもっと簡単な '>' と '='に

> 2+3;
=5
> 5*7;
=35
>

  • いっぺんに一行に書いた場合のこれは、、、、まあ仕方ないよ。マルチステートメントなんて当初の仕様に無かったし、現状では直すの難しいし(爆)

> 2+3;5*7; 2+9;
= 5
> = 35
> = 11
>

    • (ちなみに僕バージョンはそもそもgetline()で一塊ずつパースしてしまうので一行に複数式書けない><)
  • これを解決するにはget()する前の時点でキー入力待ちなのかどうか知る必要があるのでね。。。。 Token_streamなおしてやっからそれまでマテ、と
    • (要するに最初と ';' のあと'\n'があった場合に書けば良いんだよね???)

7.3 Error handling

  • とりあえず動いた、の後。まず最初にやること。それは、その幻想をぶち殺す!! 事。
  • つまり彼の魂を揺さぶるNGワード探し
    • こういうときは、「え、間違い!? え、え、そんな事無いよ!あり得ないよ!」という態度ではダメです
    • 「俺様はニンゲン様だ。どんなプログラム(含む俺の)よりも賢い!」で行きましょ
  • いわゆるテスト
    • 専任でこれを行う人もいる。人呼んでテスター(まんま)
  • 「プログラムを体系的にテストして、全てのエラーを見つける事が可能か否か?」
    • 対象があらゆるプログラムで、とすると、この質問には答がない。
    • ある程度までsystematicは可能。しかし、完璧、とまではいけない。そんなときは意味なしなテストもまあありかな、と。
  • 以前にコンパイラをテストしていた時に、Mr.Sはコンパイラのエラー報告メールをそのままコンパイラに突っ込んでやる、という変な習慣がついたらしい。
    • ヘッダから本文から全部。たしかにエラーコードも入ってる・・・が、
    • 変態的ではあるんだけど、変態的な入力にも耐性のあるプログラムになるのだ、とか
      • ・・・・うーん やっぱし Mr.Sはおもしろいひとだとおもいます
  • 一番最初に気がつく「なんぞこれ」は以下のヤツ
+1;
()
!+2
  • いきなり終了してしまう、とのこと。2番目の ()はソウジャナイんでは?と思ったらセミコロンが抜けてんのか(まだERRATAには載ってない。というか前ページ見ると自明ではある)
    • まあこれは良くあるパターンで、入力ストリームを全部読み切る前に終了して、キー入力待ちになるため。
      • どっちかと言うと僕は ;;; のほうがよっぽどおどろいたやい val = 0; しておいてくれい
  • で、修正案として出ているのが特定の(あまり出てこないであろう)文字を待つように変更とか
    • って何考えてるのその文字がきたらアウトじゃないのよ。終了時面倒くさいし
      • あ、一応ちゃんと書いてある。けど "~なんて滅多にこないからダイジョーブ"ってなんよ。普通に getline()で読み飛ばせば良いじゃん・・・
    • むう、まあ新しい関数をなるべく教えたくないと言う教条なのかな
  • で、次は、、というとテストパターンをいちいちキーボードから入れるな、と。リダイレクト使えとか、別にUnixじゃなくても今時使えない環境の方が少なくないかな? うーんというか当たり前すぎる。
    • 10章でファイルを使う例をやるそうな
  • で、そんつぎは 1+2; q が上手く動かない例。。。
    • あれれれれ、まさかここ(;の後、ドロップスルーするため、次のexpression()を期待してしまい 直後に expression()じゃないものを書けない)直してない設定なのか。
    • ここではしかし、僕がやったようにブロックで囲うのではなく、そもそも valがいらないように、';'を読み飛ばして、expression()を直接表示、にしてるみたいです。
      • なるほどなあ。これだと ";;;"も自動解決するのか。

7.4 Negative numbers

  • 負数使えないじゃん!
    • あれれ? cin使ってるんじゃなかったけ? と思ったらそうか NUMBER=[0-9.]にしてしまってるからprimary()で「数字でも括弧でもない!」になるのか。。。
  • まあここは正当に文法に
Primary:
  Number
  "(" Expression ")"
  "-" Primary   // added
  "+" Primary   // added
  • として RDPを直す方針。まあ普通

7.5 Remainder: %

  • え、剰余いるの?
  • つーかMr.S 浮動小数点数ではmodulo 定義されてない、を繰り返してるんだけど普通に回数分引けば勘違い、、a - int(a/b)*b で良いし。そもそもfmod()あるじゃん・・・
  • narrow_castの例を無理くり出したいだけなのかなー・・・

7.6 Cleaning up the code

  • いわゆるリファクタリング
  • 他人がメンテナンス可能になるまでは完了とは言えないとのこと
7.6.1 Symbolic constants
  • const char number ='8'とかで
    • ああ、数字トークン種別を'8'とか言ってて「おいおい Avoid magic (ry」とか言ってたら、ここで直す予定だったのか。
  • 上手く名前を付ける事でコメントが不要になる。これはとても重要
  • しかしやり過ぎには注意. '+'とか'('とか自明なものにまでつける意味はあまり無い
    • 個人的にはコンパイラのエラーチェックに引っかかるようになるのは大きいと思うのですが、まあ確かに数字一つ一つに点けるのはバカらしい
  • promptや結果出力まで直して、とりあえずオK。
    • promptは何となくconstにすべきじゃない気がする。
      • いかんこの章にきてから文句ばっかり言ってるw
7.6.2 Use of functions
  • 関数構造はプログラム構造を反映しているべき。名前は論理的な分割を表すべき
  • で、ここまでのところおおむねその目的は達成しているように見える
  • ただ一つ main()関数を除いて。現在のmain()は以下の処理2つを抱えてる。
    1. 全体の骨組み:プログラムを開始、終了&致命的なエラーの処理
    2. 計算ループ
  • 4.5.1でも言ってたけど、関数は論理的に1アクションにしぼるべき. main()だって関数。骨組みのみにするべき
    • (大賛成。main()は概要の設計図であるべき。大規模プロジェクトでは、できれば別ファイルに分けたいところ)
  • 計算ループをcalculate()関数に抽象化して、mian()から呼び出すようにするのがここでの変更
    • exceptionがチェインするので可能なのですね。こういうこと。素晴らしい
7.6.3 Code layout
  1. バグは汚いコードに潜む。読みにくいから
  2. バグはスクロール範囲外に潜む。読めないから
  • 1のために1行1caseを守って、種類の違うものが視覚的に混ざらないようにする。
    case 'q': case ';': case '(': case ')': case '+': case '-': case '*': case '/': 
        return Token(ch);        // let each character represent itself
    • 確かにこれは汚い。演算に使われる記号と、実行制御の記号を混ぜるのイクナイ。というか少し例がぅわぁざとらしい :(
      • これは 1行1caseじゃなく、種類の違うもの混ぜるなよ、という話だと思う
  • 2のために1行複数caseを許して、画面内に1関数全部はいるようにするとか
    • んえ?!?
      • いやいや、結局読みやすく理解しやすく書くのが目的なわけだから・・・
      • ここの例では '0'-'9'とかについてそういってます。 これこそ手続き抽象化しようよ。。。
      • ・・・switch-caseが使えなくなるか・・・ 言語設計がわるいんじゃないの?(爆)

微妙です

  • 重要:リファクタリングはテストとセットで。修正によって動作が変わる事があり得る。回帰テストというやつですね
  • 人間はついさっきの事は良く覚えているもの。つまり小規模な変更のうちにエラーを見つけておけば、楽にエラーをつぶせる
    • モンスターは巨大化する前に倒すのが鉄則です。巨大化を許してこちらも巨大ロボで倒すなんてーのは効率悪杉
  • テストは、早めに、頻繁に
    • 自動化して、簡単に、ですね
7.6.4 Commenting

個人的には一番難しいテーマだと思う

  • 良いコメント、大変重要
  • しかし狂乱のプログラミングの最中にはしばしば忘れ去られる
  • リファクタリング、というのがコメントを見直すのに一番最適な時
    1. コメントは正しいまま?(コメント書いた後にコードを直したはず)
    2. 読み手にとって適切な情報?(たいていそうなってない)
    3. コード読むのを邪魔するほどくどくない?
  • 3番について;コードで書ける事はコードに書け!
    • まず、言語を知っている人に明らかな事は書かない、とか
      • ユーモアのセンスを鋭敏にして、後々笑い話としてみんなに話したくなるような笑えるバカを探すと、ダメコメントってのが見えてくる気がします
      • To mock a mocking birdとかそういうトレーニングにいいのかもw

To Mock a Mockingbird

To Mock a Mockingbird

  • 計算機の例では 文法 をコメントに書く、なんてのが良いかも。いかな再帰下降パーサが理解しやすいと言っても、合ってるかどうかを判断するのはコードだけからでは超困難。
    • ついでにブロックコメント /* ... */を照会してます。 いままでにもシレッと出てた気がしますが・・・
      • やっぱり Mr.Sおもしろい
    • あと更新履歴をコードに書くのどうかなぁ。。。RCS使う方が良いと思うなあ・・・意図しない嘘が混入しやすいし