DEV Community

Ge Ji
Ge Ji

Posted on

Dart Lesson 21: code conventions and static analysis

In previous lessons, we learned about error handling and logging, mastering core techniques for making programs more robust. Today we'll focus on the fundamental guarantees of code quality — code conventions and static analysis. Consistent code not only improves team collaboration efficiency but also reduces potential errors, while static analysis tools help us find issues before code runs, serving as important safeguards for high-quality code.

I. Why Do We Need Code Conventions?

In individual projects, code style might only affect yourself, but in team collaboration, inconsistent code styles can lead to:

  • Extra mental effort when reading others' code due to style differences
  • Inefficient code reviews with debates focusing on formatting rather than logic
  • Numerous meaningless "formatting change" commits in version control
  • Hidden logical errors (messy formatting Covering up code problems)

The official Dart documentation provides a detailed Style Guide that defines a set of consistent code conventions, aiming to make all Dart code look like it was written by the same person.


II. Core Content of Dart's Official Style Guide

  1. Naming Conventions Naming is the most basic and important part of code conventions. Dart has clear naming requirements for different elements:
Element Type Naming Style Example Description
Classes, enums, mixins PascalCase class UserService {} Each word starts with a capital letter, no underscores
Functions, methods, variables camelCase void getUserInfo() {} First word lowercase, subsequent words start with capital letters
Constants UPPER_CASE_WITH_UNDERSCORES const MAX_SIZE = 100; Words separated by underscores, emphasizing immutability
Libraries, packages, directories lowercase_with_underscores user_service.dart Avoid uppercase letters for filesystem compatibility
Private members camelCase with leading underscore int _count = 0; Underscore prefix indicates privacy, visible only within the library

Examples:

// Correct class naming
class OrderProcessor {
  // Correct private variable
  int _totalAmount = 0;

  // Correct method naming
  void calculateTotal() {
    // Implementation logic
  }
}

// Correct constant naming
const DEFAULT_TIMEOUT = 5000;
const MAX_RETRY_COUNT = 3;

// Correct library import
import 'data/user_repository.dart';
Enter fullscreen mode Exit fullscreen mode

Note: Dart doesn't have a true "private" keyword. Instead, privacy is convention-based using underscore prefixes — a "soft constraint".

2. Code Formatting Rules

(1) Indentation and Line Breaks
  • Use 2 spaces for indentation (Tabs are not recommended)
  • Keep line length between 80-120 characters (break longer lines)
  • Opening braces { stay on the same line as declarations
  • Closing braces } go on their own line, aligned with the corresponding declaration

Examples:

// Correct formatting
if (user.isActive) {
  sendNotification(user);
} else {
  print('User is inactive');
}

// Line break for long expressions
final result = calculate(
  firstParameter,
  secondParameter,
  thirdParameter,
);
Enter fullscreen mode Exit fullscreen mode
(2) Space Usage
  • Add space after keywords (if, for, return, etc.)
  • Add space after commas ,
  • Add spaces around operators (+, =, ==, etc.)
  • Add spaces between parameters in function parameter lists

Examples:

// Correct space usage
void updateUser(String name, int age) {
  if (age > 18) {
    this.name = name;
    this.age = age;
  }
}

// Incorrect example (missing necessary spaces)
void updateUser(String name,int age){
  if(age>18){
    this.name=name;
  }
}
Enter fullscreen mode Exit fullscreen mode
(3) Blank Line Usage
  • Leave one blank line between functions/methods
  • Leave blank lines between logical blocks (e.g., between variable declarations and business logic)
  • Avoid consecutive blank lines

Examples:

class UserManager {
  final UserRepository _repository;

  UserManager(this._repository);

  // Blank line between methods
  Future<User> getUser(String id) async {
    return await _repository.fetchUser(id);
  }

