Skip to content

Instantly share code, notes, and snippets.

@dokimyj
Last active September 19, 2021 09:17
Show Gist options
  • Save dokimyj/3717b539c89ed206d52fc7b06dea3465 to your computer and use it in GitHub Desktop.
Save dokimyj/3717b539c89ed206d52fc7b06dea3465 to your computer and use it in GitHub Desktop.
[JP] the Art of Readable Code Summary

序。何がいいコード?

  • 良いコードの判断が一気にできる場合も、そうでもない場合も存在する。
  • 良いコードの第一基準とは:「可読性」
    • 読者が読みやすくて、「完全な理解」にかかる時間が短いコードこそいいコード
    • コード的に、オーバヘッドが発生しない方が良いコード
  • 短いコード != 良いコード:コードを短縮しすぎて見づらくなることも絶対ある(括弧が重ねすぎなど)
  • 良いコードの他の条件ともコンフリクトしない

I. 表面的な改善

  • 良い変数名・コメント・コードの仕組み・空白の紡ぎとは?
  • 的確なところに適用させることを含む
  • 表面と言っても、全ラインにより影響があるから結構重要

1. 情報を名前に含める

  • tmpなどぼんやりする単語は選ばない
  • 一般的すぎの名前は避ける
  • 具体的な名前を選ぶ
  • 語尾・語頭に追加情報をつける
  • 名前の長さを決める
  • 名前形式を統一する

具体的の例

  • ex1) メソッド def get_page
    • どのページを、ページの何を獲得するメソッドか見せる名前にする
    • ページを持ってくるソースや方法などを表す方向で名前をつける方が良い(fetch_page_~~, download_page_~~など)
  • ex2) コンストラクター int Size()
    • どのサイズかわからないから、PageSize() とか MaxMemorySize() など
    • もしかしたら容量じゃなく、Height()Capacity() になるかもしれない
  • ex3) Thread クラスの中の Stop() の場合
    • もちろん動作としてストップさせるかもしれないけど、もっと明確化できれば…
    • スレッド Kill することだったら Kill() になる
    • 一時停止させることで、いつか再開される可能性が場合は Pause() にもなれる

色々使おう (類語辞書活用)

  • 英語は重複を避けるためにいろんな語彙を作ってきた言語
  • 多様な語彙で命名しても、個性より的確・明瞭に

ジェネリック(一般名)を避ける

  • 変数名の場合、単に役割だけを載せると retvaltmp_~~ になる場合が多すぎて、具体的にどう導出した値が割り当てられているかから命名することも良い
  • ただし、コンピューティングの合間に臨時的に使われる臨時変数などは tmp など、役割が明確に臨時である場合は一般的な命名でも良い。
  • その他、変数などのライフサイクルにより、命が短すぎる(他の動作や結果に影響がないツール変数)場合はそんなに悩まず、一般名詞でおk
  • ループ文のインデックスが中のところで使われる場合、(重ねループはさらに)普通の i, j, k より (用途やスコープがわかる prefix)_i をつける
  • キャメルケース・スネークケース・ケバブケースなど、適切に切り分け、意味を明瞭に表す

2. 誤解させない名前をつける

単に単語一つで終わらせないように

  • filterget などはプログラミング的に誤解を呼びガチ
  • とあるグループから絞り出して該当なしを除外するメソッドは filter より exclude をつける
  • get は DB に記録されているデータを持ってくるだけなど、単純なところのみにつけるように (中で計算や動作が行われる場合、それによってつける)

最大、最小、包括、除外を表す接頭語を活用

  • 英語の場合、以上・超過、もしくは未満・以下を明確に表す単語がない
  • 最大・最小を表すために max_min_ を変数名の先頭につけるのが非常に推奨
  • 範囲を定義する際
    • 最初の含めるには begin, 最後の値を除外するには end をつける
    • 先頭と最後、両方を含めるために firstlast もいい接頭語になりそう
  • bool 系の命名法
    • is~~(状態) や has~~(抱合) が理想的
    • 否定表現は避ける (ex: disable_ssl)

