Software quality is notoriously difficult to measure objectively. How do you quantify whether code is “good” or “bad”? Enter the Chidamber and Kemerer (CK) Metrics Suite - a pioneering set of object-oriented design metrics that provide concrete, measurable indicators of code quality. Introduced in 1994, these metrics have become fundamental tools for software engineers, architects, and quality assurance teams worldwide.

In this comprehensive guide, we’ll explore each CK metric, understand how they’re calculated, and discover why they’re crucial for maintaining healthy, maintainable codebases.

What Are CK Metrics?

The CK Metrics Suite consists of six core metrics designed specifically for object-oriented programming:

  1. WMC - Weighted Methods per Class
  2. DIT - Depth of Inheritance Tree
  3. NOC - Number of Children
  4. CBO - Coupling Between Objects
  5. RFC - Response for a Class
  6. LCOM - Lack of Cohesion of Methods

Additionally, modern implementations often include supplementary metrics like CA (Afferent Coupling) and CE (Efferent Coupling).

The CK Metrics Explained

1. WMC - Weighted Methods per Class

What it measures: The complexity of a class based on the sum of complexities of its methods.

How it’s calculated:

WMC = Σ(complexity of each method in the class)

For each method, complexity is typically measured using Cyclomatic Complexity:

  • Each method starts with complexity 1
  • Each decision point (if, while, for, case) adds 1
  • Each logical operator (&&, ||) adds 1

Example in Kotlin:

class UserService {
    fun validateUser(user: User): Boolean {  // CC = 3
        if (user.email.isEmpty()) return false      // +1
        if (user.age < 18) return false            // +1
        return true
    }
    
    fun processOrder(order: Order): String {  // CC = 4
        when (order.status) {                      // +3 (3 cases)
            "pending" -> return "Processing"
            "shipped" -> return "In transit"
            else -> return "Unknown"
        }
    }
}
// WMC = 3 + 4 = 7

Why it matters:

  • High WMC indicates complex classes that are difficult to understand, test, and maintain
  • Threshold: WMC > 20 suggests the class may have too many responsibilities
  • Refactoring signal: Consider breaking complex classes into smaller, focused components

2. DIT - Depth of Inheritance Tree

What it measures: The maximum depth of inheritance from the root class to the current class.

How it’s calculated:

DIT = maximum depth from root class to current class

Example:

// DIT = 0 (no inheritance)
class User
 
// DIT = 1 (inherits from User)  
class AdminUser : User()
 
// DIT = 2 (inherits from AdminUser)
class SuperAdminUser : AdminUser()

Important note: Interface implementation doesn’t count toward DIT - only class inheritance matters.

Why it matters:

  • Deep inheritance makes code harder to understand and modify
  • High DIT increases the likelihood of errors and makes debugging complex
  • Threshold: DIT > 5 suggests overly complex inheritance hierarchies
  • Best practice: Favor composition over deep inheritance

3. NOC - Number of Children

What it measures: The number of immediate subclasses of a class.

How it’s calculated:

NOC = count of direct subclasses

Example:

abstract class Vehicle {  // NOC = 2
    abstract fun start()
}
 
class Car : Vehicle() {   // NOC = 0
    override fun start() = println("Car started")
}
 
class Motorcycle : Vehicle() {  // NOC = 0
    override fun start() = println("Motorcycle started")
}

Why it matters:

  • High NOC indicates that a class is being reused extensively
  • Too many children can indicate improper abstraction
  • Threshold: NOC > 10 suggests the parent class may be too general
  • Design insight: High NOC might indicate a need for better class design

4. CBO - Coupling Between Objects

What it measures: The number of classes this class is coupled to (uses or is used by).

How it’s calculated: CBO counts relationships including:

  • Inheritance relationships
  • Method calls to other classes
  • Property types from other classes
  • Parameter types from other classes

Example:

class OrderService(
    private val userRepository: UserRepository,  // +1
    private val emailService: EmailService       // +1
) {
    fun processOrder(order: Order): Receipt {    // +2 (Order, Receipt)
        val user = userRepository.findById(order.userId)
        emailService.sendConfirmation(user.email)
        return Receipt(order.id, order.total)
    }
}
// CBO = 4 (UserRepository, EmailService, Order, Receipt)

Why it matters:

  • High coupling makes classes difficult to modify and test in isolation
  • Increased CBO leads to ripple effects when changes are made
  • Threshold: CBO > 10 indicates excessive coupling
  • Refactoring opportunity: Consider dependency injection or interface abstractions

5. RFC - Response for a Class

What it measures: The number of methods that can potentially be executed in response to a message received by an object.

How it’s calculated:

RFC = local methods + external methods called

Example:

class PaymentProcessor {
    fun processPayment(amount: Double) {     // Local method
        validateAmount(amount)               // Local method call
        chargeCard(amount)                   // Local method call
        logger.info("Payment processed")     // External method call
        emailService.sendReceipt()           // External method call
    }
    
    private fun validateAmount(amount: Double) { }  // Local method
    private fun chargeCard(amount: Double) { }      // Local method
}
// RFC = 3 local + 2 external = 5

