CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-radon

Code Metrics in Python - comprehensive tool for computing various software metrics

Pending
Overview
Eval results
Files

maintainability.mddocs/

Maintainability Index

Compound metric combining Halstead volume, cyclomatic complexity, and lines of code to determine overall code maintainability. Uses the same formula as Visual Studio's maintainability index calculation to provide a single score indicating how easy code will be to maintain over time.

Capabilities

Main Analysis Functions

Primary functions for computing maintainability index from source code.

def mi_visit(code, multi):
    """
    Visit code and compute Maintainability Index.
    
    Analyzes Python source code to calculate a composite maintainability
    score based on Halstead metrics, cyclomatic complexity, and code size.
    
    Parameters:
    - code (str): Python source code to analyze
    - multi (bool): Whether to count multi-line strings as comments
    
    Returns:
    float: Maintainability Index score (typically 0-100+)
    """

def mi_compute(halstead_volume, complexity, sloc, comments):
    """
    Compute Maintainability Index from component metrics.
    
    Uses the standard MI formula:
    MI = max(0, (171 - 5.2 * ln(V) - 0.23 * G - 16.2 * ln(L)) * 100 / 171)
    
    Where:
    - V = Halstead Volume
    - G = Cyclomatic Complexity  
    - L = Source Lines of Code
    
    Parameters:
    - halstead_volume (float): Halstead volume metric
    - complexity (float): Average cyclomatic complexity
    - sloc (int): Source lines of code
    - comments (int): Number of comment lines
    
    Returns:
    float: Maintainability Index score
    """

def mi_parameters(code, count_multi=True):
    """
    Get all parameters needed for MI computation from source code.
    
    Convenience function that extracts Halstead volume, cyclomatic 
    complexity, SLOC, and comment counts in one call.
    
    Parameters:
    - code (str): Python source code to analyze
    - count_multi (bool): Whether to count multi-line strings as comments
    
    Returns:
    tuple: (halstead_volume, avg_complexity, sloc, comments)
    """

def mi_rank(score):
    """
    Rank Maintainability Index score with letter grades.
    
    Ranking system:
    - A: Score > 19 (High maintainability)
    - B: Score 9-19 (Medium maintainability)  
    - C: Score ≤ 9 (Low maintainability)
    
    Parameters:
    - score (float): Maintainability Index score
    
    Returns:
    str: Letter grade (A, B, or C)
    """

Maintainability Index Formula

The Maintainability Index uses a well-established formula that combines multiple code quality metrics:

MI = max(0, (171 - 5.2 × ln(HV) - 0.23 × CC - 16.2 × ln(LOC)) × 100 / 171)

Where:

  • HV = Halstead Volume (program volume in bits)
  • CC = Average Cyclomatic Complexity
  • LOC = Source Lines of Code (excluding comments and blanks)

Interpretation

  • High MI (> 19): Code is well-structured and easy to maintain
  • Medium MI (9-19): Code has moderate maintainability concerns
  • Low MI (≤ 9): Code may be difficult to maintain and understand

Usage Examples

Basic Maintainability Analysis

from radon.metrics import mi_visit, mi_rank

# Well-structured code
good_code = '''
def calculate_average(numbers):
    """Calculate the average of a list of numbers."""
    if not numbers:
        return 0
    return sum(numbers) / len(numbers)

def find_maximum(numbers):
    """Find the maximum value in a list."""
    if not numbers:
        return None
    return max(numbers)
'''

# Poorly structured code
poor_code = '''
def complex_function(a,b,c,d,e,f):
    if a>0:
        if b>0:
            if c>0:
                if d>0:
                    if e>0:
                        if f>0:
                            return a+b*c/d-e+f
                        else:
                            return a+b*c/d-e
                    else:
                        return a+b*c/d
                else:
                    return a+b*c
            else:
                return a+b
        else:
            return a
    else:
        return 0
'''

# Analyze maintainability
good_mi = mi_visit(good_code, multi=True)
poor_mi = mi_visit(poor_code, multi=True)

print(f"Well-structured code MI: {good_mi:.2f} ({mi_rank(good_mi)})")
print(f"Poorly structured code MI: {poor_mi:.2f} ({mi_rank(poor_mi)})")

# Output:
# Well-structured code MI: 74.23 (A)
# Poorly structured code MI: 12.45 (B)

Manual MI Calculation

from radon.metrics import mi_compute, mi_parameters, h_visit
from radon.complexity import cc_visit, average_complexity
from radon.raw import analyze

code = '''
def process_user_data(user_input, validation_rules):
    """
    Process and validate user input data.
    
    Args:
        user_input: Dictionary containing user data
        validation_rules: List of validation functions
    
    Returns:
        Processed and validated user data
    """
    # Validate input format
    if not isinstance(user_input, dict):
        raise TypeError("User input must be a dictionary")
    
    # Apply validation rules
    for rule in validation_rules:
        if not rule(user_input):
            raise ValueError("Validation failed")
    
    # Process the data
    processed_data = {}
    for key, value in user_input.items():
        if isinstance(value, str):
            processed_data[key] = value.strip().lower()
        elif isinstance(value, (int, float)):
            processed_data[key] = value
        else:
            processed_data[key] = str(value)
    
    return processed_data
'''

# Method 1: Use convenience function
halstead_vol, avg_complexity, sloc, comments = mi_parameters(code)
mi_score = mi_compute(halstead_vol, avg_complexity, sloc, comments)

