Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Responsibility Analysis

Overview

Responsibility analysis is a core feature of Debtmap that helps identify violations of the Single Responsibility Principle (SRP), one of the fundamental SOLID design principles. By analyzing function and method names, Debtmap automatically infers the distinct functional responsibilities within a code unit and detects when a single module, struct, or class has taken on too many concerns.

This chapter provides an in-depth look at how Debtmap determines responsibilities, categorizes them, and uses this information to guide refactoring decisions.

What Are Responsibilities?

In the context of Debtmap, a responsibility is a distinct functional domain or concern that a code unit handles. Examples include:

  • Data Access - Getting and setting values from data structures
  • Validation - Checking inputs, verifying constraints, ensuring correctness
  • Persistence - Saving and loading data to/from storage
  • Computation - Performing calculations and transformations
  • Communication - Sending and receiving messages or events

According to the Single Responsibility Principle, each module should have one and only one reason to change. When a module handles multiple unrelated responsibilities (e.g., validation, persistence, AND computation), it becomes:

  • Harder to understand - Developers must mentally juggle multiple concerns
  • More fragile - Changes to one responsibility can break others
  • Difficult to test - Testing requires complex setup across multiple domains
  • Prone to coupling - Dependencies from different domains become entangled

Debtmap’s responsibility analysis automatically identifies these violations and provides concrete recommendations for splitting modules along responsibility boundaries.

How Responsibilities Are Detected

Pattern-Based Inference

Debtmap uses prefix-based pattern matching to infer responsibilities from function and method names. This approach is both simple and effective because well-named functions naturally express their intent through conventional prefixes.

Implementation Location: src/organization/god_object_analysis.rs:316-386

The infer_responsibility_from_method() function performs case-insensitive prefix matching:

#![allow(unused)]
fn main() {
pub fn infer_responsibility_from_method(method_name: &str) -> String {
    let lower_name = method_name.to_lowercase();

    if lower_name.starts_with("format_") || lower_name.starts_with("render_") {
        return "Formatting & Output".to_string();
    }
    if lower_name.starts_with("parse_") || lower_name.starts_with("read_") {
        return "Parsing & Input".to_string();
    }
    // ... additional patterns
}
}

This approach works across languages (Rust, Python, JavaScript/TypeScript) because naming conventions are relatively consistent in modern codebases.

Responsibility Categories

Debtmap recognizes 11 built-in responsibility categories plus a generic “Utilities” fallback:

CategoryPrefixesExamples
Formatting & Outputformat_, render_, write_, print_format_json(), render_table(), write_report()
Parsing & Inputparse_, read_, extract_parse_config(), read_file(), extract_fields()
Filtering & Selectionfilter_, select_, find_filter_results(), select_top(), find_item()
Transformationtransform_, convert_, map_, apply_transform_data(), convert_type(), map_fields()
Data Accessget_, set_get_value(), set_name()
Validationvalidate_, check_, verify_, is_*validate_input(), check_bounds(), is_valid()
Computationcalculate_, compute_calculate_score(), compute_sum()
Constructioncreate_, build_, new_*, make_create_instance(), build_config(), new_user()
Persistencesave_, load_, store_save_data(), load_cache(), store_result()
Processingprocess_, handle_process_request(), handle_error()
Communicationsend_, receive_send_message(), receive_data()
Utilities(all others)helper(), do_work(), utility_fn()

Grouping Methods by Responsibility

Once individual methods are categorized, Debtmap groups them using group_methods_by_responsibility():

Implementation Location: src/organization/god_object_analysis.rs:268-280

#![allow(unused)]
fn main() {
pub fn group_methods_by_responsibility(methods: &[String]) -> HashMap<String, Vec<String>> {
    let mut groups: HashMap<String, Vec<String>> = HashMap::new();
    for method in methods {
        let responsibility = infer_responsibility_from_method(method);
        groups.entry(responsibility).or_default().push(method.clone());
    }
    groups
}
}

