Concepts
Learn the core architectural concepts that power tdepend
Fitness Functions
Fitness functions are automated checks that ensure your architecture stays aligned with your intended design over time. They encode architectural rules as measurable, testable metrics that can be continuously evaluated in your development workflow.
In tdepend, metrics like distance, cyclic dependencies, abstractness, and coupling can all be used as fitness functions inside CI to prevent architectural drift. You can enforce these through both CLI and programmatic API.
CLI Example:
tdepend analyze --ci --failOnCycle
Programmatic Example:
import { analyze } from 'tdepend';
const result = await analyze({
rootDir: 'src',
failOnCycle: true,
failOnThreshold: true
});
if (!result.report.success) {
console.error('Architecture violations!');
process.exit(1);
} This approach enables evolutionary architecture - your architecture evolves safely because fitness functions continuously guard against violations.
Distance Metric
Distance measures how far a component is from the ideal balance between abstractness and stability. This is one of the most important metrics for identifying architectural problems.
Distance = | Abstractness + Instability - 1 |
Interpreting Distance Values:
- • D = 0.0: Perfect balance (on the main sequence)
- • D < 0.3: Healthy architecture
- • 0.3 ≤ D ≤ 0.6: Acceptable with monitoring
- • D > 0.6: Design smell - requires refactoring
The Main Sequence Zones:
- • Zone of Pain: Highly stable but concrete (A ≈ 0, I ≈ 0). Hard to change and tightly coupled. Example: God objects, rigid frameworks.
- • Zone of Uselessness: Highly abstract but unstable (A ≈ 1, I ≈ 1). Over-engineered abstractions with no dependents. Example: Unused interfaces.
- • Main Sequence: Balanced components that are either stable and abstract (interfaces, contracts) or unstable and concrete (implementations).
You can access distance metrics programmatically to create custom quality gates:
const highDistance = result.metrics
.filter(m => m.distance > 0.8)
.map(m => ({
file: m.filePath,
distance: m.distance,
zone: getZone(m.abstractness, m.instability)
})); Abstractness
Abstractness describes how many of a component's elements are abstractions (interfaces, abstract classes) versus concrete implementations.
Abstractness = Number of abstract types / Total number of types
High abstractness means the component defines contracts rather than implementations.
Coupling (Afferent & Efferent)
Coupling measures how much a component depends on others (Ce) and how many depend on it (Ca).
- • Afferent Coupling (Ca): incoming dependencies
- • Efferent Coupling (Ce): outgoing dependencies
Instability = Ce / (Ca + Ce)
These values help determine how stable or volatile a module is.
Tarjan's SCC Algorithm
Tarjan's algorithm detects strongly connected components (SCCs) in a directed graph with O(V + E) time complexity, making it highly efficient even for large codebases.
In tdepend, SCCs correspond to cyclic dependencies between modules or namespaces. Any SCC with more than one node is treated as a cycle that should be reviewed or refactored.
Why Cycles Are Problematic:
- • Make testing difficult (can't test components in isolation)
- • Prevent tree-shaking and code splitting
- • Create tight coupling and fragile architecture
- • Make the codebase harder to understand and maintain
- • Can cause initialization order issues
Cycle Detection in tdepend:
tdepend automatically detects all cycles during analysis and can normalize them with unique hash-based IDs for consistent tracking across runs:
// Exported cycle format
{
"id": "a3f2b1c4d5e6f7g8", // Unique hash
"nodes": [
"/path/to/moduleA.ts",
"/path/to/moduleB.ts",
"/path/to/moduleC.ts"
],
"length": 3
} This enables tracking architectural debt over time and identifying cycles that persist across refactorings.
Programmatic Access:
const result = await analyze({ rootDir: 'src' });
if (result.cycles.length > 0) {
console.log(`Found ${result.cycles.length} cycles:`);
for (const cycle of result.cycles) {
const files = cycle.map(p => p.split('/').pop());
console.log(` ${files.join(' → ')}`);
}
}