
「良いコードを書く」より先に考えたい、開発哲学の育て方
はじめに:なぜ「哲学」なのか
日々コードを書いていると、判断に迷う場面が数え切れないほど訪れます。
- このロジック、サービス層に書くべきか、ドメイン層に書くべきか
- テストをどこまで書けば「十分」と言えるのか
- 技術的負債を返済するタイミングは今なのか、後なのか
- チームメンバーの書いたコード、指摘すべきか見守るべきか
こうした問いに対して、「正解」はひとつではありません。状況によって答えが変わりますし、同じ状況でも人によって判断が分かれます。
しかし、経験を積んだエンジニアの多くは、こうした曖昧な局面で迷いが少ないように見えます。彼らは特別な知識を持っているのでしょうか。それとも、単に経験の蓄積なのでしょうか。
私の観察と経験から言えば、その差は「知識量」ではなく「判断の軸」にあります。この軸のことを、本記事では開発哲学と呼びます。
開発哲学とは、技術選定やコード設計の背後にある、その人なりの価値観や原則のことです。言語化されていないことも多く、本人すら意識していない場合もあります。しかし、この哲学があるかないかで、日々の意思決定の質と速度は大きく変わります。
本記事では、開発哲学とは何か、なぜ重要なのか、そしてどうすれば自分の中に育てていけるのかを、できるだけ具体的に掘り下げていきます。
前提整理:本記事で扱う「開発哲学」の定義
開発哲学とは何か
開発哲学という言葉は、人によって異なる意味で使われます。本記事では、以下のように定義します。
開発哲学とは、ソフトウェア開発における意思決定の拠り所となる、個人またはチームの価値観・原則・信念の体系である。
抽象的に聞こえるかもしれませんので、具体例を挙げます。
| 場面 | 哲学がない場合の判断 | 哲学がある場合の判断 |
|---|---|---|
| 新機能の設計 | 「とりあえず動くものを作る」 | 「変更が発生しやすい箇所を特定し、そこを疎結合にする」 |
| コードレビュー | 「動いているから問題ない」 | 「次の開発者が読んで30秒で意図が分かるか」を基準にする |
| 技術選定 | 「流行っているから採用」 | 「チームの学習コストと運用負荷を考慮して選ぶ」 |
| 障害対応 | 「原因を直せば終わり」 | 「同種の問題を検知・予防する仕組みを考える」 |
哲学があると、判断に一貫性が生まれます。一貫性があると、チームメンバーが行動を予測しやすくなり、コミュニケーションコストが下がります。
開発哲学は「正解」ではない
重要な注意点があります。開発哲学は、唯一の正解ではありません。
- 「テストカバレッジは80%以上を維持すべき」という哲学もあれば
- 「カバレッジよりも重要なパスのテストを厚くすべき」という哲学もあります
どちらが正しいかは、プロジェクトの性質やチームの状況によって変わります。大切なのは、「なぜその原則を選んだのか」を説明でき、状況に応じて柔軟に見直せることです。
想定読者
本記事は、以下のような方を想定しています。
- 実務経験1〜7年程度のエンジニア
- 「もっと良い判断ができるようになりたい」と考えている方
- 技術書を読んでも、実務での活かし方がピンとこない方
- 先輩や上司の判断の背景を理解したい方
逆に、「すでに自分なりの哲学が確立していて、それを言語化できる」方にとっては、確認や整理の材料になるかもしれません。
第1章:開発哲学がなぜ必要なのか
情報過多時代の判断疲れ
現代のソフトウェア開発は、選択肢が多すぎます。
- 言語だけでも数十種類
- フレームワークは毎年新しいものが登場
- アーキテクチャパターンも、モノリス、マイクロサービス、モジュラーモノリス……
- CI/CD、IaC、オブザーバビリティ、コンテナ、サーバーレス……
この状況で、すべての選択肢を比較検討してから判断するのは現実的ではありません。どこかで「切る」必要があります。
哲学は、この「切る」ための基準を提供します。
例えば、「チームが習熟していない技術は、よほどの理由がない限り採用しない」という原則があれば、流行りの技術に振り回されずに済みます。これは保守的すぎると感じる方もいるでしょうが、少なくとも判断の軸があることで、議論が建設的になります。
経験だけでは足りない理由
「経験を積めば自然と判断力がつく」という考え方があります。これは半分正しく、半分は誤解です。
経験は確かに重要です。しかし、経験から学ぶには振り返りと言語化が必要です。
私がこれまで見てきた中で、経験年数は長いのに判断がブレる方には、ある共通点がありました。それは、「なぜその判断をしたのか」を問われたときに、「前もそうだったから」「なんとなくそう思った」としか答えられないことです。
一方、経験年数が浅くても判断に一貫性がある方は、「この状況ではAを重視すべきだと考えた。理由は〇〇だから」と説明できます。
経験を哲学に昇華させるには、意識的なプロセスが必要なのです。
哲学がチームにもたらす価値
開発哲学は、個人の判断力を高めるだけでなく、チーム全体にも影響を与えます。
1. 暗黙知の共有
「うちのチームでは、こういう場合はこうする」という暗黙のルールは、どのチームにも存在します。しかし、それが言語化されていないと、新しいメンバーが加わったときに混乱が生じます。
開発哲学を言語化し、ADR(Architecture Decision Record)やチームのWikiに残すことで、知識の継承がスムーズになります。
2. 議論の質の向上
「AとBどちらが良いか」という議論は、判断基準がないと平行線になりがちです。「うちのチームはXを重視する」という合意があれば、「Xの観点ではAの方が優れている」という形で議論を収束させやすくなります。
3. 意思決定の委譲
チームの哲学が共有されていると、リーダーがすべての判断に関わる必要がなくなります。メンバーが自律的に判断できるようになり、チーム全体のスループットが上がります。
第2章:実践すべき開発哲学の例
ここからは、実務で役立つと私が考える開発哲学をいくつか紹介します。
これらは「正解」ではなく「参考」です。そのまま取り入れるのではなく、自分の状況に照らして咀嚼してください。
2-1. YAGNI(You Aren't Gonna Need It)
概要: 「今必要でない機能は作らない」という原則です。
なぜ重要か:
将来の拡張に備えて汎用的に作ろうとすると、以下の問題が起きます。
- 実装コストが増える
- コードが複雑になり、理解しづらくなる
- 結局使われない機能が残る
- 予測が外れたとき、修正コストが高くなる
実務での適用:
// 悪い例:将来のために汎用化しすぎる
class DataProcessor<T, U, V> {
// 複雑なジェネリクスと設定オプション
// 実際には1種類のデータしか処理しない
}
// 良い例:今必要なものだけ作る
class UserDataProcessor {
// シンプルで目的が明確
// 後から拡張が必要になれば、その時点で対応する
}
注意点:
YAGNIは「設計を考えなくてよい」という意味ではありません。「変更しやすい構造にしておくが、今は最小限で実装する」というバランスが大切です。
2-2. 可逆性を重視する
概要: 判断を後から変更できる余地を残す。
なぜ重要か:
ソフトウェア開発では、最初から完璧な判断ができることは稀です。要件は変わり、理解は深まり、状況は変化します。
重要なのは、「間違えないこと」ではなく、「間違いに気づいたときに修正できること」です。
実務での適用:
- データベーススキーマの変更は難しいので、最初に慎重に設計する
- 一方、アプリケーションコードは比較的変更しやすいので、早めに実装して検証する
- 外部サービスとの統合は、抽象層を挟んで依存を疎にする
- 大きな判断(言語、フレームワーク、アーキテクチャ)は、複数案を検討し、切り戻し計画を考える
例: 外部APIとの統合
// 直接依存(可逆性が低い)
import { StripeClient } from 'stripe';
class PaymentService {
async charge(amount: number) {
const stripe = new StripeClient();
return stripe.charges.create({ amount });
}
}
// 抽象層を挟む(可逆性が高い)
interface PaymentGateway {
charge(amount: number): Promise<ChargeResult>;
}
class StripeGateway implements PaymentGateway {
async charge(amount: number) {
// Stripe固有の実装
}
}
class PaymentService {
constructor(private gateway: PaymentGateway) {}
async charge(amount: number) {
return this.gateway.charge(amount);
}
}
後者は、決済プロバイダを変更する可能性がある場合に有効です。ただし、「絶対に変更しない」と確信できるなら、前者の方がシンプルです。
2-3. 明示性を優先する
概要: 暗黙の挙動より、明示的な記述を選ぶ。
なぜ重要か:
コードは書く時間より読む時間の方が長いです。自分が書いたコードでも、半年後には他人が書いたもののように感じます。
暗黙の挙動(マジックメソッド、暗黙の型変換、設定より規約など)は、慣れていれば便利ですが、知らない人には罠になります。
実務での適用:
# 暗黙的(何が起きるか分かりにくい)
class User:
def __getattr__(self, name):
return self._data.get(name)
# 明示的(挙動が予測しやすい)
class User:
def __init__(self, name: str, email: str):
self.name = name
self.email = email
トレードオフ:
明示性を追求しすぎると、冗長で読みにくくなることもあります。チームの慣習や言語の文化を考慮して、バランスを取る必要があります。
2-4. 失敗を前提に設計する
概要: 「正常系」だけでなく「異常系」を最初から考慮する。
なぜ重要か:
本番環境では、想定外のことが必ず起きます。
- ネットワークは遅延し、切断する
- 外部サービスはダウンする
- データは想定外の形式で届く
- ユーザーは想定外の操作をする
「正常に動く」ことを前提にしたシステムは、異常時に壊滅的な障害を起こします。
実務での適用:
- タイムアウトを明示的に設定する
- リトライ戦略を設計する(指数バックオフなど)
- サーキットブレーカーを導入する
- 入力値は信頼せず、バリデーションする
- 失敗時のログを十分に残す
// 失敗を考慮していない
async function fetchUser(id: string) {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
// 失敗を前提にしている
async function fetchUser(id: string): Promise<Result<User, FetchError>> {
try {
const response = await fetch(`/api/users/${id}`, {
signal: AbortSignal.timeout(5000), // タイムアウト
});
if (!response.ok) {
return { ok: false, error: new FetchError(response.status) };
}
const data = await response.json();
const parsed = UserSchema.safeParse(data); // バリデーション
if (!parsed.success) {
return { ok: false, error: new ValidationError(parsed.error) };
}
return { ok: true, value: parsed.data };
} catch (e) {
return { ok: false, error: FetchError.fromException(e) };
}
}
後者は冗長に見えますが、本番で問題が起きたときのデバッグ効率が格段に違います。
2-5. 計測してから最適化する
概要: 推測ではなく、データに基づいて判断する。
なぜ重要か:
パフォーマンス最適化は、間違った場所に時間を使いがちです。
「このループが遅そう」と思って最適化しても、実際のボトルネックは別の場所(データベースクエリ、ネットワーク遅延など)であることが多いです。
実務での適用:
- まず動くものを作る
- 本番に近い環境でプロファイリングする
- ボトルネックを特定する
- 効果が大きい箇所から改善する
- 改善後に再度計測して効果を確認する
よくある失敗:
- 「N+1クエリがあるから遅い」と思い込んで最適化したが、実際は呼び出し回数が少なく影響がなかった
- 「マイクロ最適化」に時間を費やしたが、全体の処理時間に対する寄与は0.1%未満だった
第3章:知見獲得までのプロセス
ここまで開発哲学の例を紹介してきましたが、本記事で最も伝えたいのは「哲学の内容」ではなく「哲学を獲得するプロセス」です。
他人の哲学をそのまま借りても、自分のものにはなりません。自分で考え、試し、振り返ることで、初めて血肉になります。
3-1. 観察する
最初のステップは、観察です。
コードを観察する:
- オープンソースプロジェクトのコードを読む
- 自社の既存コードベースを読む
- レビューでの指摘内容を記録する
読むときは、「何をしているか」だけでなく「なぜこう書いたのか」を考えます。
人を観察する:
- 先輩やテックリードがどう判断しているか注目する
- 判断の背景を質問する
- 設計レビューや技術選定の議論に参加する
プロジェクトを観察する:
- うまくいったプロジェクトとそうでないプロジェクトの違いを分析する
- 障害報告書を読み、根本原因を考える
3-2. 仮説を立てる
観察から得た気づきを、仮説の形で言語化します。
例:
- 「テストがないコードは、変更時にバグが発生しやすいのではないか」
- 「早期に本番環境で検証したプロジェクトは、後から大きな手戻りが少ないのではないか」
- 「ドキュメントが充実しているチームは、オンボーディングが速いのではないか」
仮説は正しくなくても構いません。大切なのは、言語化することです。
3-3. 検証する
仮説を実際の開発で試してみます。
- 自分が担当する機能で、仮説に基づいた設計や実装を試す
- チームに提案し、フィードバックを得る
- 結果を記録する
検証で重要なのは、成功も失敗も同等に価値があるということです。
「テストを書いたのにバグが出た」という経験は、「どういうテストを書くべきか」の学びにつながります。
3-4. 振り返る
検証結果を振り返り、仮説を更新します。
定期的に振り返りの時間を取ることをお勧めします。週に30分でも、月に1回でも構いません。
振り返りの問い:
- 今週、どんな判断をしたか
- その判断の根拠は何だったか
- 結果はどうだったか
- 次回、同じ状況があればどう判断するか
私の場合、技術的な振り返りを手帳やNotionに書き溜めています。半年後、一年後に読み返すと、自分の考え方の変化が分かって興味深いです。
3-5. 言語化して共有する
自分の中で整理できたら、他者に説明してみます。
- チーム内での勉強会
- 社内ブログや社外ブログ
- コードレビューでのコメント
- 後輩への説明
説明しようとすると、自分の理解が曖昧な部分が見えてきます。「なんとなくこう思う」では説明できず、論理を整理する必要があります。
また、他者からのフィードバックで視野が広がります。「その考え方は〇〇の状況では当てはまらないのでは?」という指摘は、哲学を精緻化するヒントになります。
3-6. 更新し続ける
開発哲学は、一度確立したら終わりではありません。
- 技術は進歩する
- 自分の経験は蓄積される
- チームやプロジェクトの状況は変わる
5年前の「ベストプラクティス」が、今では「アンチパターン」になっていることもあります。
「自分の哲学を疑う」姿勢を持ち続けることが、成長し続けるエンジニアの特徴だと思います。
第4章:落とし穴と対処法
開発哲学を育てる過程で、陥りやすい落とし穴があります。
4-1. 原則の絶対化
症状: 「〇〇は絶対にやるべき/やるべきでない」と固執する。
例:
- 「DRY原則があるから、少しでも重複があれば共通化すべき」
- 「マイクロサービスは正義、モノリスは悪」
対処:
原則はすべて「ある文脈で」有効なものです。文脈が変われば、最適解も変わります。
「この原則は、どういう状況で有効か」「どういう状況では例外になるか」を常に考えます。
例えば、DRY原則は「知識の重複」を避けることが本質であり、「コードの重複」を機械的に排除することではありません。異なる文脈で同じコードが存在していても、それぞれ独立に変更される可能性があるなら、無理に共通化しない方が良い場合もあります。
4-2. 流行への過剰適応
症状: 新しい概念やツールに飛びつき、すぐに取り入れようとする。
例:
- 「AIペアプログラミングがあれば、テストは不要になる」
- 「サーバーレスがトレンドだから、すべてLambdaで作るべき」
対処:
新しい技術やプラクティスには、必ずトレードオフがあります。
採用を検討する際は、以下を問います:
- この技術が解決する問題は、自分たちにとって重要か
- 導入・学習・運用のコストはどれくらいか
- 失敗した場合のリカバリーは可能か
「枯れた技術」にも価値があります。十分に検証され、エッジケースが潰されており、情報が豊富だからです。
4-3. 経験の過大評価
症状: 「自分はこれでうまくいった」という経験を一般化しすぎる。
例:
- 「前のプロジェクトではMongoDBでうまくいったから、今回もMongoDBにしよう」
- 「私はテストを書かなくてもバグを出さない」
対処:
成功体験は、その状況特有の要因に依存していることがあります。
- チーム構成が違えば、同じ手法でも結果が変わる
- 規模が違えば、同じアーキテクチャでも適合度が変わる
- 「バグを出さない」のではなく、「バグに気づいていない」可能性もある
自分の経験を相対化し、「この経験は、どういう条件下で有効か」を考える習慣をつけます。
4-4. 理想主義への傾倒
症状: 理論的に「正しい」設計や手法に固執し、現実の制約を軽視する。
例:
- 「クリーンアーキテクチャを完全に適用しなければならない」
- 「テストカバレッジ100%を達成するまでリリースできない」
対処:
ソフトウェア開発は、理想と現実のバランスを取る営みです。
- 期日がある
- 予算がある
- チームのスキルに限界がある
- ユーザーは完璧を待っていない
「正しさ」の追求は大切ですが、「今、この状況で、どこまでやるか」を判断する力も同様に大切です。
第5章:規模・フェーズ別の考え方
開発哲学は、チームの規模やプロダクトのフェーズによっても調整が必要です。
スタートアップ初期(1〜5名)
重視すべきこと:
- スピード
- 柔軟性
- 仮説検証
適した哲学:
- 「動くものを早く出し、フィードバックを得る」
- 「完璧な設計より、変更しやすい最小限の設計」
- 「技術的負債は承知の上で、後で返済する覚悟を持つ」
注意点:
「スピード重視」を「品質無視」と混同しないこと。最低限のテスト、デプロイの自動化、ログの整備は、早い段階で導入した方がトータルでは速くなります。
成長期(5〜30名)
重視すべきこと:
- スケーラビリティ(チームの)
- 知識の共有
- 品質の安定
適した哲学:
- 「暗黙知を形式知に変える」
- 「属人化を防ぐ設計・ドキュメント」
- 「新メンバーが迷わない仕組みを作る」
注意点:
このフェーズでルールを増やしすぎると、スピードが落ちます。「なぜこのルールが必要か」を常に問い、不要になったルールは廃止する勇気を持ちます。
成熟期(30名以上)
重視すべきこと:
- 安定性
- 予測可能性
- ガバナンス
適した哲学:
- 「変更の影響範囲を限定する設計」
- 「自動化による一貫性の担保」
- 「チーム間の疎結合、チーム内の密結合」
注意点:
安定性を重視しすぎると、変化への対応が遅くなります。「守るべきもの」と「変えるべきもの」を明確に分け、変化を許容する領域を意識的に設けます。
まとめ:開発哲学を育てるために
本記事で伝えたかったことを整理します。
開発哲学とは
- 技術的な意思決定の背後にある、価値観・原則・信念
- 判断に一貫性をもたらし、コミュニケーションコストを下げるもの
- 唯一の正解ではなく、状況に応じて調整すべきもの
実践すべき哲学の例
- YAGNI:今必要なものだけを作る
- 可逆性:後から変更できる余地を残す
- 明示性:暗黙の挙動より明示的な記述を選ぶ
- 失敗前提:異常系を最初から考慮する
- 計測優先:推測ではなくデータで判断する
知見獲得のプロセス
- 観察する(コード、人、プロジェクト)
- 仮説を立てる(言語化する)
- 検証する(試してみる)
- 振り返る(結果から学ぶ)
- 共有する(他者に説明する)
- 更新し続ける(固定化しない)
避けるべき落とし穴
- 原則の絶対化
- 流行への過剰適応
- 経験の過大評価
- 理想主義への傾倒
読者へのメッセージ
開発哲学は、一朝一夕で身につくものではありません。年単位の時間がかかりますし、完成することもありません。
しかし、「自分はどういう判断基準を持っているのか」を意識し始めるだけで、日々の開発の質は変わり始めます。
今日からできることがあるとすれば、それは「なぜ」を問う習慣をつけることです。
- なぜこの設計にしたのか
- なぜこのライブラリを選んだのか
- なぜこのテストを書いたのか(書かなかったのか)
「なんとなく」を減らし、「なぜなら」を増やす。それが、開発哲学を育てる第一歩です。
皆さんの開発が、より良い判断に支えられたものになることを願っています。