  // Blank line between methods
  Future<void> saveUser(User user) async {
    // Blank line between logical blocks
    if (user.isValid) {
      await _repository.insertUser(user);
    } else {
      throw InvalidUserException();
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Comment Conventions

  • Single-line comments: Use // with a space between // and comment content
  • Documentation comments: Use /// (single-line) or /** ... */ (multi-line) for describing classes, methods, and variables. These can be parsed by DartDoc to generate documentation.
  • Comment placement: Single-line comments usually go above the code they describe or to the right of the same line (but avoid long end-of-line comments)

Examples:

/// User service class that handles user-related operations
/// 
/// Provides user query, creation, update, and other functions,
/// relying on [UserRepository] for data persistence.
class UserService {
  final UserRepository _repository;

  /// Creates a [UserService] instance
  /// 
  /// The [repository] parameter must not be null, otherwise
  /// an [ArgumentError] will be thrown.
  UserService(this._repository) {
    if (_repository == null) {
      throw ArgumentError('repository must not be null');
    }
  }

  /// Retrieves user information
  /// 
  /// [userId]: Unique user identifier
  /// Returns: [Future<User>] containing user information
  Future<User> getUser(String userId) async {
    // Check user ID format (temporary comment, should be moved to validation method)
    if (userId.isEmpty) {
      throw ArgumentError('userId cannot be empty');
    }

    return await _repository.findById(userId);
  }
}
Enter fullscreen mode Exit fullscreen mode

4. Other Important Conventions

  • Avoid global variables: Prefer class member variables or local variables
  • Prefer final and const: Clearly indicate whether variables are mutable for better performance
  • Avoid dynamic type: Specify concrete types unless necessary to leverage Dart's type checking
  • Collection initialization: Prefer literal syntax ([] instead of List(), {} instead of Map())
  • Condition checks: Avoid unnecessary == true or == false (use if (isValid) instead of if (isValid == true))

III. Automatic Formatting: dart format

Manually following all formatting conventions is tedious. Dart provides the official formatting tool dart format that can automatically adjust code to match the style guide.

1. Basic Usage

Execute these commands in your project root directory:

# Format a specific file
dart format lib/main.dart

# Format an entire directory (commonly used)
dart format lib/

# Format all Dart files in the current directory
dart format .
Enter fullscreen mode Exit fullscreen mode

The tool will directly modify file contents to format code according to standard styles.

2. Dry Run Mode

To check which files need formatting without actually modifying them, use the --dry-run option:

dart format --dry-run lib/
Enter fullscreen mode Exit fullscreen mode

Example output:

Formatted lib/src/user_service.dart
Formatted lib/main.dart
Enter fullscreen mode Exit fullscreen mode

The displayed files are those that need formatting.

3. Integration in Development Tools

Mainstream Dart/Flutter development tools have built-in dart format support:

  • VS Code:

    • After installing Dart and Flutter extensions, formatting happens automatically on save by default
    • Manual trigger: Right-click menu > "Format Document" or use shortcut Shift+Alt+F (Windows) / Shift+Option+F (Mac)
  • Android Studio/IntelliJ:

    • Install Dart and Flutter plugins
    • Configure auto-formatting: File > Settings > Languages & Frameworks > Flutter > Formatting > Format on save
    • Manual trigger: Right-click menu > "Reformat Code" or use shortcut Ctrl+Alt+L (Windows) / Option+Command+L (Mac)

Recommendation: In team projects, configure "format on save" uniformly to avoid formatting inconsistencies.


IV. Static Analysis: dart analyze

While dart format mainly handles code formatting issues, dart analyze is a static code analysis tool that detects potential errors, style issues, and performance concerns before code runs.

1. Basic Usage

Execute in your project root directory:

dart analyze
Enter fullscreen mode Exit fullscreen mode

The tool will analyze all Dart files in the project and output a list of issues, example:

error • lib/src/user_service.dart:15:7 • The parameter 'userId' can't have a value of 'null' because of its type 'String', but the implicit default value is 'null'. • missing_default_value_for_parameter
info • lib/utils/validator.dart:23:10 • This function has a return type of 'bool', but doesn't end with a return statement. • missing_return
warning • lib/main.dart:45:20 • Unused local variable 'temp'. • unused_local_variable
Enter fullscreen mode Exit fullscreen mode

Each issue includes:

  • Severity (error > warning > info)
  • File path and line number
  • Issue description
  • Issue code (rule name)

2. Issue Severity Levels

  • error: Problems that may prevent code from running (e.g., type mismatches, undefined variables)
  • warning: Issues that won't cause compilation errors but are likely logical errors (e.g., unused variables, dead code)
  • info: Code that runs correctly but doesn't follow best practices (e.g., unnecessary type casts)

3. Configuring Analysis Rules

You can customize analysis rules through the analysis_options.yaml file in your project root:

# analysis_options.yaml
include: package:lints/recommended.yaml

linter:
  rules:
    # Enable additional rules
    prefer_const_constructors: true
    avoid_print: true

    # Disable rules that don't apply
    omit_local_variable_types: false

analyzer:
  # Exclude files that don't need analysis
  exclude:
    - '**/*.g.dart'  # Exclude code-generated files
    - 'test/mocks/**'
Enter fullscreen mode Exit fullscreen mode

include: package:lints/recommended.yaml imports the officially recommended rule set, containing most useful checks. Common rules:

  • prefer_const_constructors: Recommends using const constructors (performance optimization)
  • avoid_print: Discourages using print (recommends logging tools instead)
  • unused_import: Detects unused imports
  • non_constant_identifier_names: Checks if variable names follow camelCase convention
  • dead_code: Detects code that will never execute

4. Real-time Checking in Development Tools

VS Code and Android Studio run static analysis in real-time and display issues in the editor:

  • Errors: Red wavy underlines
  • Warnings: Yellow wavy underlines
  • Info: Gray wavy underlines

Hovering over the wavy lines shows issue descriptions and fix suggestions.

5. Integration with CI/CD

In team development, it's recommended to integrate dart analyze into your continuous integration (CI) process to ensure all submitted code passes static checks:

Example GitHub Actions configuration (.github/workflows/analyze.yml):

name: Analyze
on: [pull_request, push]

jobs:
  analyze:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: dart-lang/setup-dart@v1
      - run: dart pub get
      - run: dart analyze
Enter fullscreen mode Exit fullscreen mode

This way, static analysis runs automatically with each code submission or PR creation, and merging is blocked if it fails.


V. Other Useful Tools

1. The lints Package

The lints package provides officially maintained code check rule sets, forming the basis of analysis_options.yaml:

  1. Add the dependency:
dev_dependencies:
  lints: ^6.0.0
Enter fullscreen mode Exit fullscreen mode

2 . Import in analysis_options.yaml:

# Basic rule set (suitable for all projects)
include: package:lints/core.yaml

# Recommended rule set (adds more best practices to core)
# include: package:lints/recommended.yaml
Enter fullscreen mode Exit fullscreen mode

2. dartdoc for Documentation Generation

Generates HTML documentation from code documentation comments (/// or /** ... */):

# Install dartdoc (if not installed)
dart pub global activate dartdoc

# Generate documentation
dartdoc
Enter fullscreen mode Exit fullscreen mode

Generated documentation is in the doc/api directory and can be viewed in a browser.

3. flutter_lints (For Flutter Projects)

Flutter projects should use the flutter_lints package, which includes Flutter-specific check rules:

dev_dependencies:
  flutter_lints: ^6.0.0
Enter fullscreen mode Exit fullscreen mode
# analysis_options.yaml
include: package:flutter_lints/flutter.yaml
Enter fullscreen mode Exit fullscreen mode

VI. Comprehensive Practice: Example of Conventional Code

Below is an example of code that follows Dart conventions, including naming, formatting, comments, and other best practices:

/// Handles network requests related to users
///
/// Encapsulates API calls for user registration, login, 
/// information updates, etc. Internally uses [_dio] for
/// network requests and [_logger] for logging.
class UserApiClient {
  final Dio _dio;
  final Logger _logger;

  /// Creates a [UserApiClient] instance
  ///
  /// [dio]: Configured Dio instance, must not be null
  /// [logger]: Logger instance, must not be null
  UserApiClient({
    required Dio dio,
    required Logger logger,
  })  : _dio = dio,
        _logger = logger;

  /// User login
  ///
  /// [username]: User name (non-null)
  /// [password]: Password (non-null, at least 6 characters)
  /// Returns: [Future<LoginResponse>] containing login result
  /// May throw [NetworkException] or [ApiException]
  Future<LoginResponse> login({
    required String username,
    required String password,
  }) async {
    try {
      _logger.i('User login', extra: {'username': username});

      final response = await _dio.post(
        '/auth/login',
        data: {
          'username': username,
          'password': password,
        },
      );

      return LoginResponse.fromJson(response.data);
    } on DioException catch (e) {
      _logger.e('Login failed', error: e);
      throw NetworkException.fromDioError(e);
    } catch (e) {
      _logger.e('Login processing failed', error: e);
      throw ApiException('An error occurred during login');
    }
  }

// Other methods...
}
Enter fullscreen mode Exit fullscreen mode

Analysis of convention points in the above code:

  1. Class name UserApiClient uses PascalCase, clearly expressing its function
  2. Private variables _dio and _logger use underscore prefixes
  3. Method name login uses camelCase and starts with a verb
  4. Detailed documentation comments explaining functionality, parameters, return values, and exceptions
  5. Reasonable blank lines separating logical blocks
  6. Function parameters use named parameters with required for clear required items
  7. Clear exception handling using custom exceptions

Top comments (0)