本記事ではOOPの基礎的なパラダイムとAIの利活用について記載しております。
当たり前な事ばかり書いているかもしれませんが、発展途上エンジニアのアウトプットにお付き合いいただけますと幸いです。
はじめに
「似たような機能だけど、少しだけ仕様が違う…」
この「少しだけ違う」機能の量産は、多くの開発現場が抱える根深い課題です。
コードはコピペで肥大化し、保守性は低下の一途をたどります。
この課題に対し、私たちは 「拡張性」 そのものを設計するというアプローチで改善に向けてトライしました。
そのポイントとなったのが、
- abstractクラスによるスケーラビリティの高いClass設計
- その設計思想をAIに実行させるための指示書(.mdc)
の組み合わせです。
執筆にあたって実際に現場で試してみたのですが、本当にプロンプト一つで要件を満たすコードを生成する事が出来て感動した為、その勢いで記事を執筆しています笑
(動く事だけを着目すると本当に1文字も書かずに完成しました。
実際にmainにマージするとなったら生成されたコードに対してのレビューと多少のチューニングで済みそうな精度になったのでその共有をさせていただきます。)
本記事では、この2つが生み出す相乗効果と、機能拡張をプロンプト一発で完了させ人間がレビューするまでのプロセスを高速化するためにやった事について記事を書かせていただきます。
技術Stack
※Class設計とAIへの指示書についての解説なので言語とかは何でもいいです
- Laravel(v12系)
- テキストエディタ:Cursor
第1章:設計 - 抽象Classによる「拡張のテンプレート化」
機能拡張(似た機能の量産)で最も重要なのは、「何が共通で、何が異なる部分なのか」を見極め、変更箇所を 「最小限」 に抑えることです。
そのために、抽象Classを徹底的に活用し、機能追加のためのテンプレートとなるClass設計を徹底しました。
(今回は抽象classを使用しましたが、そうでなくてもOOPに則ったClass設計を指します。)
1. 処理の"骨格"を設計した抽象Classを用いて固定する
まず、一連の処理の流れ(アルゴリズム)を、外部から呼び出すpublicメソッドとしてabstractクラスに定義します。
※publicで定義しているgenerate()は継承先のClass視点、receptionになるメソッドとして定義する事としています。
abstract class AbstractSampleService
{
/**
* このメソッドが処理全体の骨格を定義するテンプレート
*/
public function generate(): array
{
// 1. データのバリデーション (共通処理)
$this->validateCommonInputs();
// 2. メインデータの取得 (個別処理)
$mainData = $this->fetchMainData();
// 3. データの加工 (個別処理だが、デフォルト実装も用意)
$processedData = $this->processData($mainData);
// 4. ヘッダーの生成 (共通処理)
$common = $this->generateCommon();
return array_merge($common, $processedData);
}
// --- 具象クラスに実装を強制する"穴" ---
abstract protected function fetchMainData(): array;
// --- 具象クラスが任意で上書きできる"部品" ---
protected function processData(array $data): array
{
// デフォルトの加工処理
return $data;
}
// --- 具象クラスからは変更させない"共通部品" ---
private function validateCommonInputs(): void { /* ... */ }
private function generateCommon(): array { /* ... */ }
}
この設計のポイントは、publicなgenerate()メソッドが処理の実行順序を完全にコントロールしている点です。
具象クラスを作る開発者は、この骨格を変えることはできず、定められた 「穴(abstractメソッド)」と「部品(オーバーライド可能なprotectedメソッド)」 を埋めることだけに集中できます。
2. 責務をprotectedメソッドへ細かく切り分ける
次に重要なのが、abstractクラス内の処理を、意味のある単位でprotectedメソッドに徹底的に切り分けることです。
設計思想として 「abstract classの中でrepositoryやServiceのような責務」 として関数で切り出しています。
例えば、sampleコードを例に出すと、
- fetchMainData()はデータの取得責務
- ユースケースにもよりますが、ロジックが本当に似通うのであればテーブル単位くらいの粒度で分けるとより変更容易性が向上する気がしました。
- 一部変更がある可能性がある部分をテーブル単位くらいで切り離しておくと、必要最小限の変更で後は共通処理として大きな変更を伴わずに済みます。
- processData()はビジネスロジック責務
といった具合に、各メソッドが小さな役割を持ちます。
この設計により、変更容易性が劇的に向上します。
例えば、ほぼ同じようなロジックではあるが機能を追加するような時に「データの取得方法だけが違う」場合、開発者はfetchMainData()メソッドをオーバーライドするだけで済みます。
他の共通処理や全体の流れについて一切気にする必要はありません。
変更ステップが最小化され、影響範囲も明確になるのです。
ここまでオブジェクト指向の話になってしまっていますが、
Class設計を徹底する事でAIへの指示書とその理解促進に繋げやすくなったのでここまではその前提となる解説でした。
次の章で詳細を記載させていただきます。
第2章:AI指示書 - 設計思想を"実行"させる魔法のルールブック
さて、この「拡張テンプレート設計(Class設計)」を、AIにどう理解させ、遵守させるのでしょうか。
ここで「AIへの指示書(.mdc)」が役割を果たします。
この設計と指示書の組み合わせは、親和性が高く相性の良さを感じました。
-
まず最初に感じた事が、「指示が驚くほどシンプルになる」 事でした。
テンプレートとなるClass設計を行った抽象classが整備されているため、AIへの指示は「差分」を伝えるだけで済みます。
「AbstractSampleServiceを継承してNewInvoiceServiceを作って。fetchMainDataメソッドを、請求テーブルからデータを取得するようにオーバーライドしてほしい」
これだけで、AIはやるべきことを正確に理解します。
骨格全体を説明する必要はありません。 -
AIの作業範囲を限定し、暴走を防ぐ。
AIが変更すべき箇所は「オーバーライドすべきprotectedメソッド」に限定されます。これにより、AIが勝手な解釈で共通処理であるprivateメソッドを書き換えたり、処理の順序を変更したりする暴走のリスクを完全に排除できます。 -
指示書で「オーバーライドのお作法」を叩き込む
.mdcファイルに、オーバーライド時の詳細なルールを書き込みます。
抽象classを利用する際のお作法を示した指示書のサンプル
※実際の物は載せられないので色々と書き換えて全体の構造だけ載せています。
### ⚪︎⚪︎機能 自動生成プロンプト
このドキュメントは、特定の基底サービスクラスを基に、〇〇機能のデータベースSeederを自動生成するためのAIへの指示書です。
---
### **概要**
Class設計についての概要を記載
---
### **前提条件**
プロンプトが正しく機能するために必要な、対象ファイルの場所やクラスの継承関係など、満たしているべき条件を列挙します。
---
### **自動生成プロンプト**
AIにSeederファイルを生成させるための具体的な指示セクションです。
- **対象ファイル**: AIが読み込む入力ファイルと、生成すべき出力先のファイルパスの命名規則を定義します。
- **参考実装**: AIがお手本とすべき、既存の実装済みファイルを明示
- **生成要件**: Class設計について詳細な説明、public,protectedメソッドの意図と使用例を具体的に説明
- **【重要】実装パターンで絶対に守るべき事項**:
コードの品質を担保するため、「絶対に間違えてはいけない」クリティカルな実装パターンを、良い例(✅)と悪い例(❌)で具体的に提示します。これには、正しいメソッドの使用法、配列キーの命名、データ構造の定義順序などが含まれます。
---
### **使用例**
実際にAIに指示を出す際の、具体的なプロンプト入力例を2パターン紹介します。
---
### **生成後の確認ポイント**
AIによってコードが生成された後、人間がレビューすべきチェック項目(構文、妥当性、整合性など)をリストアップします。
---
### **拡張ガイドライン**
将来的に新しい概念やカスタム要素が追加された場合に、どのように関連ファイルを更新・拡張すればよいかの指針を示します。
---
### **実装手順**
抽象classを継承後、具象Class内での実装手順を説明
---
### 実装要件
1. `fetchMainData()`を実装する際は、必ず`InvoiceRepository`を使用すること。
2. `processData()`内で税率計算が必要な場合は、`TaxCalculator::calculate()`を呼び出すこと。
3. このクラスでは、直接DBクエリを書いてはならない。
### **トラブルシューティング**
この自動生成で起こりがちな典型的な問題(キーの重複、ルールの競合など)と、その解決策を提示します。
---
### **関連ドキュメント**
この指示書の内容を補完する、関連設計ドキュメントへのリンクを示します。
このように具体的なクラス名やメソッド名を指示書に含めることで、AIは設計者が意図した通りの、高品質で規約に準拠したコードを生成します。
第3章:実践 - Class設計と指示書の組み合わせで"機能拡張"のコード生成をプロンプト一つで完了させる
開発体験の変化を下記に示します。
【タスク:新しい種類の⚪︎⚪︎機能に追加実装を行う】
Before:場当たり的なAI活用
- 既存の似たようなServiceクラスを探してきて、全体をAIに渡す。
- 「これを参考にして、新しい〇〇のServiceを作って」と曖昧に指示。
- AIはファイルのどこを変更すべきか分からず、時には不要な部分まで変更したコードを生成。
- 結局、開発者がコード全体をレビューし、設計思想から外れた部分を手で修正する羽目に…。
After:Class設計と指示書によるAI活用
-
開発者は 「今回の差分は何か?」 だけを思考する。
「今回追加(拡張)する機能では、△△△の情報も一緒に取得する必要があるな」 -
AIに、差分とテンプレート(Class設計を行った抽象class)の利用を明確に指示する。
最終的なプロンプト@作成した指示書.mdc 〇〇を追加(拡張)を追加したいです。 また、△△△の情報も一緒に取得する必要があります、×××の抽象メソッドで△△△の情報をfetchして追加実装してください。
-
AIは指示書(.mdc)のルールも参照し、指示されたメソッドだけを、完璧な品質で実装する。
-
開発者は、本質的なビジネスロジックの確認に集中できる。
まとめ
「似たような機能の拡張」という普遍的な課題への最適解は、場当たり的な実装でも、AIへの丸投げでもありません。
それは、拡張のパターンを予測し、 抽象クラスで「テンプレート化」するクラス設計 と、その設計思想をAIに遵守させるための「詳細な指示書」 の組み合わせです。
良いClass設計は、AIに「何をすべきか」の明確な道筋を示し、良い指示書(プロンプト)は、AIがその道から外れないように導きます。
Class設計そのものがAIへのプロンプトを強くサポートする事になると感じます。
そして今回、改めて感じましたが下流工程をよく知り理解している事がAIと向き合うスタートラインだと感じました。
今後も時代に置いていかれないよう、普遍的な技術の基礎的な知識の学習と共にAI君と仲良くさせていただきたいです。