or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

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

halstead.mddocs/

0

# Halstead Metrics

1

2

Software complexity measurement based on operators and operands analysis using Halstead's software science metrics. Calculates vocabulary, length, volume, difficulty, effort, time estimates, and bug predictions to quantitatively assess code complexity and programming difficulty.

3

4

## Capabilities

5

6

### Main Analysis Functions

7

8

Primary functions for computing Halstead metrics from source code or AST nodes.

9

10

```python { .api }

11

def h_visit(code):

12

"""

13

Compile code into AST and compute Halstead metrics.

14

15

Analyzes Python source code to extract operators and operands,

16

then calculates comprehensive Halstead software science metrics.

17

18

Parameters:

19

- code (str): Python source code to analyze

20

21

Returns:

22

Halstead: Named tuple with 'total' (file-level metrics) and 'functions' (per-function metrics)

23

"""

24

25

def h_visit_ast(ast_node):

26

"""

27

Visit AST node using HalsteadVisitor to compute metrics.

28

29

Directly analyzes an AST node without parsing source code.

30

Used internally by h_visit() and for custom AST processing.

31

32

Parameters:

33

- ast_node: Python AST node to analyze

34

35

Returns:

36

Halstead: Named tuple with total and per-function metrics

37

"""

38

39

def halstead_visitor_report(visitor):

40

"""

41

Create HalsteadReport from a configured HalsteadVisitor.

42

43

Converts visitor's collected operator/operand data into

44

a structured report with computed metrics.

45

46

Parameters:

47

- visitor (HalsteadVisitor): Visitor instance after AST traversal

48

49

Returns:

50

HalsteadReport: Complete metrics including h1, h2, N1, N2, volume, difficulty, etc.

51

"""

52

```

53

54

### Halstead Data Types

55

56

Named tuples containing Halstead metrics and analysis results.

57

58

```python { .api }

59

# Individual Halstead metrics report

60

HalsteadReport = namedtuple('HalsteadReport', [

61

'h1', # Number of distinct operators

62

'h2', # Number of distinct operands

63

'N1', # Total number of operators

64

'N2', # Total number of operands

65

'vocabulary', # h = h1 + h2 (vocabulary)

66

'length', # N = N1 + N2 (program length)

67

'calculated_length', # h1 * log2(h1) + h2 * log2(h2)

68

'volume', # V = N * log2(h) (program volume)

69

'difficulty', # D = (h1 / 2) * (N2 / h2) (programming difficulty)

70

'effort', # E = D * V (programming effort)

71

'time', # T = E / 18 (programming time in seconds)

72

'bugs' # B = V / 3000 (estimated delivered bugs)

73

])

74

75

# Combined results for file and functions

76

Halstead = namedtuple('Halstead', [

77

'total', # HalsteadReport for the entire file

78

'functions' # List of HalsteadReport objects for each function

79

])

80

```

81

82

## Halstead Metrics Explained

83

84

The Halstead metrics are based on counting operators and operands in source code:

85

86

### Basic Counts

87

- **h1**: Number of distinct operators (e.g., +, -, if, def, return)

88

- **h2**: Number of distinct operands (e.g., variable names, literals, function names)

89

- **N1**: Total occurrences of all operators

90

- **N2**: Total occurrences of all operands

91

92

### Derived Metrics

93

- **Vocabulary (h)**: h1 + h2 - The total number of unique symbols

94

- **Length (N)**: N1 + N2 - The total number of symbols

95

- **Calculated Length**: h1 × log₂(h1) + h2 × log₂(h2) - Theoretical minimum length

96

- **Volume (V)**: N × log₂(h) - Number of bits needed to encode the program

97

- **Difficulty (D)**: (h1 ÷ 2) × (N2 ÷ h2) - How difficult the program is to understand

98

- **Effort (E)**: D × V - Mental effort required to understand the program

99

- **Time (T)**: E ÷ 18 - Estimated time to understand (in seconds)

100

- **Bugs (B)**: V ÷ 3000 - Estimated number of delivered bugs

101

