##はじめに
この本を読了して勉強になったのは以下の2点だ。
・ソースコードを読む時の意識
・ソースコードを書く時の意識
###・ソースコードを読む時の意識
実際にエンジニアとして業務を行うときに一番イメージと異なったのは、コードを書く時間よりも、読む時間の方が長いというこどだ。もちろん新機能の開発などではそうではないが、それ以外の保守運用、バグなどの調査では基本的にソースコードを書くのではなく読むことに長い時間を割く。
なのでどこに着目して読むべきか。ここが大事になる。例えば、この本には変数名にわかりやすい意味を持たせる。変数名は短いコメントだと考える。ということが紹介されていた。この本を読むまで変数名やメソッド名を意識することは少なかった。研修の際も変数名は基本的に何でもいいと考えていた。これを意識して読むことで調査やバグ修正の際に変数名やメソッド名などから意味を類推したりして原因箇所の特的ができている。
他にも、この本はソースコードを書く人のために向けられた本ではあると思うがそれを逆に考えるとソースコードの読み方にもつながった。
##ソースコードを書く時の意識
上記にも記載したが、エンジニアはソースコードを書くときよりも読む時間の方が長いことが多い。そのため、自分が書いたコードが次に修正したり参考にされる場合に読む時間を短くさせるようなわかりやすいコードを書くことでソースコードを書くときに多少時間を欠けても長期的に考えるとリソースの削減につながると感じた。
具体的に個人的に意識しようと思ったのは以下の点だ。
・無駄なコードを書かない。
・変数名メソッド名に意味を持たせる。
・コメントの内容を意識する。
「無駄なコードを書かない」について、これは例えばif文などを書くときに早めにreturnを返したりすることで無駄なネスティングを防ぎロジックをシンプルにさせたりすることだ。
「変数名メソッド名に意味を持たせる。」について、例えば、変数名に重要な情報を詰め込むことが本書では紹介されていた。時間を入れているような変数ではその時間の単位(ミリ秒なのか時間なのか分数なのか)を入れることで後の人がみやすくなったり、無駄にコードを遡って考える必要がなくなる。
「コメントの内容を意識する。」について、個人的にはここが本書の中で自分に刺さったポイントだった。私はどちらかといえば、コメントは積極的に残す右方が良いという考えだったのだが、本書ではコメントは積極的には書かないことを推奨していた。書く時のポイントなども紹介されていたのでこれは積極的に取り入れたいと思った。
以下、本書の内容を章末のまとめを追いながら自分なりに考える。
##第一部 表面上の改善
###名前に情報を詰め込む
明確な単語を選ぶ。-例えばGetではなく、状況に応じて、DownloadやFetchなど
後の人が使いやすいようにメソッド名をみただけで意味を理解してもらいやすいように心がける。
例えばDBのテーブルから情報を持ってくるときに
テーブル全ての情報を持ってくるようなメソッド、部分的に持ってくるようなメソッドではどちらもGetをメソッド名に使いたくなるが、少し立ち止まってメソッド名を考えると色々浮かんでくる。
汎用的な名前を避ける
よく自分が見るのは返り値に$returnと返しているソースコードだ。これでは何が返っているのかわかりにくいのでこのような場合で工夫の余地はあると思う。
スコープの大きな変数には長い名前をつける。
長い名前がいいか悪いかは別として、スコープが大きいと最初にどんな値を入れているのか遡るのが大変なので、わかりやすく明示的な変数名にしようと心がけるようになった。
大文字やアンダースコアに意味を含める。
よく使うメソッド名だとローカル変数ではlf,他のところから参照されることがあるものならsfをつけるようにしている(ECCUBEの規約)
###3章 誤解されない名前
ここでは数値の範囲の話がメインだった。日本語でいうところの以上以下、未満、まで、より・・・・などの箇所を変数名やメソッド名に英語を使って命名する。
限界値を含めるときはmin,maxを使用する
範囲を指定するときは,firstとlastを使用する
包含/排他的範囲にはbegin, endを使用する
ここで感じたのはこれに関しては会社やグループ、チームなどで共有していく必要があることだ。1人でこの規約を守っても他の人がそれを守らなければ結局新規参入者は混乱してしまう。
###4章 美しさ
・一貫性のあるレイアウトにする。
・似ているコードは、シルエットも似ているように書く
・空行を使って、関連するコードをブロックでまとめる
ここでは理解しやすいというよりも読みやすいコードを心がけましょうということが書いていた。自分が実践しているのは以下の点だ。
例えば配列に値を入れるときに
以前の自分の例
$arr['key0'] = $foo[0];
$arr['key0001'] = $foo[1];
$arr['key01'] = $foo[2];
$arr['key00001'] = $foo[3];
読んだ後
$arr['key0'] = $foo[0];
$arr['key0001'] = $foo[1];
$arr['key01'] = $foo[2];
$arr['key00001'] = $foo[3];
このようにイコールの位置を揃えるだけでみやすくなる。
また、インデントも自分なりに意識して揃えるようにしている。
###5章 コメントすべきことを知る
コメントすべきで「ない」ことを知る
・コードを読めばすぐに理解できること
・ロジックがわかりにくい時はコメントで補完しようとするのではなく、そのロジックを見直す努力をする。
自分の考えを記録する。
・将来的に見直す必要があるときはTODOなどのコメントを使って補完する。
・定数などに説明を補完する。なぜその値で良いのかを説明する)
・引っかかりそうな罠を事前に書いておく。
読み手の立場になって考える。
自分はDocコメントをよくメソッドの上に書いてメソッドの概要を説明するようにしている。
###6章 コメントは正確で簡潔に
代名詞は避ける。
「これ」、「それ」などは使わない。
関数の動作はできるだけ正確に説明する。
本書では例をコメントすることで動作の説明をしていた。自分もこの手法は真似したいと思う
##第2部 ループとロジックの単純化
###7章 制御フローを読みやすくする
比較するコードを書く時(if(a>b)やif(a<b)など)は変化する値を左に、より安定した値をみぎに書くようにする。
これはあまり意識せずに自分はできていた。
if/elseは適切に並び替える。
一般的には肯定、単純、目立つものを先に処理する。
ネストは深くせず、早めにreturnできるならそうする。
自分は1メソッド1returnにしていたのでこれは変えて長い制御文は書かないように心がけたい。
###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) {
// 文書は読み取り専用
}
if文の条件式がグッと読みやすくなった。
論理条件をシンプルにすることで、次に読む人、Gitでレビューしてくれる人によりわかるやすく伝えることができる。
説明変数や要約変数を使って複雑で巨大なコードを分割する大切さがこの章では述べられていた。
###9章 変数と読みやすさ
邪魔な変数を削除する
中間結果などの無駄な変数を減らしてすぐに結果を返すべき。
変数のスコープをできるだけ小さくする。
変数のスコープが大きいと把握するのに時間がかかるためだ。
グローバル変数などスコープの大きいものは追跡が難しい
一度だけ書き込む変数を使う。
変数は何度でも書き換えられるのがある意味でメリットだがやりすぎると追跡が難しくなってしまう。
##第3部 コードの再構成
この章ではプロジェクト固有のコードから汎用コードを分離することを主として取り扱っている。
ほとんどのコードは汎用かできる。一般的な問題を解決するライブラリやヘルパー関数を作っていけば、プログラムに固有の小さな核だけが残る。いかにその手順を記載する。
大きな問題を小さな問題として解決する
→関数の目的を考えて、直接的に関係のない処理は別の関数としてまとめる
その際下記の内容を意識して切り分けていくと楽になる。
1.関数やコードブロックを見て「このコードの高レベルの目標は何か?」と自問する。
2.コードの各行に対して「高レベルの目標に直接的に効果があるのか?あるいは、無関係の下位問題を解決いているのか?」と自問する。
3.無関係の下位問題を解決しているコードが相当量あれば、それらを抽出して別の関数にする。
また関数を目的別に小さく分けることで
1. 呼び出し側のコードはシンプルになりリーダブルなコードになる。
2. コードが独立するので、改善が楽になる
などのメリットがある。
例として挙げられているものを参照する。
// 与えられた緯度経度に最も近い'array'の要素を返す
// 地球が完全な球体であることを前提としている
var findClosestLocation = function(lat, lng, array) {
var closest;
var closest_dist = Number.MAX_VALUE;
for(var i = 0;i < array.length;i++) {
var lat_rad = radians(lat);
var lng_rad = radians(lng);
var lat2_rad = radians(array[i].latitude);
var lng2_rad = radians(array[i].lngitude);
// 「球面三角法の第二余弦定理」を使う
var dist = Math.acos(
Math.sin(lat_rad) * Math.sin(lat2_rad) +
Math.cos(lat_rad) * Math.cos(lat2_rad) *
Math.cos(lng2_rad - lng_rad)
);
if(dist < closest_dist) {
closest = array[i];
closest_dist = dist;
}
}
return closest;
}
例えば上記のプログラムの高レベルの目標は「与えられた地点から最も近い場所を見つけること」になる。
では2地点の距離を計算する処理に関しては高レベルの問題(プログラムの目的)からは外れるのでこれが別関数として分離できるというものである。
実際にspherical_distanceとして分離する。
var spherical_distance = function(lat1, lng1, lat1, lng2) {
var lat1_rad = radians(lat1);
var lng1_rad = radians(lng1);
var lat2_rad = radians(lat2);
var lng2_rad = radians(lng2);
// 「球面三角法の第二余弦定理」を使う
return Math.acos(
Math.sin(lat1_rad) * Math.sin(lat2_rad) +
Math.cos(lat1_rad) * Math.cos(lat2_rad) *
Math.cos(lng2_rad - lng1_rad);
);
}
切り出した後のものを見てみるとかなりシンプルになっていることがわかる。
// 与えられた緯度経度に最も近い'array'の要素を返す
// 地球が完全な球体であることを前提としている
var findClosestLocation = function(lat, lng, array) {
var closest;
var closest_dist = Number.MAX_VALUE;
for(var i = 0;i < array.length;i++) {
var lat_rad = radians(lat);
var lng_rad = radians(lng);
var lat2_rad = radians(array[i].latitude);
var lng2_rad = radians(array[i].lngitude);
// 「球面三角法の第二余弦定理」を使う
var dist = spherical_distance(lat, lng, array[i].latitude, array[i].longtude);
if(dist < closest_dist) {
closest = array[i];
closest_dist = dist;
}
}
return closest;
}
spherical_distancは汎用的な関数となり、将来的に活用機会がありそうだ。また、この関数の単体のテストも可能になるので分離することのメリットが大きい。
より実務レベルで考えてみると、順番の並び替えの機能があったとして、その関数を汎用コードとして保存しておけば、店舗の並び替えや商品の並び替えなど様々な場面で活用できる。
###純粋なユーティリティコード
プログラムの核となる基本的なタスクのこと
例えば
文字列の操作
ハッシュテーブルの使用
ファイルの読み書き
などだ。
このようなものはライブラリにあることが多いが、そうでない場合は自分で作ってどこかに保存しておくと便利だ
###汎用コードをたくさん作る
上記のように沢山の汎用コードを作成したりプロジェクトの中から汎用コードを切り分けたりすることで、
プロジェクトをまたいで様々な場面で用いることができるようになる、
###まとめ
プロジェクト固有のコードから汎用コードを分離する。
##11章 一度に1つのことを
コードの中でいろいろなことをやっていると理解しにくい。例えば
オブジェクトを生成
データをきれいにして
入力をパースして
ビジネスロジックに適用する
ようなコードをごちゃまぜになっているとコードは読みにくくなってしまう。
一度に一つのタスクをするための手順
タスクを列挙する(入力が妥当かチェックする、ツリーのすべてのノードをイテレートするなど)
タスクをできるだけ異なる関数に分割する。
本書の11章では上記を具体的な例を記載されていた。
####まとめ
1.コードに複数のタスクを詰め込みすぎない。
2.一度に一つのタスクを行う。
3-1.読みにくおコードがあれば、そこれ行われているタスクを全て列挙する。
3-2.別の関数やクラスに分割できるタスクがある。
3-3.分割する。
##12章 コードに思いを込める。
アイシュタインの名言に
おばあちゃんがわかるように説明できなれば、本当に理解したとは言えない
というものがある。
プログラムでもどうように自分よりも知識が少ない人が理解できるような簡単な言葉で説明する必要がある。
コードをより明確にする手順として下記の例が紹介されていた。
コードの動作の簡単な説明をこととばで同僚にもわかるように説明する。
その説明の中で使っているキーワードやフレーズに注目する。
その説明に合わせてコードを書く
同僚に説明とまでは行かなくても自分でロジックの説明文を書いてみるだけでも効果がありそうだ。
本書ではプログラムに自分なりの説明を加えた結果さらに良い解決方法を思いついた例がいくつか紹介されていた。
(ユーザーに管理者権限があるか確認する, もし権限がなければユーザに知らせる)
$is_admin = is_admin_request();
if ($document) {
if (!$is_admin && ($document['username'] != $_SESSION['username'])) {
return not_authorized();
}
} else {
if (!$is_admin) {
return not_authorized();
}
}
if文のネスティングが深くややわかりにくい
ロジックを簡単に説明するとこのようになる。
■ 権限あり
1)管理者
2)文書の所有者(文書がある場合)
3)その他 は権限なし
このようにロジックを明確にした上で書き直す
if (is_admin_request()) {
// 権限あり
} elseif ($document && ($document['username'] == $_SESSION['username'])) {
// 権限あり
} else {
return not_authorized();
}
わかりやすくなったと思う。
ラバーダッキング・・(アヒルのゴム製のおもちゃ)や熊のぬいぐるみに対して、声を出してコードの説明をする。説明することで書くコードが明確化される。
という手法も紹介されていた。
###まとめ
ロジックをわかりやすく言葉で説明できるようにする。
##13章短いコードを書く
###鍵となる考え方
最も読みやすいコードは何も書かれていないコードだ
実際に行うこと
1.過剰な機能、不要な機能は実装しない。
2.コードを小さく保つ
2-1. 汎用コードを作って、重複コードを削除する。
2-2.未使用のコードやムユの機能を削除する。
2-3.プロジェクトとサブプロジェクトに分割する。
2-4.コードの「重量」を意識する。軽量で機敏にしておく
3.身近なライブラリ、APIを知って使ってみる。
##14章テストと読みやすさ
この章ではテストコードの書き方について記載していた。
本章でいうテストとは
本物のコードの振る舞いを確認するための全てのコードのこと
###鍵となる考え
他のプログラマが安心しつとの追加や変更ができるように、テストコードを読みやすくする。
テストコードが大きくて恐ろしいものだとしたら、以下のようなことが起きる。
テストを直すのを恐れて本物のコードを更新するのを恐れる
新しいコードを書いたときにテストを書かなくなる
また、本物のコードは修正されて新しくなっているのに、テストコードが古いままだと正しく動作するかわかりづらくなってしまう。
他のプログラマが安心してテストの追加や変更ができるようにテストコードを読みやすくする
・テストのトップレベルはできるだけ簡潔にする。入出力のテストはコード1行で記述できると良い
・失敗したらわかりやすいエラーメッセージを
・単純な入力値を使う
・テストに説明的な名前をつけて何をテストしているのかわかりやすくする
15章は実際に本書の内容を用いた実践的な内容でありまとめるようなものではないので省略する。
##感想
開発する上で、意識をしようと思ったのは
. 第三者の目(次に読む人)を意識する
・ コードを短く簡潔に書く
・ コードの役割を明確、簡潔な言葉で説明できるようにする(例えばPRを出す際にどのような改修をしたのかという項目があるがそのような場面)
これまで課題、タスクに対して淡々とこなすのみで読みやすいコード、再利用しやすいコードを意識することがなかった。
自分が書いたコードはいい意味でも悪い意味でも会社の財産になるので良い意味で財産として残していけるように基本をしっかりと押さえて良いコードが書けるようになりたい。