リーダブルコード2周目を読んで出来ていなかったことまとめ
リーダブルコードを改めて読み返して、「ここ出来てない」「ここ気を付けたい」と思った所を自分の体験とともにまとめてみた。
具体的な単語を選ぶ
メソッド名は、getではなくダウンロードなど何をしているか分かる単語を選択する。
また、getには処理が軽いイメージがあるためgetの中で、複雑な処理をしたりある変数を書き変える処理をしてはいけない。
「ユーザーの期待に合わせる」にも書いてあるが、基本的にgetやsizeなどよく使われる単語は処理が軽いなどイメージがついているので、それに合わせて実装する。
単語は安易に選びがちなので、適切な単語を選ぶようにしたい。
プログラマーのためのネーミング辞書 | codic は便利でよく使っている。
別のものも併用して選ぶべきなのかもしれない。
その他の重要な属性を追加する
変数の意味を間違えてしまった時にバグになりそうにところだけ使うことが大事だ。特にセキュリティのバグのような深刻な被害が出るところに使うといいだろう。
前にクラスの変数がメソッドの処理で変換後の状態になっているのに気付かず、PRで読み違うという現象が起こった。
具体的にいうと、マウスクリックした時の座標を格納する変数だったのにマウスアップ後の座標が格納されていたというものであった。
最終的にマウスアップ後の座標がほしかったためマウスクリック時の座標はいらなくなったのだが、変数名はマウスクリック時の座標の名前のままだったのでPRで読んでも理解できなかった。
変数の意味が変わった場合は変数名を変えるべきだし、変わらなくても良いのであれば凡的な名前になっているため具体的にすべきだと思った。
ブール値の名前を適切にする
私は基本的にisを使いがちだったので、これを機に幅を広げたい。
使い方例
is:
その状態であるどうか。
例.isError
has:
その状態になったかどうか。
例.hasError
持っているかどうか。
例.hasSpace
can:
出来るかどうか。
例.canDownload
should:
すべきかどうか。
例.shouldDownload
need:
必要かどうか。
例.needPassword
exists:
存在するかどうか。
例.UserNameExists
名前を肯定形にすると、短くてすむし読みやすい。
また、これらのメソッドの中でとある変数の値を変えたりなどの処理は行うべきでない。
参考にしたいサイト↓
booleanメソッドの命名規則 - Qiita
真偽値を返す関数のネーミング - Qiita
個人的な好みより一貫性を選択する
個人的な好みで書き方がバラバラになるより、一貫性のあるコードの方が読みやすい。
予めプロジェクトの規約を決めておき、自分の好みより規約に従うことを優先するようにしたい。
例えば、クックパッドさんはリソースの定義ポリシーを整備している。
Android アプリのリソース定義ポリシーを整備した話 - クックパッド開発者ブログ
大きなアプリほどこういった規則を設けないと、見にくいコードになる。
見にくいコードになるとレビューやデバックに時間がかかる。
見にくいコードだと担当者に聞いたりコミュニケーションコストがかかる。
いいことはない。
面倒でも予め規則を作っておくということは重要である。
「監督のコメンタリー」を入れる
このロジックにした理由やテストに失敗する理由、コードが汚いこと修正方針などを書いておく。
個人的にこのメソッドを書いておいて後から助かったことがあるので、書くとよいと思う。
特にロジックに関しては、こういうロジックの方がいいのでは?という論争が一度は起きるものなので、自分が検討した結果は何かに残しておくと無駄な時間が発生しない。
ただ、コメントを付けなれば理解しにくいメソッドの場合、メソッド名を変えたりメソッドを分割したりできないか考えることも必要。
実行の流れを追いやすい書き方をする
関数ポインタや無名関数の構成要素は、バグを見つけるのが難しくなったりするためコード全体に占める割合を大きくしないよう心掛ける。
無名関数は使ってしまうこともあるが、書籍にもあるように「コンパイル時に判別できないので、どのコードが実行されるのかわからない。」という理由により簡単なロジックのみ使用し使いすぎないようにしたいと思った。
巨大な式を分割する
式やコードの塊を表す変数を使えば、管理や把握を容易にすることができる。
例えば、if文の条件式を変数して分かりやすい名前を付けると読みやすいコードになる。
特に巨大な文を分割するときに有効な手段になる。
複雑なロジックで条件式が大きくなってしまうときなどはよりシンプルな条件にならないか立ち止まって考えるべき。
例えば、ド・モルガンの法則で条件を整理したり、条件を否定にして考えてみたりすると別の条件が浮かぶ可能性がある。
また、自分頭いい!と思いながら書いた複雑なロジックは他の人が読んだ時分かりにくいコードになっている可能性が十分にあるから気を付ける。
試行錯誤しながらプログラムを書いているとどうしても条件式が大きくなる時があるので、変数など使って読みやすくなるよう気を付けたいしPRでも指摘していかないといけないと感じた。
変数も読みやすさに留意する
変数のスコープをできるだけ小さくする。
変数が人知れず変わっていることを防ぐため。
変数は使うところの近くで宣言する。
一度だけ書き込む変数を使う。
永久に変更されない定数などの方が使いやすい。
Kotlinだとletなどを使う用にする。
変数の操作する場所は、なるべく少なくする。
操作する場所が増えると、現在値の判断が難しくなる。
無関係の下位問題を抽出する
複雑なロジックを別の関数にすることで、他者がコードを見るときに上位の問題に集中できるようになる、関数を再利用できるといったメリットがある。
既存のインターフェイスが複雑な場合、ラッパー関数を作って対応する。
サイクロマティック複雑度を測ってみると客観的にどのコードが複雑か分かるのでおすすめ。
10以下が望ましい。バグが多発するところは、複雑度が高い傾向にある。
詳しくは、循環的複雑度について - Qiita へ
一度に一つのことをするようにする
関数はタスクで分け、タスクごとに1つの処理を行うようにする。
タスクは、一言で表せるようにする。
例えば、「○○を取得する。なければ××にする」など一言で収まらない場合は、タスクが1つの処理を行えていない。
関数の処理を文章にした時に複数のタスクを行っている場合、順番を入れ替えたり分けたりすることでシンプルにならないかチェックする。
例えば、「何も入力されていなかったらデフォルト値を入れる」という仕様には、まず先にデフォルト値を入れてしまい、入力されていたら文字列を置き換えるようにするとシンプルになる。
短いコードを書く
要求に合わせたロジックを考えるべき。
要求よりも複雑なことを実現しようとしたり、現状の問題に合っていない解決策を講じてもコストがかかる。
例えば、数件の店舗検索システムで経度緯度を使って検索したり、必ず順番通りに実行されているオブジェクトのキャッシュにLRU方式を適用したり。
標準ライブラリを積極的に使う。
成熟したライブラリは膨大な時間をかけて設計やテストが行われているため質が保証されている。
あくまで成熟したライブラリに限る。
複雑なコードは共通化すべきである。
一度共通化すると分けるのが大変になるため、安易にまとめない。
共通できそうに見えても本質的には異なる場合は注意すること。
テストも読みやすさに留意する
大切ではない詳細はユーザーから隠し、大切な詳細は目立つようにする
大切ではない詳細、例えばテスト用の変数を用意する処理は、関数にしてしまう。
大切な詳細、例えば「こういう状況と入力から、こういう振る舞いと出力を期待する」ような処理は、1行にまとめる。
また、ヘルパー関数を作ってこの関数を使うだけでテストが出来る状態にしておくと。テストが書きやすくなる。
テストの関数には1つの機能のみをテストするようにするべき。
その方が読みやすい。
Assetメソッドでエラーメッセージが不十分な場合は、自作することも視野に入れる。
テストには簡単に読める単純な値を使う。
入力値を大量に使うようなテストは、入力値を作成する関数を作っておくことで見やすくする。
テストの関数名は、テストするクラス・関数やテストする状況やバグ名を付ける。
テストの関数は、他から呼び出されることはないため、名前は説明的でも良い。
テストに優しい開発 とは
密結合なプログラムだと、テストを書くときに密結合なクラスを作成したりするので面倒、テストをしてもどれが原因か分かりにくい。
疎結合だと並行開発テストが可能になる。
外部コンポーネントが多いと、テストで初期値を設定する必要があり面倒、さらにプログラムを全体を把握し無ければならなくなる。
ただし、テストはプロジェクトの性質によって重要性が変わってくる。
プロトタイプではテスト実施無しでよいし医療機器は入念なテストが必要である。
そもそもカバレッジ率が100%になるのは現実的に無理である。
テストがプロジェクト開発の邪魔になってはいけない。
以前、開発の早めの段階でテストを書いていると、テスト修正に時間が割かれて開発の時間が取れなくなってしまうことがあった。
設計開発の基盤が出来上がってから書いた方が良いと思われる。
パフォーマンスを考慮する
以下のようなコードになっていた時は修正を考える。
- メモリを無限に使用する
例えば、古い不要なイベントを持ち続ける
→メモリ使用量を予測できるように変更する - メソッドの処理時間がかかりすぎる
例えば、O(n)かかる
→メソッドの処理時間がかからない設計を検討する
パフォーマンスと精度はトレードオフであることに留意する。
時間に依存したシステムは時間の呼び出しが一か所に集まっている方がテストしやすく、バグが少なくなる。