1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C#で設計原則をどこまで適用するか — 規模別の線引きガイド

1
Last updated at Posted at 2026-04-06

SOLID、DRY、KISS、YAGNI——ソフトウェア設計の原則を学ぶと「全部守らないとダメなのか?」という疑問にぶつかる。

正直に書くと、私は小さなWinFormsツール(1画面、1機能)にDIコンテナとインターフェース分離を入れて、「なんでこんなに面倒なんだ」と思ったことがある。3ファイルで済むはずのツールが15ファイルに膨れ上がった。

設計原則は「常に全部適用する」ものではない。プロジェクトの規模によって、どこまで適用するかの線引きが必要。この記事では、C#プロジェクトを3段階の規模に分けて、各設計原則の適用基準を整理する。

まず4つの原則をおさらい

知っている人は読み飛ばしてOK。

原則 意味 一言で言うと
SOLID 5つの設計原則(SRP, OCP, LSP, ISP, DIP) クラス設計のベストプラクティス
DRY Don't Repeat Yourself(同じことを繰り返すな) 重複コードを排除する
KISS Keep It Simple, Stupid(シンプルに保て) 不要な複雑さを入れない
YAGNI You Aren't Gonna Need It(それ、今いらない) 将来の拡張を先回りしない

ここで気づくと思うが、SOLIDとKISS/YAGNIは方向性が逆のことがある。SOLIDを忠実に守ると抽象化レイヤーが増えてコードは複雑になる。KISSは「シンプルに保て」と言う。この矛盾をどう解決するかが、規模別の線引きのポイントになる。

プロジェクト規模の3段階

C#プロジェクトを規模で分けると、だいたいこの3つに収まる。

規模 目安
S(小規模) 1〜3画面、コード1,000行以下 個人用ツール、バッチ処理、PoC
M(中規模) 5〜20画面、コード5,000〜30,000行 部門ツール、社内業務アプリ
L(大規模) 20画面以上、10万行超 全社システム、製品、OSS

ここからが本題。各原則を規模別に「どこまでやるか」を整理する。

規模別 適用マトリクス

先に全体像を出す。

原則 S(小規模) M(中規模) L(大規模)
SRP(単一責任) △ ゆるく ○ クラス単位 ◎ メソッド単位
OCP(開放閉鎖) ✕ 不要 △ 変更頻度高い箇所のみ ◎ 全面適用
LSP(リスコフ置換) ✕ 継承自体が少ない ○ 意識する ◎ テストで検証
ISP(インターフェース分離) ✕ 不要 △ 大きいI/Fのみ分離 ◎ 細かく分離
DIP(依存性逆転) ✕ 直接依存でOK △ 外部依存のみ ◎ DIコンテナ使用
DRY △ 3回以上なら ○ 2回以上なら ◎ 1回でも共通化検討
KISS ◎ 最優先 ○ SRPとバランス △ 構造が優先される場面も
YAGNI ◎ 最優先 ○ 基本守る △ 拡張計画があるなら例外

この表の読み方:小規模ではKISS/YAGNIが最優先、大規模ではSOLIDが最優先。中規模はバランスを取る。

以下、特に判断が難しいポイントをコード例つきで解説する。

SRP(単一責任原則)— 分けすぎると逆に辛い

SRPは「クラスを変更する理由は1つだけにせよ」という原則。

S(小規模): ゆるくていい

個人用ツールで「データ取得 + 表示 + 保存」を1クラスに書くのは、SRP的にはアウト。でも3ファイルで完結するツールを10ファイルに分割するメリットはない。

// S規模: 1クラスにまとめてOK
public class DataExporter
{
    public List<Record> LoadFromDatabase() { /* ... */ }
    public void ExportToCsv(List<Record> records) { /* ... */ }
    public void ShowResult(int count) { /* ... */ }
}

「将来分割が必要になったら、そのときやる」でいい。これがYAGNI。

M(中規模): クラス単位で分ける

部門ツールになると、複数人が触る可能性がある。ここからはSRPを意識する。

// M規模: 責務ごとにクラスを分離
public class RecordRepository
{
    public List<Record> LoadAll() { /* ... */ }
}

public class CsvExporter
{
    public void Export(List<Record> records, string path) { /* ... */ }
}

public class ExportViewModel
{
    private readonly RecordRepository _repo;
    private readonly CsvExporter _exporter;
    // ...
}

ただし、1メソッド1クラスまで細かくする必要はない。「変更理由が明らかに異なるもの」を分ければ十分

L(大規模): メソッドレベルで意識する

全社システムでは、1つのクラスが肥大化すると保守コストが跳ね上がる。メソッドレベルで「この処理は本当にこのクラスの責務か?」を問う。

DIP(依存性逆転)— DIコンテナが必要になるライン

DIPは「上位モジュールは下位モジュールに依存すべきでない」という原則。C#ではインターフェースとDIコンテナで実現する。

S(小規模): 直接newでいい

// S規模: インターフェースもDIも不要
public class ReportGenerator
{
    public void Generate()
    {
        var db = new SqliteConnection("Data Source=app.db");
        var records = db.Query<Record>("SELECT * FROM records");
        File.WriteAllText("report.csv", ToCsv(records));
    }
}

これを「テスタビリティのためにインターフェースを挟む」のは、テストを書く予定がない小規模ツールでは過剰。