Output Structure:

  • Keys: Responsibility category names (e.g., “Data Access”, “Validation”)
  • Values: Lists of method names belonging to each category

The responsibility count is simply the number of unique keys in this HashMap.

Example Analysis

Consider a Rust struct with these methods:

#![allow(unused)]
fn main() {
impl UserManager {
    fn get_user(&self, id: UserId) -> Option<User> { }
    fn set_password(&mut self, id: UserId, password: &str) { }
    fn validate_email(&self, email: &str) -> bool { }
    fn validate_password(&self, password: &str) -> bool { }
    fn save_user(&self, user: &User) -> Result<()> { }
    fn load_user(&self, id: UserId) -> Result<User> { }
    fn send_notification(&self, user_id: UserId, msg: &str) { }
    fn format_user_profile(&self, user: &User) -> String { }
}
}

Debtmap’s Analysis:

MethodInferred Responsibility
get_userData Access
set_passwordData Access
validate_emailValidation
validate_passwordValidation
save_userPersistence
load_userPersistence
send_notificationCommunication
format_user_profileFormatting & Output

Result:

  • Responsibility Count: 5 (Data Access, Validation, Persistence, Communication, Formatting)
  • Assessment: This violates SRP - UserManager has too many distinct concerns

Responsibility Scoring

Integration with God Object Detection

Responsibility count is a critical factor in God Object Detection. The scoring algorithm includes:

responsibility_factor = min(responsibility_count / 3.0, 3.0)
god_object_score = method_factor × field_factor × responsibility_factor × size_factor

Why divide by 3.0?

  • 1-3 responsibilities: Normal, well-scoped module
  • 4-6 responsibilities: Warning signs, approaching problematic territory
  • 7+ responsibilities: Severe violation, likely a god object

Language-Specific Thresholds

Different languages have different expectations for responsibility counts:

LanguageMax ResponsibilitiesRationale
Rust5Strong module system encourages tight boundaries
Python3Duck typing makes mixing concerns more dangerous
JavaScript/TypeScript3Prototype-based, benefits from focused classes

These thresholds can be customized in .debtmap.toml:

[god_object_detection.rust]
max_traits = 5      # max_traits = max responsibilities

[god_object_detection.python]
max_traits = 3

Confidence Determination

Responsibility count contributes to overall confidence levels:

Implementation Location: src/organization/god_object_analysis.rs:234-266

#![allow(unused)]
fn main() {
pub fn determine_confidence(
    method_count: usize,
    field_count: usize,
    responsibility_count: usize,
    lines_of_code: usize,
    complexity_sum: u32,
    thresholds: &GodObjectThresholds,
) -> GodObjectConfidence {
    let mut violations = 0;

    if responsibility_count > thresholds.max_traits {
        violations += 1;
    }
    // ... check other metrics

    match violations {
        5 => GodObjectConfidence::Definite,
        3..=4 => GodObjectConfidence::Probable,
        1..=2 => GodObjectConfidence::Possible,
        _ => GodObjectConfidence::NotGodObject,
    }
}
}

Advanced Responsibility Detection

Module-Level Analysis

For large modules without a single dominant struct, Debtmap performs module-level responsibility detection:

Implementation Location: src/organization/god_object_detector.rs:682-697

The classify_responsibility() function provides extended categorization:

#![allow(unused)]
fn main() {
fn classify_responsibility(prefix: &str) -> String {
    match prefix {
        "get" | "set" => "Data Access",
        "calculate" | "compute" => "Computation",
        "validate" | "check" | "verify" | "ensure" => "Validation",
        "save" | "load" | "store" | "retrieve" | "fetch" => "Persistence",
        "create" | "build" | "new" | "make" | "init" => "Construction",
        "send" | "receive" | "handle" | "manage" => "Communication",
        "update" | "modify" | "change" | "edit" => "Modification",
        "delete" | "remove" | "clear" | "reset" => "Deletion",
        "is" | "has" | "can" | "should" | "will" => "State Query",
        "process" | "transform" => "Processing",
        _ => format!("{} Operations", capitalize_first(prefix)),
    }
}
}