102

## Usage Examples

103

104

### Basic Halstead Analysis

105

106

```python

107

from radon.metrics import h_visit

108

109

code = '''

110

def factorial(n):

111

if n <= 1:

112

return 1

113

else:

114

return n * factorial(n - 1)

115

116

def fibonacci(n):

117

if n <= 1:

118

return n

119

return fibonacci(n - 1) + fibonacci(n - 2)

120

'''

121

122

# Analyze Halstead metrics

123

result = h_visit(code)

124

125

# File-level metrics

126

total = result.total

127

print(f"File Halstead Metrics:")

128

print(f" Distinct operators (h1): {total.h1}")

129

print(f" Distinct operands (h2): {total.h2}")

130

print(f" Total operators (N1): {total.N1}")

131

print(f" Total operands (N2): {total.N2}")

132

print(f" Vocabulary: {total.vocabulary}")

133

print(f" Length: {total.length}")

134

print(f" Volume: {total.volume:.2f}")

135

print(f" Difficulty: {total.difficulty:.2f}")

136

print(f" Effort: {total.effort:.2f}")

137

print(f" Time: {total.time:.2f} seconds")

138

print(f" Estimated bugs: {total.bugs:.4f}")

139

140

# Per-function metrics

141

print(f"\nFunction-level metrics:")

142

for i, func_metrics in enumerate(result.functions):

143

print(f" Function {i+1}:")

144

print(f" Volume: {func_metrics.volume:.2f}")

145

print(f" Difficulty: {func_metrics.difficulty:.2f}")

146

print(f" Effort: {func_metrics.effort:.2f}")

147

```

148

149

### Comparing Code Complexity

150

151

```python

152

from radon.metrics import h_visit

153

154

# Simple code

155

simple_code = '''

156

def add(a, b):

157

return a + b

158

'''

159

160

# Complex code

161

complex_code = '''

162

def complex_calculator(operation, *args, **kwargs):

163

operations = {

164

'add': lambda x, y: x + y,

165

'subtract': lambda x, y: x - y,

166

'multiply': lambda x, y: x * y,

167

'divide': lambda x, y: x / y if y != 0 else None

168

}

169

170

if operation not in operations:

171

raise ValueError(f"Unknown operation: {operation}")

172

173

if len(args) < 2:

174

raise ValueError("Need at least 2 arguments")

175

176

result = args[0]

177

for arg in args[1:]:

178

result = operations[operation](result, arg)

179

180

return result

181

'''

182

183

simple_result = h_visit(simple_code)

184

complex_result = h_visit(complex_code)

185

186

print("Simple function:")

187

print(f" Volume: {simple_result.total.volume:.2f}")

188

print(f" Difficulty: {simple_result.total.difficulty:.2f}")

189

190

print("Complex function:")

191

print(f" Volume: {complex_result.total.volume:.2f}")

192

print(f" Difficulty: {complex_result.total.difficulty:.2f}")

193

```

194

195

### AST-Level Analysis

196

197

```python

198

import ast

199

from radon.metrics import h_visit_ast

200

201

code = '''

202

def greet(name, greeting="Hello"):

203

message = f"{greeting}, {name}!"

204

print(message)

205

return message

206

'''

207

208

# Parse to AST manually

209

ast_tree = ast.parse(code)

210

211

# Analyze using AST directly

212

result = h_visit_ast(ast_tree)

213

214

print(f"AST Analysis Results:")

215

print(f" Vocabulary: {result.total.vocabulary}")

216

print(f" Volume: {result.total.volume:.2f}")

217

print(f" Estimated development time: {result.total.time:.1f} seconds")

218

```

219

220

### Custom Visitor Processing

221

222

