Skip to content
This repository has been archived by the owner on Jul 16, 2023. It is now read-only.

Commit

Permalink
Merge pull request #196 from dkrutskikh/weight-of-class
Browse files Browse the repository at this point in the history
Weight Of Class metric
  • Loading branch information
dkrutskikh committed Jan 15, 2021
2 parents 65f1357 + f721d3b commit 99d619a
Show file tree
Hide file tree
Showing 10 changed files with 86 additions and 10 deletions.
4 changes: 4 additions & 0 deletions bin/metrics.dart
Expand Up @@ -30,6 +30,7 @@ Future<void> main(List<String> args) async {
int.tryParse(arguments[metrics.numberOfArgumentsKey] as String ?? ''),
int.tryParse(arguments[metrics.numberOfMethodsKey] as String ?? ''),
int.tryParse(arguments[metrics.maximumNestingKey] as String ?? ''),
double.tryParse(arguments[metrics.weightOfClassKey] as String ?? ''),
arguments[reporterName] as String,
arguments[verboseName] as bool,
arguments[gitlabCompatibilityName] as bool,
Expand Down Expand Up @@ -59,6 +60,7 @@ Future<void> _runAnalysis(
int numberOfArgumentsWarningLevel,
int numberOfMethodsWarningLevel,
int maximumNestingWarningLevel,
double weightOfClassWarningLevel,
String reporterType,
bool verbose,
bool gitlab,
Expand Down Expand Up @@ -99,6 +101,8 @@ Future<void> _runAnalysis(
options.metricsConfig.numberOfMethodsWarningLevel,
maximumNestingWarningLevel: maximumNestingWarningLevel ??
options.metricsConfig.maximumNestingWarningLevel,
weightOfClassWarningLevel: weightOfClassWarningLevel ??
options.metricsConfig.weightOfClassWarningLevel,
);

Reporter reporter;
Expand Down
7 changes: 7 additions & 0 deletions doc/metrics/weight-of-class.md
@@ -0,0 +1,7 @@
# Weight Of Class

Number of **functional** public methods divided by the total number of public methods.

This metric tries to quantify whether the measured class' interface reveals more data than behaviour. Low values (less than 33%) indicate that the class reveals much more data than behaviour, which is a sign of poor encapsulation.

Our definition of **functional method** excludes getters and setters.
2 changes: 2 additions & 0 deletions lib/src/config/analysis_options.dart
Expand Up @@ -85,6 +85,8 @@ metrics.Config _readMetricsConfig(Map<String, Object> configMap) {
.as<int>(metrics.numberOfMethodsDefaultWarningLevel),
maximumNestingWarningLevel: configMap[metrics.maximumNestingKey]
.as<int>(metrics.maximumNestingDefaultWarningLevel),
weightOfClassWarningLevel: configMap[metrics.weightOfClassKey]
.as<double>(metrics.weightOfClassDefaultWarningLevel),
);
}
}
Expand Down
22 changes: 17 additions & 5 deletions lib/src/config/cli/arguments_parser.dart
Expand Up @@ -64,10 +64,10 @@ void _appendReporterOption(ArgParser parser) {
}

@immutable
class _MetricOption {
class _MetricOption<T> {
final String name;
final String help;
final int defaultValue;
final T defaultValue;

const _MetricOption(this.name, this.help, this.defaultValue);
}
Expand Down Expand Up @@ -99,6 +99,11 @@ void _appendMetricsThresholdOptions(ArgParser parser) {
'Maximum nesting threshold',
maximumNestingDefaultWarningLevel,
),
_MetricOption(
weightOfClassKey,
'Weight Of Class threshold',
weightOfClassDefaultWarningLevel,
),
];

