良いコードの書き方|可読性・保守性を高める10の原則と実践例
良いコードとは、正しく動くだけでなく、他人が読んで理解でき、安全に変更できるコードのことです。動くコードと良いコードは別物で、その差は開発が長期化するほど保守コストとして跳ね返ります。この記事では、言語を問わず通用する10の原則を、Javaの具体例とともにまとめます。原則そのものはPython・JavaScript・Goなどでも同じように使えます。「良いコード」という考え方の背景を先に押さえたい場合はリーダブルコードとは(優れたコードの特徴)もあわせて読むと理解が深まります。
まとめ:良いコードを支える10の原則
迷ったら「半年後の自分が読んで、こわがらずに直せるか」で判断します。最も効くのは名前・スコープ・重複の3点で、ここが整うだけで読みやすさは大きく変わります。以下が本記事で扱う原則です。
- 意図が伝わる名前をつける(略語・連番を避ける)
- 変数のスコープと状態を最小化する
- 重複を避ける(DRY)
- 1つの責務に絞る(単一責任・SRP)
- 分岐はガード節で浅くする
- 関数とクラスは小さく保つ
- 継承よりコンポジションを優先する
- マジックナンバーを排除し、コメントには「なぜ」を書く
- 静的解析ツールで品質チェックを自動化する
- AI生成コードのレビューを前提に可読性を上げる
良いコードとは何か:可読性・保守性・変更容易性の3条件
良いコードの条件は3つに集約できます。第一に可読性で、コードを読んだ人が処理の意図と流れをすぐに追えること。第二に保守性で、バグ修正や仕様変更を少ない手間で行えること。第三に変更容易性で、ある箇所を直したときに他への影響が狭く閉じていることです。この3つは独立ではなく、可読性が高いコードは自然と保守しやすく、変更にも強くなります。
逆に言えば、動作が正しくても読み手を迷わせるコードは「良いコード」ではありません。ソフトウェアの費用の大半は最初の実装ではなく、その後の保守・改修で発生します。読みやすさへの投資は、長期のプロジェクトほど確実に回収できます。
意図が伝わる命名
名前はコードのなかで最も頻繁に読まれる情報です。calcやdata1のような名前は、何を計算し何を保持しているのかを読み手に推測させ、その推測が誤解とバグを生みます。名前だけで役割が分かるようにするのが基本です。
| 避けたい名前 | 意図が伝わる名前 |
|---|---|
| d, tmp, data1 | elapsedDays, taxIncludedPrice, activeUsers |
| flag, check() | isPublished, hasPermission() |
| manager, util | OrderValidator, DateFormatter |
真偽値はis/has/canで始め、メソッド名は動詞で始めると意図が明確になります。名前は短さより明確さを優先しますが、スコープが狭いループ変数のiのように、範囲が限定的で慣習が確立している場合は短い名前で構いません。言語ごとのキャメルケース・スネークケースの使い分けはプログラミングの命名規則まとめで確認できます。
変数スコープと状態の最小化
変数は「使う直前で宣言し、使い終わったら消える」状態が理想です。スコープが広いほど、その変数がどこで書き換えられるか読み手が追う範囲が増え、意図しない変更によるバグの温床になります。ループでしか使わない変数はループの内側で宣言します。
for (int i = 0; i < orders.size(); i++) {
int amount = orders.get(i).total();
System.out.println(amount);
}
ここでamountはループの外からは見えず、各回の値が次の回に漏れることもありません。クラスのフィールド(インスタンス変数)で足りる情報をメソッド内のローカル変数で済ませられるなら、フィールドにはしません。状態を持つ場所が少ないほど、コードは追いやすくなります。
重複の排除(DRY)
同じ知識をコードの複数箇所に書くと、片方だけ直して片方を直し忘れる不整合が起きます。DRY(Don't Repeat Yourself)は、同じ知識を1か所にだけ置く原則です。設定値や定数は1つのクラスにまとめ、各所からは参照だけさせます。
public final class ApiConfig {
public static final String BASE_URL = "https://api.example.com";
public static final int TIMEOUT_MS = 5000;
}
ただし、たまたま同じ形をしているだけのコードまで無理に共通化すると、後で別々に変更したくなったとき逆に足かせになります。DRYが対象とするのは「同じ形」ではなく「同じ知識」です。
責務の分離(単一責任・SRP)
単一責任の原則(SRP)は、1つのクラスやメソッドが担う責務を1つに絞る考え方です。データベース処理・業務ロジック・画面表示を同じクラスに詰め込むと、変更理由が複数になり、片方の修正が別の機能を壊します。責務を分ければ、変更の影響範囲がそのクラスの内側に閉じます。判断に迷う場合はSRP(単一責任の原則)とはで、違反例と過剰分割の見極めを確認してください。
ガード節による浅いネスト
条件分岐が入れ子になるほど、いまどの条件下にいるのかを読み手が把握しづらくなります。ネストが3段を超えたら、まず設計を疑う合図です。有効なのがガード節で、満たすべきでない条件を先にreturnで弾き、本来の処理をネストの浅い位置に置きます。
// ネストが深い書き方
if (user != null) {
if (user.isActive()) {
sendMail(user);
}
}
// ガード節で浅くした書き方
if (user == null) return;
if (!user.isActive()) return;
sendMail(user);
後者は「対象外を先に除外し、残りが本処理」という流れが一目で分かります。else句を減らすことで、正常系のコードが縦一列に並び、読み下しやすくなります。
関数とクラスの小型化
1つの関数が何十行にもなり、画面をスクロールしないと全体を追えないなら、複数の責務が混ざっているサインです。目安としてメソッドは数十行、クラスは200行程度を超えたら分割を検討します(これは厳密な上限ではなく、見直しのきっかけです)。大きなメソッドは意味のまとまりごとに小さなメソッドへ抽出(Extract Method)します。
小さく保つと、名前でその処理の意図を説明でき、単体テストも書きやすくなります。テストしづらいと感じたら、たいていサイズか責務の分割が足りていません。分割のタイミングや判断基準はリファクタリングを行うべきタイミングで具体的に解説しています。
継承よりコンポジションの優先
継承は親クラスの変更が子クラスへ直接波及し、クラス同士が強く結びつきます。多くの場面では、機能を「継承する」より、必要な部品をオブジェクトとして「持たせる」コンポジションのほうが変更に強くなります。「車はエンジンである(継承)」より「車はエンジンを持つ(コンポジション)」のほうが現実に即しています。
class Engine {
void start() { System.out.println("engine start"); }
}
class Car {
private final Engine engine = new Engine();
void run() { engine.start(); }
}
共通の振る舞いを複数のクラスに持たせたいときは、インターフェースで「できること」を定義します。IS-A関係(AはBの一種)が明確なときだけ継承を使い、HAS-A関係(AはBを持つ)ならコンポジションを選ぶ、という基準で使い分けると迷いません。
マジックナンバーの排除と「なぜ」を書くコメント
コードに直接書かれた意味不明な数値(マジックナンバー)は、読み手にその根拠を推測させます。名前付き定数にすれば、値の意味と変更箇所が一目で分かります。
// 悪い例:60 が何を指すか読み手に伝わらない
if (idleSeconds > 60) logout();
// 良い例:定数名が意図を語る
static final int SESSION_TIMEOUT_SECONDS = 60;
if (idleSeconds > SESSION_TIMEOUT_SECONDS) logout();
コメントも同じ発想で、「何をしているか」はコード自身が語るべきなので、コメントには「なぜそうしたか」を書きます。仕様上この順序でなければならない理由、あえて非効率な実装を選んだ背景など、コードからは読み取れない判断を残すのが良いコメントです。処理を日本語に翻訳しただけのコメントは、コード変更時に更新され忘れて嘘になりやすいため、むしろ削るべきです。
静的解析ツールによる品質チェックの自動化
命名やネストの深さといった観点は、人のレビューだけに頼らず機械的にチェックできます。代表的なJava向けツールは次のとおりです。なお、かつて広く使われたFindBugsは2015年の3.0.1を最後にメンテナンスが止まり、Java 9以降に追従できていません。現在はそのフォークであるSpotBugsが正統な後継です。
| ツール | 役割 |
|---|---|
| Checkstyle | 命名・書式などコーディング規約の準拠チェック |
| PMD | 未使用変数・過度な複雑さなどの検出 |
| SpotBugs(旧FindBugs後継) | バイトコード解析による潜在バグの検出 |
| SonarQube | 上記を横断した品質の可視化・継続監視 |
これらをCI(継続的インテグレーション)に組み込み、コミットのたびに自動実行すれば、規約違反を人間がレビューで指摘する手間が減り、指摘が属人化しません。フォーマッタ(Spotlessなど)で整形を自動化すれば、インデントや空白の議論そのものが消えます。良いコードは「気をつける」より「仕組みで守る」ほうが安定します。
AI生成コード時代に高まる可読性の価値
生成AIがコードを書く場面が増えたことで、「良いコード」の価値はむしろ高まっています。AIは一見それらしいコードを大量に生成しますが、その正しさを判断するのは人間のレビューです。読みにくいコードはAIが出しても人が検証できず、責務が肥大化したコードはAIも文脈を取り違えて誤った修正を提案します。
ここまでの原則――小さな関数、明確な名前、浅い分岐、単一責任――は、人が読むためだけでなく、AIに正確な変更を促すための前提でもあります。プロンプトで「良いコードを書いて」と指示するより、レビュー時に本記事の観点で機械的に点検し、静的解析で裏を取るほうが確実です。AIに任せる領域が広がるほど、生成物の良し悪しを見抜く人間側の基準が競争力になります。ここは横並びの解説では触れられにくい論点ですが、実務では最も差がつくところだと考えています。
よくある質問
良いコードとは一言で言うと何ですか?
他人が読んで理解でき、安全に変更できるコードです。正しく動くことは最低条件にすぎず、可読性・保守性・変更容易性の3つを満たして初めて良いコードと呼べます。
Javaで良いコードを書くための最初の一歩は?
名前を丁寧につけることから始めるのが効果的です。変数・メソッド・クラスの名前が役割を語れば、コメントに頼らずとも意図が伝わります。あわせてCheckstyleやSpotBugsを導入し、規約違反や潜在バグを自動で検出できる環境を整えると、独学でも品質を安定させやすくなります。
リーダブルコードの原則との違いは?
本記事の原則の多くは、書籍『リーダブルコード』が示す考え方と重なります。違いは対象範囲で、リーダブルコードが命名やコメントなど「読みやすさ」に焦点を当てるのに対し、本記事は設計(コンポジション・単一責任)や自動化(静的解析)まで含めます。書籍側の要点はリーダブルコードとは(優れたコードの特徴)で整理しています。
避けるべきコードのアンチパターンは?
意味の分からない名前(data1など)、コピペで増えた重複コード、3段以上の深いネスト、数百行の巨大メソッド、根拠不明のマジックナンバーが代表例です。いずれも本記事の各原則の裏返しで、レビューで見つけたら分割・命名・定数化で解消します。
良いコードが書けるようになるには?
他人のコードを読む量を増やし、自分のコードをレビューしてもらう機会を作るのが近道です。原則を知識として覚えるより、指摘を受けて直す往復のなかで身につきます。静的解析ツールの指摘も、無償で受けられるレビューとして使えます。