0
# Code Metrics
1
2
PMD Java provides a comprehensive metrics system with 12 built-in metrics for measuring code quality, complexity, and maintainability. These metrics follow established standards and can be configured with various options.
3
4
## Capabilities
5
6
### Metrics System
7
8
The metrics system is built on the `Metric` interface and can be used to calculate various code quality measures.
9
10
```java { .api }
11
/**
12
* Built-in Java metrics for code quality analysis
13
*/
14
public final class JavaMetrics {
15
// Complexity metrics
16
public static final Metric<ASTExecutableDeclaration, Integer> CYCLO;
17
public static final Metric<ASTExecutableDeclaration, Integer> COGNITIVE_COMPLEXITY;
18
public static final Metric<ASTExecutableDeclaration, BigInteger> NPATH;
19
20
// Size metrics
21
public static final Metric<JavaNode, Integer> LINES_OF_CODE;
22
public static final Metric<JavaNode, Integer> NCSS;
23
24
// Class metrics
25
public static final Metric<ASTTypeDeclaration, Integer> NUMBER_OF_ACCESSORS;
26
public static final Metric<ASTTypeDeclaration, Integer> NUMBER_OF_PUBLIC_FIELDS;
27
public static final Metric<ASTTypeDeclaration, Double> TIGHT_CLASS_COHESION;
28
public static final Metric<ASTTypeDeclaration, Integer> WEIGHED_METHOD_COUNT;
29
public static final Metric<ASTTypeDeclaration, Double> WEIGHT_OF_CLASS;
30
31
// Coupling metrics
32
public static final Metric<JavaNode, Integer> ACCESS_TO_FOREIGN_DATA;
33
public static final Metric<JavaNode, Integer> FAN_OUT;
34
}
35
36
/**
37
* Generic metric interface
38
*/
39
public interface Metric<T, R> {
40
/**
41
* Compute the metric value for the given node with options
42
*/
43
R computeFor(T node, MetricOptions options);
44
45
/**
46
* Get the display name of this metric
47
*/
48
String getDisplayName();
49
50
/**
51
* Get the abbreviated name of this metric
52
*/
53
String getAbbreviatedName();
54
}
55
56
/**
57
* Options for configuring metric calculations
58
*/
59
public interface MetricOptions {
60
/**
61
* Check if a specific option is enabled
62
*/
63
boolean isEnabled(MetricOption option);
64
}
65
```
66
67
### Complexity Metrics
68
69
#### Cyclomatic Complexity (CYCLO)
70
71
Measures the number of independent paths through a block of code using McCabe's original definition.
72
73
```java { .api }
74
/**
75
* Cyclomatic Complexity metric
76
* Counts independent paths through code
77
*/
78
public static final Metric<ASTExecutableDeclaration, Integer> CYCLO;
79
80
/**
81
* Options for cyclomatic complexity calculation
82
*/
83
public enum CycloOption implements MetricOption {
84
/** Do not count the paths in boolean expressions as decision points */
85
IGNORE_BOOLEAN_PATHS("ignoreBooleanPaths"),
86
87
/** Consider assert statements as if they were if (..) throw new AssertionError(..) */
88
CONSIDER_ASSERT("considerAssert");
89
90
String valueName();
91
}
92
```
93
94
**Usage Example:**
95
```java
96
// Calculate cyclomatic complexity for a method
97
ASTMethodDeclaration method = /* ... */;
98
int complexity = JavaMetrics.CYCLO.computeFor(method, MetricOptions.emptyOptions());
99
100
// With options to ignore boolean paths
101
MetricOptions options = MetricOptions.ofOption(CycloOption.IGNORE_BOOLEAN_PATHS);
102
int simpleComplexity = JavaMetrics.CYCLO.computeFor(method, options);
103
```
104
105
#### Cognitive Complexity (COGNITIVE_COMPLEXITY)
106
107
Measures how difficult it is for humans to read and understand a method, with emphasis on nesting.
108
109
```java { .api }
110
/**
111
* Cognitive Complexity metric
112
* Measures human readability complexity with nesting emphasis
113
*/
114
public static final Metric<ASTExecutableDeclaration, Integer> COGNITIVE_COMPLEXITY;
115
```
116
117
**Usage Example:**
118
```java
119
// Calculate cognitive complexity
120
ASTMethodDeclaration method = /* ... */;
121
int cognitiveComplexity = JavaMetrics.COGNITIVE_COMPLEXITY.computeFor(method, MetricOptions.emptyOptions());
122
```
123
124
#### NPath Complexity (NPATH)
125
126
Counts the number of acyclic execution paths through a piece of code.
127
128
```java { .api }
129
/**
130
* NPath Complexity metric
131
* Counts acyclic execution paths (exponential growth with sequential decisions)
132
*/
133
public static final Metric<ASTExecutableDeclaration, BigInteger> NPATH;
134
```
135
136
**Usage Example:**
137
```java
138
// Calculate NPath complexity
139
ASTMethodDeclaration method = /* ... */;
140
BigInteger npathComplexity = JavaMetrics.NPATH.computeFor(method, MetricOptions.emptyOptions());
141
142
// Check if complexity is over threshold
143
if (npathComplexity.compareTo(BigInteger.valueOf(200)) > 0) {
144
System.out.println("Method is too complex: " + npathComplexity);
145
}
146
```
147
148
### Size Metrics
149
150
#### Lines of Code (LINES_OF_CODE)
151
152
Simply counts the number of lines of code including comments and blank lines.
153
154
```java { .api }
155
/**
156
* Lines of Code metric
157
* Counts total lines including comments and blank lines
158
*/
159
public static final Metric<JavaNode, Integer> LINES_OF_CODE;
160
```
161
162
#### Non-Commenting Source Statements (NCSS)
163
164
Counts the number of statements, roughly equivalent to counting semicolons and opening braces.
165
166
```java { .api }
167
/**
168
* Non-Commenting Source Statements metric
169
* Counts actual statements excluding comments and blank lines
170
*/
171
public static final Metric<JavaNode, Integer> NCSS;
172
173
/**
174
* Options for NCSS calculation
175
*/
176
public enum NcssOption implements MetricOption {
177
/** Counts import and package statements (makes metric JavaNCSS compliant) */
178
COUNT_IMPORTS("countImports");
179
180
String valueName();
181
}
182
```
183
184
**Usage Example:**
185
```java
186
// Calculate NCSS for a class
187
ASTClassDeclaration clazz = /* ... */;
188
int statements = JavaMetrics.NCSS.computeFor(clazz, MetricOptions.emptyOptions());
189
190
// With imports counted
191
MetricOptions options = MetricOptions.ofOption(NcssOption.COUNT_IMPORTS);
192
int statementsWithImports = JavaMetrics.NCSS.computeFor(clazz, options);
193
```
194
195
### Coupling Metrics
196
197
#### Access to Foreign Data (ACCESS_TO_FOREIGN_DATA)
198
199
Counts usages of foreign attributes (not belonging to the current class).
200
201
```java { .api }
202
/**
203
* Access to Foreign Data metric
204
* Counts usages of foreign attributes through direct access or accessors
205
*/
206
public static final Metric<JavaNode, Integer> ACCESS_TO_FOREIGN_DATA;
207
```
208
209
#### Fan-Out (FAN_OUT)
210
211
Counts the number of other classes a given class or operation relies on.
212
213
```java { .api }
214
/**
215
* Class Fan-Out metric
216
* Counts dependencies on other classes (excludes java.lang by default)
217
*/
218
public static final Metric<JavaNode, Integer> FAN_OUT;
219
220
/**
221
* Options for fan-out calculation
222
*/
223
public enum ClassFanOutOption implements MetricOption {
224
/** Whether to include classes in the java.lang package */
225
INCLUDE_JAVA_LANG("includeJavaLang");
226
227
String valueName();
228
}
229
```
230
231
**Usage Example:**
232
```java
233
// Calculate fan-out excluding java.lang
234
ASTClassDeclaration clazz = /* ... */;
235
int fanOut = JavaMetrics.FAN_OUT.computeFor(clazz, MetricOptions.emptyOptions());
236
237
// Include java.lang dependencies
238
MetricOptions options = MetricOptions.ofOption(ClassFanOutOption.INCLUDE_JAVA_LANG);
239
int totalFanOut = JavaMetrics.FAN_OUT.computeFor(clazz, options);
240
```
241
242
### Object-Oriented Metrics
243
244
#### Weighed Method Count (WEIGHED_METHOD_COUNT)
245
246
Sum of the statistical complexity of the operations in the class using CYCLO.
247
248
```java { .api }
249
/**
250
* Weighed Method Count metric
251
* Sum of cyclomatic complexity of all methods in a class
252
*/
253
public static final Metric<ASTTypeDeclaration, Integer> WEIGHED_METHOD_COUNT;
254
```
255
256
#### Tight Class Cohesion (TIGHT_CLASS_COHESION)
257
258
Measures the relative number of method pairs that access common attributes.
259
260
```java { .api }
261
/**
262
* Tight Class Cohesion metric
263
* Measures method pairs that access common attributes (0.0 to 1.0)
264
*/
265
public static final Metric<ASTTypeDeclaration, Double> TIGHT_CLASS_COHESION;
266
```
267
268
**Usage Example:**
269
```java
270
// Calculate class cohesion
271
ASTClassDeclaration clazz = /* ... */;
272
double cohesion = JavaMetrics.TIGHT_CLASS_COHESION.computeFor(clazz, MetricOptions.emptyOptions());
273
274
if (cohesion > 0.7) {
275
System.out.println("High cohesion class");
276
} else if (cohesion < 0.5) {
277
System.out.println("Low cohesion class - consider refactoring");
278
}
279
```
280
281
#### Weight of Class (WEIGHT_OF_CLASS)
282
283
Ratio of "functional" public methods to total public methods.
284
285
```java { .api }
286
/**
287
* Weight of Class metric
288
* Ratio of functional methods to total public methods (0.0 to 1.0)
289
*/
290
public static final Metric<ASTTypeDeclaration, Double> WEIGHT_OF_CLASS;
291
```
292
293
### Method and Field Count Metrics
294
295
#### Number of Accessors (NUMBER_OF_ACCESSORS)
296
297
Counts getter and setter methods in a class.
298
299
```java { .api }
300
/**
301
* Number of Accessor Methods metric
302
* Counts getter and setter methods
303
*/
304
public static final Metric<ASTTypeDeclaration, Integer> NUMBER_OF_ACCESSORS;
305
```
306
307
#### Number of Public Fields (NUMBER_OF_PUBLIC_FIELDS)
308
309
Counts public fields in a class.
310
311
```java { .api }
312
/**
313
* Number of Public Attributes metric
314
* Counts public fields
315
*/
316
public static final Metric<ASTTypeDeclaration, Integer> NUMBER_OF_PUBLIC_FIELDS;
317
```
318
319
## Usage Examples
320
321
### Basic Metric Calculation
322
323
```java
324
import net.sourceforge.pmd.lang.java.metrics.JavaMetrics;
325
import net.sourceforge.pmd.lang.metrics.MetricOptions;
326
327
// Calculate complexity for a method
328
public void analyzeMethod(ASTMethodDeclaration method) {
329
// Cyclomatic complexity
330
int cyclo = JavaMetrics.CYCLO.computeFor(method, MetricOptions.emptyOptions());
331
332
// Cognitive complexity
333
int cognitive = JavaMetrics.COGNITIVE_COMPLEXITY.computeFor(method, MetricOptions.emptyOptions());
334
335
// NPath complexity
336
BigInteger npath = JavaMetrics.NPATH.computeFor(method, MetricOptions.emptyOptions());
337
338
// Lines of code
339
int loc = JavaMetrics.LINES_OF_CODE.computeFor(method, MetricOptions.emptyOptions());
340
341
System.out.println("Method: " + method.getName());
342
System.out.println(" Cyclomatic Complexity: " + cyclo);
343
System.out.println(" Cognitive Complexity: " + cognitive);
344
System.out.println(" NPath Complexity: " + npath);
345
System.out.println(" Lines of Code: " + loc);
346
}
347
```
348
349
### Class-Level Analysis
350
351
```java
352
// Comprehensive class analysis
353
public void analyzeClass(ASTClassDeclaration clazz) {
354
MetricOptions emptyOptions = MetricOptions.emptyOptions();
355
356
// Size metrics
357
int loc = JavaMetrics.LINES_OF_CODE.computeFor(clazz, emptyOptions);
358
int ncss = JavaMetrics.NCSS.computeFor(clazz, emptyOptions);
359
360
// Coupling metrics
361
int atfd = JavaMetrics.ACCESS_TO_FOREIGN_DATA.computeFor(clazz, emptyOptions);
362
int fanOut = JavaMetrics.FAN_OUT.computeFor(clazz, emptyOptions);
363
364
// Object-oriented metrics
365
int wmc = JavaMetrics.WEIGHED_METHOD_COUNT.computeFor(clazz, emptyOptions);
366
double tcc = JavaMetrics.TIGHT_CLASS_COHESION.computeFor(clazz, emptyOptions);
367
double woc = JavaMetrics.WEIGHT_OF_CLASS.computeFor(clazz, emptyOptions);
368
369
// Method and field counts
370
int accessors = JavaMetrics.NUMBER_OF_ACCESSORS.computeFor(clazz, emptyOptions);
371
int publicFields = JavaMetrics.NUMBER_OF_PUBLIC_FIELDS.computeFor(clazz, emptyOptions);
372
373
System.out.println("Class: " + clazz.getSimpleName());
374
System.out.println("Size Metrics:");
375
System.out.println(" Lines of Code: " + loc);
376
System.out.println(" NCSS: " + ncss);
377
System.out.println("Coupling Metrics:");
378
System.out.println(" ATFD: " + atfd);
379
System.out.println(" Fan-Out: " + fanOut);
380
System.out.println("OO Metrics:");
381
System.out.println(" WMC: " + wmc);
382
System.out.println(" TCC: " + String.format("%.2f", tcc));
383
System.out.println(" WOC: " + String.format("%.2f", woc));
384
System.out.println("Counts:");
385
System.out.println(" Accessors: " + accessors);
386
System.out.println(" Public Fields: " + publicFields);
387
}
388
```
389
390
### Metrics with Options
391
392
```java
393
// Using metric options for customized calculations
394
public void analyzeWithOptions(JavaNode node) {
395
// Cyclomatic complexity ignoring boolean paths
396
MetricOptions cycloOptions = MetricOptions.ofOption(
397
JavaMetrics.CycloOption.IGNORE_BOOLEAN_PATHS,
398
JavaMetrics.CycloOption.CONSIDER_ASSERT
399
);
400
401
if (node instanceof ASTExecutableDeclaration) {
402
ASTExecutableDeclaration method = (ASTExecutableDeclaration) node;
403
int cyclo = JavaMetrics.CYCLO.computeFor(method, cycloOptions);
404
System.out.println("Simplified Cyclomatic Complexity: " + cyclo);
405
}
406
407
// NCSS including imports
408
MetricOptions ncssOptions = MetricOptions.ofOption(JavaMetrics.NcssOption.COUNT_IMPORTS);
409
int ncss = JavaMetrics.NCSS.computeFor(node, ncssOptions);
410
System.out.println("NCSS with imports: " + ncss);
411
412
// Fan-out including java.lang
413
MetricOptions fanOutOptions = MetricOptions.ofOption(JavaMetrics.ClassFanOutOption.INCLUDE_JAVA_LANG);
414
int fanOut = JavaMetrics.FAN_OUT.computeFor(node, fanOutOptions);
415
System.out.println("Total Fan-Out: " + fanOut);
416
}
417
```
418
419
### Custom Metrics Analysis Visitor
420
421
```java
422
// Visitor to collect metrics across an entire compilation unit
423
public class MetricsCollector extends JavaVisitorBase<Void, Void> {
424
private List<ClassMetrics> classMetrics = new ArrayList<>();
425
private List<MethodMetrics> methodMetrics = new ArrayList<>();
426
427
@Override
428
public Void visit(ASTClassDeclaration node, Void data) {
429
// Collect class-level metrics
430
ClassMetrics metrics = new ClassMetrics();
431
metrics.className = node.getSimpleName();
432
metrics.loc = JavaMetrics.LINES_OF_CODE.computeFor(node, MetricOptions.emptyOptions());
433
metrics.ncss = JavaMetrics.NCSS.computeFor(node, MetricOptions.emptyOptions());
434
metrics.wmc = JavaMetrics.WEIGHED_METHOD_COUNT.computeFor(node, MetricOptions.emptyOptions());
435
metrics.tcc = JavaMetrics.TIGHT_CLASS_COHESION.computeFor(node, MetricOptions.emptyOptions());
436
437
classMetrics.add(metrics);
438
439
return super.visit(node, data);
440
}
441
442
@Override
443
public Void visit(ASTMethodDeclaration node, Void data) {
444
// Collect method-level metrics
445
MethodMetrics metrics = new MethodMetrics();
446
metrics.methodName = node.getName();
447
metrics.cyclo = JavaMetrics.CYCLO.computeFor(node, MetricOptions.emptyOptions());
448
metrics.cognitive = JavaMetrics.COGNITIVE_COMPLEXITY.computeFor(node, MetricOptions.emptyOptions());
449
metrics.npath = JavaMetrics.NPATH.computeFor(node, MetricOptions.emptyOptions());
450
metrics.loc = JavaMetrics.LINES_OF_CODE.computeFor(node, MetricOptions.emptyOptions());
451
452
methodMetrics.add(metrics);
453
454
return super.visit(node, data);
455
}
456
457
public List<ClassMetrics> getClassMetrics() { return classMetrics; }
458
public List<MethodMetrics> getMethodMetrics() { return methodMetrics; }
459
460
static class ClassMetrics {
461
String className;
462
int loc, ncss, wmc;
463
double tcc;
464
}
465
466
static class MethodMetrics {
467
String methodName;
468
int cyclo, cognitive, loc;
469
BigInteger npath;
470
}
471
}
472
```
473
474
## Metric Interpretation Guidelines
475
476
### Complexity Thresholds
477
478
- **Cyclomatic Complexity**: Methods with CYCLO > 10 should be considered for refactoring
479
- **Cognitive Complexity**: Methods with > 15 cognitive complexity are hard to understand
480
- **NPath**: Methods with NPath > 200 are generally too complex
481
482
### Object-Oriented Quality
483
484
- **Tight Class Cohesion**:
485
- > 70%: High cohesion, single responsibility
486
- < 50%: Low cohesion, possibly doing too much
487
- **Weight of Class**:
488
- < 30%: Poor encapsulation, reveals too much data
489
- > 70%: Good behavioral interface
490
491
### Coupling Guidelines
492
493
- **Access to Foreign Data**: > 3 for operations suggests encapsulation issues
494
- **Fan-Out**: High values indicate tight coupling to many other classes