```python

223

from radon.metrics import halstead_visitor_report

224

from radon.visitors import HalsteadVisitor

225

import ast

226

227

code = '''

228

def process_data(data_list):

229

results = []

230

for item in data_list:

231

if isinstance(item, (int, float)):

232

results.append(item * 2)

233

elif isinstance(item, str):

234

results.append(item.upper())

235

return results

236

'''

237

238

# Create and use visitor manually

239

ast_tree = ast.parse(code)

240

visitor = HalsteadVisitor()

241

visitor.visit(ast_tree)

242

243

# Generate report from visitor

244

report = halstead_visitor_report(visitor)

245

246

print(f"Custom Analysis:")

247

print(f" Operators found: {len(visitor.distinct_operators)}")

248

print(f" Operands found: {len(visitor.distinct_operands)}")

249

print(f" Volume: {report.volume:.2f}")

250

print(f" Difficulty: {report.difficulty:.2f}")

251

252

# Examine specific operators and operands

253

print(f" Sample operators: {list(visitor.distinct_operators)[:5]}")

254

print(f" Sample operands: {list(visitor.distinct_operands)[:5]}")

255

```

256

257

## Understanding Halstead Metrics

258

259

### Interpreting Volume

260

- **Low volume (< 100)**: Simple functions or scripts

261

- **Medium volume (100-1000)**: Typical application functions

262

- **High volume (> 1000)**: Complex algorithms or large functions

263

264

### Interpreting Difficulty

265

- **Low difficulty (< 10)**: Easy to understand and maintain

266

- **Medium difficulty (10-30)**: Moderate complexity

267

- **High difficulty (> 30)**: Challenging to understand

268

269

### Interpreting Effort

270

- **Low effort (< 1000)**: Minimal mental effort required

271

- **Medium effort (1000-10000)**: Moderate mental effort

272

- **High effort (> 10000)**: Significant mental effort required

273

274

## Integration with Other Metrics

275

276

Halstead metrics work well in combination with other radon analyses:

277

278

```python

279

from radon.metrics import h_visit, mi_compute

280

from radon.complexity import cc_visit, average_complexity

281

from radon.raw import analyze

282

283

code = '''

284

def complex_algorithm(data, threshold=0.5):

285

results = []

286

for i, item in enumerate(data):

287

if isinstance(item, dict):

288

score = sum(v for v in item.values() if isinstance(v, (int, float)))

289

if score > threshold:

290

results.append({'index': i, 'score': score, 'data': item})

291

elif isinstance(item, (list, tuple)):

292

nested_score = sum(x for x in item if isinstance(x, (int, float)))

293

if nested_score > threshold:

294

results.append({'index': i, 'score': nested_score, 'data': item})

295

return sorted(results, key=lambda x: x['score'], reverse=True)

296

'''

297

298

# Get all metrics

299

halstead = h_visit(code)

300

complexity_blocks = cc_visit(code)

301

raw_metrics = analyze(code)

302

303

# Extract values for MI calculation

304

halstead_volume = halstead.total.volume

305

avg_complexity = average_complexity(complexity_blocks)

306

sloc = raw_metrics.sloc

307

comments = raw_metrics.comments

308

309

# Calculate maintainability index

310

mi_score = mi_compute(halstead_volume, avg_complexity, sloc, comments)

311

312

print(f"Comprehensive Analysis:")

313

print(f" Halstead Volume: {halstead_volume:.2f}")

314

print(f" Cyclomatic Complexity: {avg_complexity:.2f}")

315

print(f" Source Lines: {sloc}")

316

print(f" Maintainability Index: {mi_score:.2f}")

317

```

318

319

## Error Handling

320

321

Halstead analysis handles various edge cases:

322

323

- **Empty code**: Returns HalsteadReport with zero values

324

- **Invalid syntax**: AST parsing errors are propagated

325

- **Division by zero**: Returns appropriate defaults when h2=0 for difficulty calculation

326

- **Mathematical edge cases**: Handles log₂(0) by treating empty vocabulary as 1

327

328

## Integration with CLI

329

330

Halstead metrics integrate with radon's command-line interface:

331

332

```bash

333

# Command-line equivalent of h_visit()

334

radon hal path/to/code.py

335

336

# Per-function analysis

337

radon hal --functions path/to/code.py

338

339

# JSON output for programmatic processing

340

radon hal --json path/to/code.py

341

```