Manage compatibility not as "code that remains" but as a "contract and removal plan."
Core Principles
Backward compatibility itself is not evil. When the "specifications, tests, removal plans, and measurement" that support compatibility are missing, compatibility requirements flow into the implementation and turn into garbage code.
| State | How compatibility is handled | Result |
|---|---|---|
| Managed | Contract (specification) + tests + removal SLO + measurement | Foundation for stability (asset) |
| Unmanaged | Ad-hoc workarounds in the implementation | Bloat of compatibility layers (liability) |
Feedback Loop of Becoming a Liability
既存クライアント → 「壊すな」の要求
→ 公開APIの固定・増殖
→ 非推奨APIの温存 + 互換アダプタ/分岐追加
→ 複雑性増大・テスト範囲拡大
→ 保守コスト増・変更速度低下
→ 期限圧力で近道実装
→ さらにAPI固定... (ループ)
Decision Flow
互換性に関わる変更を検出
↓
公開API境界は明確か?
├─ NO → 境界を仕様化してから進む
└─ YES ↓
非推奨化ポリシーはあるか?
├─ NO → ポリシーを策定(撤去SLO含む)
└─ YES ↓
互換処理はどこにあるか?
├─ コードベース全体に散在 → 互換層を局所化(Adapter/Strangler Fig)
└─ 局所化済み ↓
撤去計画はあるか?
├─ NO → 撤去タイムライン + 計測を設定
└─ YES → 計画に従って進行
Antipattern Detection
If you find the following patterns, they are signs of compatibility debt:
❌ if (version < X) { /* 旧挙動 */ } else { /* 新挙動 */ } // バージョン分岐の散在
❌ @Deprecated が付いたまま何年も残るAPI
❌ LegacyXxxAdapter, OldXxxWrapper が増殖
❌ "互換性のため" というコメントが散在
❌ 同じ機能の新旧2つのエンドポイントが併存
❌ テストに "backward compat" 系のスキップ/無視がある
❌ 破壊的変更がドキュメント化されていない
Countermeasure Patterns
1. Clarification of Public API Boundaries
Limit the scope of compatibility and prevent unnecessary compatibility code.
✅ 公開APIを仕様として宣言する(SemVer, OpenAPI等)
✅ 内部APIと公開APIを構造的に分離する
✅ 「何を壊さないか」のスコープを文書化する
❌ すべてのAPIを暗黙的に互換対象にする
❌ 内部実装の変更が外部に漏れる構造
Example of Boundary Isolation(Linux kernel strategy):
- User-space ABI: Do not break it as much as possible (stable boundary)
- Kernel-internal API: Freely changeable (evolvable)
2. Deprecation Cycle
Institutionalize removal and reduce the code graveyard.
非推奨宣言 → 移行期間(猶予SLO) → 利用率計測 → 削除
| Phase | Action | Deliverable |
|---|---|---|
| Declaration | Deprecation mark + presentation of alternatives | Deprecation annotations, migration guide |
| Grace Period | Migration support + usage tracking | Telemetry, migration status dashboard |
| Deletion | Remove after confirming zero usage | Deletion PR, release notes |
Guideline for Grace Period(adjust according to project scale):
- Internal API: 1-2 release cycles
- Public library: At least 1 major version
- Platform API: 2 years or more (large-scale ecosystem)
3. Localization of Compatibility Layers
The main battlefield is not scattering compatibility handling "throughout the entire codebase."
Adapter Pattern: Consolidate the differences between incompatible interfaces in one place
// ❌ 散在: あちこちでバージョン分岐
function processOrder(order: Order) {
if (order.apiVersion < 2) {
// v1の処理...
} else {
// v2の処理...
}
}
// ✅ 局所化: アダプタで吸収
interface OrderProcessor { process(order: Order): Result }
class OrderV1Adapter implements OrderProcessor {
constructor(private inner: OrderProcessorV2) {}
process(order: OrderV1): Result {
const converted = convertV1toV2(order)
return this.inner.process(converted)
}
}
Strangler Fig Pattern: Replace legacy incrementally
旧システム ←→ [ルーティング層] ←→ 新システム
↑
段階的に新へ転送を増やし、最終的に旧を除去
4. Contract Testing (Consumer-Driven Contract)
Verify compatibility based on the user's "way of using" it.
✅ 消費者が使う部分だけを契約として固定
✅ 使われない挙動は自由に変更可能
✅ CIで互換性の回帰を自動検出
❌ "全部テストする" で範囲が爆発
❌ 互換テストがないまま「互換性を守る」と宣言
5. Compatibility Gate for AI-Generated Code
Treat AI output as "untrusted input" and constrain compatibility, dependencies, and safety with gates.
互換ポリシー/公開API定義
→ 参照ドキュメント/RAG
→ AI生成
→ 静的解析/セキュリティ検査 + 互換テスト/契約テスト
→ PR作成
→ 人間レビュー(互換・依存・撤去計画)
→ マージ
→ 互換メトリクス/非推奨利用率の監視
Risks Specific to AI:
- "Plausible" reproduction of deprecated APIs
- Generation of non-existent dependency package names (package hallucination)
- Uncontrolled proliferation of compatibility layers (review oversights)
Application Guidelines
Recommendations
@Deprecated/#[deprecated]APIs that remain for a long time with still attached- Version branches scattered throughout the codebase
- "For compatibility" comments proliferating
- Insufficient documentation of breaking changes
- Gradual migration from legacy systems
Avoid Over-Application
- Private APIs used only internally
- When compatibility is not needed at the prototype stage
- New APIs with no clients requiring compatibility
Review Checklist
API Boundaries
- Is the boundary of the public API clearly defined as a specification?
- Are internal APIs and public APIs structurally separated?
- Is the scope of compatibility (what will not be broken) documented?
Deprecation Management
- Are alternatives explicitly stated for deprecated APIs?
- Is a removal timeline (grace SLO) set?
- Are there means to measure usage rates (telemetry, etc.)?
- Is the deletion of deprecated APIs included in the release plan?
Design of Compatibility Layers
- Is compatibility handling localized (consolidated into Adapters, etc.)?
- Are version branches not scattered throughout the code?
- Are gradual migration strategies such as Strangler Fig considered?
- When using Feature Flags, is a removal plan set?
Test
- Are there regression tests for each compatibility target?
- Are contract tests (CDC) introduced (external APIs/microservices)?
- Is static analysis to detect breaking changes incorporated into CI?
Additional Checks When AI Is Involved
- Is AI-generated code not using deprecated APIs?
- Are dependency packages verified to actually exist?
- Are compatibility tests also applied to AI-generated code?
Related Skills (Recommended for Concurrent Reading)
When using this skill, also refer to the following skills:
clean-architecture: Design of the adapter layer where compatibility layers are placedrepository-design: Compatibility management of repositories as public API boundariesintent-based-dedup: Judgment not to unify intentional duplication of old and new APIs