refactor-module
Terraform設定をモジュール化するかどうか判断し、適切なモジュール境界を定め、過度な抽象化やモジュール乱立を防ぎ、状態移行を円滑に進めるための意思決定を支援するSkill。
📜 元の英語説明(参考)
Terraform module extraction decision framework. Use when: (1) deciding whether to create module vs keep inline, (2) determining module boundaries, (3) avoiding over-abstraction, (4) handling state migration. NOT a Terraform tutorial. Focuses on the refactoring decision (when to modularize, when to keep inline) and anti-patterns that create module sprawl. Triggers: terraform module, refactor terraform, module boundaries, when to create module, terraform abstraction, module sprawl, state migration.
🇯🇵 日本人クリエイター向け解説
Terraform設定をモジュール化するかどうか判断し、適切なモジュール境界を定め、過度な抽象化やモジュール乱立を防ぎ、状態移行を円滑に進めるための意思決定を支援するSkill。
※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o refactor-module.zip https://jpskill.com/download/9180.zip && unzip -o refactor-module.zip && rm refactor-module.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/9180.zip -OutFile "$d\refactor-module.zip"; Expand-Archive "$d\refactor-module.zip" -DestinationPath $d -Force; ri "$d\refactor-module.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
refactor-module.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
refactor-moduleフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 1
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
Terraformモジュールのリファクタリング - 意思決定エキスパート
前提: Terraformの構文をご存知であること。これは、いつモジュール化するか、インラインのままにするかを扱います。
モジュールへのリファクタリングの前に:戦略的評価
モジュールを抽出する前に、以下の質問を自問してください。
1. 重複の苦痛分析
- 使用回数は?: 1~2回の使用 → インラインのまま(3回目を待つ)、3回以上 → モジュール候補
- 同一性は?: 設定の類似度が50%未満 →
localsを使用、80%以上 → モジュール化が理にかなう - 変更頻度: 毎週変更 → モジュール化しない(不安定なAPI)、四半期ごと以上 → モジュール化しても安全
- 変更範囲: リソース全体 → モジュール、変数のみ →
localsで十分
2. チームの準備状況評価
- テストカバレッジ: カバレッジ50%未満 → リスクが高すぎる(破壊的な変更が検出されない)
- 状態のバックアップ: 状態はリモートバックエンドにあるか?移行が失敗した場合にロールバックできるか?
- コンシューマー数: 1~3人のコンシューマー → 更新が簡単、10人以上 → モジュールの変更 = 10個のPR
- ドキュメント: チームは
terraform state mvの使い方を知っているか?最初にトレーニングが必要か?
3. 費用対効果分析
- モジュールのオーバーヘッド: バージョニング戦略 + 自動テスト + 移行ドキュメント + 状態移行のリスク
- 重複の苦痛: 一貫性のない設定 + バグ修正に3つ以上のPRが必要 + コンプライアンスのずれのリスク
- 損益分岐点: 4回以上の同一の使用 + 安定したAPI + コンプライアンスの必要性がある場合にモジュール化する価値がある
- 価値実現までの時間: シンプルなモジュール = 2時間、状態を含む複雑なモジュール = 2日間の計画 + 4時間の実装
重要な決定:モジュール vs インライン
モジュールの作成を検討していますか?
│
├─ パターンが1回使用された → モジュール化しない(インラインのまま)
│ └─ 2回目の使用を待つ
│ 理由:時期尚早な抽象化は変更が難しい
│
├─ パターンが2~3回使用された → モジュール化するかもしれない
│ ├─ 設定が同一 → モジュール化する
│ ├─ 類似しているが異なる → しない(代わりに`locals`を使用する)
│ └─ 異なるチームが使用している → しない(調整のオーバーヘッド)
│
├─ パターンが4回以上使用された → モジュール化する
│ └─ 設定が安定している場合(すべてのスプリントで変更されない)
│ 理由:モジュールの変更には、すべてのコンシューマーの更新が必要
│
└─ コンプライアンス/セキュリティ要件 → モジュール化する
└─ チーム全体で標準を強制する
理由:モジュール = コンプライアンスのための唯一の信頼できる情報源
落とし穴: 過剰なモジュール化。モジュールは間接性、バージョニング、テストのオーバーヘッドを追加します。
経験則: 重複の苦痛が抽象化の苦痛よりも大きくなるまで、モジュールの作成を我慢してください。
アンチパターン
❌ #1: 漏洩した抽象化
問題: モジュールが50個の変数を公開し、リソースをラップしているだけ
// ❌ 間違い - 1:1の変数マッピング
module "vpc" {
source = "./modules/vpc"
cidr_block = var.cidr_block
enable_dns_hostnames = var.enable_dns_hostnames
enable_dns_support = var.enable_dns_support
instance_tenancy = var.instance_tenancy
enable_classiclink = var.enable_classiclink
enable_classiclink_dns_support = var.enable_classiclink_dns_support
assign_generated_ipv6_cidr_block = var.assign_generated_ipv6_cidr_block
// ... さらに43個の変数
}
// なぜ悪いのか:何も抽象化せず、コードを移動しているだけ
修正: 高レベルのインターフェース
// ✅ 正しい - 意味のある抽象化
module "vpc" {
source = "./modules/vpc"
network_config = {
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b"]
public_subnets = 2
private_subnets = 2
}
// 50個の変数の代わりに4個
}
テスト: モジュールの変数がリソース引数と1:1で一致する場合、それは真の抽象化ではありません。
これが誤解を招きやすく、デバッグが難しい理由: モジュールは完全に機能します—テストは合格し、terraform applyは成功します。問題は数か月かけてゆっくりと発生します。すべてのリソース引数がモジュールの変数になり(+50個の変数)、ドキュメントが扱いにくくなります(50個の変数のうち、実際に必要なものはどれですか?)、コンシューマーはモジュールを使用するためにAWS VPCの内部構造を知る必要があります(抽象化の目的を打ち負かす)。3〜6か月のメンテナンス地獄が必要です—新しいAWS機能を追加するには、モジュールの更新、バージョンの更新、コンシューマーの移行が必要です—チームがモジュールが何も抽象化せず、間接性を追加しているだけであることに気付くまで。その時までに、10人以上のコンシューマーがいて、モジュールを巻き戻す方が、モジュールと一緒に生活するよりも苦痛です。
❌ #2: 時期尚早なモジュール化
問題: 最初の使用後にモジュールを作成すると、間違った抽象化になる
// 最初の使用:シンプルなS3バケット
resource "aws_s3_bucket" "data" {
bucket = "company-data"
}
// 開発者:「これをモジュールにしましょう!」
// 1人のユーザーでモジュールを作成
// 2回目の使用:バージョニングが必要
// 3回目の使用:レプリケーションが必要
// 4回目の使用:ライフサイクルルールが必要
// 5回目の使用:暗号化が必要
// 結果:モジュールには30個の変数があり、誰もそれらすべてを使用しない
修正: 2〜3回の実際の使用を待ってから、共通のパターンを抽出する
// 3回の使用を見た後、共通のパターンが現れます:
module "s3_bucket" {
source = "./modules/s3"
name = "company-data"
type = "data" | "logs" | "artifacts" // 3つの既知のパターン
// モジュールはタイプごとに適切なデフォルトを処理します
}
ルール: 最初はインライン。2回目はコピー&ペースト。3回目は抽象化。
これが誤解を招きやすく、デバッグが難しい理由: 最初の使用はクリーンに見えます(シンプルなS3モジュール、3つの変数)。2回目の使用にはバージョニングが必要です(2つの変数を追加)。3回目にはレプリケーションが必要です(5つの変数を追加)。4回目にはライフサイクルルールが必要です(8つの変数を追加)。各追加は個別に考えると合理的です。6か月後、モジュールには30個の変数、20個のブール値のフィーチャーフラグ、複雑な条件付きロジックがあります。誰もすべての機能を使用しません(各コンシューマーは変数の30%を使用します)。すべての変更は誰かを壊すリスクがあります。間違った抽象化が早期に(パターンが現れる前に)組み込まれ、リファクタリングには10以上のチームの調整が必要です。モジュールが間違って成長したことに気付くまでに3〜6か月の機能リクエストが必要ですが、元に戻すにはすべてのコンシューマーを移行する必要があります—誰も時間がない2か月のプロジェクトです。
❌ #3: 状態移行の悪夢
問題: 状態移行を計画せずにモジュールにリファクタリングする
// 以前:インラインリソース
resource "a
(原文がここで切り詰められています) 📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
Terraform Module Refactoring - Decision Expert
Assumption: You know Terraform syntax. This covers when to modularize vs keep inline.
Before Refactoring to Module: Strategic Assessment
Ask yourself these questions BEFORE extracting a module:
1. Duplication Pain Analysis
- How many usages?: 1-2 usages → Keep inline (wait for third), 3+ → Module candidate
- How identical?: <50% same config → Use locals, >80% same → Module makes sense
- Change frequency: Changes weekly → DON'T modularize (unstable API), Quarterly+ → Module safe
- Scope of changes: Entire resource → Module, Just variables → Locals sufficient
2. Team Readiness Assessment
- Test coverage: <50% coverage → TOO RISKY (breaking changes won't be caught)
- State backup: Is state in remote backend? Can we roll back if migration fails?
- Consumer count: 1-3 consumers → Easy to update, 10+ → Module change = 10 PRs
- Documentation: Does team know how to use
terraform state mv? Need training first?
3. Cost/Benefit Analysis
- Module overhead: Versioning strategy + automated tests + migration docs + state migration risk
- Duplication pain: Inconsistent configs + bug fixes need 3+ PRs + compliance drift risk
- Break-even point: Module worth it when 4+ identical usages + stable API + compliance need
- Time to value: Simple module = 2 hours, Complex with state = 2 days planning + 4 hours execution
Critical Decision: Module vs Inline
Considering creating a module?
│
├─ Pattern used once → DON'T modularize (keep inline)
│ └─ Wait for second usage
│ WHY: Premature abstraction harder to change
│
├─ Pattern used 2-3 times → MAYBE modularize
│ ├─ Identical config → Modularize
│ ├─ Similar but different → DON'T (use locals instead)
│ └─ Different teams using → DON'T (coordination overhead)
│
├─ Pattern used 4+ times → Modularize
│ └─ IF config is stable (not changing every sprint)
│ WHY: Module changes require updating all consumers
│
└─ Compliance/security requirement → Modularize
└─ Enforce standards across teams
WHY: Module = single source of truth for compliance
The trap: Over-modularization. Modules add indirection, versioning, testing overhead.
Rule of thumb: Resist creating modules until pain of duplication > pain of abstraction.
Anti-Patterns
❌ #1: Leaky Abstractions
Problem: Module exposes 50 variables, just wrapping resources
// ❌ WRONG - 1:1 variable mapping
module "vpc" {
source = "./modules/vpc"
cidr_block = var.cidr_block
enable_dns_hostnames = var.enable_dns_hostnames
enable_dns_support = var.enable_dns_support
instance_tenancy = var.instance_tenancy
enable_classiclink = var.enable_classiclink
enable_classiclink_dns_support = var.enable_classiclink_dns_support
assign_generated_ipv6_cidr_block = var.assign_generated_ipv6_cidr_block
// ... 43 more variables
}
// WHY IT'S BAD: Not abstracting anything, just moving code
Fix: High-level interface
// ✅ CORRECT - meaningful abstraction
module "vpc" {
source = "./modules/vpc"
network_config = {
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b"]
public_subnets = 2
private_subnets = 2
}
// 4 variables instead of 50
}
Test: If module variables match resource arguments 1:1, it's not a real abstraction.
Why this is deceptively hard to debug: Module works perfectly—tests pass, terraform apply succeeds. The problem emerges slowly over months: every resource argument becomes a module variable (+50 variables), documentation becomes unwieldy (which of 50 variables do I actually need?), consumers still need to know AWS VPC internals to use the module (defeating abstraction purpose). Takes 3-6 months of maintenance hell—adding new AWS features requires module updates, version bumps, consumer migrations—before team realizes the module isn't abstracting anything, just adding indirection. By then, you have 10+ consumers and unwinding the module is more painful than living with it.
❌ #2: Premature Modularization
Problem: Create module after first usage, becomes wrong abstraction
// First usage: Simple S3 bucket
resource "aws_s3_bucket" "data" {
bucket = "company-data"
}
// Developer: "Let's make this a module!"
// Creates module with 1 user
// Second usage: Needs versioning
// Third usage: Needs replication
// Fourth usage: Needs lifecycle rules
// Fifth usage: Needs encryption
// Result: Module has 30 variables, nobody uses all of them
Fix: Wait for 2-3 real usages, then extract common pattern
// After seeing 3 usages, common pattern emerges:
module "s3_bucket" {
source = "./modules/s3"
name = "company-data"
type = "data" | "logs" | "artifacts" // 3 known patterns
// Module handles appropriate defaults per type
}
Rule: First time: inline. Second time: copy-paste. Third time: abstract.
Why this is deceptively hard to debug: First usage looks clean (simple S3 module, 3 variables). Second usage needs versioning (add 2 variables). Third needs replication (add 5 variables). Fourth needs lifecycle rules (add 8 variables). Each addition seems reasonable in isolation. After 6 months, module has 30 variables, 20 boolean feature flags, complex conditional logic. Nobody uses all features (each consumer uses 30% of variables). Every change risks breaking someone. The wrong abstraction was baked in early (before patterns emerged) and now refactoring requires coordinating 10+ teams. Takes 3-6 months of feature requests to realize the module grew wrong, but reversing it requires migrating all consumers—a 2-month project nobody has time for.
❌ #3: State Migration Nightmare
Problem: Refactor to module without planning state migration
// Before: Inline resources
resource "aws_vpc" "main" { ... }
resource "aws_subnet" "private" { ... }
// After: Module
module "network" {
source = "./modules/network"
}
// Result: Terraform wants to destroy old resources, create new ones
// State addresses changed: aws_vpc.main → module.network.aws_vpc.main
Fix: Move state before deploying
# Plan the move
terraform state list # See current addresses
# Move resources into module
terraform state mv aws_vpc.main module.network.aws_vpc.main
terraform state mv aws_subnet.private module.network.aws_subnet.private
# Verify no changes
terraform plan # Should show: No changes
WARNING: State moves are dangerous. Always backup state first:
terraform state pull > backup-$(date +%s).tfstate
Why this is deceptively hard to debug: NO ERROR MESSAGE. terraform apply shows plan to destroy VPC, create "new" VPC with identical config. Looks like Terraform bug or state corruption. You spend 20-30 minutes checking: Terraform version? Backend config? State file corrupted? The error is invisible—state addresses changed (aws_vpc.main → module.network.aws_vpc.main) but Terraform doesn't say "you moved code without moving state." It just says "resource doesn't exist in state, will create new one." Developers approve the plan thinking it's safe (identical config), then production VPC gets destroyed and recreated, taking down all services. The fix (state mv) is 2 minutes, but discovering that's the problem takes 20-30 minutes of debugging—and by then you may have already applied and caused an outage.
❌ #4: Module Version Hell
Problem: 20 consumers on different module versions, breaking changes
// app1
module "vpc" { source = "git::...?ref=v1.0.0" }
// app2
module "vpc" { source = "git::...?ref=v1.2.0" }
// app3
module "vpc" { source = "git::...?ref=v2.0.0" } // Breaking changes
// Need bugfix in v1.x but already on v2.x
// Result: Maintain 3 versions or force painful upgrades
Fix: Semantic versioning + deprecation period
// v1.x: Stable, bug fixes only
// v2.x: New features, maintains v1 compatibility mode
// v3.x: Remove deprecated features
// v2 module supports both:
variable "legacy_mode" {
default = false // New consumers get new behavior
}
// Gives consumers 6 months to migrate
Rule: Breaking changes require major version + migration guide.
Why this is deceptively hard to debug: Version divergence happens slowly over weeks/months. App1 upgrades to v2.0 (works fine). App2 stays on v1.2 (no time to upgrade). App3 needs bugfix only in v1.x but you're maintaining v2.x now. Takes 2-3 weeks before pattern emerges: every module change requires checking "which versions need this fix?" You're backporting fixes to 3 versions, testing each, cutting multiple releases. CI builds slow down (testing v1.x, v2.x, v3.x). Documentation fragments (README has v1 vs v2 sections). Consumers report bugs fixed in v2 still present in v1—but they can't upgrade due to breaking changes. After 3-6 months you have 5 major versions to support, or you force painful migrations that break production apps.
Decision Frameworks
When to Extract Common Code
Have repeated Terraform code?
│
├─ Repeated data sources → Use locals (NOT module)
│ └─ data "aws_ami" { ... } appears 3 times
│ WHY: Data sources don't benefit from modules
│
├─ Repeated resource patterns → Check usage count
│ ├─ 1 usage → Keep inline
│ ├─ 2-3 usages → Use locals or workspaces
│ └─ 4+ usages → Consider module
│
└─ Compliance requirement → Module immediately
└─ Must enforce security standards
WHY: Module = single compliance enforcement point
Module Boundaries
Where to draw module boundary?
│
├─ By lifecycle → Good boundary
│ └─ VPC (rarely changes) vs EC2 (often changes)
│ WHY: Separate change frequencies
│
├─ By team ownership → Good boundary
│ └─ Team A owns networking, Team B owns compute
│ WHY: Clear ownership and responsibility
│
├─ By technology → Bad boundary
│ └─ "Database module", "Compute module", "Network module"
│ WHY: Real apps need cross-cutting concerns
│
└─ By Terraform resource type → Bad boundary
└─ aws_vpc module, aws_subnet module, aws_route_table module
WHY: Too granular, loses cohesion
Good module: VPC + subnets + route tables + NAT gateway (cohesive networking unit) Bad module: Just VPC (consumer has to wire up subnets manually)
Module Design Patterns
Pattern 1: Composition (Preferred)
// Small, focused modules
module "vpc" { ... }
module "eks" {
vpc_id = module.vpc.id // Compose modules
}
module "rds" {
subnet_ids = module.vpc.private_subnet_ids
}
Benefit: Mix and match, replace parts independently.
Pattern 2: Monolithic (Avoid)
// One module does everything
module "infrastructure" {
// Creates VPC + EKS + RDS + everything
}
Problem: Can't replace just VPC, all-or-nothing.
When monolithic is OK: Compliance module that must enforce standards together.
Refactoring Process
Step 1: Identify Duplication (Don't Rush)
# Find repeated patterns
grep -r "resource \"aws_s3_bucket\"" .
# If 1-2 results: keep inline
# If 3+ results: analyze differences
Step 2: Validate True Duplication
# Are configs actually identical?
diff app1/s3.tf app2/s3.tf
# If > 50% different: NOT true duplication
# Solution: locals or data sources, not module
Step 3: Design Module Interface
// ❌ WRONG - expose every resource argument
variable "acl" {}
variable "versioning" {}
variable "lifecycle_rules" {}
variable "replication" {}
// ... 20 more variables
// ✅ CORRECT - expose intent
variable "bucket_type" {
type = string
validation {
condition = contains(["data", "logs", "artifacts"], var.bucket_type)
}
}
// Module maps type to appropriate settings
Step 4: Extract + Test
# Create module
mkdir -p modules/s3
mv s3.tf modules/s3/main.tf
# Test in isolation
cd modules/s3
terraform init
terraform plan -var bucket_type=data
Step 5: Migrate State (Critical)
# Backup state
terraform state pull > backup.tfstate
# Move resources
terraform state mv aws_s3_bucket.data module.s3.aws_s3_bucket.main
# Verify
terraform plan # Should show: No changes
Error Recovery Procedures
When State Migration Causes Destroy/Create Plan
Recovery steps:
- STOP: Do NOT apply. Run
terraform state pull > emergency-backup.tfstateimmediately - Diagnose: Run
terraform state listto see current addresses vs expected module addresses - Fix state: Run
terraform state mvcommands for each resource that moved into module - Fallback: If you already applied and destroyed resources, restore from backup:
terraform state push emergency-backup.tfstate, then import destroyed resources:terraform import module.network.aws_vpc.main vpc-xxxxx
When Module Has Too Many Variables (Leaky Abstraction)
Recovery steps:
- Audit usage: Survey all consumers—which variables do they actually use? (Often 20% of variables)
- Identify patterns: Group consumers by usage pattern (data buckets, log buckets, artifact buckets)
- Redesign interface: Replace 50 variables with
bucket_typeenum + sensible defaults per type - Fallback: If redesign too risky, create
v2module with clean interface, deprecatev1over 6 months
When Premature Module Needs Different Features Per Consumer
Recovery steps:
- Count feature flags: If >10 boolean toggles, module is wrong abstraction
- Split by usage: Create separate modules per use case (simple-bucket, versioned-bucket, replicated-bucket)
- Migrate incrementally: New consumers use new modules, old consumers stay on v1 (deprecate over time)
- Fallback: If splitting too complex, add
advanced_configescape hatch allowing raw HCL passthrough for edge cases
When Multiple Module Versions Cause Maintenance Hell
Recovery steps:
- Audit versions: Run
grep -r 'source.*?ref=' . | sort | uniq -cto see version distribution - Create migration path: Write automated migration script (sed/awk) to update HCL from v1 → v2
- Coordinate upgrades: Schedule "module upgrade week" where all teams migrate together
- Fallback: If coordination impossible, use monorepo with workspace protocol (
source = "../../modules/vpc") to eliminate versions—all consumers use same code, breaking changes impossible
When to Load Full Reference
MANDATORY - READ ENTIRE FILE: references/state-migration.md when:
- Migrating 5+ resources into module with complex dependencies
- Encountering 3+ state-related errors during migration (resource not found, duplicate)
- Setting up automated state migration for 10+ similar refactorings
- Need rollback procedures for failed state migration in production
MANDATORY - READ ENTIRE FILE: references/module-testing.md when:
- Module has 3+ consumers and needs automated testing
- Setting up CI/CD pipeline for module with 5+ test scenarios
- Implementing contract tests between module versions
- Need to test 10+ module input combinations
Do NOT load references for:
- Basic refactoring decisions (use Strategic Assessment section)
- Single resource state moves (use Error Recovery section above)
- Deciding whether to create module (use Critical Decision section)
Resources
- Official Docs: https://developer.hashicorp.com/terraform/language/modules (for syntax)
- This Skill: When to modularize, module boundaries, anti-patterns