3. その他美学のための技

  • 一貫的なレイアウト、パターンがあり、読む方が慣れるように
  • 似てる仕事をするコードはなるべく似た見た目を保つように
  • 関係あるコードは一緒にまとめる

行を片付ける

  • 変数の初期化、メソッドの呼び出しの際、一行の中で処理するか、引数毎に改行するか明確にして統一する
  • コードの長さ、引数の数を考慮し、読みやすい方に関して読者側の目線で工夫しながらコードを書く
  • 同じコード束の中、非一貫的な書き方は必ず統一する

列の整列

  • 変数の位置、括弧の終わり目などは整列する
  • ただ、チーム内の約束やコード管理のために省略はおk
  • 役割、ロジックの類似性により改行を決める

変数・メソッドを書く順番について

  • ウェブ開発の場合、クライアント側での表示順番や入力順番と統一する
  • 重要度により、一番重要な変数から降順
  • 重要度がない場合、アルファベット順番も活用できる

一貫的な括弧使い

  • どちらのコードでも冗長性を保つ形でメソッド・オブジェクトの括弧をつける
  • チームでのスタイルがあればチームのスタイルに従う
  • 「正しさより一貫的なスタイル」

4. コメントの内容

  • コメントは「読者が作者が知ってる程度まで理解できるように書く」
  • 何をコメントするか、もしくはコメントしないか
  • コード作成時に考慮した部分などを記録するように
  • 読者の立場から、読む方はどの情報が必要か考えながらコメントを書く

コメントしないこと

  • コードを見ればすぐわかること
  • プログラマーなら誰でも知ってる基本的なこと
  • その他、新しくない情報

直感的なコメント

  • 文章より条件などをコード的に書いて見せる
  • 悪い変数名やメソッド名などに言い訳をつけるようなコメントはしない

作成時に考えた部分、悩んだ部分、改善の所を記録する

  • 開発の中、実際試してダメだった修正案
  • 最善だったこと、改善のマージンがない場合
  • 改善が必要だが、今度の修正に含めるには関係性が薄い所 (TODO:)

読者の立場から見ながらつける

  • 読者が気になりそうな部分
  • ありうるトラップ
  • モジュールやメソッドの概要・要約を初見でもすぐわかるように

5. コメント見直し

  • コメントはいつもコスパが最重要
  • 抽象的、曖昧な表現は避ける
  • 例をつけるのも説明しやすくなる
  • 理解しやすい、直感的な言葉を使う
  • できる限り、引数の変数名・コメントを明瞭化
  • 情報が圧縮されているコメントをつける

II. ループとロジックを単純化する

  • 読む方の精神的な負荷を減らすように
  • 複雑な制御文、大きすぎる表現、大数や多すぎる変数は避けよう
  • 読む方を毎度インタビューに招かせるコードはよくない

1. 制御フローを読みやすく

  • if文、ループ、その他制御文は自然な流れになるように
  • 読者が読み辞め、再び読むべきのコードは悪い
  • スレッド、インタラプト、例外処理、匿名ファンクション、仮想メソッドは把握しにくい

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 など一行条件式で避けれる

2. ロジックの細分化

  • コードチャンクより、細かく熟す方が消化し安い

説明・要約変数

  • リターンの結果物をそのまま使うことは効率的だが、結果物を表すに直感的ではない
  • リターンの結果を説明、もしくは要約してあげるように適切な変数名をつけて説明する必要がある

ショートサーキット

  • ド・モルガンの法則を活用する: !A && !B && !C == !(A||B||C)
  • ||&& で検証の漏れが発生する可能性がある (前の条件を満たしたら後ろはスキップするため)
    • だが、推奨
    • 特に変数に値をアサインする際、x = a || b || c のように truthy のみ割り当てることは見事