Why it matters:

  • High RFC indicates complex classes with many responsibilities
  • Testing complexity increases with RFC
  • Threshold: RFC > 50 suggests the class is doing too much
  • Maintenance impact: High RFC classes are harder to understand and modify

6. LCOM - Lack of Cohesion of Methods

What it measures: How well the methods of a class work together by sharing instance variables.

How it’s calculated:

LCOM = P - Q
where:
P = number of method pairs that don't share instance variables
Q = number of method pairs that do share instance variables
Result is clamped to minimum 0

Example:

class UserProfile {
    private val name: String = ""
    private val email: String = ""
    private val loginCount: Int = 0
    
    fun updateName(newName: String) {        // Uses: name
        // implementation
    }
    
    fun updateEmail(newEmail: String) {      // Uses: email
        // implementation  
    }
    
    fun incrementLogin() {                   // Uses: loginCount
        // implementation
    }
    
    fun getDisplayInfo(): String {           // Uses: name, email
        return "$name ($email)"
    }
}
 
// Method pairs analysis:
// updateName-updateEmail: no shared variables (P)
// updateName-incrementLogin: no shared variables (P) 
// updateName-getDisplayInfo: share 'name' (Q)
// updateEmail-incrementLogin: no shared variables (P)
// updateEmail-getDisplayInfo: share 'email' (Q)
// incrementLogin-getDisplayInfo: no shared variables (P)
 
// P = 4, Q = 2, LCOM = 4 - 2 = 2

Why it matters:

  • High LCOM suggests a class has multiple responsibilities (violates Single Responsibility Principle)
  • Low cohesion makes classes harder to understand and maintain
  • Threshold: LCOM > 5 indicates potential design problems
  • Refactoring target: High LCOM classes are prime candidates for splitting

Supplementary Metrics

CA - Afferent Coupling

What it measures: The number of classes that depend on this class.

CE - Efferent Coupling

What it measures: The number of classes this class depends on (similar to CBO).

Quality Thresholds and Interpretation

MetricExcellentGoodModeratePoorCritical
WMC≤ 10≤ 20≤ 30≤ 50> 50
DIT≤ 2≤ 4≤ 6≤ 8> 8
NOC≤ 3≤ 5≤ 8≤ 12> 12
CBO≤ 5≤ 10≤ 15≤ 20> 20
RFC≤ 15≤ 30≤ 50≤ 75> 75
LCOM= 0≤ 2≤ 5≤ 10> 10

Why CK Metrics Matter

1. Objective Quality Assessment

CK metrics provide concrete, measurable indicators of code quality, removing subjectivity from code reviews.

2. Early Warning System

High metric values often indicate design problems before they become critical issues.

3. Refactoring Guidance

Metrics help prioritize which classes need attention and what type of refactoring might help.

4. Team Communication

Metrics provide a common vocabulary for discussing code quality across development teams.

5. Technical Debt Measurement

Track metrics over time to measure technical debt accumulation or reduction.

Practical Application: Building a Quality Dashboard

When implementing CK metrics in your development workflow:

  1. Automate Collection: Integrate metrics collection into your CI/CD pipeline
  2. Set Team Thresholds: Establish quality gates based on your team’s standards
  3. Track Trends: Monitor metrics over time, not just snapshots
  4. Focus on Outliers: Pay attention to classes with extremely high values
  5. Combine with Other Metrics: Use alongside test coverage, complexity, and performance metrics

Common Pitfalls and Misconceptions

Metrics as Absolute Truth

Metrics are indicators, not definitive judgments. A high CBO might be justified for a well-designed facade class.

Optimizing Metrics Over Design

Don’t sacrifice good design to improve metric scores. The goal is better code, not better numbers.

Ignoring Context

A high WMC in a state machine class might be appropriate, while the same value in a data class indicates problems.

All-or-Nothing Approach

Start with one or two metrics and gradually incorporate others as your team becomes comfortable.

Tools and Implementation

Several tools can calculate CK metrics for various programming languages:

  • For Java/Kotlin: CK Metrics tool, SonarQube, Eclipse Metrics
  • For C#: NDepend, Visual Studio Code Metrics
  • For C++: Understand, CCFinderX
  • Multi-language: SonarQube, CodeClimate

For our Kotlin implementation example, we built a comprehensive analyzer that:

  • Parses Kotlin code using PSI (Program Structure Interface)
  • Calculates all CK metrics with proper handling of Kotlin-specific constructs
  • Generates interactive HTML reports with visualizations
  • Provides actionable suggestions for improvement

Conclusion

CK metrics offer a powerful lens through which to view code quality. While they’re not a silver bullet, they provide valuable insights that can guide refactoring decisions, highlight design problems, and help maintain code quality over time.

The key is to use these metrics as part of a broader quality strategy, combined with good design principles, thorough testing, and regular code reviews. When applied thoughtfully, CK metrics can help teams build more maintainable, understandable, and robust software systems.

Remember: the goal isn’t perfect metric scores, but better, more maintainable code. Use CK metrics as a compass to guide your journey toward higher code quality.


Want to see CK metrics in action? Check out our open-source Kotlin metrics analyzer that implements all these calculations and generates comprehensive quality reports: Kotlin Metrics Tool