print(f"Using mi_parameters:")
print(f"  Halstead Volume: {halstead_vol:.2f}")
print(f"  Average Complexity: {avg_complexity:.2f}")
print(f"  SLOC: {sloc}")
print(f"  Comments: {comments}")
print(f"  MI Score: {mi_score:.2f}")

# Method 2: Manual calculation
halstead = h_visit(code)
complexity_blocks = cc_visit(code)
raw_metrics = analyze(code)

manual_mi = mi_compute(
    halstead.total.volume,
    average_complexity(complexity_blocks),
    raw_metrics.sloc,
    raw_metrics.comments
)

print(f"\nManual calculation:")
print(f"  MI Score: {manual_mi:.2f}")
print(f"  Rank: {mi_rank(manual_mi)}")

Comparing Code Versions

from radon.metrics import mi_visit, mi_rank

# Original implementation
original = '''
def search_items(items, query, case_sensitive=False):
    results = []
    for item in items:
        if case_sensitive:
            if query in item:
                results.append(item)
        else:
            if query.lower() in item.lower():
                results.append(item)
    return results
'''

# Refactored implementation
refactored = '''
def search_items(items, query, case_sensitive=False):
    """
    Search for items containing the query string.
    
    Args:
        items: List of strings to search
        query: Search query string
        case_sensitive: Whether search should be case sensitive
    
    Returns:
        List of matching items
    """
    if not case_sensitive:
        query = query.lower()
        return [item for item in items if query in item.lower()]
    return [item for item in items if query in item]
'''

original_mi = mi_visit(original, multi=True)
refactored_mi = mi_visit(refactored, multi=True)

print(f"Original implementation:")
print(f"  MI: {original_mi:.2f} ({mi_rank(original_mi)})")
print(f"Refactored implementation:")
print(f"  MI: {refactored_mi:.2f} ({mi_rank(refactored_mi)})")
print(f"Improvement: {refactored_mi - original_mi:.2f} points")

Analyzing Multi-line Strings Impact

from radon.metrics import mi_visit

code_with_docstrings = '''
def data_processor(data):
    """
    Process input data with comprehensive validation and transformation.
    
    This function handles various data types and applies necessary
    transformations to ensure data quality and consistency.
    
    Args:
        data: Input data in various formats
    
    Returns:
        Processed and validated data
    
    Raises:
        ValueError: If data format is invalid
        TypeError: If data type is unsupported
    """
    if not data:
        return None
    return str(data).strip()
'''

# Compare counting docstrings as comments vs not
mi_with_comments = mi_visit(code_with_docstrings, multi=True)
mi_without_comments = mi_visit(code_with_docstrings, multi=False)

print(f"Counting docstrings as comments: {mi_with_comments:.2f}")
print(f"Not counting docstrings as comments: {mi_without_comments:.2f}")
print(f"Difference: {mi_with_comments - mi_without_comments:.2f}")

Understanding MI Components

Halstead Volume Impact

Higher Halstead volume (more complex code) decreases MI:

from radon.metrics import mi_visit

simple = "def add(a, b): return a + b"
complex_ops = """
def calculate(a, b, c, d):
    return ((a + b) * c / d) ** 2 - (a - b) % c + (c & d) | (a ^ b)
"""

print(f"Simple function MI: {mi_visit(simple, True):.2f}")
print(f"Complex operations MI: {mi_visit(complex_ops, True):.2f}")

Cyclomatic Complexity Impact

Higher complexity decreases MI:

simple_logic = '''
def process(x):
    return x * 2
'''

complex_logic = '''
def process(x):
    if x < 0:
        if x < -10:
            return x * -1
        else:
            return 0
    elif x > 0:
        if x > 10:
            return x * 2
        else:
            return x
    else:
        return 1
'''

print(f"Simple logic MI: {mi_visit(simple_logic, True):.2f}")
print(f"Complex logic MI: {mi_visit(complex_logic, True):.2f}")

Lines of Code Impact

More lines of code decrease MI:

concise = '''
def factorial(n):
    return 1 if n <= 1 else n * factorial(n - 1)
'''

verbose = '''
def factorial(n):
    if n <= 1:
        result = 1
    else:
        result = n
        for i in range(n - 1, 1, -1):
            result = result * i
    return result
'''

print(f"Concise implementation MI: {mi_visit(concise, True):.2f}")
print(f"Verbose implementation MI: {mi_visit(verbose, True):.2f}")

Best Practices for High MI

Code Structure

  • Keep functions small and focused
  • Minimize nested control structures
  • Use descriptive variable names
  • Avoid complex expressions

Documentation

  • Add meaningful docstrings and comments
  • Document complex algorithms
  • Explain business logic

Refactoring Strategies

  • Extract complex logic into helper functions
  • Use early returns to reduce nesting
  • Replace complex conditionals with guard clauses
  • Simplify mathematical expressions

Error Handling

MI calculation handles various edge cases:

  • Empty code: Returns maximum MI score (100)
  • Division by zero: Handles SLOC=0 by using 1 as minimum
  • Negative complexity: Uses absolute value for calculations
  • Mathematical edge cases: Handles log(0) and negative MI scores

Integration with CLI

Maintainability Index integrates with radon's command-line interface:

# Command-line equivalent of mi_visit()
radon mi path/to/code.py

# Show actual MI values
radon mi --show path/to/code.py

# Filter by MI threshold
radon mi --min B --max A path/to/code.py

# JSON output for programmatic processing
radon mi --json path/to/code.py

Install with Tessl CLI

npx tessl i tessl/pypi-radon

docs

cli.md

complexity.md

halstead.md

index.md

maintainability.md

raw-metrics.md

visitors.md

tile.json