以上、超過、以下、未満

  • 範囲がかぶるか確認する際、「A の最初と B の最後が被らない」 と 「A の最後と B の最初が被らない」などを比較するのは誤りやすい
  • さらに、この各ケースに当てはまる場合のみ return false にしてどちらも当てはまらない場合 return true する方が楽だ

巨大ロジックを分け熟す

  • 一口に噛みすぎるとコードも読みにくいし、バグの可能性が上がる
  • 特に if ~ else 系の条件が多くなると更に読みづらくなる
    • 繰り返しのメソッドのリターンは変数として予め宣言
    • 長すぎるリタラルも予め宣言
  • 似てる動作をするロジックをメソッド化して、引数だけ入れて動かして変数を割り当てれる (C++ の場合だけど)

3. 変数の数を減らす

  • 変数の数が多くと変数が把握しにくい
  • スコープが広い程に変数の把握周期が長い
  • 変数の値が頻繁に変わると現在の値がわかりにくい

→ 変数の数、範囲、変化の回数は減らすべし

変数を除却するには

  • **2.**の「説明・要約する変数」とは違う、説明や要約にもならない不要な変数を減らす
  • 誰が見ても確かなリターンを保つ場合、臨時の変数はいらない (ex: Time.now.month など)
  • 中間結果を出したまま1条件式を閉じて、改めて条件式を開き、最終処理をしてリターンするのはいらない
  • while で 制御用の bool + continue より break がマシ

変数のスコープを減らす

  • 1メソッドの内で使う変数をメンバー変数として宣言しないように
  • if(変数 = とあるメソッド) 形式を使われる言語はこちらを活用する
  • JS の場合、 var なしで宣言する変数は完全にグローバルになってしまうため、注意を
  • var xxx = (function() { ... }()); の匿名関数の中に宣言された変数はプライベート変数になる
  • 領域が広い変数より、メソッドを作ってローカル変数をメソッドの引数として入れよう
  • 仕方なくからの変数を予め宣言するべきであれば、変数を使う所と近いところで宣言しよう

変数はなるべく immutable

  • 変数の値が変わる度、読者がその変数の値を覚えるのが難しくなる → 非同期処理の変数置き換え問題などと関連あり?

III. コードの再構成

  • メソッドやプログラムの最終目的に合わない問題の解決は分離する
  • 1コードには1タスク
  • ひとまずコードの役名を説明し、その説明をクリーンコードのために使う

1. 関係性薄い問題は分離する

  • コードの作成後に 「このコードの高水準のゴールは?」と自問する
  • コードの一行毎に 「これは↑のゴールに関係ある? もし関係ない小問題の解決のためではない?」と自問する
  • ある程度「関係ない行」が集まったら追出してコードを分離
  • 完全に分離する対象 (要約している dokimyj の個人的な意見です)
    • http 連結(もしくは curl)して結果を集めるコード
    • 外部モジュールに連携するコード (git など)
    • ファイルストリーム専用のコード
    • エラーやログを出すコード
  • 完全に分離したらその小問題用のコード自体の改善箇所が見えるかも?
  • Wrapper を使う場面
    • 使っているライブラリのインターフェースが綺麗ではない場合
    • 必要な物がインターフェースに欠けた場合
    • ただ、上記でもコアのコードとは分離して実装

2. 問題は一つずつ

  • 初期化、変数の洗浄、入力パシング、ビジネスロジックが混ぜ込んだら可読性が崩れる
  • コードは一つのタスクを担うこと
  • やってるタスクを全まとめて、タスク毎に分離

3. コードは考え通り

  • コードでやりたいことを文章で書く (英語で)
  • キーワードになる文句に注目
  • その注目した部分を実装

ロジックを明確に

  • 認証ロジックで「未認可」状態をリターンする
    • if 条件に否定を連続で入れるより、
    • 肯定の条件を通しながら else で「未認可」をリターンする

ライブラリの活用

  • 直接実装するより、ライブラリにすでにある機能をただ気づかない可能性もある
  • 役名や動作を描写しながら既存のことを活用できるか考慮
  • 使っているライブラリを調べるのも大事 (dokimyj の感想)

