- 良いコードの判断が一気にできる場合も、そうでもない場合も存在する。
- 良いコードの第一基準とは:「可読性」
- 読者が読みやすくて、「完全な理解」にかかる時間が短いコードこそいいコード
- コード的に、オーバヘッドが発生しない方が良いコード
- 短いコード != 良いコード:コードを短縮しすぎて見づらくなることも絶対ある(括弧が重ねすぎなど)
- 良いコードの他の条件ともコンフリクトしない
Last active
September 19, 2021 09:17
-
-
Save dokimyj/3717b539c89ed206d52fc7b06dea3465 to your computer and use it in GitHub Desktop.
[JP] the Art of Readable Code Summary
- 良い変数名・コメント・コードの仕組み・空白の紡ぎとは?
- 的確なところに適用させることを含む
- 表面と言っても、全ラインにより影響があるから結構重要
tmp
などぼんやりする単語は選ばない- 一般的すぎの名前は避ける
- 具体的な名前を選ぶ
- 語尾・語頭に追加情報をつける
- 名前の長さを決める
- 名前形式を統一する
- ex1) メソッド
def get_page
- どのページを、ページの何を獲得するメソッドか見せる名前にする
- ページを持ってくるソースや方法などを表す方向で名前をつける方が良い(
fetch_page_~~
,download_page_~~
など)
- ex2) コンストラクター
int Size()
- どのサイズかわからないから、
PageSize()
とかMaxMemorySize()
など - もしかしたら容量じゃなく、
Height()
やCapacity()
になるかもしれない
- どのサイズかわからないから、
- ex3)
Thread
クラスの中のStop()
の場合- もちろん動作としてストップさせるかもしれないけど、もっと明確化できれば…
- スレッド Kill することだったら
Kill()
になる - 一時停止させることで、いつか再開される可能性が場合は
Pause()
にもなれる
- 英語は重複を避けるためにいろんな語彙を作ってきた言語
- 多様な語彙で命名しても、個性より的確・明瞭に
- 変数名の場合、単に役割だけを載せると
retval
やtmp_~~
になる場合が多すぎて、具体的にどう導出した値が割り当てられているかから命名することも良い - ただし、コンピューティングの合間に臨時的に使われる臨時変数などは tmp など、役割が明確に臨時である場合は一般的な命名でも良い。
- その他、変数などのライフサイクルにより、命が短すぎる(他の動作や結果に影響がないツール変数)場合はそんなに悩まず、一般名詞でおk
- ループ文のインデックスが中のところで使われる場合、(重ねループはさらに)普通の
i
,j
,k
より(用途やスコープがわかる prefix)_i
をつける - キャメルケース・スネークケース・ケバブケースなど、適切に切り分け、意味を明瞭に表す
filter
やget
などはプログラミング的に誤解を呼びガチ- とあるグループから絞り出して該当なしを除外するメソッドは
filter
よりexclude
をつける get
は DB に記録されているデータを持ってくるだけなど、単純なところのみにつけるように (中で計算や動作が行われる場合、それによってつける)
- 英語の場合、以上・超過、もしくは未満・以下を明確に表す単語がない
- 最大・最小を表すために
max_
やmin_
を変数名の先頭につけるのが非常に推奨 - 範囲を定義する際
- 最初の含めるには
begin
, 最後の値を除外するにはend
をつける - 先頭と最後、両方を含めるために
first
とlast
もいい接頭語になりそう
- 最初の含めるには
- bool 系の命名法
is~~
(状態) やhas~~
(抱合) が理想的- 否定表現は避ける (ex:
disable_ssl
)
- 一貫的なレイアウト、パターンがあり、読む方が慣れるように
- 似てる仕事をするコードはなるべく似た見た目を保つように
- 関係あるコードは一緒にまとめる
- 変数の初期化、メソッドの呼び出しの際、一行の中で処理するか、引数毎に改行するか明確にして統一する
- コードの長さ、引数の数を考慮し、読みやすい方に関して読者側の目線で工夫しながらコードを書く
- 同じコード束の中、非一貫的な書き方は必ず統一する
- 変数の位置、括弧の終わり目などは整列する
- ただ、チーム内の約束やコード管理のために省略はおk
- 役割、ロジックの類似性により改行を決める
- ウェブ開発の場合、クライアント側での表示順番や入力順番と統一する
- 重要度により、一番重要な変数から降順
- 重要度がない場合、アルファベット順番も活用できる
- どちらのコードでも冗長性を保つ形でメソッド・オブジェクトの括弧をつける
- チームでのスタイルがあればチームのスタイルに従う
- 「正しさより一貫的なスタイル」
- コメントは「読者が作者が知ってる程度まで理解できるように書く」
- 何をコメントするか、もしくはコメントしないか
- コード作成時に考慮した部分などを記録するように
- 読者の立場から、読む方はどの情報が必要か考えながらコメントを書く
- コードを見ればすぐわかること
- プログラマーなら誰でも知ってる基本的なこと
- その他、新しくない情報
- 文章より条件などをコード的に書いて見せる
- 悪い変数名やメソッド名などに言い訳をつけるようなコメントはしない
- 開発の中、実際試してダメだった修正案
- 最善だったこと、改善のマージンがない場合
- 改善が必要だが、今度の修正に含めるには関係性が薄い所 (
TODO:
)
- 読者が気になりそうな部分
- ありうるトラップ
- モジュールやメソッドの概要・要約を初見でもすぐわかるように
- コメントはいつもコスパが最重要
- 抽象的、曖昧な表現は避ける
- 例をつけるのも説明しやすくなる
- 理解しやすい、直感的な言葉を使う
- できる限り、引数の変数名・コメントを明瞭化
- 情報が圧縮されているコメントをつける
- 読む方の精神的な負荷を減らすように
- 複雑な制御文、大きすぎる表現、大数や多すぎる変数は避けよう
- 読む方を毎度インタビューに招かせるコードはよくない
- if文、ループ、その他制御文は自然な流れになるように
- 読者が読み辞め、再び読むべきのコードは悪い
- スレッド、インタラプト、例外処理、匿名ファンクション、仮想メソッドは把握しにくい
- 片方変数: 「対象変数 >=< 基準になるリタラル」が読みやすい (ex:
length < 10
) - 両方変数: 「基準になる変数 >=< 対象」が読み易い (ex:
expected > received
) if ~ else
の if 条件にはなるべく否定を入れないように- 三行オペレーターは
if ~ else
よりシンプルな場合のみ使う
do ~ while
は避ける:while(条件)
を非常に推奨- 早期
return
は必要なもの、finally
などで最終処理をまとめればおk - ただ、
goto
でマルチエンドポイントになるのは非推奨
- 作成後に一歩後ろに去ってコード全体を眺める
- ネストが多いコードは可読性が下がる
if
の場合、return
を早期宣言することでネスティッドを避けれる- ループの場合、
if(ループを回避する条件) continue;
やnext if
など一行条件式で避けれる
- コードチャンクより、細かく熟す方が消化し安い
- リターンの結果物をそのまま使うことは効率的だが、結果物を表すに直感的ではない
- リターンの結果を説明、もしくは要約してあげるように適切な変数名をつけて説明する必要がある
- ド・モルガンの法則を活用する: !A && !B && !C == !(A||B||C)
||
や&&
で検証の漏れが発生する可能性がある (前の条件を満たしたら後ろはスキップするため)- だが、推奨
- 特に変数に値をアサインする際、
x = a || b || c
のように truthy のみ割り当てることは見事
- 範囲がかぶるか確認する際、「A の最初と B の最後が被らない」 と 「A の最後と B の最初が被らない」などを比較するのは誤りやすい
- さらに、この各ケースに当てはまる場合のみ
return false
にしてどちらも当てはまらない場合return true
する方が楽だ
- 一口に噛みすぎるとコードも読みにくいし、バグの可能性が上がる
- 特に
if ~ else
系の条件が多くなると更に読みづらくなる- 繰り返しのメソッドのリターンは変数として予め宣言
- 長すぎるリタラルも予め宣言
- 似てる動作をするロジックをメソッド化して、引数だけ入れて動かして変数を割り当てれる (C++ の場合だけど)
- 変数の数が多くと変数が把握しにくい
- スコープが広い程に変数の把握周期が長い
- 変数の値が頻繁に変わると現在の値がわかりにくい
→ 変数の数、範囲、変化の回数は減らすべし
- **2.**の「説明・要約する変数」とは違う、説明や要約にもならない不要な変数を減らす
- 誰が見ても確かなリターンを保つ場合、臨時の変数はいらない (ex:
Time.now.month
など) - 中間結果を出したまま1条件式を閉じて、改めて条件式を開き、最終処理をしてリターンするのはいらない
- while で
制御用の bool + continue
よりbreak
がマシ
- 1メソッドの内で使う変数をメンバー変数として宣言しないように
if(変数 = とあるメソッド)
形式を使われる言語はこちらを活用する- JS の場合、 var なしで宣言する変数は完全にグローバルになってしまうため、注意を
var xxx = (function() { ... }());
の匿名関数の中に宣言された変数はプライベート変数になる- 領域が広い変数より、メソッドを作ってローカル変数をメソッドの引数として入れよう
- 仕方なくからの変数を予め宣言するべきであれば、変数を使う所と近いところで宣言しよう
- 変数の値が変わる度、読者がその変数の値を覚えるのが難しくなる → 非同期処理の変数置き換え問題などと関連あり?
- メソッドやプログラムの最終目的に合わない問題の解決は分離する
- 1コードには1タスク
- ひとまずコードの役名を説明し、その説明をクリーンコードのために使う
- コードの作成後に 「このコードの高水準のゴールは?」と自問する
- コードの一行毎に 「これは↑のゴールに関係ある? もし関係ない小問題の解決のためではない?」と自問する
- ある程度「関係ない行」が集まったら追出してコードを分離
- 完全に分離する対象 (要約している dokimyj の個人的な意見です)
- http 連結(もしくは curl)して結果を集めるコード
- 外部モジュールに連携するコード (git など)
- ファイルストリーム専用のコード
- エラーやログを出すコード
- 完全に分離したらその小問題用のコード自体の改善箇所が見えるかも?
- Wrapper を使う場面
- 使っているライブラリのインターフェースが綺麗ではない場合
- 必要な物がインターフェースに欠けた場合
- ただ、上記でもコアのコードとは分離して実装
- 初期化、変数の洗浄、入力パシング、ビジネスロジックが混ぜ込んだら可読性が崩れる
- コードは一つのタスクを担うこと
- やってるタスクを全まとめて、タスク毎に分離
- コードでやりたいことを文章で書く (英語で)
- キーワードになる文句に注目
- その注目した部分を実装
- 認証ロジックで「未認可」状態をリターンする
- if 条件に否定を連続で入れるより、
- 肯定の条件を通しながら
else
で「未認可」をリターンする
- 直接実装するより、ライブラリにすでにある機能をただ気づかない可能性もある
- 役名や動作を描写しながら既存のことを活用できるか考慮
- 使っているライブラリを調べるのも大事 (dokimyj の感想)
*rubberducking
デバッグや問題解決に着手する前に、ぬいぐるみや誰かに今の状況、コードの目的などを説明する
→ その過程の中で自然に問題が解決される
→ もし説明ができなかったら、要件やプロセスに何かが欠けている
- 一番読み易いコードは「コードなし(
no code at all
)」 - だいぶの場面で、我らがわざとコードを書く必要は実質的にない
- 既存ライブラリが綺麗で立派なコードを纏っている
- ちゃんとみると一部分コードを書かずに妥協できる部分がある
- 要件を減らすことができたら問題の解決が簡単になる
- 機能が増えると、コードの量は必然的に増える
- ユティリティーコードはなるべく多めに作ろう
- 使わないコードや機能は除却しましょう
- ちゃんと分離し、各コードを独立的にしましょう
- コードベースは「軽い」状態をいつも維持するように意識しましょう
- コードを書かなくていい
- ライブラリでデザイン・デバッグ・リファクタリング・最適化・テストをやってるから、その機能にかかる手間が減る
- だから定期的に 15分間自分が使っている言語のライブラリ API を読みましょう
- テストコードが保つべきの徳目
- 読み易い
- 他の方が書き換え・追記しやすい
- テストが紛らわしい・読みづらい場合
- 他の方が実際コードの修正を躊躇
- テストを追記しない
- 重要ではない情報を隠す
- 重要な情報は明らかに見せる
- 繰り返しは先頭で(予め)宣言
- 不要コードはなくす
- エラーは見やすく書き直す(
assert()
など) - 欲しい出力があれば直接書いてもおk
- 一番シンプルな例を選ぶ
- ただ、コードを完璧に試せるサンプルであること
- 余計に大きめ、華やかな例は使わない
- ケースによりテストを分離して繰り返す
Test_
接頭語をつける- そのあとは「どの機能か」「何をリターンするか」「どの場面を試すか」をつける
- 名前が長くなっても、実際このメソッドを使うのはテストのみなので問題ない
- なるべく明確にテストの目的を表す説明として書きましょう
- 逆にテストを書いてからコードを書く
- コードをデザインしやすくなる
- 独立的なコードも書きやすくなる
- テストし辛いコード
特徴 | テスト場面の問題 | デザイン場面の問題 |
---|---|---|
グローバル変数の利用 | 最初に変数の洗浄が必要 | どの機能から影響されるかわからない。機能も独立的に考えられない。理解するために全体を理解すべし。 |
外部コンポーネンツに依存しすぎ | セットアップが多すぎてテストし難い | 依存性が一つ崩れるとシステムが崩れ易い。リファクタリングが難しい。失敗と復旧の道が多すぎる。 |
こーどの結果が確定的ではない | 不安定、偶々の失敗が無視される | プログラムの競合状態や再現できないバグが発生する可能性。説明しづらくなる。バグ追跡・解決が難しい。 |
- テストしやすいコード
特徴 | テスト場面の利点 | デザイン場面の利点 |
---|---|---|
変数の汚染がほぼなし | テストを書く時の手間が減る | シンプル、わかり易い |
1コード1タスク | テストケースが減る | 更に簡単、更にモジュール化、更に独立的 |
他のコードへの依存が少ない | テストが独立的 | システムが並列的、他の部分を邪魔さずにコードの除却・修正ができる |
シンプルでちゃんと定義されているインターフェース | 挙動を明確に試せる | 習い易い、再使用率が上がる |
- ただ、過度な TDD 志向は
- 可読性を犠牲しながらテスト中心設計
- 100% カバレッジに執着しすぎ
- 開発の本質を失い、テストの通過のみを求める開発 になるから注意を。
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment