or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cli.mdcomplexity.mdhalstead.mdindex.mdmaintainability.mdraw-metrics.mdvisitors.md

maintainability.mddocs/

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

```