- 今後コードレビュー時に「これってリーダブルコードの◯◯だよね?、こう直したほうがいいよね」と引用できレビューの時間の短縮化が実現できる。
- リーダブルコードに登場する特殊ワードをチームのコーディングの共通語とする。デザインパターンのカタログ的な考え。
- リーダブルコードを読んでない人も内容を短時間で把握できる。
- チーム全体のコードを読みやすくメンテナンスしやすくし品質をあげる。
- リーダブルコード教になりチーム以外にも広める。コードの品質が悪い人にはリーダブルコードの読書を進めてみる。ひどいコードのままコーディングしてもらうよりその時間を読書の時間に割り当てることも考える(結果的に時間が還元できるなら)。
- リーダブルコードに書いてあることが全てのプロジェクトに適用てきるわけではないけれでもコーディングルールを考える際の指針にすることはできる。
- コーディングルールをまったく知らない人 → リーダブルコードを読んで一定の基準のコーディングルールを学ぶ。
- コーディングルールをある程度知ってる人 → リーダブルコードを読んで曖昧なオレオレルールの勘違いを正す。
- コーディングルールをかなり知っている人 → リーダブルコードを読んで複数のコーディングルールからプロジェクトに沿った正しいものを選択できるようになる。リーダブルコードを用いてコーディング初心者を指導する。
- サンプルコードはC++、Python、JavaScript、Javaで記述されている (JavaScriptが比較的多い、C++の基本文法は知っておいたほうが理解しやすい)。
- 特定の言語に関するテクニックなどはほとんどなくコンピュータ言語に共通の話題がほとんどである。
- **「こうしなさい。」ではなく「こうしたほうがよい。」**とやさしく丁寧に書かれてある。(原著もそうらしい)
- フレームワーク、デザインパターンに関するテクニック的な記載もない。本当に汎用的なコーディングの入門書になっている。
- 長年のコーダーであればわかりきってる事も多いがここまでわかりやすく簡潔にまとめてある本は稀である。
- チームで読書会を行いチームで採用する考えをまとめテンプレート化する。
- 既存のチームのコードからリーダブルコードのアンチパターンに該当するコードを見つけ業務固有の変数名を変更し汎用的なアンチパターンとしてインターネットに公開する。
#本書の目的
- 君のコードをよくすること
- コードは理解しやすくなければならない
#1章 理解しやすいコード
- コードは理解しやすくなければならない。
- コードは他の人が最短期間で理解できるように書かなければならない。
- コードは短くしたほうがいい。だけど「理解するまでにかかる時間」を短くするほうが大切
#第一部 表面上の改善
- 名前に情報を詰め込む
- 類語辞典でもっとカラフルな名前を探す
単語 | 代替案 |
---|---|
send | deliver,dispatch,announce,distribute,route |
find | search,etract,locate,recover |
start | launch,create,begin,open |
make | create,set up,build,generate,compose,add,new |
- tmpやretvalなどの汎用的すぎる名前は避ける
- i j k などはループの変数として理解できるため使ってもよいこととする
- スコープが小さければ短い名前でもいい
- 長い名前を入力するのは問題じゃない(今はIDEやエディタが補完するため)
- stringをstr、documentをdocと省略するのは問題ないが、チーム独自の省略規則はやめよう
- 名前が「他の意味と間違えられることはないだろうか?」と何度も自問自答する
- 限界値を示すときは min_ max_を使う
- 範囲を示すときは first_ last_を使う
- 包括的範囲には begin_ end_ を使う
- getXXXXは変数へのアクセッサの意味として認知されているのでそのような処理以外でこのメソッド名を使用しないこと
- インデントがととのって適切に改行された美しいコードを目指す。プログラミングの時間のほとんどはコードを読む時間なのでコードは読みやすく美しくする
command[] = {
{"-t", "-bcc", "-x" },
{"-g", "-gt", "-j" },
}
- 間違ったスタイルでコーディングしている既存のプロジェクトについては既存のスタイルを踏襲する。一貫性のほうが大事
- 空行をつかって適切なブロックに文を直す
- コメントを読むとその分だけコードを読む時間がなくなる。コメントは画面を占領してします。言い換えれあば、コメントにはそれだけの価値を持たせるべき。
- 監督コメンタリーを入れる(「ここのコードは◯◯という背景でこうなったのだ」という説明を入れる。なんらかのロジックと比較した結果こちらのほうがよいと判断してこうなったと書くのもいい.要は他の人が見た時に「別の方法があるのになんでこうしたんだろうか?」という疑問をもたせないようにする)
- コードに欠陥あることを認める(パフォーマンスやアルゴリズムの選定、ロジックがスマートでない)があることを認めており、いつか直さないといけないことをコメントしておく
- コードからすぐに分かることをコメントに書かない
- 関数名が変だとか変数名が変だとか修正可能なものを補うコメント
THREAD_NUM = 8
- なぜ8なのかはこのコードからはわからない。デフォルト値である理由をかくとよい
- 関数やクラスを文章化するときには、「このコードを見てビックリすることは何だろう?どんなふうに間違えて使う可能性があるだろう?」と自分に問いかける。
- 高レベルなコメントを書く(クラス自体に対するコメント、クラスファイル間で該当のクラスはどのような役割を持っているかを説明する)
- 頭のなかにあるコメントをとにかく抜き出す
- コメントを読んで改善できるポイントを見るける
- コメントを改善する
- コメントは領域に対する情報の比率が高くなければならない
- 情報密度の高い言葉を使う(専門用語、コンピュータ用語)
#第二部 ループとロジックの単純化
左側 | 右側 |
---|---|
「調査対象」の式、変化する。 | 「比較対象」の式、変化しない。 |
読みやすい
if (length > 10 )
読みにくい
if (10 < length)
if (my_money = NULL) {}
という書き間違いを防ぐために
if (NULL = my_money ) {}
とする。
- C/C++ではif内での代入が可能だがJavaではそもそも不可能
- 行が短くなる
- 読みにくい
- デバッガでステップ実行できなくなる
- 行数を短くするよりも、他の人が理解するのにかかる時間を短くする。
- 基本的にはif~elseが望ましい、三項演算子で簡潔になるときには使用してよい。
- 条件式が下にあり普通の条件式とは異なりよみにくい、whileループで実現すべき
do {
} while ()
do~whileはJava/PHPなど主要な言語には実装されている。
- 関数で複数のreturnを使ってはいけないと思い込んでるのは間違い
早めにreturnすることで「ガード節」をつくる。
public boolean checkBadWord(String str) {
if (str == null) {return false};
//以降なんらかの処理
}
- if を入れ子にしない→理由:コードを読む人は条件を精神的にスタックにPUSHしないといけない
//TODO Q問題. 上記を踏まえて以下のコードを改善する
- スレッド、シグナル、割り込みハンドラ、例外、関数ポインタ、無名関数、仮想メソッドはコードを追うのが難しくなるためこのようなコードが全体の割合を占めないように注意する必要がある。
if line.split(':')[0].strip() == "root" { }
username = line.split(':')[0].strip();
if username == "root" {}
if (request.user.id == document.owner_id) {
// ユーザーはこの文章を編集できる
}
if (request.user.id != document.owner_id) {
// ユーザーはこの文章を編集できない
}
final boolean user_owns_document = (request.user.id != document.owner_id)
if(!user_owns_document) {
// ユーザーはこの文章を編集できない
}
if(user_owns_document) {
// ユーザーはこの文章を編集できる
}
assert((!(bucket = FindBucket(key))) || !bucket->IsOccupied());
bucket = FindBucket(key);
if(bucket != NULL) assert(!bucket->IsOccpuied());
引数の結果を返すOR演算子 (Python JavaScript Ruby)
x = a || b || c
上記の場合、先に真と判定された値が返却される。
x = false || "aaa" || "bbb"
puts x
>>> aaa
[P113]
var remove_one = function (array, value_to_remove) {
var index_to_remove = null;
for ( var i=0; i < array.length; i += 1) {
if(array[i] === value_to_remove) {
index_to_remove = i;
break;
}
}
if (index_to_remove !== null) {
array.splice(index_to_remove, 1);
}
};
index_to_removeは中間結果を保持する変数であり、これを使わなくても同じ事が実現できる
[P113]
var remove_one = function (array, value_to_remove) {
for (var i =0; i < array.length; i+= 1) {
if(array[i] === value_to_remove) {
array.splice(i, 1);
return;
}
}
}
boolean done = false;
while (/* condition */ && !done) {
// 何らかの処理
if(...) {
done = true;
continue;
}
}
これは**「ループの途中から抜けだしてはいけない」という暗黙的なルールを守ろうとしている。 doneのような変数を「制御フロー変数」**と本書では読んでいる。
制御フロー変数はうまくプログラミングすれば削除できる。
P114
while(/* condition */) {
// 何らかの処理
if (...) {
break;
}
}
- 「グローバル変数を避ける」というのは当たり前のルールとして認知されている
- これを発展させた考えとして 「変数のことが見えすコード行数をできるだけ減らす。」 = スコープを縮める →理由:変数を追うために読むコードが大量になる
class LargeClass {
string str_;
void Method1() {
str_ = ...;
Method2();
}
void Method2() {
// str_変数を使うコード
}
メンバ変数→ローカル変数に変更する
class LargeClass {
void Method1() {
string str_ = ...;
Method2(str_);
}
void Method2(String str_) {
// str_変数を使うコード
}
- クラスのメンバ変数へのアクセスを減らす方法としてメソッドをできるだけstaticにするという手法もある
JSでグローバル変数を使ってしまってるコード
submitted = false;
var submit_form = function(form_name) {
if (submitted) {
return;//二重投稿禁止
}
submitted = true;
}
クロージャーを使用してプライベート変数に変更
var submit_form = (function () {
var submitted = false;
return function (form_name) {
if (submitted) {
return;
}
submitted = true;
};
}());
<script>
var f = function () {
// 変数iにはvarがない!!!!
for(i=0; i < 10; += 1);
};
f();
</script>
この場合変数iは他のJSファイルからアクセスできてしまう。JavaScriptのベストプラクティスは**「変数を定義するときには常にvarキーワードをつける。」**だ。
- C++やJavaではif for tryなどのブロックスコープがありブロックスコープで定義された変数はそのブロックでしかアクセスできない。
- PythonやJavaScriptではブロック外から変数へアクセスできてしまう。
// Java
if (true) {
int x = 1;
}
x++; // コンパイラー
// JavaScript
if (true) {
x = 1;
}
x++; // コンパイラーにならない
console.log(x);
結果: 2
// JavaScript
if (true) {
var x = 1;
}
x++; // コンパイラーにならない
console.log(x);
結果: 2
- もともとC言語では変数を関数やブロックの先頭で定義する必要があった(C99とC++にこの制限はない)
- 変数を定義する箇所が早すぎるとその事を覚えておきながらコードを書いたり読んだりしないといけなるなるので基本的には変数は使う直前に定義したほうがよい。
(要約者コメント:C言語がこうだったため「変数は先頭に定義すべき」という強迫観念を持っている人は結構いる気がする)
- 変数の生存期間(スコープ)が長いとコードが追いかけにくいという理由と同様に「変数がなんども値が変更されるとコードを追いかけにくい」という特性もある。
- 変更の必要のない変数は積極的にconst(C++)、final(Java)を使用する
- 同様の理由でString型(Java/Pyhton)はイミュータブル(不変)となっている
(要約者コメント:Scalaは変数をイミュータブル(val)であることを推奨している。関数型言語が「副作用を最小限に抑える」という考えのため)
■アンチパターン
var setFirstEmptyInput = function (new_value) {
var found = false;
var i = 1;
var elem = document.getElementById('input' + i);
while (elem !== null) {
if (elem.value === '') {
found = true;
break;
}
i++;
elem = document.getElementById('input' + i);
}
if (found) elem.value = new_value;
return elem;
};
■修正例
var setFirstEmptyInput = function (new_value) {
for (var i = 1; true; i++ ) {
var elem = document.getElementById(' input' + i);
if (elem === null)
return null; // Search Failed. No empty input found.
if (elem.value === '') {
elem.value = new_value;
return elem;
}
}
};
目的:プロジェクト固有のコードから汎用コードを分離する メリット:汎用問題から切り離された境界線上の明確な業務固有の問題に集中できる
- 汎用コードは素晴らしい、プロジェクトから完全に切り離されているからだ。このようなコードは開発もテストも楽だ。すべてのコードがこうなればいいのに!
- このライブラリや言語にXYZの関数がればなぁと思ったらその関数を自分でかけばよい、それが汎用コードになる。
巨大な関数の中から、汎用的な処理を見つけ出しメソッド化していく
- プログラムの各種言語には汎用的なタスクを処理するライブラリが実装されている、でもたまに欲しい機能がない時がある。
- その場合はその関数を作り汎用コード化すればよい
ファイルを全部読み込むコード
// PHP
file_get_contents("filename")
// Python
open("filename").read()
C++にはそれがないので自分で作る必要があるが、それを作って汎用化する
- タスクをできるだけ異なる関数に分割する。
- コードを書く前にロジックを説明する文章を考えてコードを書く
- ラバーダッキング・・(アヒルのゴム製のおもちゃ)や熊のぬいぐるみに対して、声を出してコードの説明をする。説明することで書くコードが明確化される。
- 汎用的なユーティリティを作って使いまわす、標準ライブラリのAPIや汎用ライブラリを広く知って使う。
- たまには標準ライブラリのすべての関数・モジュール型の名前を15分かけてよんでみよう。
- 平均的なソフトウェアエンジニアが書く出荷用のコードは10行。この10行というのはよくテストされ文章化されてる製品用ということ、できるだけこの出荷用のコードを再利用したほうがよい。
- ヨーダ「冒険、興奮、ジェダイはそんなものを求めてはおらん。」
テストコードも読みやすさが大切だ。
テストコードが大きく恐ろしいものだと以下のようなことがおきる
- テストを直すのを恐れて本物のコードを更新するのを恐れる
- 新しいコードを書いたときにテストを書かなくなる
■要約者コメント 「たしかに、あるあるでテストがあまりに大きくわかりずらいものであった場合、その本物のコードを修正するのをやめて別のクラスを作り、そのクラスのテストコードを作成し、本物のコードはそのクラスを呼ぶ箇所だけ追加する。。。とかはやったことがある。」
あとでテストを書くつもりでコードを書くと、おもしろいこと起きる。**テストしやすいようにコードを設計するようになるのだ!**このようにコードを書いていけば、いいコードがかけるようになる!
TDD(テスト駆動開発)は本物のコードを書く前にテストコードを書こうという手法。 TDDを導入するかはともかく、テストを意識しながらコードをかけばいいコードになる。
- テストのために本物のコードの読みやすさを犠牲にしたり、テスト都合で本物のコードに手を加えない
- カバレッジ100%にしないと気がすまないのはよくない。現実的にはカバレッジが100%になることはない。
「分/時間カウンタ」を設計・実装する ことで見えてくる問題を紹介している、演習的な内容
外部の視点を得るというのは、コードが「ユーザーフレンドリー」かどうかを確認する優れた手段である。 素直に第一印象を聞いてみよう。他の人も同じことを思っているかもしれない。「他の人」には六ヶ月後の自分も含まれている。
登場した章 | 単語,用語 |
---|---|
2章 | カラフルな名前 |
5章 | 高レベルのコメント |
5章 | ライダーズブロック |
7章 | ヨーダ記法 |
7章 | ガード節 |
8章 | 説明変数 |
8章 | 要約変数 |
8章 | (ブール演算子の)短絡評価 |
9章 | 制御フロー変数 |
9章 | ブロックスコープ |
10章 | メソッドの抽出 |
12章 | ラバーダッキング |
14章 | テストに優しい開発 |
15章 | 外部の視点を得る |
要約者がお勧めなものを抽出
- Code Complete
- Effective Java 第2版
- オブジェクト指向における再利用のためのデザインパターン
- ★★★ Joel on Software 「あなたが絶対にすべてきでないこと PART1」「ジョエル・テスト:いいプログラムへの12のステップ」
ジョエル・テスト
ソース管理システムを使っているか?
1オペレーションでビルドを行えるか?
毎日ビルドを行うか?
障害票データベースを持っているか?
新しいコードを書くまえにバグを修正するか?
更新可能なスケジュール表を持っているか?
仕様書を持っているか?
プログラマは静かな労働環境にあるか?
買える範囲で一番良い開発ツールを使っているか?
テスト担当者はいるか?
プログラマを採用するときにコードを書かせるか?
「廊下での使い勝手テスト」を行っているか?
http://japanese.joelonsoftware.com/Articles/TheJoelTest.html
興味深く拝見させていただきました。
誤字らしきものを見つけたので指摘いたします。
2章の find の代替案:etract -> extract ではないでしょうか?