| name | Dependency Mapping |
| description | Map dependencies and coupling in legacy codebase to understand what breaks when you change something and identify refactoring risks |
| when_to_use | when you need to understand dependencies and coupling before making changes, or identifying what will break if you modify a component |
| version | 1.0.0 |
| languages | all |
Dependency Mapping
Overview
Before changing code, know what depends on it. Dependency mapping reveals the blast radius of your changes.
Core principle: Understand coupling before changing. High coupling = high risk. Map first, change second.
When to Use
Map dependencies when:
- Before refactoring or removing components
- Planning to extract module/service
- Investigating why "simple" changes break things
- Onboarding to unfamiliar codebase
- Deciding what to test after changes
Types of Dependencies
1. Direct Dependencies (Imports/Requires)
What: Code explicitly imports/requires another module
import { UserService } from './services/user'; // Direct dependency
class OrderController {
constructor(private userService: UserService) {}
// OrderController depends on UserService
}
Tools:
# Find what imports this file (JavaScript/TypeScript)
grep -r "import.*UserService" --include="*.ts" --include="*.js"
# Find what imports this file (C#)
grep -r "using.*UserService" --include="*.cs"
# Find what this file imports
grep "^import" src/services/user.ts # JS/TS
grep "^using" src/Services/UserService.cs # C#
2. Data Dependencies (Shared Database/State)
What: Components share data structures or database tables
UserService → users table ← AuthService
Both depend on users table structure. Schema change affects both.
How to find:
# Find all SQL queries against table
grep -r "FROM users" --include="*.ts"
grep -r "users\." --include="*.sql"
3. Temporal Dependencies (Order Matters)
What: Component A must run before Component B
// Migrations must run in order
001_create_users.sql
002_add_email_to_users.sql // Depends on 001 existing
Red flag: Race conditions if order not guaranteed
4. Runtime Dependencies (Services, APIs)
What: Code depends on external service being available
class PaymentService {
async charge() {
await this.stripe.charge(); // Runtime dependency on Stripe API
}
}
Impact: Service down = feature down
Mapping Techniques
Static Analysis (Code Structure)
Tool: dependency-cruiser (JavaScript/TypeScript)
npm install -g dependency-cruiser
depcruise --include-only "^src" --output-type dot src | dot -T svg > deps.svg
Tool: madge (JavaScript)
npm install -g madge
madge --image deps.png src/
Tool: NDepend (.NET)
# Commercial tool for .NET dependency analysis
# Generates dependency graphs and metrics
# https://www.ndepend.com/
Tool: grep (any language)
# What depends on UserService? (JavaScript/TypeScript)
grep -r "UserService" --include="*.ts" --include="*.js" src/
# What depends on UserService? (C#)
grep -r "UserService" --include="*.cs" src/
# What does UserService depend on?
grep "^import" src/services/UserService.ts # JS/TS
grep "^using" src/Services/UserService.cs # C#
Dynamic Analysis (Runtime Behavior)
Technique: Add logging
class UserService {
findById(id: string) {
console.log(`UserService.findById called from ${new Error().stack}`);
// ...
}
}
Run application, see who calls what.
Database Query Analysis
Find shared tables:
# Which files query users table?
grep -r "FROM users" --include="*.ts" --include="*.sql"
# Result shows coupling through data
Git Co-change Analysis
Technique: Files that change together
# Files changed together with UserService
git log --format="" --name-only -- src/services/UserService.ts | \
sort | uniq -c | sort -rn | head -20
Files that change together often = coupled.
Visualizing Dependencies
Simple Text Map
OrderController
→ UserService
→ Database (users table)
→ CacheService (Redis)
→ PaymentService
→ Stripe API
→ Database (payments table)
Dependency Matrix
| UserService | PaymentService | EmailService | |
|---|---|---|---|
| OrderController | ✓ | ✓ | ✓ |
| UserController | ✓ | ✓ | |
| AdminController | ✓ | ✓ |
Row depends on columns.
Layered Architecture
┌─────────────────────────────┐
│ Controllers (HTTP Layer) │
├─────────────────────────────┤
│ Services (Business Logic) │
├─────────────────────────────┤
│ Repositories (Data Access) │
├─────────────────────────────┤
│ Database │
└─────────────────────────────┘
Violations: Controller → Repository directly (skips Service layer)
Measuring Coupling
Afferent Coupling (Ca): Who depends on me?
UserService is depended on by:
- OrderController
- AuthController
- AdminController
Ca = 3 (high coupling - many depend on it)
High Ca = Risky to change (many things break)
Efferent Coupling (Ce): Who do I depend on?
OrderController depends on:
- UserService
- PaymentService
- EmailService
- NotificationService
Ce = 4 (high coupling - depends on many)
High Ce = Hard to test (many dependencies to mock)
Instability (I = Ce / (Ca + Ce))
UserService: Ca=5, Ce=2
I = 2/(5+2) = 0.28 (stable - more depended on than depending)
UtilityFunction: Ca=0, Ce=10
I = 10/(0+10) = 1.0 (unstable - all dependencies, no dependents)
I near 0: Stable (hard to change safely) I near 1: Unstable (easy to change, low impact)
Identifying Problem Areas
1. God Objects (High Ca)
UserService depended on by 50 files
Problem: Change breaks everything Solution: Split into smaller services
2. Dependency Cycles
A → B → C → A (cycle!)
Problem: Can't change one without others Solution: Break cycle with interface/event
3. Shotgun Surgery
Change "user email format" requires editing:
- UserService.ts
- UserController.ts
- UserValidator.ts
- EmailService.ts
- NotificationService.ts
...20 more files
Problem: Single concept scattered across many files Solution: Extract into single place
4. Wrong Layer Dependencies
Controller → Database directly (skipping Service layer)
Problem: Violates architecture, hard to test Solution: Enforce layer boundaries
Quick Analysis Commands
JavaScript/TypeScript:
# Count dependencies (imports) per file
find src -name "*.ts" -exec sh -c \
'echo "$(grep -c ^import "$1") $1"' _ {} \; | sort -rn
# Find files with >20 imports (high coupling)
find src -name "*.ts" -exec sh -c \
'count=$(grep -c ^import "$1"); [ $count -gt 20 ] && echo "$count $1"' _ {} \;
# Find most-imported files (high Ca)
grep -rh "^import.*from" --include="*.ts" src/ | \
sed "s/.*from ['\"]//;s/['\"].*//" | \
sort | uniq -c | sort -rn | head -20
C#/.NET:
# Count dependencies (usings) per file
find . -name "*.cs" -exec sh -c \
'echo "$(grep -c "^using " "$1") $1"' _ {} \; | sort -rn
# Find files with >20 usings (high coupling)
find . -name "*.cs" -exec sh -c \
'count=$(grep -c "^using " "$1"); [ $count -gt 20 ] && echo "$count $1"' _ {} \;
# Find most-used namespaces (high Ca)
grep -rh "^using " --include="*.cs" . | \
sed "s/using //;s/;.*//" | \
sort | uniq -c | sort -rn | head -20
Checklist
- Mapped direct dependencies (imports/requires)
- Identified data dependencies (shared tables/state)
- Found temporal dependencies (order requirements)
- Documented runtime dependencies (external services)
- Measured coupling (Ca, Ce for key components)
- Identified god objects (high Ca)
- Found dependency cycles
- Checked for wrong-layer violations
- Visualized dependency graph
- Estimated blast radius of planned changes
Anti-Patterns
❌ Changing Without Mapping
Bad: "This looks simple" → Change → 10 things break Good: Map dependencies → Understand impact → Change safely
❌ Ignoring Data Dependencies
Bad: "No code imports it, safe to change" Good: "Check who queries this table first"
Data dependencies are invisible in import statements.
Example: Planning UserService Refactor
Step 1: Map dependencies
# Who imports UserService?
grep -r "UserService" --include="*.ts" src/
# Result: 35 files depend on it (Ca = 35)
# What does UserService import?
grep "^import" src/services/UserService.ts
# Result: Depends on DB, Cache, Logger (Ce = 3)
# Instability: I = 3/(35+3) = 0.08 (very stable = risky to change!)
Step 2: Analyze dependents
Controllers: 12 files
Services: 15 files
Background jobs: 5 files
Tests: 3 files
Step 3: Plan safe refactor
Option A: Big-bang (risky - 35 files break)
Option B: Add interface, change implementation (safer - 0 files break)
Option C: Strangler fig pattern (safest - gradual migration)
Decision: Option C (strangler fig) due to high coupling.
Integration with Other Skills
- skills/refactoring/strangler-fig-pattern - Replace highly-coupled components safely
- skills/refactoring/seam-finding - Dependencies reveal seams
- skills/analysis/code-archaeology - Understand why dependencies exist
- skills/testing/test-driven-development - Mock dependencies in tests
Remember
- Map before changing
- High Ca = high risk
- Data dependencies are invisible
- Cycles = trouble
- Tools automate mapping
- Visualization reveals patterns
- Coupling metrics guide decisions