for (final metric in metrics) {
Expand All @@ -107,9 +112,16 @@ void _appendMetricsThresholdOptions(ArgParser parser) {
help: metric.help,
valueHelp: '${metric.defaultValue}',
// ignore: avoid_types_on_closure_parameters
callback: (String i) {
if (i != null && int.tryParse(i) == null) {
print("'$i' invalid value for argument ${metric.name}");
callback: (String value) {
var invalid = true;
if (metric.defaultValue is int) {
invalid = value != null && int.tryParse(value) == null;
} else if (metric.defaultValue is double) {
invalid = value != null && double.tryParse(value) == null;
}

if (invalid) {
print("'$value' invalid value for argument ${metric.name}");
}
},
);
Expand Down
4 changes: 4 additions & 0 deletions lib/src/config/config.dart
Expand Up @@ -5,12 +5,14 @@ const linesOfExecutableCodeKey = 'lines-of-executable-code';
const numberOfArgumentsKey = 'number-of-arguments';
const numberOfMethodsKey = 'number-of-methods';
const maximumNestingKey = 'maximum-nesting';
const weightOfClassKey = 'weight-of-class';

const cyclomaticComplexityDefaultWarningLevel = 20;
const linesOfExecutableCodeDefaultWarningLevel = 50;
const numberOfArgumentsDefaultWarningLevel = 4;
const numberOfMethodsDefaultWarningLevel = 10;
const maximumNestingDefaultWarningLevel = 5;
const weightOfClassDefaultWarningLevel = 0.33;

/// Reporter config to use with various [Reporter]s
@immutable
Expand All @@ -20,6 +22,7 @@ class Config {
final int numberOfArgumentsWarningLevel;
final int numberOfMethodsWarningLevel;
final int maximumNestingWarningLevel;
final double weightOfClassWarningLevel;

const Config({
this.cyclomaticComplexityWarningLevel =
Expand All @@ -29,5 +32,6 @@ class Config {
this.numberOfArgumentsWarningLevel = numberOfArgumentsDefaultWarningLevel,
this.numberOfMethodsWarningLevel = numberOfMethodsDefaultWarningLevel,
this.maximumNestingWarningLevel = maximumNestingDefaultWarningLevel,
this.weightOfClassWarningLevel = weightOfClassDefaultWarningLevel,
});
}
4 changes: 4 additions & 0 deletions lib/src/metrics_analyzer.dart
Expand Up @@ -78,6 +78,8 @@ class MetricsAnalyzer {
.map((entity) => entity.path))
.toList();

final woc = WeightOfClassMetric();

for (final filePath in filePaths) {
final normalized = p.normalize(p.absolute(filePath));

Expand Down Expand Up @@ -137,6 +139,8 @@ class MetricsAnalyzer {
methodsCount: NumberOfMethodsMetric()
.compute(classDeclaration, functions)
.value,
weightOfClass:
woc.compute(classDeclaration, visitor.functions).value,
),
);
}
Expand Down
11 changes: 7 additions & 4 deletions lib/src/models/component_record.dart
Expand Up @@ -6,9 +6,12 @@ class ComponentRecord {
final int lastLine;

final int methodsCount;
final double weightOfClass;

const ComponentRecord(
{@required this.firstLine,
@required this.lastLine,
@required this.methodsCount});
const ComponentRecord({
@required this.firstLine,
@required this.lastLine,
@required this.methodsCount,
@required this.weightOfClass,
});
}
6 changes: 5 additions & 1 deletion lib/src/models/component_report.dart
Expand Up @@ -4,6 +4,10 @@ import 'package:meta/meta.dart';
@immutable
class ComponentReport {
final MetricValue<int> methodsCount;
final MetricValue<double> weightOfClass;

const ComponentReport({@required this.methodsCount});
const ComponentReport({
@required this.methodsCount,
@required this.weightOfClass,
});
}
26 changes: 26 additions & 0 deletions lib/src/reporters/utility_selector.dart
Expand Up @@ -90,6 +90,13 @@ class UtilitySelector {
component.methodsCount, config.numberOfMethodsWarningLevel),
comment: '',
),
weightOfClass: MetricValue<double>(
metricsId: '',
value: component.weightOfClass,
level: _violationLevelPercentInvert(
component.weightOfClass, config.weightOfClassWarningLevel),
comment: '',
),
);

static FunctionReport functionReport(FunctionRecord function, Config config) {
Expand Down Expand Up @@ -227,6 +234,25 @@ class UtilitySelector {
rhs.maximumNestingLevelViolations,
);

static MetricValueLevel _violationLevelPercentInvert(
double value,
double warningLevel,
) {
if (warningLevel == null) {
return MetricValueLevel.none;
}

if (value < warningLevel * 0.5) {
return MetricValueLevel.alarm;
} else if (value < warningLevel) {
return MetricValueLevel.warning;
} else if (value < (warningLevel * 2)) {
return MetricValueLevel.noted;
}

return MetricValueLevel.none;
}

static MetricValueLevel _maintainabilityIndexViolationLevel(double index) {
if (index < 10) {
return MetricValueLevel.alarm;
Expand Down
10 changes: 10 additions & 0 deletions test/stubs_builders.dart
Expand Up @@ -8,11 +8,13 @@ ComponentRecord buildComponentRecordStub({
int firstLine = 0,
int lastLine = 0,
int methodsCount = 0,
double weightOfClass = 1,
}) =>
ComponentRecord(
firstLine: firstLine,
lastLine: lastLine,
methodsCount: methodsCount,
weightOfClass: weightOfClass,
);

FunctionRecord buildFunctionRecordStub({
Expand All @@ -39,6 +41,8 @@ FunctionRecord buildFunctionRecordStub({
ComponentReport buildComponentReportStub({
int methodsCount = 0,
MetricValueLevel methodsCountViolationLevel = MetricValueLevel.none,
double weightOfClass = 1,
MetricValueLevel weightOfClassViolationLevel = MetricValueLevel.none,
}) =>
ComponentReport(
methodsCount: MetricValue<int>(
Expand All @@ -47,6 +51,12 @@ ComponentReport buildComponentReportStub({
level: methodsCountViolationLevel,
comment: '',
),
weightOfClass: MetricValue<double>(
metricsId: '',
value: weightOfClass,
level: weightOfClassViolationLevel,
comment: '',
),
);

FunctionReport buildFunctionReportStub({
Expand Down

0 comments on commit 99d619a

Please sign in to comment.