先日『リーダブルコード』という書籍を読み、読みやすいコードを書くための手法について学習しました。書籍を読んで学んだことや、追加で調べたことについてメモを残しておこうと思い今回の記事を作成しました。
①命名
変数名や関数名などの命名時、名前に情報を付加することでコードが読みやすくなります。以下のポイントを意識して命名するとよいそうです。
明確な単語を選ぶ
例えば関数名なら「どこから」「どういう動作で」「どんな状態の変化を伴うのか」など、その関数の役割を考慮した単語を使用することで関数名から処理が想像しやすくなる
【例1】
△:GetPage()
⇒「get」だけだとどこから取得するのか分からない
◎:
FetchPage() DownloadPage() (インターネットから取得する)
GetCachedPage() (キャッシュから取得する)
【例2】
△:Thread.Stop()
◎:
Pause() (あとからResume()できる)
Kill() (取消不可能)
命名時に参考にできそうなサイト
・シソーラス(類語辞典)
⇒より適切な単語が見つかることがあります
単語のニュアンスについて紹介している記事
(検索すると他にも色々出てくると思います)
・メソッド(関数)の命名で使えそうな英単語を集めてみた
・関数名によく使われる英単語(動詞)の意味とニュアンス
・ネーミング | codic
⇒日本語を入力すると関数名・変数名として変換してくれます
※命名時は翻訳等で出てきた単語をそのまま使用するのではなく、出てきた単語が適切かを調べた上で決定するのがよい
汎用的な名前の使い方
意味なく汎用的な名前をつけるのはNG、基本的にはその変数や関数の内容を表す名前にする
- NG例:戻り値の変数名を
retvalにする- どのような値を戻しているのかを変数名にする
汎用的な名前であることに意味がある場合は使用するべき
- 2つの変数の値を入れ替える際に使用する退避用変数名を
tmpにする- tmpには退避用としての使い方以外に役割がないことを示すことができる
-
tmp_fileなど補足情報を付ける手法もある
- ループイテレータの i, j, k などもその名前であることでループイテレータであると示せるのでOK
-
usersという配列のカウンタ用ならusers_iuiといった命名にするなどして補足情報を付ける手法もある
-
名前に情報を追加する
名前は短いコメントのように使えるので、重要な情報があれば名前で示すこともできる
-
例1:仮引数に単位を付加する
CreateCache(int size)⇒CreateCache(int size_mb) -
例2:バグやインシデントの原因になりそうな箇所へ注意喚起の意味で情報を付加する
comment⇒unescaped_comment
★POINT
すべての変数に情報を追加するのではなく、変数の意味を理解してもらいたい箇所のみに付ける
情報の選択
情報を入れ過ぎて名前が長くなるとコードが読みづらくなるため、付加する情報は重要なものに絞ることが大切
- スコープが狭い場合は短い名前でもよい
- 近くのコードを数行読むだけで用途を理解できる変数の名前は簡潔でよい
- 省略しても伝わる一般的な単語は省略してもよい
- 例:
String⇒str
- 例:
- 不要な単語は省略する
- 例:
ConvertToString⇒ToString
- 例:
名前のフォーマットで情報を伝える
- 変数名なら小文字、定数なら大文字など、種別ごとにフォーマットを決めておく
一般的な用途に合わせる
例えば getXXX() のようなメソッドはメンバの値を返すだけの軽量アクセサであることが一般的なため、その中に複雑な処理を書くことは避ける。
(軽いメソッドだと思って呼び出して処理が重くなるなどの可能性がある)
命名時は一般的な用語を使用すると伝わりやすい
・限界値を含める時→min, max
・範囲指定→first, last
・ブール値→isXXX, hasXXX, canXXX, shouldXXX(true/falseの意味を明確にする)
②見た目を整える
見た目が揃っているとコードが読みやすくなる。
- 関連するコードを近くにまとめておく
- 改行位置やフォーマットを整える
- 場合によってはヘルパーメソッドの使用も検討する
- 見た目が煩雑な場合、他のメソッドに処理をまとめて見た目をきれいにできることもある
縦の線を並べる
見た目がきれいになるほか、誤字にも気づきやすくなる
name = user.get('name');
company = user.get('company');
age = ser.get('age'); // 誤字があり他の行とずれているのが見つけやすい
birthday = user.get('birthday');
IDEの自動成型機能などを活用するのも◎
羅列する順番に意味を持たせる
対応するHTMLのinput順、重要なものから、アルファベット順...など
どの順番で並べてもよいが、一連のコードでは同じ順番を使用する
③コメントの活用
書いた方がいいこと
- なぜこのような実装にしたのか(他の手法ではなく)という理由の説明
- 他の人がこっちの手法の方がよいのでは?と試して同じ結果を辿る可能性があるので、採用理由を書いておくとよい(監督コメンタリー)
- コードの欠陥について書く。以下の書き方が一般的
- TODO: あとで手をつける
- FIXME: 既知の不具合がある
- HACK: あまり綺麗ではない解決策
- XXX: 重大な問題があり危険
- 定数について、なぜその値が設定されているのかという背景
- 読み手が疑問に思いそうな箇所に理由を書いておく
- ファイルやクラスに全体像(全体概要)のコメントを書く
- コードブロックに概要をコメントしておく
VisualStudioだとTODOコメントからタスク一覧を表示する機能があります
【Visual Studio】TODOコメントの表示と使い方 ~タスク一覧機能~
コード コメントとショートカットでタスク リストを使用する
書かない方がいいこと
- コードからすぐにわかること
- コメントを読んだ方が早く理解できる場合は書いた方がよいが、
コメントを読む時間とコードを読む時間が変わらない場合は書かない方がよい
- コメントを読んだ方が早く理解できる場合は書いた方がよいが、
- 変数名や関数名などを補足するコメント
- コメントでなく変数名・関数名自体を改善できないか考える
コメントを書く際のポイント
- 言葉で説明が難しい場合は関数の入出力の実例をコメントに残す
- コードの意図は詳細レベルではなく高レベルで記載する
- NG例:listを逆順にイテレートする
- OK例:数量が多い順にソートする
- 目的が明確になることで、この記述では数量が多い順にはソートできない、この前の処理で既にソート済みなど、バグチェックや冗長検査にも活用できる
- 情報密度の高い言葉を使う
- 表現が長くなってしまう時には、それを表す用語が既に存在している可能性もある
- インラインコメントで引数を補足する
- 引数が何の値か呼び出し元から分かりにくい時や、関数で宣言されている不適切な引数名を簡単には変更できない時などに使用するとよい
例Connect(/* timeout_ms = */ 10, /* use_encryption = */ false)
④コードの構成、フローの見直し
制御フロー
- 条件式は「調査対象」(比較演算子)「比較対象」とする方が読みやすい
- OK:
length <= 10 - NG:
10 >= length - 不変的な値や安定的な値はあとにおくのがgood
- OK:
- if/elseの並び順
- 関心を引く条件や目立つ条件を先に書く
- 否定形(
if(!isXXX))よりも肯定形(if(isXXX))を使う - 単純な条件を先に書く⇒ifとelseが同じ画面に表示されると見やすい
- 不要なネストは排除する
ネストが多いと読みづらい。下記の方法で排除する。- 失敗ケースを早めに関数からreturnする
- continueを使用して早めにループから抜ける
※continueはループを行ったり来たりして読みづらくなることがあるので使用箇所に注意
長い式の分割
- 説明変数を使う
# ifの条件が複雑で何がtxtと一致すればよいのか分かりづらい if filename.lower().split(".")[-1] == "txt": # 変数名を見ると拡張子がtxtと一致すればよいことがわかる extension = filename.lower().split(".")[-1] if extension == "txt":
- 要約変数を使う
# NG # 商品が販売期間内なら購入可能、期間外なら閲覧のみ if (request.date >= product.salesPeriod.startDate && request.date <= product.salesPeriod.endDate) : isOrderButtonEnabled = true if (request.date < product.salesPeriod.startDate || request.date > product.salesPeriod.endDate) : isOrderButtonEnabled = false # OK # 商品が販売期間内かどうかを要約変数に持っておくよう修正すると読みやすくなる final boolean isInSalePeriod = (request.date >= product.salesPeriod.startDate && request.date <= product.salesPeriod.endDate) if isInSalePeriod : ...
- ド・モルガンの法則を使う
プログラムに置き換えると…
if(!(a && !b))⇒if (!a || b)
変数
- 変数が多いと追跡が難しい
- 変数のスコープが大きいとスコープを把握する時間が長くなる
- 変数が頻繁に変更されると現状の値の把握が難しくなる
これらを改善するために以下のようなことができます
変数の削除
- 不適切な説明変数や要約変数は削除する
- 説明や要約がなくても伝わる場合は不要
- スコープを縮める
- 変数は使用する直前に宣言する
- メソッドをstaticにしてメンバ変数と関係ないことを明示
- 大きなクラスを小さなクラスに分割する
- 分割後のクラスが独立していればOK、相互参照が頻発するような分割はNG
- 一部のメソッドでしか使用しない変数はローカル変数にする
class SampleClass { String str; void methodA { //strを使う } void methodB { //strを使う } //strを使わないメソッドが大量にある } //例えば以下のように書き換えるとmethodA,B以外からstrが見えなくなる class SampleClass { void methodA { String str; methodB(str); } void methodB(String str) { }
所感
コードが読みやすくまとまっていると、改修などで他の人がコードを触る際に修正しやすくなったり、バグが検出しやすくなったりといいことがたくさんあります。きれいなコードを書けるよう意識していきたいと思いました