はじめに
こんにちは。
リーダブルコードの社内向け輪読会の資料です。
以下記事の続きです。
今回は第Ⅲ部10-11章です。
前回までの第Ⅰ部では表面上の細かな変更、第Ⅱ部では「ループとロジック」に注目してコードの小さな変更を扱っていました。
今回の第Ⅲ部では、コードのより大きな変更を扱います。
第10章 無関係の下位問題を抽出する
この章では、無関係の下位問題を積極的に抽出することを推奨しています。
「無関係の下位問題」という表現がわかりづらいのですが、プロジェクト固有のコードとよくある一般的な処理を扱う汎用コードを分離するのがよい、ということです。
汎用コードを分離するメリット
- 大きなコードから下位問題の解決のための汎用コードを排除でき、コード全体の見通しがよくなる
- 抽出した汎用コードの改善が容易になる
- 汎用コードを他のコードに流用できる
これらを実現するために、コードを読む際に3つの観点を重視しています。
- 関数やコードブロックを見て 「このコードの高レベルの目標は何か?」 と自問する
- コードの各行に対して 「高レベルの目標に直接的に効果があるのか? あるいは、無関係の下位問題を解決しているのか?」 と自問する
- 無関係の下位問題を解決しているコードが相当量あれば、それらを抽出して別の関数にする
具体例
10.1 高レベルの目標に直接効果がない、無関係の下位問題の例
高レベルの目標「(地球上の)与えられた地点から最も近い場所を見つける」ための大きなコードから、下位問題「2つの地点の球面距離を算出する」ためのコードを抜き出しています。
これによって、コードの見通しが良くなり、高レベルの目標に集中しやすいコードになっています。
10.2, 10.3, 10.4 汎用コードの例
「ファイルの中身を読み込む」、「ディクショナリをキレイに印字する」といった、よく行う処理=典型的な下位問題を抽出しています。
また、プロジェクトから完全に切り離された汎用コードをたくさん作り、専用のディレクトリ(例:util/)を用意することで、複数のプロジェクトでかんたんに流用できるメリットがあります。
10.5 プロジェクトに特化した機能の例
汎用的な下位問題でなくとも、プロジェクトに特化した下位問題でも積極的に抽出する利点に触れています。
複雑なコードを読みやすくできる利点があるからです。
10.6, 10.7 インターフェースの改善の例
Javascriptでクッキーを扱う既存のインターフェースを例に、get_cookie
, set_cookie
, delete_cookie
というラッパー関数(下位問題)を抽出することで、インターフェースを簡潔にしています。
10.8 下位問題を無闇に抽出しすぎてもいけない
下位問題を抽出すると、ごくわずかにコードが読みにくくなるコストが発生します。
小さな関数を作りすぎて、読みにくくなるコストが下位問題を抽出するメリットを上回らないよう注意が必要です。
第11章 一度に1つのことを
この章の要点は、コードは一度に1つのタスクを行うことです。
これは、一度に複数のタスクをこなすコードは理解しづらく、バグがあっても気づきづらいためです。
具体的には、2つの手順を重視しています。
- コードが行なっているタスクをすべて列挙する
- タスクをできるだけ異なる関数に分割する。少なくとも異なる領域に分割する
実践例
ユーザの所在地を読みやすい文字列(「都市, 国」)に整形するJavascriptのコードを例に挙げています。
var place = location_info["LocalityName"]; //例: "Santa Monica"
if(!place) {
place = location_info["SubAdministrativeAreaName"]; //例:"Los Angeles"
}
if(!place) {
place = location_info["AdministrativeAreaName"]; //例:"California"
}
if(!place) {
place = "Middle-of-Nowhwere";
}
if(location_info["CountryName"]) {
place += ", " + location_info["CountryName"]; //例:"USA"
} else {
place += ", Planet Earth";
}
このコードを一度に1つのタスクになるようにリファクタします。
このコードで行なっているタスクをすべて列挙すると、以下のようになります。
- location_infoディクショナリから値を抽出する。
- 「都市」の優先順位を調べる。何も見つからなければデフォルト値
Middle-of-Nowhere
にする。(= 「『前半』をつくる」領域) - 「国」を取得する。なければ
Planet Earth
にする。(= 「『後半』をつくる」領域) -
place
を更新する。(= 「『前半』と『後半』をつなぎ合わせる」領域)
上記の通り、ここではタスクの列挙と併せて領域の分離も行われていました。
これらのタスクを一つずつ順にこなすようにリファクタすると、以下のようになります。
// 1.location_infoディクショナリから値を抽出する
var town = location_info["LocalityName"];
var city = location_info["SubAdministrativeAreaName"];
var state = location_info["AdministrativeAreaName"];
var country = location_info["CountryName"];
// 2. 「前半」をつくる
var first_half = "Middle-of-Nowhere";
if(state && coutry !== "USA") {
first_half = state;
}
if(city) {
first_half = city;
}
if(town) {
first_half = town;
}
// 3. 「後半」をつくる
var second_half = "Planet Earth";
if(country) {
second_half = country;
}
if(state && coutry === "USA") {
second_half = state;
}
// 4. 「前半」と「後半」をつなぎ合わせる
return first_half + ", " + second_half;
おわりに
だんだんと実践的、具体的で難しい内容になってきましたが、今回もリーダブルにできました。
次回もリーダブルにしていきます。
次回記事に続く。
(参考文献)Dustin Boswell,Trevor Foucher.リーダブルコード より良いコードを書くためのシンプルで実践的なテクニック,講談社,2012,237p.