プログラミングでは、コードを書く前に「どう捉えるか」を決める力が大切です。
抽象化は、似たものをまとめて本質だけを扱う考え方で、理解や変更を楽にします。
本記事では、初心者でも手を動かして身につけられるように、具体例とコツを段階的に解説します。
抽象化は複雑さを減らす最強の思考ツールです。
抽象化とは?プログラミングの基本
抽象化の意味と効果
一言で言うと
抽象化とは、たくさんの具体的なものから共通する部分だけを取り出し、扱いやすい形にまとめることです。
「共通の型(かたち)や言葉」を作ることで、考える量を減らします。
例えば「リンゴ」「みかん」を「果物」として扱えば、果物に共通する操作だけ考えればよくなります。
なぜ役立つか
抽象化があるとコードの重複が減り、間違いの入り込む場所が少なくなります。
一箇所を直すだけで全体が良くなるので保守が楽になります。
また、名前や関数にまとめることで意図が読み取りやすくなり、チームや未来の自分にとっても親切です。
読みやすさは正しさの土台です。
具体化との違い
比較して理解する
抽象化の反対は具体化です。
抽象化はまとめる方向、具体化は分ける方向の思考です。
どちらも必要で、行き来しながら設計します。
「まず抽象化で骨組みを作り、必要に応じて具体化で細部を詰める」流れが基本です。
| 観点 | 抽象化 | 具体化 |
|---|---|---|
| ねらい | 共通点でまとめて単純化 | 具体的な違いを扱う |
| 例 | 「支払い」インターフェース | 「カード決済」「現金」実装 |
| メリット | 再利用・変更が易しい | 個別最適・性能調整がしやすい |
| 注意点 | やりすぎると分かりにくい | 重複や散らばりが増える |
良い設計は抽象と具体のバランスで成り立ちます。
使う場面
どんな時に抽象化するか
同じようなコードを2回以上書いた時、条件分岐(if)が長くなって読みにくい時、入出力と計算が混ざってテストしにくい時は抽象化のタイミングです。
「重複」「長い条件」「混ざり」を見つけたら小さくまとめてみましょう。
名前が曖昧で意図が伝わらない時も、命名の抽象化が効きます。
例でわかる抽象化のステップ
例1: 重複処理を関数化
悪い例(同じ計算が2回)
同じ割引計算を2箇所で書くと、修正漏れの原因になります。
重複はバグを招く温床です。
// 悪い例
const priceA = 1200;
const finalA = priceA - Math.floor(priceA * 0.1); // 10%引き
const priceB = 800;
const finalB = priceB - Math.floor(priceB * 0.1); // 10%引き
良い例(関数にまとめる)
共通処理を関数にすると、変更が一箇所で済みます。
意図(10%引き)を名前に閉じ込めるのがコツ。
function applyTenPercentOff(price) {
return price - Math.floor(price * 0.1);
}
const finalA = applyTenPercentOff(1200);
const finalB = applyTenPercentOff(800);
学び
重複→関数化が最初の一歩です。
後から「割引率を引数にする」など、段階的に一般化できます。
最初から万能関数を作ろうとしないことが失敗を減らします。
例2: 条件分岐を整理
悪い例(ifが長い)
条件が増えると読みづらくなります。
「どの条件が先か」を理解する負荷が大きくなります。
function shippingFee(region, weight) {
if (region === "local") {
if (weight < 1) return 300;
if (weight < 5) return 600;
return 900;
} else if (region === "remote") {
if (weight < 1) return 600;
if (weight < 5) return 900;
return 1200;
}
}
良い例(表で表現する)
データの形に置き換えると、読みやすく変更もしやすくなります。
条件を「表」にしてロジックから切り離す考え方が抽象化です。
const feeTable = {
local: [{ limit: 1, fee: 300 }, { limit: 5, fee: 600 }, { limit: Infinity, fee: 900 }],
remote: [{ limit: 1, fee: 600 }, { limit: 5, fee: 900 }, { limit: Infinity, fee: 1200 }]
};
function shippingFee(region, weight) {
const rules = feeTable[region];
return rules.find(r => weight < r.limit).fee;
}
学び
分岐の羅列→データ駆動にすると、追加や変更が表の更新だけで済みます。
「新しい地域」が増えてもコード本体は変えません。
例3: データの形をそろえる
悪い例(形がバラバラ)
APIごとに違うデータ形をそのまま使うと混乱します。
「どの形に合わせるか」を毎回考えるコストが発生します。
// API A: { firstName, lastName }
const a = { firstName: "Taro", lastName: "Yamada" };
// API B: { name }
const b = { name: "Hanako Suzuki" };
良い例(アダプタで統一)
最初に「アプリ内の標準形」を決め、入り口で変換します。
内側では常に同じ形を扱うのが抽象化の基本。
function toUser(model) {
if ("firstName" in model && "lastName" in model) {
return { fullName: `${model.firstName} ${model.lastName}` };
}
if ("name" in model) {
return { fullName: model.name };
}
return { fullName: "Unknown" };
}
const users = [toUser(a), toUser(b)];
学び
データは境界でそろえると、以降の関数がシンプルになります。
変換は入口で一度だけが鉄則です。
例4: 入出力を分ける
悪い例(混在してテスト困難)
計算と画面表示が混ざると、テストや再利用が難しくなります。
副作用(表示や書き込み)と純粋な計算は分けましょう。
function showTaxed(price) {
const taxed = Math.floor(price * 1.1);
console.log(`税込: ${taxed}円`);
}
良い例(純粋関数とIOを分離)
計算は関数、表示は呼び出し側に分けます。
純粋関数は入力→出力だけで動き、テストが簡単です。
function addTax(price, rate = 0.1) {
return Math.floor(price * (1 + rate));
}
// 表示や保存は外側で行う
const price = 1000;
const taxed = addTax(price);
console.log(`税込: ${taxed}円`);
学び
入出力と計算の分離ができると、同じロジックをWebでもCLIでも使い回せます。
テスト可能性が設計の品質を押し上げます。
例5: 命名で意図を表す
悪い例(曖昧な名前)
名前が目的を語らないと、読み手が推測する必要が出ます。
推測をなくす名前は最小の抽象化です。
function calc(a, b) { return a - b; } // 何を計算?
良い例(意図を直接書く)
「何のための結果か」を名前に含めます。
関数名と引数名でドキュメント化しましょう。
function remainingBudget(totalBudget, spent) {
return totalBudget - spent;
}
学び
命名は設計です。
使う側が迷わない言葉にすると、コード全体の負担が減ります。
短さより明確さを優先しましょう。
うまい抽象化の作り方とコツ
ステップ1: 共通点を見つける
観察から始める
まずは似ている箇所を並べ、同じ部分と違う部分を分けます。
同じ目的・同じ入力・同じ出力に注目すると共通点が見えます。
最初は紙に書き出すだけでも効果があります。
見える化が出発点です。
ステップ2: 入力/出力と前提を決める
小さな契約をつくる
関数にするなら「入るもの」「出るもの」「守る前提(例: 単位は円)」をはっきりさせます。
入出力が決まると中身は自由に変えられます。
エラー時の振る舞いも一緒に決めておくと安心です。
ステップ3: わかる名前をつける
読み手の言葉で
ユーザーの行動や業務の言葉から名前を付けます。
「どう実装するか」ではなく「何をしたいか」を名前に込めます。
例として「10%引き」ならapplyTenPercentOff、「集計」ならsummarizeOrdersのようにします。
ステップ4: 小さく試して直す
まず1箇所に適用
いきなり全体に広げず、まず1〜2箇所に当ててみます。
小さい成功を確認してから広げると安全です。
使いづらければ引数や名前を直しましょう。
抽象化は一度で決まらなくて大丈夫です。
ステップ5: 抽象化のやりすぎを避ける
未来のために作りすぎない
「使うかもしれない」汎用化は危険です。
今の要件を満たす最小限に留めると失敗しにくいです。
必要になった時に広げる方が、結局速くて安全です。
YAGNI(やぐに)の精神を大切にします。
設計に効く抽象化の使い方
再利用性と保守性が上がる理由
変更の影響が狭くなる
抽象化した関数やモジュールは、決まった入出力でつながっています。
中身を変えても外への影響が限定されるため、修正が怖くありません。
結果として再利用もしやすくなります。
| 状態 | 変更の影響 | 心配ごと |
|---|---|---|
| 抽象化なし(べた書き) | 広い | 同じ修正を何度も、どこが壊れるか不明 |
| 良い抽象化あり | 狭い | 入出力が守れているかだけ確認 |
影響範囲を狭めることが保守性の核心です。
関数/モジュール分割の目安
迷ったときの基準
1つの関数は1つの目的に絞ります(単一責任)。
名前に「and」や「または」が出てきたら分割のサインです。
行数の目安としては、画面に収まる程度(おおよそ20〜30行)に収めると読みやすくなります。
外部とのやり取り(入出力)が増えたらモジュール単位で分けましょう。
依存を減らしシンプルに
依存とは何か
依存とは「AがBなしでは動けない」関係です。
依存が多いと変更に弱くなります。
依存を減らす基本は、外から必要な情報を渡してもらう(引数)ことです。
グローバル変数に直接触れず、設定や外部サービスは注入(渡す)方式にします。
// 悪い: 関数が直接グローバル設定を読む
const RATE = 0.1;
function total(price) { return Math.floor(price * (1 + RATE)); }
// 良い: 必要な情報を引数で受け取る
function total(price, rate) { return Math.floor(price * (1 + rate)); }
「中で決めない、外から受け取る」だけで設計は大きく良くなります。
コードレビューのチェック
抽象化の観点で見る
レビューでは次の観点を短い質問にして確認します。
質問で発見が増え、修正が具体的になります。
- 同じ処理を2回以上書いていませんか。
- 関数名は「何をするか」を語っていますか。
- 入出力がはっきりし、テスト可能ですか。
- 条件分岐はデータや小関数に分解できますか。
- 変更の影響範囲は小さく保てていますか。
毎日の練習メニュー
小さく続けて感覚をつかむ
毎日15〜30分でできる練習を積み重ねます。
「見る→直す→振り返る」の流れを癖にすると上達が速いです。
まず、昨日のコードから重複を1つ見つけて関数化します。
次に、長いifを1つだけ表に置き換えます。
最後に、曖昧な名前を1つだけ改善し、差分を読み返して学びをメモします。
少しずつでも毎日が効果的です。
まとめ
抽象化は、共通点を見つけて名前や関数、データの形に落とし込み、複雑さを小さく保つ技法です。
重複を減らし、条件を整理し、入出力を分け、意図が伝わる名前を付けるだけで、初心者のコードは驚くほど読みやすくなります。
やりすぎには注意しつつ、今必要な最小の抽象化から始め、毎日小さく改善しましょう。
抽象化は道具であり、練習で必ずうまくなります。
