AgentSkillsCN

Dependency Mapping

映射遗留代码库中的依赖关系和耦合,以了解更改某处时会破坏什么,并识别重构风险

SKILL.md
--- frontmatter
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

typescript
import { UserService } from './services/user';  // Direct dependency

class OrderController {
  constructor(private userService: UserService) {}
  // OrderController depends on UserService
}

Tools:

bash
# 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

code
UserService → users table ← AuthService

Both depend on users table structure. Schema change affects both.

How to find:

bash
# 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

typescript
// 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

typescript
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)

bash
npm install -g dependency-cruiser
depcruise --include-only "^src" --output-type dot src | dot -T svg > deps.svg

Tool: madge (JavaScript)

bash
npm install -g madge
madge --image deps.png src/

Tool: NDepend (.NET)

bash
# Commercial tool for .NET dependency analysis
# Generates dependency graphs and metrics
# https://www.ndepend.com/

Tool: grep (any language)

bash
# 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

typescript
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:

bash
# 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

bash
# 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

code
OrderController
  → UserService
      → Database (users table)
      → CacheService (Redis)
  → PaymentService
      → Stripe API
      → Database (payments table)

Dependency Matrix

UserServicePaymentServiceEmailService
OrderController
UserController
AdminController

Row depends on columns.

Layered Architecture

code
┌─────────────────────────────┐
│  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?

code
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?

code
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))

code
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)

code
UserService depended on by 50 files

Problem: Change breaks everything Solution: Split into smaller services

2. Dependency Cycles

code
A → B → C → A  (cycle!)

Problem: Can't change one without others Solution: Break cycle with interface/event

3. Shotgun Surgery

code
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

code
Controller → Database directly (skipping Service layer)

Problem: Violates architecture, hard to test Solution: Enforce layer boundaries

Quick Analysis Commands

JavaScript/TypeScript:

bash
# 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:

bash
# 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

bash
# 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

code
Controllers: 12 files
Services: 15 files
Background jobs: 5 files
Tests: 3 files

Step 3: Plan safe refactor

code
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