M(中規模): 外部依存だけ抽象化する

データベースや外部APIなど、環境によって差し替えたいものだけインターフェースを挟む。

// M規模: DB接続だけ抽象化
public interface IRecordStore
{
    List<Record> GetAll();
}

public class SqliteRecordStore : IRecordStore { /* ... */ }

public class ReportGenerator
{
    private readonly IRecordStore _store;

    public ReportGenerator(IRecordStore store)
    {
        _store = store;
    }
}

DIコンテナは使わず、エントリポイントで手動で注入するレベルでOK。

L(大規模): DIコンテナ + 全面的なインターフェース

// L規模: Microsoft.Extensions.DependencyInjection を使用
services.AddScoped<IRecordStore, SqliteRecordStore>();
services.AddScoped<IReportFormatter, CsvReportFormatter>();
services.AddScoped<IReportGenerator, ReportGenerator>();

大規模では依存関係の管理自体が複雑になるため、DIコンテナの導入コストは十分ペイする。

DRY — 「3回ルール」のすすめ

DRYは「コードの重複を排除せよ」だが、小規模で過剰に適用すると逆効果になることがある。

S規模での失敗例

2箇所で似たようなバリデーションロジックがあったので共通メソッドに切り出した。すると、片方だけ仕様変更が入ったときに、共通メソッドにif分岐が増えて読みにくくなった。

私が使っている基準: 3回ルール

重複回数 対応
1回目 そのまま書く
2回目 コメントで「ここにも同じロジックあり」と書く
3回目 共通化する

3回同じコードを書いたら、それはパターンが確定している。2回目の時点ではまだ「たまたま似ているだけ」の可能性がある。

M/L規模では2回目から共通化を検討していい。チームで開発していると、重複に気づかないまま増殖するリスクが高いから。

KISS vs SOLID — 矛盾したとき、どちらを優先するか

ここが一番聞かれる質問だと思う。答えは 「規模による」

小規模 → KISS優先

SOLIDを守るためにコードが複雑になるなら、小規模ではKISSを優先する。

// SOLID的には正しいが、小規模ツールには過剰
public interface ILogger { void Log(string message); }
public interface IValidator { bool Validate(Input input); }
public interface IProcessor { Result Process(Input input); }
public interface INotifier { void Notify(Result result); }

// KISS的に書くと
public class SimpleProcessor
{
    public void Run(Input input)
    {
        if (!IsValid(input)) return;
        var result = DoProcess(input);
        Console.WriteLine($"Done: {result}");
    }
}

大規模 → SOLID優先

大規模では「今シンプルに書く」より「将来の変更に耐えられる構造」の方が重要。初期の複雑さは、長期的な保守コストの削減で回収できる。

中規模 → 判断基準を持つ

中規模が一番悩ましい。私が使っている判断基準はこれ:

「このコードを、半年後の自分(または別の人)が修正するとき、今の構造で困らないか?」

答えがYesなら今のままでいい。Noなら構造を見直す。

実践的な判断フロー

迷ったときのフローチャート。

1. このプロジェクトの規模は?
   ├─ S(小規模) → KISS/YAGNI優先。SOLIDは意識しなくていい
   ├─ M(中規模) → SRP + 外部依存のDIP。あとはケースバイケース
   └─ L(大規模) → SOLID全面適用。DIコンテナ導入

2. 「将来のために」抽象化しようとしていないか?
   ├─ Yes → YAGNI違反。今必要な分だけにする
   └─ No → そのまま進める

3. コードが複雑になってきた。分割するか?
   ├─ 複数人が触る → 分割する(SRP)
   └─ 自分だけ → 読みやすさ(KISS)を優先

よくある質問

Q: 小規模で作り始めたものが中規模に成長したらどうする?

リファクタリングのタイミングは「変更が辛くなったとき」。予防的にリファクタリングするのではなく、痛みを感じてから構造を変える方が的確な判断ができる。ただし、テストがないとリファクタリングは怖い。テストだけは規模に関係なく書いておくと後で助かる。

Q: チーム開発では、全員が同じ基準を持てる?

ADR(Architecture Decision Record)やコーディング規約に「このプロジェクトではSOLIDのうちSRPとDIPだけ適用する」と明文化する。基準が曖昧だと、メンバーごとに「ここは抽象化すべき」「不要でしょ」の判断がバラつく。

Q: テストのためだけにインターフェースを入れるのはあり?

M規模以上なら、テストのためのインターフェース導入は正当なコスト。S規模では、テスト自体を省略するか、インターフェースなしでテストできる構造(publicメソッドだけテスト)を選ぶ。

まとめ

設計原則は「守るか守らないか」の二択ではなく、「どこまで適用するか」のグラデーション

規模 最優先 意識する 後回しでいい
S KISS, YAGNI DRY(3回ルール) SOLID全般
M SRP, DRY DIP(外部依存のみ) OCP, ISP
L SOLID全般 DRY(厳格に) KISS(構造優先の場面あり)

原則を全部守ろうとして動けなくなるより、規模に合った基準で判断して、手を動かし続ける方が良いコードになる。コードは書いて、使って、困って、直して——のサイクルで良くなっていくものだから。


📝 この記事は Zenn で最初に公開されました。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?