#code completeのまとめ
10+n年前にまとめたテキストが発掘されたので転記
多分重要な部分だけつまみ食いした感じもある
10年経ってるけどあんまり変わってない部分も多いから現状でも役に立つ部分は多そう
プログラム書き始めてしばらく経ったメンバーに本をプレゼントして
要約すると言う仕事をさせることで中身を読ませる仕事をさせると
数年後にすごく感謝されると思いますよ、部長さん。
他、マーティン・ファウラーの「リファクタリング」もおすすめです。
最近(2018年4月)に改訂版も出たっぽいでし
2018.9.10
typo修正しました。リクエスト飛んでくるって便利ですね。
反映の操作がよくわかってないので手作業で修正してますが<(;'A`)>
##第1部 基礎を固める
##第2部 高品質なコードの作成
##第5章コンストラクションにおける設計
###5.1 設計の難題
・設計の特徴
やっかいな問題
妥協と優先順位の産物
制限がある
非決定論的(人によって最終結果が変わるし、どれも間違っているわけではない)
ヒューリスティックなプロセス(すべて予測可能というわけではなく、試行錯誤が必要だったりする)
創発的(唯一の答えがあるわけではなく、進化し、改善していくもの)
###5.2 重要な設計概念
・複雑さを克服する方法
×
単純な問題に対する複雑な解決策
複雑な問題に対する単純で誤った解決策
複雑な問題に対する不適切で複雑な解決策
○
一度に対処しなければならない本質的な複雑さを最小限に抑える
偶発的な複雑さを必要以上増やさない
・望ましい特性
最小限の複雑さ
保守性
祖結合
拡張性
再利用性
高いファンイン(機能とクラスが一箇所に集中している状態)
低いファンアウト(色々よその機能やクラスを沢山呼び出さない状態)
移植性
無駄の無さ
快速化
標準化手法
###5.3 構成要素の設計:ヒューリスティクス
ヒューリスティクスを効果的に用いる方法
抽象化、インターフェースの抽出、カプセル化、情報の隠蔽など
・変更の可能性が高い箇所
変更の可能性が高い部分は分離したり囲い込んでおくほうが良い
業務ルール
ハードウェアへの依存部分
入出力
プログラミング言語の標準ではない機能
設計とコンストラクションの難度が高い領域
状態変数
データサイズの制約
###5.4 設計のプラクティス
・手順
反復
分割攻略
トップダウン方式、ボトムアップ方式
実験的プロトタイプ
コラボレーティブな設計
###5.5 一般的な手法へのコメント
昔は事前の設計重視だったが、現在は「事前に少し準備しておく」手法に変化している
どれだけ事前に設計すれば良いのかは答えが無いが、「すべて設計」と「なにもしない」は大きな誤り
###5.6 参考資料
※書籍一覧
###5.7 まとめ
複雑さに気をつける
単純にする(=複雑さを最小限に抑える+複雑さを必要以上に増やさない)
反復する
情報隠蔽は特に価値が高い(何を隠すかよく考えること)
この本の設計論は1つの論にすぎず、他にも沢山ある
##第6章クラスの作成
###6.1 クラスの基礎:抽象データ型(ADT)
・ADT:Abstract Data Type(抽象データ型)
データとそのデータに作用する操作をまとめたもの
→身近なところでリファクタリングのメソッド作成やリテラルの抽出など
・利点
実装の詳細を隠蔽
変更がプログラム全体に影響しない
インターフェースが提供する情報をより明確に出来る
パフォーマンスを改善し易くなる
プログラムの正しさがより際立つ
プログラムが一目でわかるようになる
プログラムのあちこちでデータをやり取りする必要が無い
下位レベルの実装構造ではなく、現実世界のエンティティを扱うことが出来る
###6.2 良いクラスインターフェイス
良いクラスを作るためには、良いインターフェースを作る必要がある
インターフェースを表す抽象概念をうまく作成し、実装を隠す必要がある
・良い抽象化
一貫性のある抽象化を実現
クラスがどのような抽象化を実装しているのか理解する
サービスは逆のサービスと2つ1組にして提供する
関係の無い情報は別のクラスへ移動
インターフェースは出来るだけ意味的なものではなく、プログラム的なものにする
※:呼び出される順番がわかりやすいように、と言う意味です
インターフェースの変更中に抽象化が損なわれないように注意する
インターフェースの抽象化と矛盾するパブリックメンバを追加しない
抽象化とモジュール凝集度を同時に検討する
・良いカプセル化
クラスとメンバへのアクセスを出来るだけ制限する
メンバデータを外部に公開しない
実装上のプライベートな部分をクラスのインターフェースに含めない
クラスのユーザについてあれこれ推測しない
フレンドクラスを使用しない
パブリックルーチンしか使用しないからと言って、ルーチンをパブリックインターフェースに追加してはならない
書き手の便宜よりも、読み手の便宜を優先する
カプセル化の意味的な違反には非常に細心の注意を払う
強すぎる結合度に注意する
###6.3 設計と実装の問題
内部クラスの設計と実装も、インターフェース定義と同様に重要である
・包含(has a)の関係
・継承(is a)の関係
###6.4 クラスを作成する理由
・理由
現実世界のオブジェクトをモデリングする
抽象オブジェクトをモデリングする
複雑さを緩和する
複雑さを分離する
複雑さを隠蔽する
変更による影響を限定する
グローバルデータを隠蔽する
引数の受け渡しを合理化する
制御を一元化する
コードの再利用を促進する
プログラムのファミリを計画する
関連する操作をパッケージに纏める
特定のリファクタリングを実行する
・望ましくないクラス
「ゴッド」クラスを作成しない
不適切なクラスを排除する
クラスの名前を動詞にしない
###6.5 言語固有の問題
プログラム言語によって継承などの取り組みが大きく違う
###6.6 クラスを超えて:パッケージ
パッケージ+クラスで意味を成す命名規則
###6.7 参考資料
###6.8 まとめ
・一貫性が重要
・インターフェースは「システムインターフェース」「設計」「実装」を隠蔽しなければならない
・継承は使い方に注意が必要(複雑さを増大させるため)
・クラスは機能作成と同じくらい設計にも注意を払うこと
##第7章高品質なルーチン
###7.1 ルーチンを作成する理由
・作成する理由
複雑さを低減
中間部分をわかり易く抽象化
コードの重複を避ける
サブクラスを作成し易くなる
処理順序を隠蔽する
ポインタの処理を隠蔽する
移植性を向上させる
複雑な論理評価を単純化する
パフォーマンスを向上させる
すべてのルーチンが小さいことを保障する?
※:これは微妙。ルーチンが大きいほうがいい場合もあるので。
・単純な処理もルーチンにした方が良い
見易くなる、内容がわかり易くなるので
###7.2 ルーチンレベルでの設計
コードの凝集度と強度について
・凝集度を高めるための概念
機能的凝集度:ルーチンの名前と機能が一致
情報的凝集度:ルーチンを呼び出す順番を有する
連絡的凝集度:同じデータを使用する場合
時間的凝集度:同時に実行されるルーチンをまとめる
手順的凝集度:特定の順序で実行する
論理的凝集度:複数の処理が1つのルーチンに含まれる。制御フラグで動きが変化
暗号的凝集度:関連の無いルーチンを結びつける。設計の見直しや再実装が迫られる
###7.3 良いルーチン名
・つけてはいけない名前
意味の無い動詞
あいまいな動詞
どっちつかずな動詞
数字が付いている、数字で区別する
短すぎる、長すぎる
・つけるべき名前
ルーチンが行うことをすべて説明している
関数名に戻り値の説明がある
効果的な動詞、オブジェクトを返す
正確な反意語
一般的な処理の規約でまとまった名前
・反意語の例
P212
###7.4 ルーチンの長さ
1画面分、もしくは1~2Pくらい
50行から100行で
サイズが増えるとエラーが増える、複雑さ、開発コストが増える
###7.5 ルーチンの引数の使用
・引数のガイドラインの一例
入力、変更、出力の順で配置
独自のinキーワードとoutキーワードの作成を検討する
複数のルーチンが似たような引数を使用する場合、それらの順番を統一する
すべての引数を使用する
状態変数、エラー変数は最後に配置する
ルーチンの引数を作業用変数として使用しない
引数に関するインターフェースの条件を明記する
引数の数は7つ以内に制限
入力、変更、出力引数の命名規則を検討する
ルーチンのインターフェースの抽象化を維持するために必要な変数またはオブジェクトを渡す
名前つきの引数を利用する
実引数が仮引数と一致することを確認する
###7.6 関数の使用に関する注意点
これまでの話は言語によって違うので注意が必要
###7.7 マクロルーチンとインラインルーチン
マクロの注意点について
現在のプログラミング言語はマクロに変わる選択肢がいろいろある
###7.8 まとめ
・ルーチンを作成する理由は、頭で理解し易くするため
・ルーチンを独立させることがもっとも効果的なのは、単純な処理の場合
・ルーチンは凝集度を持つ
・ルーチンの名前は品質をあらわす
・関数を使うのは、関数名で説明される特定の値を返す場合だけにすべき
・マクロルーチンの仕様には注意を払うこと(できれば使わない)
##第8章防御的プログラミング
###8.1 無効な入力への防御
プログラムへの入力を誤れば、誤った値が出力されるのは当然だが、
良いプログラムは誤った値を返すのではなく、エラーやメッセージを出力したり、そもそも入力をさせない
・入力に対する防御方法
入力データ値をすべて確認する
すべての入力引数を確認する
不正な入力を処理する方法を決定する
###8.2 アサーション
アサート:主張
アサーションを用いることで、エラーが把握し易くなる
ユーザに表示するものではなく、開発用のものなので製品には組み込まない
・ガイドラインの例
エラー発生が予想できる場合はエラー処理コード、発生がしてはならない状況の場合はアサーションを使用
アサーションに実行コードを組み込まない
事前条件と事後条件の文書化と検証にアサーションを使用する
件老成の高いコードは、エラーをアサートしてから処理する
###8.3 エラー処理テクニック
・処理テクニックの例
当たり障りの無い値を返す
次に有効なデータで代用する
前回と同じ答えを返す
有効な値のうち、もっとも近いもので代用する
ファイルに渓谷メッセージを記録する
エラーコードを返す
エラー処理ルーチン/オブジェクトを呼び出す
エラーが発生した場所でエラーメッセージを表示する
ローカルでもっともうまくいく方法でエラーを処理する
処理を中止する
###8.4 例外
・例外使用時の注意点
無視すべきではないエラーは例外を使用してプログラムの他の部分に伝える
本当に例外的な状況でのみ、例外をスローする
例外を責任逃れに使用しない
その場でキャッチする場合を除き、コンストラクタとデストラクタで例外をスローしない
※:C++だとメモリリークに発展する恐れがある
正しい抽象化レベルで例外をスローする
例外メッセージに例外の原因に関するすべての情報を盛り込む
空のcatchブロックを書かない
ライブラリコードがスローする例外を知る
例外レポート用ルーチンでの集中管理を検討する
プロジェクトで例外の使用方法を標準化する
例外に変わる手段を検討する
###8.5 バリケードによるエラーの被害の囲い込み
入力データはすべて安全なものにしてから作業を行う
入力データは入力されたときに正しい値に変換する
###8.6 デバッグエイド
デバッグエイド:デバッグを補助するツール
###8.7 製品コードに防御的プログラミングをどれくらい残すか
開発段階ではエラーを見逃さないように目立つようにするが、製品では目立たなくする
ならば、どれだけアサーションなどを残すか
・ガイドラインの一例
重要なエラーを検査するコードは残す
些細なエラーを検査するコードは削除する
処理を中断するようなコードは削除する
プログラムを上品にクラッシュさせるコードは残す
テクニカルサポート担当者のためのエラーを記録する
わかりやすいエラーメッセージは残す
###8.8 防御的プログラミングに対する防御
度を過ぎるとプログラムが肥大し、遅くなり、複雑になる
また、防御的プログラミング自体は防御されているわけではないので
エラーが検出される可能性がある
優先順位を決めて設定する必要がある
###8.9 参考資料
###8.10 まとめ
・「ゴミ入れ、ゴミ出し(エラー有り入力は不正な出力になる)」よりは、もっと洗練された方法でエラーを処理すべきである
・防御的プログラミングはエラーの検出と修正を容易にして、製品版コードへの被害を食い止める
・アサーションはエラーを早期に検出するのに役に立つ
・不正な入力に対処する方法を決定することは、重要な決断である
・例外は通常のコードとは別の次元の流れで、エラー処理を実行するための手段になる
##第9章擬似コードによるプログラミング
###9.1 クラスとルーチンの作成手順の概要
・クラスの作成順序
クラス全体を設計する
→クラスの各ルーチンを作成する
→クラス全体をレビューし、テストする
・ルーチンの作成順序
ルーチンを設計
→設計を確認(必要があれば設計に戻る)
→コーディング
→レビューし、テスト(必要があればコーディングに戻る)
→終了、もしくはルーチンの設計に戻る
###9.2 擬似コードプログラミングプロセス(PPP)
擬似コード
アルゴリズム、ルーチン、クラス、プログラムの仕組みを説明するための略式の表記方
・効果的に使用するためのガイドライン
特定の処理を正確に説明する文章に使用する
プログラミング言語の構文要素を使用しない
※:上位レベルで設計を行うことができるので、やる
目的のレベルで擬似コードを書く
そのままコーディングできるレベルで書く
※:略式にしすぎない
・利点
レビューが容易になる
反復の後押しをする
変更を容易にする
コメントの作成を最小限に抑える
設計書よりも管理しやすい
###9.3 PPPを使ったルーチンの作成
作業については下記の順で行う
・ルーチンの設計
準備が整っていることを確認
ルーチンが解決する問題を定義
ルーチンの名前付け
ルーチンのテスト方法を決定
ライブラリの提供している機能を調査
エラー処理について定義
効率について考慮
アルゴリズムとデータ型を調査
擬似コードを記述
データについて考慮
擬似コードを検査
擬似コードで試行、良いものを採用
・ルーチンのコーディング
ルーチンを宣言
擬似コードを概略レベルのコメントとして転用
各コメントの下にコードを記入
コードを更に分離すべきか確認
・コードの検査
エラーのルーチンを頭で確認
ルーチンをコンパイル
デバッガでコードを実行
コードをテスト
ルーチンからエラーを除去
・残りの仕上げ作業
インターフェースの検査
全体的な設計の品質の検査
ルーチンの変数を検査
ステートメントとロジックを検査
レイアウトを検査
ルーチン内のドキュメントを検査
重複コメントの削除
・必要に応じて繰り返す
上記を必要に応じて繰り返す
###9.4 PPP以外の方法
・テストファースト開発
・リファクタリング
・契約による設計
事前条件と事後条件を重視
・ハック
###9.5 まとめ
・クラスやルーチンの作成は反復型のプロセスであることが多い
・良い議事コードを書くにはわかりやすく、抽象的で、目的のレベルで書く必要がある
・最初に思いついた設計で満足しないこと
・段階ごとに成果物を第三者にも確認してもらう
##第3部 変数
##第10章変数の使用
変数はコンストラクションの基本
###10.1 データリテラシー
効果的なデータを作成するためには、最初にデータの種類を知る必要がある
###10.2 変数宣言のガイドライン
変数をつける作業は小さな作業であるが、
正しい習慣を身につけておくことでプロジェクト全体の作業時間が短縮される
・変数宣言時の注意「暗黙の宣言」
プログラミング言語によっては、変数に「暗黙の宣言」が出来るものがある
(例:Javaのintは宣言せずに使用すると「0」として初期化される)
この機能と作成したプログラムと間でバグが混入する可能性がある
ちなみにどの言語でも、最も危険な機能の一つに数えられる
上記の問題に対する対処法の例
・暗黙の宣言を無効化する(コンパイラに依存)
・全ての変数を宣言する(全てのエラーが解決される訳ではない)
・命名規則を使用する
・変数名を確認する
###10.3 変数の初期化のガイドライン
・変数に予想外の値が含まれる問題の起因の例
変数に値が代入されていない
メモリ領域にたまたま含まれていた値が使われる
変数の値が無効になっている
どこかで代入されたか、値が有効ではなくなっている
変数の一部に値が代入されているが、残りの部分にはされていない
初期化のミス、メモリ割り当てのミス、ポインタの初期化ミス等が原因の場合が多い
・問題を解決するためのガイドラインの例
変数は宣言時に初期化する
変数は最初に使用する場所の近くで初期化する
変数の宣言と定義は、最初に使用する場所に近くで行うのが理想的
出来るだけfinal(Java)またはconst(C++)を使用して、代入できなくする
カウンタとアキュムレータには特に注意する
クラスのメンバデータはコンストラクタで初期化する
再初期化の必要性を確認する
名前付き定数は一度だけ初期化し、変数は実行可能コードで初期化する
コンパイラの警告メッセージを利用する
入力引数の有効性を検査する
メモリアクセスチェッカーを使って無効なポインタを検査する
プログラムの先頭で作業メモリを初期化する
###10.4 スコープ
スコープ:
変数の知名度。どの範囲まで知れ渡っていて、どの範囲で参照できるのかを意味する
・スコープを利用する際のガイドライン
変数の参照はまとめる
参照してから次に参照するまでの間を短くする(変更やクリアがあるかもしれないので)
「寿命」を出来る限り短く
多くのステートメントにまたがって存続する形はアウト
・スコープを最小限に抑えるためのガイドライン
ループで使用する変数は、ループが含まれているルーチンの先頭ではなく、ループの直前で初期化する
変数の値を使用する直前まで、変数に値を代入しない
関連するステートメントはまとめて書く
関連するステートメントをまとめて別のルーチンに分ける
最も狭いスコープから始めて、必要に応じてスコープを拡大していく
スコープが広いとアクセスしやすくなる(便利になる)が、頭での理解がしにくくなる
「便利さ」と「頭での理解のしやすさ」のトレードオフだが、広いと理解しにくいし変更しにくくなる
###10.5 永続性
スコープの種類は以下の通り
・特定のコードブロックまたはルーチンがスコープを外れるまで
・プログラマが指定した期間まで
・プログラムが終了するまで
・永久に存在
永続性の問題は賞味期限に似ている
寿命が尽きた変数を使うととんでもないことになる
防ぐための方法
・デバッグコードやアサーションの使用
・使用済み変数に対してnullなどの「意味の無い値」を設定
・データが永続的でないことを前提としたコードを書く
・使用する直前に初期化する
###10.6 バインディングタイム
バインディングタイム
→変数にその値が結び付けられるタイミングのこと
バインディングタイムは遅らせると有利に働く可能性がある
遅ければ遅いほど、コードの柔軟性が増していく
ただし、早ければ早いほど複雑でなくなる
例:スタティック変数で持たせると融通が利かないが、プロパティファイルで値を埋めると変更し易い
バインドできるタイミング
・コーディング時
・コンパイル時
・ロード時
・オブジェクトのインスタンス生成時
・ジャストインタイム
###10.7 データ型と制御構造の関係
データ型と制御構造は明確な関係で結ばれている
データと制御構造との関係をイギリスのコンピュータ科学者Michael A. Jacksonは3つの例を述べている
・連結データはプログラムにおいて連続的なステートメントに変換される
・選択データはプログラムにおいてif文やcase文に変換される
・反復データはプログラムにおいてfor, repeat, whileといったループ構造に変換される
10.8 1つの目的に1つの変数
凝った方法を使えば、変数を複数の目的に使用できるが、余計なことをしない方が良い
・変数は1つの目的のみ使用する
一緒の変数を使うと、関係のないコードが関係があるように見えてしまう
・隠れたい意味をもつ変数を使わない
0や-1などの特定の数字に意味を持たせない(ハイブリッド結合とも言う)
・宣言された変数が全て使用されることを確認する
###10.9 まとめ
・初期化はエラーの原因になり易いので、記述されたテクニックを使うと良い
・変数のスコープは最小限に抑える
・同じ変数を扱うステートメントが複数ある場合、出来るだけ近くにまとめる
・早期のバインディングタイムは柔軟性を制限するが、複雑さを抑える
先送りにすると柔軟性は高まるが、その分複雑さが増す
・変数はそれぞれひとつの目的に使用する
##第11章変数名の力
###11.1 良い名前を選択するために
xとかyとか意味の無い名前を使わない(スコープが極端に短い変数は別)
ふさわしい名前であること
ただし、実用性がなくなるほど長い名前はダメ
変数の長さは10~16文字、もしくは8~20文字程度が良い
(デバッグの際に効率的になると発見された値)
ただし、必ず守らなければならないわけではなく、明確さを持っているということが最重要
スコープが極端に短い変数 : 短くてもOK(ループカウンタなど)
スコープが極端に長い変数 : 長くてもOK(グローバル変数など)
TotalやSum、Average,Max,Min,Record、String,Pointerなどの就職子をつける場合、最後につける方が良い
・意味が重要な単語が前に来るメリット
・後ろにつける約束で、同じような変数が増えないメリット
・名前が釣り合いが取れている(見易い)メリット
変数の反意語は必ず覚えること
・begin / end
・first / last
・locked / unlocked
・min/ max
・next / previous
・old / new
・opened / closed
・visible / invisible
・source / target
・source / destination
・up / down
###11.2 特殊なデータの命名
・ループ変数について
ループ変数にはi, j, kがよく使われるが、見間違えを起こし易い
意味を持つ変数名を使うと見間違えを起こしにくくなる(teamIndex, eventIndexなど)
・状態変数について
状態変数にはflagよりも良い名前をつけること
・一時変数について
一時的な変数であることに最大の注意を払うこと
・ブール変数について
一般的なブール名を覚えること
done, error, found, success, ok
真偽のわかりやすい名前をつけること
肯定的な変数名を使用すること
not~のような変数名を使わない(found = !notFoundになるので)
・列挙型について
プレフィックスを用いることでグループが明確になる
・名前付き定数について
エンティティの名前を付ける
###11.3 命名規則の力
命名規則を作成すると、以下のメリットがある
・名前の付け方を考える必要がなくなる
・他のプロジェクトでも同じ命名規則だと、プログラムが把握し易くなる
・コードをすばやく理解できるようになる
・プログラミング言語の弱点を補える
・関連する項目同士の関係を明確にする
命名規則が必要な状況
・複数のプログラマがプロジェクトに従事している
・他の人への保守の必要がある
・組織や他の人にレビューされる
・プログラムが大きくて、分けて見る必要がある
・開発期間が長い
・プロジェクトのあちこちに見慣れない単語がたくさんある
###11.4 略式の命名規則
言語に依存しない命名規則のガイドライン
・変数名とルーチン名を区別する
・クラスとオブジェクトを区別する
・グローバル変数を識別する
・メンバ変数を識別する
・型の定義を識別する
・名前付き定数を識別する
・列挙型の要素を識別する
・入力専用の引数を識別する
・名前を読みやすくする
言語固有の命名規則のガイドライン
CやC++、Javaなどには標準のスタイルと言うものが確立されている
Javaの場合
・iとjは整数インデックス
・定数は全て大文字のアンダースコア区切り
・クラスとインターフェース名の一文字目は大文字
・変数とメソッド名は一文字目が小文字
・名前の区切りにアンダースコアを使わない
・getとsetプレフィックスは、アクセサメソッドに使用
あくまでも例なので、CodeCompleteで紹介しているスタイルと異なっている箇所がある
###11.5 標準のプレフィックス
標準のプレフィックスは、UDT(ユーザ定義型)の省略形と意味を表すプレフィックスの二つの部分からなる
プレフィックスは命名規則を持つことの一般的な利点を得ることが出来る
また、名前を短くすることができるが、名前付けのミスを招く可能性がある
###11.6 短くて読みやすい名前の作成
省略形全般のガイドライン(ガイドライン同士でぶつかる場合もあるので、参考まで)
・標準の略記を使用する
・一文字目以外の全ての母音を削除する
・and, or, theなどの冠詞を削除する
・各単語の一文字目、または数文字を使用する
・各単語の1文字目、2文字目、または3文字目以降を全て切り捨てる
・各単語の最初と最後の文字を残す
・名前の中で重要な単語を最大で3つ使用する
・無駄なサフィックス(ing, edなど)を削除する
・各音節で最も目立つ音を残す
・変数の意味を変えないように注意する
・8~20文字になるまで上記を繰り返す
注意点
・1文字だけ削除する省略は避ける(意味がない、わかりにく)
・省略法に一貫性を持たせる
・発音できる名前にする
・読み間違えたり、発音を誤り易い組み合わせにしない
・類語辞典を使って名前の競合を解決する
・コードの変換表を用意して、きわめて短い名前を文章化する
・プロジェクトレベルの「省略形標準」に全ての省略形を纏める
・名前はコードの書き手ではなく読み手によって重要なものである
###11.7 避けるべき名前
避けるべき名前について
・誤解を招くような名前や省略形を使わない
・意味が似ている名前をいくつも使わない
・意味は異なるが似ている名前を使わない
・同じような発音の名前を使わない
・名前に数字を使わない
・名前の綴りを故意に変えない
・綴り間違いの多い単語は避ける
・大文字と小文字の違いだけで名前を区別しない
・複数の自然言語の組み合わせをしない
・標準のライブラリの型、変数、ルーチンの名前は使わない
・変数が表すものと全く無関係な名前を使わない
・読みにくい文字が含まれた名前を使わない
###11.8 まとめ
・良い変数名はプログラムの読みやすさを左右する
・変数名は出来る限り具体的に
・命名規則で変数のタイプが区別できるようにする
・プロジェクトの種類に関わらず、変数の命名規則を採用する
・最近のプログラミング言語で省略形が必要なことはまれであるが、
必要であればプロジェクトの辞書や標準化されたプレフィックスを使うこと
・コードは書き手のためではなく、読み手のために
##第12章基本的なデータ型
###12.1 数値全般
ミスを防ぐためのガイドライン
・マジックナンバー(説明梨に使われている数値リテラル)を使わない
・必要であれば、0や1をハードコーディングする
・0による除算を考慮する
・型の変換を明確にする
・異なる型の値を比較しない
・コンパイラの警告に注意する
###12.2 整数
注意ポイント
・整数の除算に注意する
・整数の桁あふれに注意する
・中間結果の桁あふれに注意する
###12.3 浮動小数点数
ガイドライン
・大きさが極端に異なる数の加算や減算は行わない
・等価を比較しない
・丸め誤差を考慮する
・プログラミング言語とライブラリがサポートしているデータ型を確認する
###12.4 文字と文字列
・マジックキャラクタやマジックストリングを使わない
・「1つ違い」エラーに目を光らせる(文字列インデックス)
・使用している言語や環境がUnicodeに対応しているかどうか確認する
・プログラムの開発当初から国際化と地域化の戦略を練る
・アルファベット言語のみのサポートであれば、ISO8859文字セットの使用を検討する
・文字型の変換方式を統一する
###12.5 ブール変数
使用の際の注意ポイント
・ブール変数を使ってプログラムを文書化する
・ブール変数を使って複雑な評価を単純にする
・必要であれば、ユーザ定義のブール型を作成する
###12.6 列挙型
使用の際の注意ポイント
・列挙型を使ってコードを読みやすくする
・列挙型を使ってコードの信頼性を高める
・列挙型を使ってコードを変更し易くなる
・列挙型をブール変数の代わりに使用する
・無効な値を検査する
・列挙の最初と最後の要素をループの範囲として定義する
・列挙型の最初の要素をわざと無効な値に設定する
・列挙型の最初と最後の要素の使用方法を厳密に定義する
・列挙の要素に明示的な値を割り当てることには落とし穴がある
→最初から最後までループすると、間の抜けている値も処理してしまう
###12.7 名前付き定数
・名前付き定数をデータ宣言に使用する
・例え「安全な」ものであっても、リテラルを使用しない
・適切なスコープの変数やクラスで名前付き定数をシミュレートする
・名前付き定数を一貫して使用する
###12.8 配列
・配列の全てのインデックスが配列の範囲内にあることを確認する
・配列の代わりにコンテナを使用するか、配列をシーケンシャルな構造体としてみなすことを検討する
・配列の終端を検査する
・多次元配列の場合は、インデックスが正しい順番であることを確認する
・インデックスのクロストークに注意する
・CではARRAY_LENGTH()マクロを使って配列を操作する
###12.9 ユーザー定義型の作成(型のエイリアス)
ユーザ定義型を作成する理由
・コードを変更し易くなる
・必要のない情報を公開しない
・信頼性を向上させる
・プログラミング言語の弱点を補う
作成のガイドライン
・機能に基づいた名前にする
・組み込み型ウィ再定義しない
・移植性のある型に置き換える
・typedefを使用する代わりにクラスを作成することを検討する
###12.10 まとめ
・型の規則を覚える、その上で検討する
・ユーザ定義型はプログラムをわかり易くする(言語によってサポートされていないが)
・typedefやそれに相当する機能を使って単純な型を作成する場合は、クラスの作成を検討する
##第13章特殊なデータ型
###13.1 構造体
構造体を使用する理由
・データの関係を明確にする
・データブロックの処理を単純化
・引数リストを単純化
・保守作業の軽減
###13.2 ポインタ
エラーの原因になり易い
最近の言語では提供していない
###13.3 グローバルデータ
グローバルデータ:プログラムの何処からでもアクセスできるスコープの広い変数
プログラマは皆、非常に便利であることと危険であることを理解している
使用する際の一般的な問題
・グローバル変数の不注意な変更
・エイリアス問題
・再入力問題
・コードの再利用の妨げ
・初期化の手順が一定ではない
・モジュール性が損なわれる
使用する理由
・グローバルな値の保存
・名前付き定数のエミュレーション
・列挙型のエミュレーション
・頻繁に使用するデータの合理化
・トランプデータの削除
グローバルデータを利用する前に考えるほかの手段
・最初は全ての変数をローカルで宣言し、必要に応じてグローバル化する
・グローバル変数とクラス変数を区別する
・アクセスルーチンを使用する
###13.4 参考資料
###13.5 まとめ
・構造体はプログラムの複雑さを和らげ、理解し易く保守し易いものにする
・構造体の利用は、クラスの動作が良くなるかどうかで使用を決める
・ポインタはエラーの原因になり易いので、手法を使って自衛する
・グローバル変数を使用しない。他にもっといい方法があるはずだから
・どうしてもグローバル変数を使用するときは、アクセスルーチンを用いる
##第4部 ステートメント
##第14章ストレートなコードの構成
###14.1 順序が重要なステートメント
ステートメントの順番を決めるガイドライン
・依存性が明白になるようなコード構成にする
・依存性が明白になるようなルーチン名にする
・ルーチンの引数を使って依存性を明白にする
・依存性が明白でない場合にはコメントを書く
・アサーションやエラー処理コードを使って依存性を検査する
###14.2 順序が重要でないステートメント
基本は、関連する作業をひとつに纏める、近くに置く
気をつけるポイント
・上から下へ読めるコード
・関連するステートメントのグループ化
###14.3 まとめ
・最も重視するのは、実行順序への依存性
・良いルーチン名、引数リスト、コメントなどを使って、依存性を明白にする
・コードが実行順序に依存しない場合、関連するステートメントを出来る限り近くに纏める
##第15章条件文の使用
###15.1 if文
基本
・正常なケースの実行パスを最初に書いてから、例外的なケースを書く
・等号や不等号による分岐が正しいことを確認する
・正常なケースの処理はelse文ではなくif文の後に書く
・if句には意味のあるステートメントを書く
・else句について検討する(elseが本当に必要か検討)
・else句に正確さをテストする
・if句とelse句が逆でないかどうか確認する
if-then-elseの連鎖(caseをサポートしてない場合)
・複雑な評価をブール関数の呼び出しで単純化する
・最も一般的なケースを最初に評価する
・全てのケースがカバーされていることを確認する
・if-then-elseの連鎖を、言語がサポートする他の機能で置き換える
###15.2 case文
最も効果的なcase文の順番
・アルファベット順または数値順
・正常なケースが先頭
・出現頻度の高い順
case文の使用に関するヒント
・各ケースの処理は単純にする
・case文を使用するために仮の変数を作らない
・default句ではその他扱いにするものだけを検出する
・default句を使ってエラーを検出する
・C++とJavaではフォロースルーを使用しない
・C++では、case文のフォロースルーを明確に、誤解の内容に示す
###15.3 まとめ
・if文順番に気をつける
正常ケースが先
・if-then-elseの連鎖とcase文は、最も読みやすい順序で並べる
・case文のdefault句か、if-then-else文の最後のelse句を使って、エラーを細くする
・各コードブロックに最適な制御構造を選択すること
##第16章ループの制御
###16.1 ループの種類の選択
ループの種類
・カウントループ :指定した回数だけ
・連続評価ループ :ループの終わりまで
・エンドレスループ :一旦開始したら永遠に実行を繰り返す
・反復子ループ :1つの要素を1回ずつ
while:先頭で評価するか、末尾で評価するかと言う点に注意。また、ループを抜ける条件も要注意
for:途中でループの終了条件を満たしてしまったとき、終了の仕方に注意
foreach:ループを繰り返すための計算がいらないためにエラーの可能性がない
###16.2 ループの制御
ループがうまくいかない原因はたくさんある
・ループカウンタの初期化
・ループの初期化
・ループに関する変数の初期化
・ネストが正しくない
・ループの終了が正しくない
・ループ変数がインクリメントされていない
・インクリメントが正しくない
その他多数の原因がある
防ぐためのプラクティスは
・ループに関連する要素を最小限にする
・制御は出来るだけループの外に出しておく
ループの開始のガイドライン
・ループの入り口は一箇所にする
・初期化コードはループの直前に置く
・無限ループにはwhile(true)を使用する
・forループが適している状況にはforループを使用する
・whileループの方が適している状況ではforループを使用しない
ループ本体の処理
・ループのステートメントは{と}で囲む
・空のループを使わない
・ループの前処理や後処理はループの先頭または末尾に纏める
・ループの1回の処理は1つの機能に絞る
ループの終了
・ループが終了することを確認する
・ループの終了条件を明確にする
・forループを終了させるためにループ変数を勝手に書き換えない
・ループ変数の最終値を使用するコードを書かない
・安全カウンタの使用を検討する
ループの繰上げ終了
・whileループでは、ブール変数のフラグではなくbreak文の使用を検討する
・ループをbreak文だらけにしない
・continue文はループの先頭での評価に使用する
・言語がラベルつきのbreak文をサポートしている場合は、それを使用する
・break文とcontinue文の使用には注意する
エンドポイントの確認
・終了時に特別なケースがあれば、実際に計算して調べてみること
ループ変数の使用
・配列やループの限界地には序数または列挙を使用する
・ネストしたループを読みやすくなるよう、変数に意味のある名前を付ける
・意味のある名前を使って、ループ変数のクロストークを防ぐ
・ループ変数のスコープはループ自体に制限する
ループの適切な長さ
・ループは一度に確認できる程度の長さ
・ネストは3段階までに制限する
###16.3 ループの作成─ 内から外へ
ループはケースを書いて、そのケースをループで書いて・・・と
単純なケースからはじめて、徐々にコードを増やしていくことでうまく書ける
###16.4 ループと配列の対応付け
ループと配列は関連していることが良くある
その場合、配列のインデックスとループ変数が対応している
###16.5 まとめ
・ループは複雑であるの。単純に保つ必要がある
・単純に保つためには、
変わったループを作成しない、ネストを出来る限り少なくする、
入り口と出口を明確にする、前処理や後処理のコードを一箇所に纏めるなどが挙げられる
・ループ変数は誤用され易いので、わかり易い名前を付けること
・ループ全体を入念に検討し、全てのケースで正常に実行されることを検証すること
##第17章特殊な制御構造
制御構文には「最先端の構造」と「信頼性が低く誤りであることが証明されているもの」と
その中間のものが存在する
制御構文は、注意して使用しなければならない
###17.1 ルーチンからの複数のreturn文
return文を利用するためのガイドライン
・コードが読みやすくなる場合はreturn文を使用する
後の処理が要らない場合は、途中でreturnを書くことでわかりやすくなる
・ガード句を使って複雑なエラー処理を単純化する
・各ルーチンのreturn文の数を最小限に抑える
制御が戻される部分がわかりにくいと、ルーチンを理解するのが困難になる
###17.2 再帰
再帰はいつでも役に立つ訳ではないが、うまく利用すれば的確な解決策が得られる場合もある
再帰を利用する際のヒント
・再帰を終了するための手段を講じる
再帰を抜けるパスがあることを必ず確認する(無限ループに陥りやすいので)
・安全カウンタを使って無限再帰を防ぐ
・再帰を1つのルーチンに限定する
・スタックに目を光らせる
・階乗やフィボナッチ数列に再帰を使用しない
階乗は書籍の再帰サンプルでよく使用されるが、これを実装に用いるのは馬鹿げている
###17.3 goto文
現在はあまり使われないが、古いソースには存在する
賛成派や反対派もいる
この意見に関しては、return文でも同様の話題が出てくる
(複数のreturn文、複数のループの出口、名前付きループの出口、エラー処理、例外処理など)
###17.4 特殊な制御構造の展望
プログラマがコードを使ってできることを「制限する」方が良いという考えが正しい
アンチパターン
・goto文を無制限に使用すること
・goto文のターゲットを動的に計算し、計算された場所へジャンプすること
・goto分を使って1つのルーチンを呼び出し、ルーチンの途中で実行を開始すること
・プログラムのコードをその場で生成し、そのコードを実行すること
※:goto文をreturn文に読み替えると自分たちにとってわかりやすい
上記は昔はよいものとされてきたが、現在は懐疑的になっている
###17.5 参考資料
参考資料について
###17.6 まとめ
チェックポイント
return文
・各ルーチンは必要なときだけreturn文を使用しているか
・return文によってコードが読みやすくなっているか
再帰
・再帰ルーチンに再帰を終了するコートが含まれているか
・安全カウンタを使って再帰ルーチンの終了を保障しているか
・再帰は1つのルーチンに限定されているか
・ルーチンの再帰の深さはプログラムのスタックサイズの制限内に収まっているか
・再帰はルーチンを実装するための最善の手段か。単純な反復よりも良いか
まとめ
・複数のreturn文はルーチンの可読性と保守性を向上させ、深くネストしたロジックを避ける
それでも慎重に使用すべき
・再帰は、問題の範囲が狭い場合の的確な解決策になる
が、これも慎重に使用すべき
・可読性と保守性の良いコードを書くために、goto文が最善の方法となる場合がある
が、そのケースはまれである
goto文はあくまでも最後の手段として使用する
##第18章テーブル駆動方式
テーブル駆動方式とは
論理文(ifやcase)を使って情報を検索する代わりに、テーブルの情報を参照する仕組み
ロジックの連鎖が複雑になるにしたがって、テーブル駆動の方が楽になる
###18.1 テーブル駆動方式の概論
if文の連続で文字列を処理するより、処理をする文字を格納した配列の参照テーブルを用いて
文字列を処理する方が簡単なコードになる
テーブル駆動方式の2つの注意点
・テーブルのエントリを参照する方法
「直接アクセス」「インデックスアクセス」「段階的アクセス」の3種類がある
・何を格納するかと言う点
###18.2 直接アクセステーブル
より複雑な論理制御構造に取って代わるもの
複雑な命令に従わなくても、テーブルから必要な情報を取り出せる
例:ArrayList
###18.3 インデックスアクセステーブル
インデックスキーを用いて検索、データを参照する
例:HashTable
18.4 段階型アクセステーブル
どの会談に達したかを判断することにより、各エントリを分類する
範囲検索の際に役に立つ方式
注意点
・終端に注意する
・逐次検索ではなく二分検索の仕様を検討する
・段階型アクセスの代わりにインデックスアクセスの仕様を検討する
###18.5 テーブル参照のその他の例
テーブル参照の例は色々ある
チェックリスト
・複雑なロジックの代替案としてテーブル駆動方式を検討したか
・複雑な継承構造の代替案としてテーブル駆動方式を検討したか
・テーブルのデータを外部に保存し、実行時にそれを読み取ることで、
コードを変更せずにデータを修正することを検討したか
・単純なインデックス配列を使ってテーブルに直接アクセスできない場合、
コードでインデックスの計算を複製するのではなく、
アクセスキーの計算をルーチンとして独立させているか。
###18.6 まとめ
・テーブルは、複雑なロジックや継承構造に変わる手段を提供する
ロジックや継承ツリーで複雑な場合は、参照テーブルを使って単純に出来るかどうか検討する
・テーブルを使用する上で重要になるのは、テーブルにアクセスする方法を決定すること
アクセス方法は「直接アクセス」「インデックスアクセス」「段階的アクセス」の3種類がある
・テーブルを使用する上でもう1つ重要なのは、テーブルに保存するものを正確に決定することである
##第19章制御構造の問題
全ての制御構造は論理式の評価に依存している
###19.1 論理式
論理式の真と偽には、「0、1」ではなく「true、false」を使用すること
コードの意図がより明確になる
論理評価でtrueとfalseを定義する際のヒント
・ブール値とtrue、falseは暗黙的に照合する
複雑な式は下記の手順で単純に出来る
・新しいブール変数を使って、複雑な評価をいくつかの評価に分割する
・複数な式はブール関数として独立させる
・ディシジョンテーブルを使って複雑な条件式を置き換える
肯定的な論理式のほうが理解しやすい
逆に否定文を繰り返すと理解するのに苦労する
・if文では、否定文を肯定文に置き換え、if句とelse句のコードを交換する
・ド・モルガンの定理を利用して、否定語のある論理評価を単純化する
かっこを使うとコードを読む人の負担が減る
評価の順番がわかりやすくなる
・単純な数え方を使ってかっこを対にする
・論理式には完全にかっこを付ける
言語によって評価の仕方が違う場合があるので、評価される方法を知っておく必要がある
数値を含んでいる式は、数直線の順番に並べる
○ if( (min < target) && (target < max) )
× if( (target < max) && (target > min) )
○ while ( i < ROOP_COUNT )
× while ( ROOP_COUNT > i )
プログラミング言語では「0」を複数の目的で使用するので、
0を使用する際には目的を強調するコードを書くべきである
0との比較のガイドライン
・論理変数は暗黙的に比較する
・数値は0と比較する
・Cでは、文字を終端のnull(\0)と明示的に比較する
・ポインタはNULLと比較する
特定のプログラミング言語では、論理式にさらに落とし穴がいくつかあるので注意が必要
・Cから派生した言語では、定数を比較の左側に置く
・C++では、&&、||、==のプリプロセッサマクロの作成を(あくまでも最後の手段として)検討する
・Javaでは、a==bとa.equal(b)の違いを知る
###19.2 複合文(ブロック)
複合文とは、「{}」で括ったステートメントのグループのこと
効果的に使用するガイドライン
・中かっこは対で書く
・中かっこを使って条件式を明確にする
###19.3 null文
null文とは、セミコロンだけで構成された文のことである(C++)
null文を処理するためのガイドライン
・null文を目立たせる
・null文の変わりにDoNothingプリプロセッサマクロやインライン関数を作成する
・ループ本体が空でない方がコードが明確になるかどうかを検討する
###19.4 危険なほど深いネストの回避
インデントが多い:ネストしている
3レベル以上のif文のネストを理解できる人はほとんど居ない
ネストを減らす方法
・条件文を再評価して、ネストしたif文を単純化する
・break文を使って、ネストしたif文を単純化する
・ネストしたif文をif-then-else文に書き換える
・ネストしたif文をcase文に書き換える
・深くネストしたコードをルーチンとして独立させる
・よりオブジェクト指向的なアプローチでのぞむ(ポリモーフィズムの使用等)
・深くネストしたコードの設計を見直す
###19.5 プログラミングの基礎:構造化プログラミング
###19.6 制御構造と複雑さ
###19.7 まとめ
第5部 コードの改良
##第20章 ソフトウェアの品質
###20.1 ソフトウェアの品質特性
ソフトウェアの外部特性
・正当性
・使用性
・効率性
・信頼性
・完全性
・適応性
・正確性
・剣老成
ソフトウェアの内部特性
・保守性
・柔軟性
・移植性
・再利用性
・可読性
・テスト性
・理解性
###20.2 ソフトウェアの品質改善技術
ソフトウェアの品質保証プログラム(作業)
・ソフトウェアの品質目標
・品質保証のための作業の明示
・テスト戦略
・ソフトウェアエンジニアリングのガイドライン
・非公式なテクニカルレビュー
・公式なテクニカルレビュー
品質保証のための開発プロセス
・変更管理手順
・結果の評価
・プロトタイピング
目標設定をプログラマに明示的に設定することで、品質保証は達成し易くなる
ただし、合理的であり、達成可能なものであり、コロコロ変わらないものである必要がある
###20.3 ソフトウェアの品質改善技術の相対的な効果
上記の品質保証のプラクティスを実行した際の効果
欠陥の検出率
どんな方法を使っても、75%を超えない
どんな方法を使っても、平均40%である
主な企業では、さまざまな方法を並行して使うことで、欠陥の排除率を上げている
欠陥検出のコスト
検出プラクティスにはコストの高いものや低いものがある
色々な方法はあるが、コードリーディングは割と効率よく欠陥を発見できる良い方法である
欠陥修正のコスト
欠陥がシステムに長く居れば長く居るほど、修正にコストが掛かる
欠陥の兆候が見つかれば、即修正すべきである(コストが低くなるため)
平均を上回る品質を達成するには、次の組み合わせが推奨される
・全ての要求、全てのアーキテクチャ、システムの重要な部分の設計に対する公式なインスペクション
・モデリングまたはプロトタイピング
・コードリーディングまたはインスペクション
・実行テスト
###20.4 品質保証はいつ行うか
エラーが紛れ込んだ時期が早ければ早いほど、システムの影に埋もれ易い
要求の誤りは設計の誤りになる
アーキテクチャの問題は、あらかじめ洗い出しておくのが望ましい
エラーはできるだけ早期に捕捉するのが経済的
早い段階から品質保証作業に重点をおき、プロジェクト全体で一貫して行うべきである
###20.5 ソフトウェア品質の原則
品質を改善すると、開発コストが低くなる
なぜなら、プロジェクトで最も時間が掛かる作業は、正しく動作しないコードの修正であるから
エラーを予防してデバッグを減らせば、生産性は向上する
###20.6 参考資料
###20.7 まとめ
・品質は最終的にタダになるが、安価な欠陥の予防で
高価な欠陥の修正を避けるために、リソースの再配分が必要である
・全ての品質目標を同時に達成するのは無理があるので、
メンバに目標をはっきり決めること
・どの欠陥検出テクニックも単独では効果を発揮しないので
数種類のテクニックを組み合わせて使用すること
・ソフトウェア分野の品質保証はプロセス指向である
反復的な工程がないので、ソフトウェア開発のプロセスが最終的な品質を決める
##第21章 コラボレーティブコンストラクション
コラボレーティブインストラクションとは
ペアプログラミング、公式なインスペクション、非公式なテクニカルレビュー
ドキュメントリーディングなどのコードの作成や他の作業に対する責任を
開発者が共有するテクニックのこと
当事者に見えない問題は多いので、作業を他の誰かに見てもらうことは有益である
###21.1 コラボレーティブ開発プラクティスの概要
当事者に見えない問題は多いので、作業を他の誰かに見てもらうことは有益である
また、レビューされるとわかっていると、レビューされる側は
自分のコードを入念にチェックすると言う二次的な効果もある
メンバ内の作業定義のチェックやフィードバックにも役に立つ
生み出される利益
・複数の人がコードを見るので、コードの品質が向上する
・誰かがプロジェクトから抜けても、精通しているプログラマが複数居るため影響が少ない
・複数のプログラマの中から手のあいている人に修正作業を依頼できるので
欠陥修正のサイクルが全体的に短くなる
###21.2 ペアプログラミング
XPでよく知られている方法
ガイドライン
・コーディング標準でペアプログラミングをサポートする
・ペアプログラミングをただの監視にしない
・ペアプログラミングを安易に使用しない
→作業の一部をペアプログラミングにした方が効率がいい場合もある
・ペアと作業の割り当てを定期的に交代する
・パートナーのペースを合わせる
・両方のパートナーが画面の内容を確認できるようにする
・ペアを組みたくないもの同士に無理にペアを組ませない
・初心者同士にペアを組ませない
・チームリーダーを割り当てる
利点
・一人で開発を行うよりストレスに強い
・コードの品質が向上する
・スケジュールが短縮される
・企業文化の改善、若手プログラマの育成、共同所有の促進など
コラボレーティブインストラクションの一般的な利点が全て生み出される
チェックリスト
・プログラミングに集中できるようなコーディング標準が設けられているか
・両方のパートナーが積極的に作業に参加しているか
・ペアプログラミングが効率的に効果を発揮するような割り当てを選択しているか
・作業を定期的に交代しているか
・作業ペースや性格の面でうまく折り合っているか
・調整役のチームリーダーは存在するか
###21.3 公式なインスペクション
インスペクションとは
特殊なレビューの1つ
欠陥検出に威力を発揮し、テストに比べてかなり経済的
インスペクションにおける役割
・モデレータ : インスペクションを進行させる責任を持つ
・作成者 : 設計やコードの作成者。インスペクションでは脇役に徹すること
・レビューア : 欠陥を発見する
・記録係 : インスペクションミーティングで発見されたエラーや作業項目の割り当てを記録する
・経営陣 : (通常は参加対象ではない)報告書の提出先など。インスペクションの結果を知る権利がある
基本手順
・計画
・概要説明
・準備
・ミーティング
・インスペクションレポート
・修正
・フォローアップ
・アフターミーティング
インスペクションの際に気をつけるのは、設計やコードの作成者を非難してはならない
非難する人が居るようだったら、不適切さをはっきりと指摘すべきである
欠陥を修正するのは作成者にゆだねられるので、作成者の権利は尊重しなければならない
###21.4 その他のコラボレーティブ開発プラクティス
その他のコラボレーション
・ウォークスルー
・コードリーディング
・ドッグアンドポニーショー
デモレビュー
###21.5 コラボレーティブコンストラクションテクニックの比較
方法はさまざまだが、それぞれ特性がある
ペアプログラミングはインスペクションとほぼ同じ価値がある
###21.6 参考資料
###21.7 まとめ
・コラボレーティブ開発プラクティスは、テストよりも高い確率で効果的に欠陥検出できる
・コラボレーティブ開発プラクティスは、テストとは異なる種類のエラーを検出する傾向がある
よって、レビューとテストの両方を使用する必要がある
・インスペクションにはチェックリスト、準備、明確な役割、継続的なプロセス改善が必須
・ペアプログラミングはインスペクションと同じ効果が生まれる
・公式なインスペクションは、コードだけでなく、要求、設計、テストケースなどの作業にも利用できる
・ウォークスルーとコードリーディングはインスペクションの代替手段である
コードリーディングは参加者の時間を効率的に利用する点で柔軟性に優れている
##第22章 デベロッパーテスト
「テスト」は品質向上のための最も一般的な作業である
テストの種類
・単体テスト
・コンポーネントテスト
・結合テスト
・回帰テスト
・システムテスト
ブラックボックステスト or ホワイトボックステスト
###22.1 ソフトウェア品質におけるデベロッパーテストの役割
・テストの目標はエラーを見つけること
(デバッグの目的は既に検出されているエラーを特定、修正すること)
・テストではエラーが無いことを決して証明できない
・テスト自体はソフトウェアの品質を改善しない
・テストはコードからエラーが発見されることを前提にしている
###22.2 デベロッパーテストへの推奨アプローチ
盛り込むべき基本事項
・関連する要求をそれぞれテストしてから、それらの要求が実装されていることを確認
・関連する設計項目をそれぞれテストしてから、それらの設計項目が実装されていることを確認
・「基礎テスト」を使って要求や設計をテストするテストケースに詳細なテストケースを追加
・プロジェクトで既に判明しているエラーと、過去のプロジェクトのエラーのチェックリストを使用する
不具合の検出までの時間が短くなるので
テストを先に用意しておく方が良い
また、デベロッパーテストには限界があるので他のプラクティスで補おう必要がある
(クリーンテストになりがち)
###22.3 テストの知恵袋
入力項目の全テストケースを調べるのは不可能である
また、準備できたとしても実行に時間が掛かるので効率が悪い
エラーになる可能性が高いものだけをテストしていく方が効率が良い
また、全てのステートメントを1回はテストで通しておくこと
→if文やswitch文を使うとテストケースが増える
悪いデータ
・データが小さすぎる(またはデータがない)
・データが大きすぎる
・データの種類が正しくない
・データのサイズが正しくない
・データが初期化されていない
良いデータ
・代表的なケース(期待される値の中間あたりの値)
・正常構成の最小値
・正常構成の最大値
・古いデータの互換性
###22.4 典型的なエラー
エラーは、エラーが起き易いクラスに起きる
→エラーが起きないクラスにはエラーは起きない
エラーはほぼクラス内のルーチンで起こっている
問題の多いルーチンを減らすことで、コストを軽減できる
・ほとんどのエラーの範囲はごく限られている
・多くのエラーはコンストラクション以外に原因がある
・コンストラクション時のほとんどのエラーはプログラマのミスである
・入力ミスが問題の原因であることが意外に多い
・設計への誤解はプログラマのミスの研究における永遠のテーマである
・ほとんどのエラーは簡単に修正できる
・組織ないのエラー経験がどれくらいか調べると良い
テストケース自体がエラー(不出来)で、エラーが検出できない場合もある
テストケースのエラーを減らす方法は下記の通り
・作業をチェックする
・ソフトウェアの開発と同様にテストケースを準備する
・テストケースを保管する
・単体テストをテストフレームワークに統合する
###22.5 テストサポートツール
ロジックに対しては、XUnitのようなテストフレームワークが存在する
データに対してはテストデータジェネレータを作成することで、見つけにくいエラーを発見することができる
→CodeCompleteの作者の例では自力でジェネレータを用意している
また、コードカバレッジを行わないと、テストから漏れるコードが存在してしまうので注意が必要
テストはレコード+ログを行うべき
ログが残っていれば、エラーを診断するのに役に立つだけではなくリリース後に効果的な情報を提供できる
システム自体の調査ツール(システム混乱ツール)
・メモリの設定
・メモリのシェイキング
・選択的なメモリ不足
・メモリアクセスチェック(境界検査)
エラーをエラーデータベースで管理することで、うまく管理ができる
###22.6 テストの改善
テストを行うための方法
・テスト計画
・再テスト(回帰テスト)
・自動テスト
プラスに働いたプロセスがあれば更に良くなるように変更を行う
###22.7 テスト記録の保管
プロジェクトを測定するために収集できるデータ
・欠陥の管理上の記載事項
・問題の詳細な説明
・問題を再現するための手順
・問題に対して推奨される回避作
・関連する欠陥
・問題の重大度
・欠陥の分類
・コーディングエラーの細分化
・修正によって変更されたクラスおよびルーチン
・欠陥の影響を受けたコード行数
・欠陥の検出に掛かった時間
・欠陥の修正に掛かった時間
それぞれの値を計算すると、プロジェクトが順調かどうかを判断できる
・各クラス数の欠陥の数
・各ルーチンの欠陥の数
・検出された欠陥1件あたりのテスト所要時間
・テストケース1件あたりの欠陥平均検出数
・修正された欠陥1件あたりのプログラミング平均所要時間
・テストケースがカバーするコードの割合
・各重大度の分類において突出している欠陥の数
###22.8 参考資料
###22.9 まとめ
・開発者によるテストは本格的なテスト戦略において重要な位置を占める
・コードよりもテストケースを先に書くことで時間や労力を変えず、欠陥検出/修正サイクルは短くなる
・テストはソフトウェア品質改善プログラムの一部に過ぎない
・エラーはエラーが発生し易い一部のクラスやルーチンに集中する傾向がある
・テストデータはテスト対象コードよりエラーが紛れ込み易い
テストはプログラミングと同じくらい慎重に作成すること
・自動テストは一般的に便利であり、回帰テストに欠かせない
##第23章 デバッグ
デバックとは
エラーの原因を突き止め、それを修正するプロセス
開発時間全体の50%を占める
プログラミングにおけるもっとも困難な作業
###23.1 デバッグの概要
バグ=エラー、欠陥
デバッグによって品質が改善されるとは限らない
防ぐための手段
・作業しているプログラムはどのようなものか学ぶ
・誤りの種類はどのようなものか学ぶ
・コードの読み手の立場に立って、コードの品質を調べる
・問題をどのように解決しているか調べる
・欠陥をどのように修正しているか調べる
効果の低いアプローチ(アンチパターン)
・欠陥を憶測で捜す
・問題を理解しようとしないで時間を掛けない
・真っ先に思いついた方法で修正する
###23.2 欠陥の検出
欠陥を検出し、理解することが作業の90%になる
検出と検出後の手順
1、エラーを安定させる(確実に再現させる)
2、エラーの原因を特定する
3、欠陥を修正する
4、修正をテストする
5、同様のエラーを探す
欠陥を検出するための方法
・手に入る全てのデータを使って仮説を立てる
・エラーを再現させるテストケースを改良する
・複数の単体テストでコードをテストする
・ツールを駆除する
・何種類かの方法でエラーを再現する
・データを更に収集して仮設を増やす
・否定的なテストの結果を利用する
・新しい仮説をひねり出す
・メモ帳を用意して、試してみることをリストアップする
・疑わしいコードの範囲を絞り込む
・以前に欠陥があったクラスやルーチンを疑う
・最近変更されたコードをチェックする
・調査範囲を広げる
・統合はインクリメンタルに行う
・一般的なエラーをチェックする
・誰かと問題について話す
・問題から離れてみる
・ブルートフォースデバッグ(ブルートフォース:総当り
・間に合わせのデバッグには制限時間を設ける
・ブルートフォース手法をリストアップする
構文エラーについて
・コンパイラのメッセージの行番号をあてにしない
・コンパイラのメッセージを当てにしない
・コンパイラの二番目のメッセージを当てにしない
・分割して攻略する
・コメント記号や引用符の閉じ忘れを探す
###23.3 欠陥の修正
ミスを減らすためのガイドライン
・修正する前に問題を理解する
・問題だけでなく、プログラムを理解する
・エラー診断を確認する
・リラックスする
・元のソースコードを保存する
・症状ではなく、問題を修正する
・正当な理由がある場合のみコードを変更する
・変更は一度に1つずつ
・修正を確認する
・エラーを明らかにする単体テストを追加する
・同様のエラーを探す
###23.4 デバッグの心理学的考察
人は以前見たものと現在の現象が似ていることを期待する
その心構えが修正に影響を与えることがある
###23.5 デバッグツール
・ソースコード比較ツール(diff)
・コンパイラの警告メッセージ
警告レベルを最高レベルにする
警告はエラーとみなす
プロジェクト全体でコンパイル時の設定を共有する
・構文とロジックの拡張チェック
・実行プロファイラ
パフォーマンスが良くない部分にバグがある可能性が高い
・テストフレームワーク
・デバッガ
###23.6 参考資料
###23.7 まとめ
・デバッグはソフトウェア開発の成否を握る部分
・欠陥の検出と修正を体系的に行うことが成功の鍵
・問題を修正する前に、根本的な問題を理解する
・コンパイラの警告に敏感になる
・デバッグツールはソフトウェア開発を強力にサポートするので、是非使うべき
##第24章 リファクタリング
ソースコードはリリースされた後だけでなく、
開発中もコードを保守(修正)される
CodeCompleteで基本的に書かれていることは
マーチン・ファウラーの「リファクタリング」に書かれていることと同じ
###24.1 ソフトウェアの進化の種類
ソフトウェアは変更を加えることで、進化(良い状態)したり退化(悪い状態)したりする
進化を分ける重要な違いは、変更によって改善されたか否かである
###24.2 リファクタリング概論
リファクタリング
目に見える振る舞いを変えずに、ソフトウェアの内部構造を
理解し易く安価に修正できるようにする変更
リファクタリングする兆候
・コードが重複している
・ルーチンが長すぎる
・ループが長すぎる、またはネストが深すぎる
・クラスのモジュール凝集度が小さい
・クラスのインターフェースが一貫性のある抽象化を実現していない
・引数リストの引数の数が多すぎる
・変更がクラス内部の他の部分に影響しない
・変更が発生したら、複数のクラスを同時に修正しなければならない
・継承の階層を並行して修正しなければならない
・case文を並行して修正しなければならない
・一緒に使用する関連データがクラスにまとめられていない
・ルーチンがそのクラスの機能よりも別のクラスの機能を多く使用する
・基本データ型をオーバーロードしている
・クラスがほとんど何もしない
・ルーチン間を「トランプデータ(tramp:放浪)」が流れる
・中間オブジェクトが何もしていない
・あるクラスが別のクラスと密結合している
・ルーチンの名前が不適切である
・データメンバがパブリックである
・サブクラスがスーパークラスのルーチンをほんの一部しか使用しない
・複雑なコードを説明するためにコメントが使用されている
・グローバル変数を使用している
・ルーチンの使用の際に初期化コードと後処理コードが使用されている
・プログラムに先のことを考えたコードが含まれる
###24.3 リファクタリングの詳細
データのリファクタリング
・マジックナンバーを名前付き定数に置き換える
・変数名を具体的なものに変更する
・式をインラインにする
・式をルーチンに置き換える
・中間変数を導入する
・多目的変数を複数の単一目的変数に変換する
・ローカルの目的には引数ではなくローカル変数を使用する
・データプリミティブをクラスに変換する
・一連の型コードを、クラスまたは列挙に変換する
・一連の型コードを、スーパークラスとサブクラスに変換する
・配列をオブジェクトに変更する
・コレクションをカプセル化する
・従来のレコードをデータクラスに置き換える
ステートメントレベルのリファクタリング
・論理式を分解する
・複雑な論理式をわかり易い名前の付いた論理関数にする
・条件文に分散している重複するコードを1つにまとめる
・ループ制御ではなく、breakやreturnを使用する
・ネストしたif-then-elseブロックで答えがわかったら、戻り値を代入せずに、すぐに制御を戻す
・条件文、特にcase文の繰り返しをポリモーフィズムで書き換える
・nullオブジェクトの使用
ルーチンレベルのリファクタリング
・ルーチンやメソッドを抽出する
・ルーチンのコードをインラインにする
・長いルーチンをクラスに変換する
・複雑なアルゴリズムを単純なアルゴリズムで代用する
・引数を追加する
・引数を削除する
・照会と変更を分離する
・同じようなルーチンは引数を介在してまとめる
→フレームワーク内のソースでは良く使われる方法だけど、あんまりよろしくない気がする
・入力引数によって振る舞いの異なるルーチンを分離する
・特定のフィールドではなくオブジェクト全体を渡す
・オブジェクト全体ではなく、特定のフィールドを渡す
・ダウンキャストをカプセル化する
クラス実装のリファクタリング
・値オブジェクトを参照オブジェクトに変更する
・参照オブジェクトを値オブジェクトに変更する
・仮想ルーチンをデータの初期化に置き換える
・メンバルーチンまたはメンバデータの配置を変更する
・特殊なコードをサブクラスとして抽出する
・同じようなコードをスーパークラスにまとめる
クラスインターフェースのリファクタリング
・ルーチンを別のクラスに移動する
・1つのクラスを2つにわける
・クラスを削除する
・委譲を隠蔽する
・中間オブジェクトを削除する
・継承を委譲に置き換える
・委譲を継承に置き換える
・外部ルーチンを導入する
・拡張クラスを導入する
・後悔されているメンバ変数をカプセル化する
・変更できないフィールドのset()ルーチンを削除する
・クラスの外側で使用されないルーチンを隠蔽する
・使用されないルーチンをカプセル化する
・スーパークラスとサブクラスの実装が非常に良く似ている場合は、1つにまとめる
システムレベルのリファクタリング
・制御できないデータについては、もっとも信頼のおけるデータソースを作成する
・一方向のクラス結合を双方向のクラス結合に変更する
・双方向のクラス結合を一方向のクラス結合に変更する
・単純なコンストラクタではなくファクトリメソッドを提供する
・エラーコードを例外に置き換える、または例外をエラーコードに置き換える
###24.4 安全なリファクタリング
リファクタリングは一歩間違えれば非常に危険である
危険を防止するガイドライン
・最初のコードを保存する
・リファクタリングの規模を小さく保つ
・リファクタリングは一度に1つずつ行う
・手順をリストアップする
・「駐車スペース」を設ける
・チェックポイントをこまめに作成する
・コンパイラの警告を利用する
・再テストする
・テストケースを追加する
・変更をレビューする
・リファクタリングの危険度に応じて手法を調節する
###24.5 リファクタリング戦略
リファクタリングをする時のガイドライン
・ルーチンを追加するときにリファクタリングする
・クラスを追加するときにリファクタリングする
・エラーを修正するときにリファクタリングする
・エラーが発生し易いモジュールに的を絞る
・複雑なモジュールに的を絞る
・保守環境で触った部分を改良する
・理想的なコードと乱雑なコードの間にインターフェースを定義して、コードにインターフェースを尾横断させる
###24.6 参考資料
###24.7 まとめ
・プログラムの変更は、最初の下位は辻と最初のリリース後の両方で避けられないものである
・ソフトウェアは変更によって改良することもあるし、後退することもある
・リファクタリングを成功させる1つの鍵は、警告のサインやにおいに注意すること
・リファクタリングを成功させるもう1つの鍵は、リファクタリングの手法を多く知ること
・リファクタリングを成功させる最後の鍵は、安全にリファクタリングを行う戦略を練ること
・開発時のリファクタリングは、プログラムを改善し、変更を行う絶好のチャンスである
##第25章 コードチューニング戦略
パフォーマンスチューニングはコードの可読性や保守性を損なう場合が多く
それほど重視されていなかったが、電話やPDAなどのマイクロコンピュータの類の
ソフトウェアのメモリ限界やインタープリタコードの実行時間によって、再度重要なテーマになっている
###25.1 パフォーマンスの概要
チューニングは可読性や保守性を損なう場合もあるが、コードへの影響が少ない方法もある
チューニングを行う際には、速度とサイズのどちらを改善するか決める必要がある
###25.2 コードチューニング入門
効率的なコードが必ずしも「良い」コードとは限らない
過去の定説(アンチパターンや間違いの知識)
・高級言語のコードの行数を減らすと、コンパイル後のマシンコードの速度やサイズが改善される
・ある種の演算は、おそらく他の演算よりも早い、または小さい
・コードを書くそばから最適化を行う
・速いプログラムであることは正確なプログラムであることと同じくらい重要
チューニングは完成後に行えば良い
ただし、コードを変更し易い構成にしておくこと
また、一般的な書き方をしておくことで、コンパイラが最適化を行ってくれる
一般的ではない書き方を行うと、コンパイラが最適化を行ってくれない
###25.3 脂肪や糖蜜の類
非効率の原因の一般例
・入出力処理
・ページング
・システムコール
・インタープリタ言語
・エラー
###25.4 測定
変更前と変更後の測定は重要
また、正確に測定を行うこと
###25.5 繰り返し
チューニングは1回だけではなく、繰り返し行うことで効果的になる
ただし、保守性が低くなる可能性は非常に高い
###25.6 コードチューニング手法のまとめ
手順の例
1、正しく設計された(理解し易く変更し易い)コードを使って、ソフトウェアを開発する
2、パフォーマンスが悪い場合は、下記の方法を使う
・正しく動作するコードを保存しておく
・システムを測定してホットスポットを突き止める
・設計やコードやアルゴリズムがパフォーマンスの悪い原因か調べる
また、コードチューニングが妥当か判断する
・ボトルネックをチューニングする
・チューニングの改善を一度に1つずつ測定する
・改善が無ければコードを元に戻す
3、手順2を繰り返す
###25.7 参考資料
###25.8 まとめ
・パフォーマンスはソフトウェアの全体的な品質の1つの側面に過ぎない
また、通常は最も重要なものではない
・具体的な寮を測定すること
・ほとんどのプログラムでは、ほんの一部のコードが実行時間のほとんどを占める
・チューニングは何度か繰り返すことが必要である
・チューニングをするならば、理解し易く変更し易いはっきりとしたコードを書いておく必要がある
##第26章 コードチューニングテクニック
チューニングはある意味「アンチリファクタリング」の場合もある
内部構造を改悪する場合もあるので、気をつけなければならない
また、CodeCompleteの例は経験則も含まれるので、実用には注意が必要
###26.1 ロジック
・答えがわかったところで評価をやめる
・頻度順の評価
switchやcaseは、頻度が高い順に並べる
ifがメインパターンで、elseは確率が低い場合を書く
・論理構造の比較
caseよりifの方がパフォーマンスが良い
・複雑な式をテーブル参照で置き換える
・遅延評価の使用
###26.2 ループ
プログラムのホットスポットはよくループで見つかる
・ループ外分岐
必要な評価を外で行い、ループ内では単純な処理しか行わない
・ジャミング
別名「フュージョン」。同じ制御のループがある場合、1つにまとめる
・展開
内部での処理を分解する
・ループ内処理の最小化
ループ内のステートメントを減らす
・sentinel値
sentinel=衛兵
・最もビジーなループを内側に
・強度の削減
###26.3 データ変換
・浮動小数点数から整数への変更
・配列の次元数の削減
・配列参照の削減
・補助的なインデックスの使用
文字の長さのインデックス
独立した並列インデックス構造
・キャッシュの使用
###26.4 式
・代数的な恒等式の活用
・強度の削減の使用
除算を加算に置き換える
指数関数を除算に置き換える
三角法を使用するルーチンを三角法と等価な式に置き換える
longlong型をlong型またはint型に置き換える
浮動小数点数を固定小数点数または整数に置き換える
倍精度浮動小数点数を単精度浮動小数点数に置き換える
整数の2による乗除算をシフト演算に置き換える
・コンパイル時の初期化
・システムルーチンへの警戒
・正しい方の定数の使用
・共通の部分式の削除
###26.5 ルーチン
ルーチンを適切に分解すると効果が高い
・ルーチンのインライン化
###26.6 低水準言語への書き換え
より根っこに近い低水準言語の方が早いことが多い
###26.7 物事は変わったように見えても、実は変わっていない
CPUの速度、メモリなど大分変わったけども、パフォーマンスの問題は依然として変わってない
むしろ最適化の効果に関しては、予測が難しくなっている
測定可能な改善にこだわることが、早まった最適化を思いとどまり、
理解し易いコードへのこだわりを持ち続ける良い方法である
###26.8 参考資料
###26.9 まとめ
・最適化の結果は、言語、コンパイラ、環境の違いに大きく左右される
個々の最適化を測定しなければ、それがプログラムを改善するのか改悪するのかはわからない
・1つ目の最適化は最善のものでない場合が多い
他の方法も探し続けること
・コードチューニングは原子力のようなもの(利益があるが有害)
くれぐれも注意するように
##第6部 システムの考察
##第27章 プログラムサイズが及ぼす影響
###27.1 コミュニケーションとチームの人数
###27.2 プロジェクトの規模の範囲
###27.3 プロジェクトの規模がエラーに及ぼす影響
###27.4 プロジェクトの規模が生産性に及ぼす影響
###27.5 プロジェクトの規模が開発作業に及ぼす影響
###27.6 参考資料
###27.7 まとめ
##第28章 コンストラクションの管理
###28.1 良いコーディングの奨励
###28.2 構成管理
###28.3 コンストラクションスケジュールの見積もり
###28.4 測定
###28.5 プログラマは人間である
###28.6 管理者の管理
###28.7 参考資料
28.8 まとめ
##第29章 統合
###29.1 統合手法の重要性
###29.2 統合の頻度― フェーズ型とインクリメンタル型
###29.3 インクリメンタル統合戦略
###29.4 デイリービルドとスモークテスト
###29.5 参考資料
###29.6 まとめ
##第30章 プログラミングツール
###30.1 設計ツール
###30.2 ソースコードのツール
###30.3 実行可能コードのツール
###30.4 ツール指向の環境
###30.5 独自のプログラミングツールの作成
###30.6 ツールの理想郷
###30.7 参考資料
###30.8 まとめ
##第7部 ソフトウェア職人気質とは
##第31章 レイアウトとスタイル
###31.1 レイアウトの基本
###31.2 レイアウトテクニック
###31.3 レイアウトスタイル
###31.4 制御構造のレイアウト
###31.5 ステートメントのレイアウト
###31.6 コメントのレイアウト
###31.7 ルーチンのレイアウト
###31.8 クラスのレイアウト
###31.9 参考資料
###31.10 まとめ
##第32章 読めばわかるコード
###32.1 外部ドキュメント
###32.2 ドキュメントとしてのプログラミングスタイル
###32.3 コメントを入れるか入れないか
###32.4 効果的なコメントのポイント
###32.5 コメントテクニック
###32.6 IEEE規格
###32.7 参考資料
###32.8 まとめ
##第33章 個人の資質
###33.1 個人の資質は話題からずれているか
###33.2 知性と謙虚さ
###33.3 好奇心
###33.4 知的な誠実さ
###33.5 コミュニケーションとコラボレーション
###33.6 創造性と規律
###33.7 怠惰
###33.8 気にするほどのことはない資質
###33.9 習慣
###33.10 参考資料
###33.11 まとめ
##第34章 ソフトウェア職人気質とは
###34.1 複雑さの克服
###34.2 プロセスの選択
###34.3 人間が1番、コンピュータは2番
###34.4 言語の中へのプログラミング
###34.5 集中力を助ける規約
###34.6 問題領域に立ったプログラム
###34.7 落石注意
###34.8 反復、その繰り返し
###34.9 汝、ソフトウェアと信仰を結び付けることなかれ
###34.10 まとめ
##第35章 さらに情報を得るには
###35.1 ソフトウェアコンストラクションに関する情報
###35.2 コンストラクション以外のトピック
###35.3 定期刊行物
###35.4 ソフトウェア開発者の読書計画
###35.5 専門団体への参加