Improving Code Quality With Dart Code Metrics
Tags: dart, flutter, codequality
Published: 2021-05-27 19:44:18

[Dart Code Metrics](https://github.com/dart-code-checker/dart-code-metrics) is a static code analysis tool that allows you to collect code metrics and provide additional rules for the analyzer. The tool helps developers monitor the quality of code and improve it. In this article, we'd like to share the capabilities of Dart Code Metrics with the community. This tool helped us at Wrike, and we hope it'll help you, too.

It can be launched via the command line, which is connected as a plugin to the Dart Analysis Server, or as a library. Launching via the command line allows you to easily integrate the tool into the CI/CD process, and you can get results in Сonsole, HTML, JSON, CodeClimate, or GitHub. Connecting the tool as a plugin to the Analysis Server allows you to receive real-time feedback directly from the IDE.

Why did we develop a tool like this? Wrike already has about 2.5 million lines of code written in Dart. A code base like this comes with the price of maintenance - so how do you know when it's time to refactor your code, and what do you start with?

Dart SDK is distributed with an Analyzer, which has a built-in linter with a set of rules. In the official *pub*, you can also find recommended rulesets: [pedantic](https://pub.dev/packages/pedantic), [effective_dart](https://pub.dev/packages/effective_dart), etc. This helps us avoid making mistakes and maintain the codebase as suggested by the author of the language. However, we didn't have enough analytical data on the code, and that started it all.

## Metrics

After researching [materials](https://www.springer.com/gp/book/9783540244295) on evaluating and designing program code, we realized the first step was to implement a collection of software metrics from the code.

Now the analyzer collects the following metrics:
* Cyclomatic complexity
* Lines of executable code
* Lines of code
* Number of parameters
* Number of methods
* Maximum nesting
* Weight of class

We set a base threshold value for each metric, after which code refactoring is recommended. At the same time, threshold values ​​can be easily predefined and passed to the tool when either called from the console, or during the plugin configuration for the analyzer via *analysis_options.yaml*. You can read more about this [here](https://github.com/dart-code-checker/dart-code-metrics#configuration).

In the next step the tool finds anti-patterns in the codebase, relying on data from several metrics. Only two anti-patterns are currently implemented - long-method and long-parameter-list - but we'll expand this list in the future.
While metrics like number of parameters or number of methods are easy enough to understand, what about cyclomatic complexity?

The [cyclomatic complexity](https://en.wikipedia.org/wiki/Cyclomatic_complexity) of a piece of code is the number of linearly independent routes in the code. For example, if the source code doesn't contain any branch points or loops, then its complexity is one, since there's only one route through the code. If the code has a single if-statement containing one simple condition, we have two routes through the code: the if-statement is true or false.

Many independent routes in the function body impact the code's readability and maintenance. So it's better to split the function in such cases.

The tool provides various report formats to visualize the metrics. We'll get back to this later in the "Reports" section.

## Linting

Initially, the tool only collected metrics, but then we added linting.

Other ecosystems have useful rules like unused arguments check, class member ordering check, etc. They're not available in the built-in Dart SDK linter, so we made our own linter.

Why did we decide to add linting to a separate package instead of making PR in the built-in analyzer? Because we decided to implement extended configuration capabilities. For example, we can configure the exact ordered list of class members that should be checked.

**The current list of rules:**

General rules
* avoid-unused-parameters
* binary-expression-operand-order
* double-literal-format
* member-ordering
* member-ordering-extended
* newline-before-return
* no-boolean-literal-compare
* no-empty-block
* no-equal-arguments
* no-equal-then-else
* no-magic-number
* no-object-declaration
* prefer-conditional-expressions
* prefer-trailing-comma

For *Intl* library
* prefer-intl-name
* provide-correct-intl-args

For *Dart Angular*
* avoid-preserve-whitespace-false
* component-annotation-arguments-ordering
* prefer-on-push-cd-strategy

You can find the most up-to-date list [in the documentation](https://github.com/dart-code-checker/dart-code-metrics#rules).

Stylistic rules aren't the only important things to consider; we also want to highlight potential errors like no-equal-then-else, no-equal-arguments, and more.

Our rules were partially based on issues that we'd encounter during our reviews. Ideally, we want the issues to be covered automatically so we can focus on the work that matters. Another part of our rules emerged during the process of studying other tools' rules. (Shoutout to PVS-Studio, TSLint, and ESLint for inspiration!)

Let's take a closer look at some of those rules.

**Avoid unused parameters.** Checks for unused parameters for functions or methods. An unused parameter can indicate that it's no longer needed during refactoring, or that a variable is somewhere in the body of a function (or method) instead of that parameter. In the first case the rule helps remove unused code; the second indicates a possible error.

Here's a simple example:

```dart
String method(String value) => "";
```

The *value* parameter isn't used here, and the analyzer will display the message "Parameter is unused."

Since Dart allows you to inherit from any class and requires that the parameter be included in the descendants, it can be renamed to _ in the base class, and the analyzer will skip it.

For example:
```dart
String method(String _) => "";
```

**Prefer trailing comma.** Checks for the trailing comma for arguments, parameters, enumerations, and collections, provided they span multiple lines.

For example:
```dart
void firstFunction (String firstArgument, String secondArgument, String thirdArgument) {
  ...
}
```
For a function like this, the rule will suggest adding a comma at the end so that once formatted it turns into:

```dart
void firstFunction(
  String firstArgument,
  String secondArgument,
  String thirdArgument,
) {
  ...
}
```

If the parameters were initially placed on one line, the analyzer won't consider it an error:

```dart
void secondFunction(String arg1, String arg2, String arg3) {
  ...
}
```

You can also specify a *break-on* parameter for the rule, which enables additional checking for the specified number of elements. For example, without *break-on*, the example above is considered correct. But if you configure the rule for `break-on: 2`, the analyzer will display an error for this function and suggest adding a comma.

**No equal arguments.** Checks whether the same argument is passed more than once when instantiating a class or calling a method/function.

Suppose there's a certain user class and a separate function that creates this user:

```dart
class User {
  final String firstName;
  final String lastName;
  
  const User(this.firstName, this.lastName);
}

User createUser(String lastName) {
  String firstName = getFirstName();
  return User(
    firstName,
    firstName,
  );
}
```

Both User class fields are strings, and it's easy to miss that the same variable is passed when you create it. In such cases, the rule will indicate the variable *firstName* is passed more than once, which may be an error.

**Member ordering extended.** The rule checks the class member order. The rule received an *extended* postfix since we already had a member ordering rule, but it wasn't as flexible.

The rule accepts an ordering configuration with quite a flexible template. It allows you to specify not just the type of a class member (field, method, constructor), but also keywords such as late, const, final, or, for example, nullable.

The configuration can look like this:
- public-late-final-fields
- private-late-final-fields
- public-nullable-fields
- private-nullable-fields
- named-constructors
- factory-constructors
- getters
- setters
- public-static-methods
- private-static-methods
- protected-methods
- etc.

Or be simplified to:
- fields
- methods
- setters
- getters (or, if there's no need to separate, just getters-setters)
- constructors

Read more about the rule's capabilities [in the documentation](https://github.com/dart-code-checker/dart-code-metrics/blob/master/doc/rules/member-ordering-extended.md).
Additionally, the rule may require alphabetical sorting. To do this, you need to pass `alphabetize: true` to its configuration.

## Reports
To visualize all the collected metrics and linting results, the tool conveniently provides reports in one of the following formats:
* Console
* HTML
* JSON
* CodeClimate
* GitHub

Console format is the default, and can be transferred to the console utility via the *--reporter* flag.

For example:
```bash
$ dart pub run dart_code_metrics:metrics lib
# or for a Flutter package
$ flutter pub run dart_code_metrics:metrics lib
```

When executing the command on the Dart Code Metrics codebase, we'll receive the following report in the console:

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k8n187xtijn7v8k3oegz.png)

If you want to choose a different type reporter (e.g., HTML), you need to run the following command:

```bash
$ dart pub run dart_code_metrics:metrics lib --reporter=html
# or for a Flutter package
$ flutter pub run dart_code_metrics:metrics lib --reporter=html
```

The report will be generated in the metrics folder. The resulting folder can also be passed using the *--output-directory* or *-o* flag:

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kuhc4310tirgvwfkxatw.png)

In the report, you can view each file separately:

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ing6g1vaxh3ppikdtmdi.png)

To view detailed information on metrics, you need to hover over the icons to the left of the code:

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qt96q47u8x71a2c7asob.png)

If you're using GitHub Workflows and want to get a report immediately in the created PRs, you need to add a new step to the pipeline:

```yaml
jobs:
  your_job_name:
  ...
    steps:
    ...
     - name: Run Code Metrics
       run: flutter pub run dart_code_metrics:metrics --reporter=github lib
```   

This will allow you to get reports in this format:

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sxmqcad6fdmxpe7ztim8.png)

*Detailed information about all types of metrics and configuration methods can be found [in the documentation](https://github.com/dart-code-checker/dart-code-metrics#metrics).*

## Installing

If you want to try the package for yourself, here's a short guide on connecting it as a plug-in for Dart analyzer.

**Step 1: install the package as a dev dependency.**

```bash
$ dart pub add --dev dart_code_metrics
# or for a Flutter package
$ flutter pub add --dev dart_code_metrics
```

Or, add the package manually to pubspec.yaml.

*Important: If your package hasn't been migrated to null safety yet, use version 2.5.0.*

```yaml
dev_dependencies:
  dart_code_metrics: ^3.3.3
```

Run the command to install dependencies.

```bash
$ dart pub get
# or for a Flutter package
$ flutter pub get
```

**Step 2: Add configuration to analysis_options.yaml.**

{% gist https://gist.github.com/incendial/a4977ad3c6b38e99e3b8d6975e496b24 %}

This configuration specifies the *long-method* and *long-parameter-list* anti-patterns, metrics with their threshold values, and rules. The entire list of available metrics can be found [here](https://github.com/dart-code-checker/dart-code-metrics#metrics), and the list of rules [here](https://github.com/dart-code-checker/dart-code-metrics#rules).

**Step 3: Reload the IDE for the analyzer to detect the plugin.**

*If you want to use the package as a CLI, check out the documentation [here](https://github.com/dart-code-checker/dart-code-metrics#cli). To use it as a Library, take a look at the documentation [here](https://github.com/dart-code-checker/dart-code-metrics/blob/master/example/example.dart).*

## Conclusion

Given the popularity of Flutter, we're considering what metrics and rules could help developers with this framework. We're open to suggestions; if you have ideas for rules or metrics that could be useful to Dart or Flutter developers, feel free to write to us or report an issue via [GitHub](https://github.com/dart-code-checker/dart-code-metrics). We'll be happy to make developers' lives better.

Our plans for the future can always be tracked in the public project repository in the [issues](https://github.com/dart-code-checker/dart-code-metrics/issues) list or in [projects](https://github.com/dart-code-checker/dart-code-metrics/projects), where you can also leave feedback on the tool.

Feel free to join our [community on Telegram](https://t.me/DartCodeMetrics)!