リーダブルコード要約とリーダブルコード要約の活用方法
- 今後コードレビュー時に「これってリーダブルコードの◯◯だよね?、こう直したほうがいいよね」と引用できレビューの時間の短縮化が実現できる。
- リーダブルコードに登場する特殊ワードをチームのコーディングの共通語とする。デザインパターンのカタログ的な考え。
- リーダブルコードを読んでない人も内容を短時間で把握できる。
- チーム全体のコードを読みやすくメンテナンスしやすくし品質をあげる。
- リーダブルコード教になりチーム以外にも広める。コードの品質が悪い人にはリーダブルコードの読書を進めてみる。ひどいコードのままコーディングしてもらうよりその時間を読書の時間に割り当てることも考える(結果的に時間が還元できるなら)。
- リーダブルコードに書いてあることが全てのプロジェクトに適用てきるわけではないけれでもコーディングルールを考える際の指針にすることはできる。
対象者
- コーディングルールをまったく知らない人 → リーダブルコードを読んで一定の基準のコーディングルールを学ぶ。
- コーディングルールをある程度知ってる人 → リーダブルコードを読んで曖昧なオレオレルールの勘違いを正す。
- コーディングルールをかなり知っている人 → リーダブルコードを読んで複数のコーディングルールからプロジェクトに沿った正しいものを選択できるようになる。リーダブルコードを用いてコーディング初心者を指導する。
リーダブルコードの特徴
- サンプルコードはC++、Python、JavaScript、Javaで記述されている (JavaScriptが比較的多い、C++の基本文法は知っておいたほうが理解しやすい)。
- 特定の言語に関するテクニックなどはほとんどなくコンピュータ言語に共通の話題がほとんどである。
- **「こうしなさい。」ではなく「こうしたほうがよい。」**とやさしく丁寧に書かれてある。(原著もそうらしい)
- フレームワーク、デザインパターンに関するテクニック的な記載もない。本当に汎用的なコーディングの入門書になっている。
- 長年のコーダーであればわかりきってる事も多いがここまでわかりやすく簡潔にまとめてある本は稀である。
さらなるリーダブルコード活用方法
- チームで読書会を行いチームで採用する考えをまとめテンプレート化する。
- 既存のチームのコードからリーダブルコードのアンチパターンに該当するコードを見つけ業務固有の変数名を変更し汎用的なアンチパターンとしてインターネットに公開する。
#本書の目的
- 君のコードをよくすること
- コードは理解しやすくなければならない
#1章 理解しやすいコード
- コードは理解しやすくなければならない。
- コードは他の人が最短期間で理解できるように書かなければならない。
- コードは短くしたほうがいい。だけど「理解するまでにかかる時間」を短くするほうが大切
#第一部 表面上の改善
2章 名前に情報を詰め込む
- 名前に情報を詰め込む
- 類語辞典でもっとカラフルな名前を探す
単語 | 代替案 |
---|---|
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と省略するのは問題ないが、チーム独自の省略規則はやめよう
3章 誤解されない名前
- 名前が「他の意味と間違えられることはないだろうか?」と何度も自問自答する
- 限界値を示すときは min_ max_を使う
- 範囲を示すときは first_ last_を使う
- 包括的範囲には begin_ end_ を使う
- getXXXXは変数へのアクセッサの意味として認知されているのでそのような処理以外でこのメソッド名を使用しないこと
4章 美しさ
- インデントがととのって適切に改行された美しいコードを目指す。プログラミングの時間のほとんどはコードを読む時間なのでコードは読みやすく美しくする
command[] = {
{"-t", "-bcc", "-x" },
{"-g", "-gt", "-j" },
}
- 間違ったスタイルでコーディングしている既存のプロジェクトについては既存のスタイルを踏襲する。一貫性のほうが大事
- 空行をつかって適切なブロックに文を直す
5章 コメントすべきことを知る
- コメントを読むとその分だけコードを読む時間がなくなる。コメントは画面を占領してしまいます。言い換えれば、コメントにはそれだけの価値を持たせるべき。
価値のあるコメント
- 監督コメンタリーを入れる(「ここのコードは◯◯という背景でこうなったのだ」という説明を入れる。なんらかのロジックと比較した結果こちらのほうがよいと判断してこうなったと書くのもいい.要は他の人が見た時に「別の方法があるのになんでこうしたんだろうか?」という疑問をもたせないようにする)
- コードに欠陥あることを認める(パフォーマンスやアルゴリズムの選定、ロジックがスマートでない)があることを認めており、いつか直さないといけないことをコメントしておく
価値の無いコメント
- コードからすぐに分かることをコメントに書かない
- 関数名が変だとか変数名が変だとか修正可能なものを補うコメント
定数にはコメントをつける
THREAD_NUM = 8
- なぜ8なのかはこのコードからはわからない。デフォルト値である理由をかくとよい
読み手の立場になって考える
- 関数やクラスを文章化するときには、「このコードを見てビックリすることは何だろう?どんなふうに間違えて使う可能性があるだろう?」と自分に問いかける。
- 高レベルなコメントを書く(クラス自体に対するコメント、クラスファイル間で該当のクラスはどのような役割を持っているかを説明する)
コメントを書く作業は以下の3つに分類できる
- 頭のなかにあるコメントをとにかく抜き出す
- コメントを読んで改善できるポイントを見つける
- 改善する
6章 コメントは正確で簡潔に
- コメントは領域に対する情報の比率が高くなければならない
- 情報密度の高い言葉を使う(専門用語、コンピュータ用語)
#第二部 ループとロジックの単純化
7章 制御フローを読みやすくする
ifの並び順の指針
左側 | 右側 |
---|---|
「調査対象」の式、変化する。 | 「比較対象」の式、変化しない。 |
読みやすい
if (length > 10 )
読みにくい
if (10 < length)
ヨーダ記法はIDEを使用する前提であれば警告をだすので不要
if (my_money = NULL) {}
という書き間違いを防ぐために
if (NULL = my_money ) {}
とする。
- C/C++ではif内での代入が可能だがJavaではそもそも不可能
三項演算子
メリット
- 行が短くなる
デメリット
- 読みにくい
- デバッガでステップ実行できなくなる
指針
- 行数を短くするよりも、他の人が理解するのにかかる時間を短くする。
- 基本的にはif~elseが望ましい、三項演算子で簡潔になるときには使用してよい。
do~whileループは避ける
- 条件式が下にあり普通の条件式とは異なりよみにくい、whileループで実現すべき
do {
} while ()
do~whileはJava/PHPなど主要な言語には実装されている。
関数から早く返す
- 関数で複数のreturnを使ってはいけないと思い込んでるのは間違い
早めにreturnすることで「ガード節」をつくる。
public boolean checkBadWord(String str) {
if (str == null) {return false};
//以降なんらかの処理
}
ネストを浅くする
-
if を入れ子にしない→理由:コードを読む人は条件を精神的にスタックにPUSHしないといけない
-
スレッド、シグナル、割り込みハンドラ、例外、関数ポインタ、無名関数、仮想メソッドはコードを追うのが難しくなるためこのようなコードが全体の割合を占めないように注意する必要がある。
8章 巨大な式を分割する
説明変数を用いて分割する
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
9章 変数と読みやすさ
[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にするという手法もある
JavaScriptでプライベート変数を作る
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;
};
}());
JavaScriptのグローバルスコープ
<script>
var f = function () {
// 変数iにはvarがない!!!!
for(i=0; i < 10; += 1);
};
f();
</script>
この場合変数iは他のJSファイルからアクセスできてしまう。JavaScriptのベストプラクティスは**「変数を定義するときには常にvarキーワードをつける。」**だ。
PythonとJavaScriptのネストしないスコープ
- 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;
}
}
};
10章 無関係の下位問題を抽出する
目的:プロジェクト固有のコードから汎用コードを分離する
メリット:汎用問題から切り離された境界線上の明確な業務固有の問題に集中できる
- 汎用コードは素晴らしい、プロジェクトから完全に切り離されているからだ。このようなコードは開発もテストも楽だ。すべてのコードがこうなればいいのに!
- このライブラリや言語にXYZの関数がればなぁと思ったらその関数を自分でかけばよい、それが汎用コードになる。
メソッドの抽出
巨大な関数の中から、汎用的な処理を見つけ出しメソッド化していく
純粋なユーティリティコード
- プログラムの各種言語には汎用的なタスクを処理するライブラリが実装されている、でもたまに欲しい機能がない時がある。
- その場合はその関数を作り汎用コード化すればよい
ファイルを全部読み込むコード
// PHP
file_get_contents("filename")
// Python
open("filename").read()
C++にはそれがないので自分で作る必要があるが、それを作って汎用化する
##11章 一度にひとつのことを
- タスクをできるだけ異なる関数に分割する。
12章 コードに思いをこめる
- コードを書く前にロジックを説明する文章を考えてコードを書く
- ラバーダッキング・・(アヒルのゴム製のおもちゃ)や熊のぬいぐるみに対して、声を出してコードの説明をする。説明することで書くコードが明確化される。
13章 短いコードを書く
鍵となる考え:最も読みやすいコードは何も書かれていないコードだ
- 汎用的なユーティリティを作って使いまわす、標準ライブラリのAPIや汎用ライブラリを広く知って使う。
- たまには標準ライブラリのすべての関数・モジュール型の名前を15分かけてよんでみよう。
- 平均的なソフトウェアエンジニアが書く出荷用のコードは10行。この10行というのはよくテストされ文章化されてる製品用ということ、できるだけこの出荷用のコードを再利用したほうがよい。
- ヨーダ「冒険、興奮、ジェダイはそんなものを求めてはおらん。」
選抜テーマ
14章 テストと読みやすさ
テストコードも読みやすさが大切だ。
テストコードが大きく恐ろしいものだと以下のようなことがおきる
- テストを直すのを恐れて本物のコードを更新するのを恐れる
- 新しいコードを書いたときにテストを書かなくなる
■要約者コメント
「たしかに、あるあるでテストがあまりに大きくわかりづらいものであった場合、その本物のコードを修正するのをやめて別のクラスを作り、そのクラスのテストコードを作成し、本物のコードはそのクラスを呼ぶ箇所だけ追加する。。。とかはやったことがある。」
テストに優しい開発
あとでテストを書くつもりでコードを書くと、おもしろいこと起きる。**テストしやすいようにコードを設計するようになるのだ!**このようにコードを書いていけば、いいコードがかけるようになる!
TDD(テスト駆動開発)は本物のコードを書く前にテストコードを書こうという手法。
TDDを導入するかはともかく、テストを意識しながらコードをかけばいいコードになる。
テストコードのやりすぎに注意
- テストのために本物のコードの読みやすさを犠牲にしたり、テスト都合で本物のコードに手を加えない
- カバレッジ100%にしないと気がすまないのはよくない。現実的にはカバレッジが100%になることはない。
15章 「分/時間カウンタ」を設計・実装する
「分/時間カウンタ」を設計・実装することで見えてくる問題を紹介している、演習的な内容。
外部の視点を得る
外部の視点を得るというのは、コードが「ユーザーフレンドリー」かどうかを確認する優れた手段である。
素直に第一印象を聞いてみよう。他の人も同じことを思っているかもしれない。「他の人」には六ヶ月後の自分も含まれている。
2〜13章[1部〜4部]のまとめ。 覚えておきたいこと。チームで共通の用語にできたらいいもの。用語から章の内容を連想できるようになると最高!
登場した章 | 単語,用語 |
---|---|
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オペレーションでビルドを行えるか?
毎日ビルドを行うか?
障害票データベースを持っているか?
新しいコードを書くまえにバグを修正するか?
更新可能なスケジュール表を持っているか?
仕様書を持っているか?
プログラマは静かな労働環境にあるか?
買える範囲で一番良い開発ツールを使っているか?
テスト担当者はいるか?
プログラマを採用するときにコードを書かせるか?
「廊下での使い勝手テスト」を行っているか?