rubberducking デバッグや問題解決に着手する前に、ぬいぐるみや誰かに今の状況、コードの目的などを説明する → その過程の中で自然に問題が解決される → もし説明ができなかったら、要件やプロセスに何かが欠けている

4. 直接書くコードを減らせよう

  • 一番読み易いコードは「コードなし(no code at all)」
  • だいぶの場面で、我らがわざとコードを書く必要は実質的にない
  • 既存ライブラリが綺麗で立派なコードを纏っている

用件をこなして明確化する

  • ちゃんとみると一部分コードを書かずに妥協できる部分がある
  • 要件を減らすことができたら問題の解決が簡単になる

コードベースを最小化しましょう

  • 機能が増えると、コードの量は必然的に増える
  • ユティリティーコードはなるべく多めに作ろう
  • 使わないコードや機能は除却しましょう
  • ちゃんと分離し、各コードを独立的にしましょう
  • コードベースは「軽い」状態をいつも維持するように意識しましょう

既存ライブラリを使うと

  • コードを書かなくていい
  • ライブラリでデザイン・デバッグ・リファクタリング・最適化・テストをやってるから、その機能にかかる手間が減る
  • だから定期的に 15分間自分が使っている言語のライブラリ API を読みましょう

IV. テストコードの書き方

  • テストコードが保つべきの徳目
    • 読み易い
    • 他の方が書き換え・追記しやすい
  • テストが紛らわしい・読みづらい場合
    • 他の方が実際コードの修正を躊躇
    • テストを追記しない

テストコードを読みやすく

  • 重要ではない情報を隠す
  • 重要な情報は明らかに見せる
  • 繰り返しは先頭で(予め)宣言
  • 不要コードはなくす
  • エラーは見やすく書き直す(assert()など)
  • 欲しい出力があれば直接書いてもおk

良いテストの入力を選ぶ

  • 一番シンプルな例を選ぶ
  • ただ、コードを完璧に試せるサンプルであること
  • 余計に大きめ、華やかな例は使わない
  • ケースによりテストを分離して繰り返す

テストにいい名前をつける

  • Test_ 接頭語をつける
  • そのあとは「どの機能か」「何をリターンするか」「どの場面を試すか」をつける
  • 名前が長くなっても、実際このメソッドを使うのはテストのみなので問題ない
  • なるべく明確にテストの目的を表す説明として書きましょう

TDD

  • 逆にテストを書いてからコードを書く
  • コードをデザインしやすくなる
  • 独立的なコードも書きやすくなる
  • テストし辛いコード
特徴 テスト場面の問題 デザイン場面の問題
グローバル変数の利用 最初に変数の洗浄が必要 どの機能から影響されるかわからない。機能も独立的に考えられない。理解するために全体を理解すべし。
外部コンポーネンツに依存しすぎ セットアップが多すぎてテストし難い 依存性が一つ崩れるとシステムが崩れ易い。リファクタリングが難しい。失敗と復旧の道が多すぎる。
こーどの結果が確定的ではない 不安定、偶々の失敗が無視される プログラムの競合状態や再現できないバグが発生する可能性。説明しづらくなる。バグ追跡・解決が難しい。
  • テストしやすいコード
特徴 テスト場面の利点 デザイン場面の利点
変数の汚染がほぼなし テストを書く時の手間が減る シンプル、わかり易い
1コード1タスク テストケースが減る 更に簡単、更にモジュール化、更に独立的
他のコードへの依存が少ない テストが独立的 システムが並列的、他の部分を邪魔さずにコードの除却・修正ができる
シンプルでちゃんと定義されているインターフェース 挙動を明確に試せる 習い易い、再使用率が上がる
  • ただ、過度な TDD 志向は
    • 可読性を犠牲しながらテスト中心設計
    • 100% カバレッジに執着しすぎ
    • 開発の本質を失い、テストの通過のみを求める開発 になるから注意を。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment