社内で仕事をしていると、ふと耳に入ってくる会話があります。
「この処理、そろそろ共通化したほうが良さそうですよね」
「あれ、似たようなのって前に作ってませんでした?」
「CommonUtil 増やしたくないけど……他に置き場がないんですよね」
どれも“良くしよう”という前向きな声ですが、気づけばプロジェクトの片隅で、用途が曖昧な共通クラスがじわじわ増えていきます。名前だけ立派なのに、誰が使っているか分からないクラスたちが、静かに溜まっていくのです。
「共通化しておけば整理になる」
そう信じて始めたはずなのに、気づくとコードはむしろ複雑さを増している──そんな経験はありませんか。
今回は、そんな場面でよく起きる 「共通化はクラス化しようの罠」 についてまとめます。
なぜ「クラス化」したくなるのか
社内でよく聞く声として、こんなやり取りがあります。
「似た処理があるから、どこかにまとめたい」
「コピペを減らすためにも、共通クラスにしよう」
「再利用できる場所に集めたい」
共通化という発想自体は健全です。問題は、その先で 「クラスにまとめる=正義」 と短絡的につながることです。
クラス化によって生まれる「便利そうで便利じゃない場所」
クラスを作ると、整理された印象になります。しかし現実は…
- 機能の関連が薄いメソッドがひとまとめにされる
- 「ここに入れるのは違う」と思いながらも他に置き場がない
- 似た名前のクラスが増え、逆に探しにくくなる
気づけば Common、Util、Helper、Manager…
名前だけ立派な倉庫が増えてしまいがちです。
アンチパターン例:便利クラスにまとめすぎる
public class StringUtils
{
public string Normalize(string s) { … }
public string Reverse(string s) { … }
public bool IsNullOrEmpty(string s) { … }
public string ToTitleCase(string s) { … }
// どんどん追加され、クラスが肥大化
}
- 毎回 new StringUtils() で呼ぶ必要がある
- どこに何の処理があるか分かりにくい
- テスト対象が増え、保守性が下がる
また、状態を持たないのにインスタンス変数を持たせるケースもあります。
public class Calculator
{
private int lastResult;
public int Add(int a, int b) { lastResult = a + b; return lastResult; }
public int Multiply(int a, int b) { lastResult = a * b; return lastResult; }
}
- 処理の意図と状態管理が混ざり、再利用性が低下
- 「計算の手順」と「状態」を分けて考えられなくなる
本来の共通化に必要なのは、“クラス化”ではなく“モデリング”
大事なのは 「同じ処理だからまとめる」ではなく、同じ概念だから同じ場所にいるべき」 という視点です。
- この処理はどんな文脈で生まれたか
- 何を表しているのか
- どんなデータや振る舞いと結びついているか
クラス名を見て役割が分かるか
曖昧な共通クラスは、後々「何を担当してるんだっけ?」と迷惑な存在になりがちです。
逆に、モデリングとして意味がある場合は、同じ処理が複数あっても 共通化しない方が読みやすいこともあります。
クラス化すべきかどうかの判断基準
現場で私が使うのは以下の3点です。
1. 状態(データ)とセットで意味があるか
単なる作業手順ではなく、データと振る舞いが自然な関係を持っているか。
2. クラス名から“役割”が思い浮かぶか
名前を見て「何をするクラスか」を説明できるなら存在理由がある。
3. そのクラスは“文脈の中で”必要か
その領域(ドメイン)に属していると言えるかどうか。
この3つのどれもクリアしなければ、クラス化は一度立ち止まるサインです。
クラス化が不要なケースに気づくヒント
「同じ処理だから共通化したい」と思ったときは少し視点を変えてみましょう。
ローカルメソッドで十分では?
拡張メソッドに逃す方が読みやすいのでは?
本来のクラスに自然に吸収できないか?
構造的に共通化しない方が理解しやすい場合もあります。
共通化は “減らす方向”で考える と、コードの密度が驚くほど落ち着きます。
改善例 :クラス化せずに共通化する方法
1. 拡張メソッドで共通処理を整理
public static class StringExtensions
{
public static string Normalize(this string s) =>
s?.Trim().ToUpper();
public static bool IsNullOrEmpty(this string s) =>
string.IsNullOrEmpty(s);
public static string ToTitleCase(this string s) =>
CultureInfo.CurrentCulture.TextInfo.ToTitleCase(s.ToLower());
}
// 利用例
string name = " itabashi ";
string normalized = name.Normalize();
bool empty = name.IsNullOrEmpty();
string title = name.ToTitleCase();
- 状態を持たないのでインスタンス不要
- 用途ごとにファイル分けすれば肥大化防止
- 可読性と保守性が向上
2. ローカル関数や関数化で簡単に再利用
int Add(int a, int b) => a + b;
int Multiply(int a, int b) => a * b;
int sum = Add(3, 4);
int product = Multiply(3, 4);
- 簡単な処理はローカル関数で十分
- 不要なクラスを増やさずに済む
まとめ
- 「共通化=クラス化」ではない
- 大事なのは 「モデリング視点で整理すること」
- クラス化すべきかどうかは 状態・役割・文脈 で判断
- 共通化は 減らす方向で考えるとコードの密度が落ち着く
- 共通化の本質は「整理すること」ではなく「概念や文脈に沿った再利用」です。
短絡的にクラス化する前に、一度立ち止まって考えてみましょう。