This extended mapping covers 10 core categories plus dynamic fallback for custom prefixes.

Responsibility Groups

The ResponsibilityGroup data structure tracks detailed information about each responsibility:

Implementation Location: src/organization/mod.rs:156-161

#![allow(unused)]
fn main() {
#[derive(Debug, Clone, PartialEq)]
pub struct ResponsibilityGroup {
    pub name: String,           // e.g., "DataAccessManager"
    pub methods: Vec<String>,   // Methods in this group
    pub fields: Vec<String>,    // Associated fields
    pub responsibility: String, // e.g., "Data Access"
}
}

This structure enables:

  • Refactoring recommendations - Suggest splitting by responsibility group
  • Cohesion analysis - Measure how tightly methods are related
  • Field-method correlation - Identify which fields belong to which responsibilities

Refactoring Based on Responsibilities

When Debtmap detects a module with multiple responsibilities, it generates actionable refactoring recommendations using recommend_module_splits():

Implementation Location: src/organization/god_object_detector.rs:165-177

Process:

  1. Group all methods by their inferred responsibilities
  2. Create a ModuleSplit for each responsibility group
  3. Suggest module names (e.g., “DataAccessManager”, “ValidationManager”)
  4. Estimate lines of code for each new module
  5. Order by cohesion (most focused groups first)

Example Output:

Recommended Splits for UserManager:
  1. DataAccessManager (5 methods, ~80 lines)
     - get_user, set_password, get_email, set_email, update_profile

  2. ValidationManager (4 methods, ~60 lines)
     - validate_email, validate_password, check_permissions, verify_token

  3. PersistenceManager (3 methods, ~50 lines)
     - save_user, load_user, delete_user

  4. NotificationManager (2 methods, ~30 lines)
     - send_notification, send_bulk_notifications

Practical Refactoring Patterns

Pattern 1: Extract Service Classes

Before (God Object):

#![allow(unused)]
fn main() {
struct UserManager {
    db: Database,
    cache: Cache,
    notifier: Notifier,
}

impl UserManager {
    fn get_user(&self, id: UserId) -> Option<User> { }
    fn validate_email(&self, email: &str) -> bool { }
    fn save_user(&self, user: &User) -> Result<()> { }
    fn send_notification(&self, id: UserId, msg: &str) { }
}
}

After (Split by Responsibility):

#![allow(unused)]
fn main() {
// Data Access
struct UserRepository {
    db: Database,
    cache: Cache,
}

// Validation
struct UserValidator;

// Persistence
struct UserPersistence {
    db: Database,
}

// Communication
struct NotificationService {
    notifier: Notifier,
}
}

Pattern 2: Use Facade for Composition

After splitting, create a facade to coordinate:

#![allow(unused)]
fn main() {
struct UserFacade {
    repository: UserRepository,
    validator: UserValidator,
    persistence: UserPersistence,
    notifier: NotificationService,
}

impl UserFacade {
    fn register_user(&mut self, user: User) -> Result<()> {
        self.validator.validate_email(&user.email)?;
        self.persistence.save_user(&user)?;
        self.notifier.send_welcome(&user.id)?;
        Ok(())
    }
}
}

Pattern 3: Trait-Based Separation (Rust)

Use traits to define responsibility boundaries:

#![allow(unused)]
fn main() {
trait DataAccess {
    fn get_user(&self, id: UserId) -> Option<User>;
}

trait Validation {
    fn validate_email(&self, email: &str) -> bool;
}

trait Persistence {
    fn save_user(&self, user: &User) -> Result<()>;
}

// Implement only the needed traits per struct
impl DataAccess for UserRepository { }
impl Validation for UserValidator { }
impl Persistence for UserPersistence { }
}

Data Structures

GodObjectAnalysis

The main result structure includes responsibility information:

Implementation Location: src/organization/god_object_analysis.rs:5-18

