0
# Maintainability Index
1
2
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.
3
4
## Capabilities
5
6
### Main Analysis Functions
7
8
Primary functions for computing maintainability index from source code.
9
10
```python { .api }
11
def mi_visit(code, multi):
12
"""
13
Visit code and compute Maintainability Index.
14
15
Analyzes Python source code to calculate a composite maintainability
16
score based on Halstead metrics, cyclomatic complexity, and code size.
17
18
Parameters:
19
- code (str): Python source code to analyze
20
- multi (bool): Whether to count multi-line strings as comments
21
22
Returns:
23
float: Maintainability Index score (typically 0-100+)
24
"""
25
26
def mi_compute(halstead_volume, complexity, sloc, comments):
27
"""
28
Compute Maintainability Index from component metrics.
29
30
Uses the standard MI formula:
31
MI = max(0, (171 - 5.2 * ln(V) - 0.23 * G - 16.2 * ln(L)) * 100 / 171)
32
33
Where:
34
- V = Halstead Volume
35
- G = Cyclomatic Complexity
36
- L = Source Lines of Code
37
38
Parameters:
39
- halstead_volume (float): Halstead volume metric
40
- complexity (float): Average cyclomatic complexity
41
- sloc (int): Source lines of code
42
- comments (int): Number of comment lines
43
44
Returns:
45
float: Maintainability Index score
46
"""
47
48
def mi_parameters(code, count_multi=True):
49
"""
50
Get all parameters needed for MI computation from source code.
51
52
Convenience function that extracts Halstead volume, cyclomatic
53
complexity, SLOC, and comment counts in one call.
54
55
Parameters:
56
- code (str): Python source code to analyze
57
- count_multi (bool): Whether to count multi-line strings as comments
58
59
Returns:
60
tuple: (halstead_volume, avg_complexity, sloc, comments)
61
"""
62
63
def mi_rank(score):
64
"""
65
Rank Maintainability Index score with letter grades.
66
67
Ranking system:
68
- A: Score > 19 (High maintainability)
69
- B: Score 9-19 (Medium maintainability)
70
- C: Score ≤ 9 (Low maintainability)
71
72
Parameters:
73
- score (float): Maintainability Index score
74
75
Returns:
76
str: Letter grade (A, B, or C)
77
"""
78
```
79
80
## Maintainability Index Formula
81
82
The Maintainability Index uses a well-established formula that combines multiple code quality metrics:
83
84
**MI = max(0, (171 - 5.2 × ln(HV) - 0.23 × CC - 16.2 × ln(LOC)) × 100 / 171)**
85
86
Where:
87
- **HV** = Halstead Volume (program volume in bits)
88
- **CC** = Average Cyclomatic Complexity
89
- **LOC** = Source Lines of Code (excluding comments and blanks)
90
91
### Interpretation
92
- **High MI (> 19)**: Code is well-structured and easy to maintain
93
- **Medium MI (9-19)**: Code has moderate maintainability concerns
94
- **Low MI (≤ 9)**: Code may be difficult to maintain and understand
95
96
## Usage Examples
97
98
### Basic Maintainability Analysis
99
100
```python
101
from radon.metrics import mi_visit, mi_rank
102
103
# Well-structured code
104
good_code = '''
105
def calculate_average(numbers):
106
"""Calculate the average of a list of numbers."""
107
if not numbers:
108
return 0
109
return sum(numbers) / len(numbers)
110
111
def find_maximum(numbers):
112
"""Find the maximum value in a list."""
113
if not numbers:
114
return None
115
return max(numbers)
116
'''
117
118
# Poorly structured code
119
poor_code = '''
120
def complex_function(a,b,c,d,e,f):
121
if a>0:
122
if b>0:
123
if c>0:
124
if d>0:
125
if e>0:
126
if f>0:
127
return a+b*c/d-e+f
128
else:
129
return a+b*c/d-e
130
else:
131
return a+b*c/d
132
else:
133
return a+b*c
134
else:
135
return a+b
136
else:
137
return a
138
else:
139
return 0
140
'''
141
142
# Analyze maintainability
143
good_mi = mi_visit(good_code, multi=True)
144
poor_mi = mi_visit(poor_code, multi=True)
145
146
print(f"Well-structured code MI: {good_mi:.2f} ({mi_rank(good_mi)})")
147
print(f"Poorly structured code MI: {poor_mi:.2f} ({mi_rank(poor_mi)})")
148
149
# Output:
150
# Well-structured code MI: 74.23 (A)
151
# Poorly structured code MI: 12.45 (B)
152
```
153
154
### Manual MI Calculation
155
156
```python
157
from radon.metrics import mi_compute, mi_parameters, h_visit
158
from radon.complexity import cc_visit, average_complexity
159
from radon.raw import analyze
160
161
code = '''
162
def process_user_data(user_input, validation_rules):
163
"""
164
Process and validate user input data.
165
166
Args:
167
user_input: Dictionary containing user data
168
validation_rules: List of validation functions
169
170
Returns:
171
Processed and validated user data
172
"""
173
# Validate input format
174
if not isinstance(user_input, dict):
175
raise TypeError("User input must be a dictionary")
176
177
# Apply validation rules
178
for rule in validation_rules:
179
if not rule(user_input):
180
raise ValueError("Validation failed")
181
182
# Process the data
183
processed_data = {}
184
for key, value in user_input.items():
185
if isinstance(value, str):
186
processed_data[key] = value.strip().lower()
187
elif isinstance(value, (int, float)):
188
processed_data[key] = value
189
else:
190
processed_data[key] = str(value)
191
192
return processed_data
193
'''
194
195
# Method 1: Use convenience function
196
halstead_vol, avg_complexity, sloc, comments = mi_parameters(code)
197
mi_score = mi_compute(halstead_vol, avg_complexity, sloc, comments)
198
199
print(f"Using mi_parameters:")
200
print(f" Halstead Volume: {halstead_vol:.2f}")
201
print(f" Average Complexity: {avg_complexity:.2f}")
202
print(f" SLOC: {sloc}")
203
print(f" Comments: {comments}")
204
print(f" MI Score: {mi_score:.2f}")
205
206
# Method 2: Manual calculation
207
halstead = h_visit(code)
208
complexity_blocks = cc_visit(code)
209
raw_metrics = analyze(code)
210
211
manual_mi = mi_compute(
212
halstead.total.volume,
213
average_complexity(complexity_blocks),
214
raw_metrics.sloc,
215
raw_metrics.comments
216
)
217
218
print(f"\nManual calculation:")
219
print(f" MI Score: {manual_mi:.2f}")
220
print(f" Rank: {mi_rank(manual_mi)}")
221
```
222
223
### Comparing Code Versions
224
225
```python
226
from radon.metrics import mi_visit, mi_rank
227
228
# Original implementation
229
original = '''
230
def search_items(items, query, case_sensitive=False):
231
results = []
232
for item in items:
233
if case_sensitive:
234
if query in item:
235
results.append(item)
236
else:
237
if query.lower() in item.lower():
238
results.append(item)
239
return results
240
'''
241
242
# Refactored implementation
243
refactored = '''
244
def search_items(items, query, case_sensitive=False):
245
"""
246
Search for items containing the query string.
247
248
Args:
249
items: List of strings to search
250
query: Search query string
251
case_sensitive: Whether search should be case sensitive
252
253
Returns:
254
List of matching items
255
"""
256
if not case_sensitive:
257
query = query.lower()
258
return [item for item in items if query in item.lower()]
259
return [item for item in items if query in item]
260
'''
261
262
original_mi = mi_visit(original, multi=True)
263
refactored_mi = mi_visit(refactored, multi=True)
264
265
print(f"Original implementation:")
266
print(f" MI: {original_mi:.2f} ({mi_rank(original_mi)})")
267
print(f"Refactored implementation:")
268
print(f" MI: {refactored_mi:.2f} ({mi_rank(refactored_mi)})")
269
print(f"Improvement: {refactored_mi - original_mi:.2f} points")
270
```
271
272
### Analyzing Multi-line Strings Impact
273
274
```python
275
from radon.metrics import mi_visit
276
277
code_with_docstrings = '''
278
def data_processor(data):
279
"""
280
Process input data with comprehensive validation and transformation.
281
282
This function handles various data types and applies necessary
283
transformations to ensure data quality and consistency.
284
285
Args:
286
data: Input data in various formats
287
288
Returns:
289
Processed and validated data
290
291
Raises:
292
ValueError: If data format is invalid
293
TypeError: If data type is unsupported
294
"""
295
if not data:
296
return None
297
return str(data).strip()
298
'''
299
300
# Compare counting docstrings as comments vs not
301
mi_with_comments = mi_visit(code_with_docstrings, multi=True)
302
mi_without_comments = mi_visit(code_with_docstrings, multi=False)
303
304
print(f"Counting docstrings as comments: {mi_with_comments:.2f}")
305
print(f"Not counting docstrings as comments: {mi_without_comments:.2f}")
306
print(f"Difference: {mi_with_comments - mi_without_comments:.2f}")
307
```
308
309
## Understanding MI Components
310
311
### Halstead Volume Impact
312
Higher Halstead volume (more complex code) decreases MI:
313
314
```python
315
from radon.metrics import mi_visit
316
317
simple = "def add(a, b): return a + b"
318
complex_ops = """
319
def calculate(a, b, c, d):
320
return ((a + b) * c / d) ** 2 - (a - b) % c + (c & d) | (a ^ b)
321
"""
322
323
print(f"Simple function MI: {mi_visit(simple, True):.2f}")
324
print(f"Complex operations MI: {mi_visit(complex_ops, True):.2f}")
325
```
326
327
### Cyclomatic Complexity Impact
328
Higher complexity decreases MI:
329
330
```python
331
simple_logic = '''
332
def process(x):
333
return x * 2
334
'''
335
336
complex_logic = '''
337
def process(x):
338
if x < 0:
339
if x < -10:
340
return x * -1
341
else:
342
return 0
343
elif x > 0:
344
if x > 10:
345
return x * 2
346
else:
347
return x
348
else:
349
return 1
350
'''
351
352
print(f"Simple logic MI: {mi_visit(simple_logic, True):.2f}")
353
print(f"Complex logic MI: {mi_visit(complex_logic, True):.2f}")
354
```
355
356
### Lines of Code Impact
357
More lines of code decrease MI:
358
359
```python
360
concise = '''
361
def factorial(n):
362
return 1 if n <= 1 else n * factorial(n - 1)
363
'''
364
365
verbose = '''
366
def factorial(n):
367
if n <= 1:
368
result = 1
369
else:
370
result = n
371
for i in range(n - 1, 1, -1):
372
result = result * i
373
return result
374
'''
375
376
print(f"Concise implementation MI: {mi_visit(concise, True):.2f}")
377
print(f"Verbose implementation MI: {mi_visit(verbose, True):.2f}")
378
```
379
380
## Best Practices for High MI
381
382
### Code Structure
383
- Keep functions small and focused
384
- Minimize nested control structures
385
- Use descriptive variable names
386
- Avoid complex expressions
387
388
### Documentation
389
- Add meaningful docstrings and comments
390
- Document complex algorithms
391
- Explain business logic
392
393
### Refactoring Strategies
394
- Extract complex logic into helper functions
395
- Use early returns to reduce nesting
396
- Replace complex conditionals with guard clauses
397
- Simplify mathematical expressions
398
399
## Error Handling
400
401
MI calculation handles various edge cases:
402
403
- **Empty code**: Returns maximum MI score (100)
404
- **Division by zero**: Handles SLOC=0 by using 1 as minimum
405
- **Negative complexity**: Uses absolute value for calculations
406
- **Mathematical edge cases**: Handles log(0) and negative MI scores
407
408
## Integration with CLI
409
410
Maintainability Index integrates with radon's command-line interface:
411
412
```bash
413
# Command-line equivalent of mi_visit()
414
radon mi path/to/code.py
415
416
# Show actual MI values
417
radon mi --show path/to/code.py
418
419
# Filter by MI threshold
420
radon mi --min B --max A path/to/code.py
421
422
# JSON output for programmatic processing
423
radon mi --json path/to/code.py
424
```