#![allow(unused)]
fn main() {
pub struct GodObjectAnalysis {
    pub is_god_object: bool,
    pub method_count: usize,
    pub field_count: usize,
    pub responsibility_count: usize,      // Number of distinct responsibilities
    pub lines_of_code: usize,
    pub complexity_sum: u32,
    pub god_object_score: f64,
    pub recommended_splits: Vec<ModuleSplit>,
    pub confidence: GodObjectConfidence,
    pub responsibilities: Vec<String>,    // List of responsibility names
    pub purity_distribution: Option<PurityDistribution>,
}
}

ModuleSplit

Recommendations for splitting modules:

Implementation Location: src/organization/god_object_analysis.rs:40-45

#![allow(unused)]
fn main() {
pub struct ModuleSplit {
    pub suggested_name: String,         // e.g., "ValidationManager"
    pub methods_to_move: Vec<String>,   // Methods for this module
    pub responsibility: String,          // Responsibility category
    pub estimated_lines: usize,         // Approximate LOC
}
}

Testing Responsibility Detection

Debtmap includes comprehensive tests for responsibility detection:

Implementation Location: src/organization/god_object_analysis.rs:623-838

Test Coverage

Key test cases:

  • Prefix recognition - Each of the 11 categories is tested individually
  • Case insensitivity - Format_Output and format_output both map to “Formatting & Output”
  • Multiple responsibilities - Grouping diverse methods correctly
  • Empty input handling - Graceful handling of empty method lists
  • Edge cases - Methods without recognized prefixes default to “Utilities”

Example Test:

#![allow(unused)]
fn main() {
#[test]
fn test_multiple_responsibility_groups() {
    let methods = vec![
        "format_output".to_string(),
        "parse_input".to_string(),
        "get_value".to_string(),
        "validate_data".to_string(),
    ];

    let groups = group_methods_by_responsibility(&methods);

    assert_eq!(groups.len(), 4); // 4 distinct responsibilities
    assert!(groups.contains_key("Formatting & Output"));
    assert!(groups.contains_key("Parsing & Input"));
    assert!(groups.contains_key("Data Access"));
    assert!(groups.contains_key("Validation"));
}
}

Configuration

TOML Configuration

Customize responsibility thresholds in .debtmap.toml:

[god_object_detection]
enabled = true

[god_object_detection.rust]
max_traits = 5      # Max responsibilities for Rust

[god_object_detection.python]
max_traits = 3      # Max responsibilities for Python

[god_object_detection.javascript]
max_traits = 3      # Max responsibilities for JavaScript/TypeScript

Tuning Guidelines

Strict SRP Enforcement:

[god_object_detection.rust]
max_traits = 3
  • Enforces very tight single responsibility
  • Suitable for greenfield projects or strict refactoring efforts

Balanced Approach (Default):

[god_object_detection.rust]
max_traits = 5
  • Allows some flexibility while catching major violations
  • Works well for most projects

Lenient Mode:

[god_object_detection.rust]
max_traits = 7
  • Only flags severe SRP violations
  • Useful for large legacy codebases during initial assessment

Output and Reporting

Console Output

When analyzing a file with multiple responsibilities:

src/services/user_manager.rs
  ⚠️ God Object: 18 methods, 8 fields, 5 responsibilities
     Score: 185 (Confidence: Probable)

     Responsibilities:
       - Data Access (5 methods)
       - Validation (4 methods)
       - Persistence (3 methods)
       - Communication (3 methods)
       - Formatting & Output (3 methods)

     Recommended Splits:
       1. DataAccessManager (5 methods, ~75 lines)
       2. ValidationManager (4 methods, ~60 lines)
       3. PersistenceManager (3 methods, ~45 lines)

JSON Output

For programmatic analysis, use --format json:

{
  "file": "src/services/user_manager.rs",
  "is_god_object": true,
  "responsibility_count": 5,
  "responsibilities": [
    "Data Access",
    "Validation",
    "Persistence",
    "Communication",
    "Formatting & Output"
  ],
  "recommended_splits": [
    {
      "suggested_name": "DataAccessManager",
      "methods_to_move": ["get_user", "set_password", "get_email"],
      "responsibility": "Data Access",
      "estimated_lines": 75
    }
  ]
}

Best Practices

Writing SRP-Compliant Code

  1. Name functions descriptively - Use standard prefixes (get_, validate_, etc.)
  2. Group related functions - Keep similar responsibilities together
  3. Limit responsibility count - Aim for 1-3 responsibilities per module
  4. Review regularly - Run Debtmap periodically to catch responsibility creep
  5. Refactor early - Split modules before they hit thresholds

Code Review Guidelines

When reviewing responsibility analysis results:

  1. Check responsibility boundaries - Are they logically distinct?
  2. Validate groupings - Do the recommended splits make sense?
  3. Consider dependencies - Will splitting introduce more coupling?
  4. Estimate refactoring cost - Is the improvement worth the effort?
  5. Prioritize by score - Focus on high-scoring god objects first

Team Adoption

Phase 1: Assessment

  • Run Debtmap on codebase
  • Review responsibility violations
  • Identify top 10 problematic modules

Phase 2: Education

  • Share responsibility analysis results with team
  • Discuss SRP and its benefits
  • Agree on responsibility threshold standards

Phase 3: Incremental Refactoring

  • Start with highest-scoring modules
  • Apply recommended splits
  • Measure improvement with follow-up analysis

Phase 4: Continuous Monitoring

  • Integrate Debtmap into CI/CD
  • Track responsibility counts over time
  • Prevent new SRP violations from merging

Limitations and Edge Cases

False Positives

Scenario 1: Utilities Module

#![allow(unused)]
fn main() {
// utilities.rs - 15 helper functions with different prefixes
fn format_date() { }
fn parse_config() { }
fn validate_email() { }
// ... 12 more diverse utilities
}

Issue: Flagged as having multiple responsibilities, but it’s intentionally a utility collection.

Solution: Either accept the flagging (utilities should perhaps be split) or increase max_traits threshold.

False Negatives

Scenario 2: Poor Naming

#![allow(unused)]
fn main() {
impl DataProcessor {
    fn process_data(&mut self) { /* does everything */ }
    fn handle_stuff(&mut self) { /* also does everything */ }
    fn do_work(&mut self) { /* yet more mixed concerns */ }
}
}

Issue: All methods map to “Processing” or “Utilities”, so responsibility count is low despite clear SRP violations.

Solution: Encourage better naming conventions in your team. Debtmap relies on descriptive function names.

Language-Specific Challenges

Rust: Trait implementations may group methods by trait rather than responsibility, artificially inflating counts.

Python: Dynamic typing and duck typing make responsibility boundaries less clear from signatures alone.

JavaScript: Prototype methods and closures may not follow conventional naming patterns.

Integration with Other Features

God Object Detection

Responsibility analysis is a core component of God Object Detection. The responsibility count contributes to:

  • God object scoring
  • Confidence level determination
  • Refactoring recommendations

Tiered Prioritization

High responsibility counts increase priority in Tiered Prioritization through the god object multiplier.

Risk Assessment

Modules with multiple responsibilities receive higher risk scores in risk assessment, as they are more prone to bugs and harder to maintain.

Summary

Responsibility analysis in Debtmap:

  • Automatically detects SRP violations through pattern-based method name analysis
  • Categorizes methods into 11 built-in responsibility types
  • Provides actionable refactoring recommendations with suggested module splits
  • Integrates with god object detection for holistic architectural analysis
  • Supports language-specific thresholds for Rust, Python, and JavaScript/TypeScript
  • Is fully configurable via .debtmap.toml and CLI flags

By surfacing responsibility violations early and suggesting concrete refactoring paths, Debtmap helps teams maintain clean, modular architectures that follow the Single Responsibility Principle.