dart学习第 21 节:代码规范与静态检查​​

136 阅读9分钟

前几节课中,我们学习了错误处理与日志,掌握了让程序更健壮的核心技巧。今天我们将关注代码质量的基础保障 ——代码规范与静态检查。规范的代码不仅能提升团队协作效率,还能减少潜在错误;而静态检查工具则能在代码运行前帮我们发现问题,是高质量代码的重要保障。

一、为什么需要代码规范?

在单人项目中,代码风格可能只影响你自己;但在团队协作中,混乱的代码风格会导致:

  • 阅读他人代码时需要额外的 “风格转换” 成本
  • 代码审查效率低下,争论集中在格式而非逻辑
  • 版本控制中出现大量无意义的 “格式修改” 提交
  • 隐藏真正的逻辑错误(格式混乱掩盖了代码问题)

Dart 官方提供了详细的风格指南(Style Guide) ,定义了一套统一的代码规范,目的是让所有 Dart 代码看起来像是同一个人写的。



二、Dart 官方风格指南核心内容

1. 命名规范

命名是代码规范中最基础也最重要的部分,Dart 对不同元素有明确的命名要求:

元素类型命名风格示例说明
类、枚举、混合大驼峰式(PascalCase)class UserService {}每个单词首字母大写,无下划线
函数、方法、变量小驼峰式(camelCase)void getUserInfo() {}第一个单词小写,后续单词首字母大写
常量全大写 + 下划线const MAX_SIZE = 100;单词间用下划线分隔,强调不可变性
库、包、目录小写 + 下划线user_service.dart避免大写字母,兼容不同文件系统
私有成员小驼峰 + 前导下划线int _count = 0;以下划线开头表示私有,仅当前库可见

示例:

// 正确的类命名
class OrderProcessor {
  // 正确的私有变量
  int _totalAmount = 0;

  // 正确的方法命名
  void calculateTotal() {
    // 逻辑实现
  }
}

// 正确的常量命名
const DEFAULT_TIMEOUT = 5000;
const MAX_RETRY_COUNT = 3;

// 正确的库导入
import 'data/user_repository.dart';

注意:Dart 中没有真正的 “私有” 关键字,而是通过下划线前缀约定私有性,这是一种 “软约束”。

2. 代码格式化规则

(1)缩进与换行
  • 使用 2 个空格 缩进(不推荐使用 Tab)
  • 每行代码长度控制在 80-120 字符 以内(过长时需要换行)
  • 左大括号 { 不单独换行,跟在声明后面
  • 右大括号 } 单独成行,与对应的声明对齐

示例:

// 正确格式
if (user.isActive) {
  sendNotification(user);
} else {
  print('User is inactive');
}

// 长表达式换行
final result = calculate(
  firstParameter,
  secondParameter,
  thirdParameter,
);
(2)空格使用
  • 关键字(ifforreturn 等)后加空格
  • 逗号 , 后加空格
  • 操作符(+=== 等)前后加空格
  • 函数参数列表中,参数之间加空格

示例:

// 正确的空格使用
void updateUser(String name, int age) {
  if (age > 18) {
    this.name = name;
    this.age = age;
  }
}

// 错误示例(缺少必要空格)
void updateUser(String name, int age) {
  if (age>18) {
    this.name = name;
  }
}
(3)空行使用
  • 函数 / 方法之间留一个空行
  • 逻辑块之间留空行(如变量声明与业务逻辑之间)
  • 避免连续多个空行

示例:

class UserManager {
  final UserRepository _repository;

  UserManager(this._repository);

  // 方法之间空行
  Future<User> getUser(String id) async {
    return await _repository.fetchUser(id);
  }

  // 方法之间空行
  Future<void> saveUser(User user) async {
    // 逻辑块之间空行
    if (user.isValid) {
      await _repository.insertUser(user);
    } else {
      throw InvalidUserException();
    }
  }
}

3. 注释规范

  • 单行注释:使用 //,注释内容与 // 之间留一个空格
  • 文档注释:使用 ///(单行)或 /** ... */(多行),用于类、方法、变量的说明,可被 DartDoc 工具解析生成文档
  • 注释位置:单行注释通常放在被注释代码的上方,或同一行的右侧(但避免行尾注释过长)

示例:

/// 用户服务类,处理用户相关操作
///
/// 提供用户查询、创建、更新等功能,
/// 依赖 [UserRepository] 进行数据持久化。
class UserService {
  final UserRepository _repository;

  /// 创建 [UserService] 实例
  ///
  /// [repository] 参数不能为空,否则会抛出 [ArgumentError]
  UserService(this._repository) {
    if (_repository == null) {
      throw ArgumentError('repository must not be null');
    }
  }

  /// 获取用户信息
  ///
  /// [userId]:用户唯一标识
  /// 返回:包含用户信息的 [Future<User>]
  Future<User> getUser(String userId) async {
    // 检查用户ID格式(临时注释,后续应移至验证方法)
    if (userId.isEmpty) {
      throw ArgumentError('userId cannot be empty');
    }

    return await _repository.findById(userId);
  }
}

4. 其他重要规范

  • 避免全局变量:尽量使用类成员变量或局部变量
  • 优先使用 final 和 const:明确变量是否可变,提升性能
  • 避免 dynamic 类型:除非必要,否则应指定具体类型,利用 Dart 的类型检查
  • 集合初始化:优先使用字面量语法([] 代替 List(){} 代替 Map()
  • 条件判断:避免不必要的 == true 或 == false(直接用 if (isValid) 而非 if (isValid == true)


三、自动格式化:dart format

手动遵循所有格式规范非常繁琐,Dart 提供了官方格式化工具 dart format,可以自动将代码调整为符合风格指南的格式。

1. 基本使用

在项目根目录执行以下命令:

# 格式化指定文件
dart format lib/main.dart

# 格式化整个目录(常用)
dart format lib/

# 格式化当前目录下所有 Dart 文件
dart format .

工具会直接修改文件内容,将代码格式化为标准样式。

2. 检查模式

如果只想检查哪些文件需要格式化(不实际修改),可以使用 --dry-run 选项:

dart format --dry-run lib/

输出示例:

Formatted lib/src/user_service.dart
Formatted lib/main.dart

显示的文件就是需要格式化的文件。

3. 在开发工具中集成

主流 Dart/Flutter 开发工具都内置了 dart format 支持:

  • VS Code

    • 安装 Dart 和 Flutter 插件后,默认会在保存时自动格式化
    • 手动触发:右键菜单选择 “Format Document” 或使用快捷键 Shift+Alt+F(Windows)/ Shift+Option+F(Mac)
  • Android Studio/IntelliJ

    • 安装 Dart 和 Flutter 插件
    • 配置自动格式化:File > Settings > Languages & Frameworks > Flutter > Formatting > Format on save
    • 手动触发:右键菜单选择 “Reformat Code” 或使用快捷键 Ctrl+Alt+L(Windows)/ Option+Command+L(Mac)

建议:在团队项目中,统一配置 “保存时自动格式化”,避免格式不一致的问题。



四、静态检查:dart analyze

dart format 主要处理代码格式问题,而 dart analyze 则是静态代码分析工具,可以在代码运行前检测潜在的错误、代码风格问题和性能隐患。

1. 基本使用

在项目根目录执行:

dart analyze

工具会分析项目中所有 Dart 文件,输出问题列表,示例:

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

每个问题包含:

  • 严重程度(error > warning > info
  • 文件路径和行号
  • 问题描述
  • 问题代码(规则名称)

2. 问题严重程度

  • error:可能导致代码无法运行的错误(如类型不匹配、未定义的变量)
  • warning:不会导致编译错误,但很可能是逻辑错误(如未使用的变量、死代码)
  • info:代码可以正常运行,但不符合最佳实践(如不必要的类型转换)

3. 配置分析规则

通过项目根目录的 analysis_options.yaml 文件,可以自定义分析规则

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

linter:
  rules:
    # 启用额外的规则
    prefer_const_constructors: true
    avoid_print: true

    # 禁用不适用的规则
    omit_local_variable_types: false

analyzer:
  # 排除不需要分析的文件
  exclude:
    - '**/*.g.dart'  # 排除代码生成文件
    - 'test/mocks/**'

include: package:lints/recommended.yaml 引入了官方推荐的规则集,包含了大多数实用的检查规则。常用规则:

  • prefer_const_constructors:推荐使用 const 构造函数(性能优化)
  • avoid_print:避免使用 print(推荐用日志工具)
  • unused_import:检测未使用的导入
  • non_constant_identifier_names:检查变量命名是否符合小驼峰规范
  • dead_code:检测永远不会执行的代码

4. 在开发工具中实时检查

VS Code 和 Android Studio 会实时运行静态分析,并在编辑器中显示问题:

  • 错误:红色波浪线
  • 警告:黄色波浪线
  • 信息:灰色波浪线

将鼠标悬停在波浪线上,可以查看问题描述和修复建议。

5. 与 CI/CD 集成

在团队开发中,建议将 dart analyze 集成到持续集成(CI)流程中,确保所有提交的代码都通过静态检查:

示例 GitHub Actions 配置(.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

这样,每次提交代码或创建 PR 时,都会自动运行静态检查,不通过则无法合并。



五、其他实用工具

1. lints 包

lints 包提供了官方维护的代码检查规则集,是 analysis_options.yaml 的基础:

  1. 添加依赖:
dev_dependencies:
  lints: ^6.0.0
  1. 在 analysis_options.yaml 中引入:
# 基础规则集(适合所有项目)
include: package:lints/core.yaml

# 推荐规则集(在 core 基础上增加了更多最佳实践)
# include: package:lints/recommended.yaml

2. dartdoc 生成文档

根据代码中的文档注释(/// 或 /** ... */)生成 HTML 文档:

# 安装 dartdoc(如果未安装)
dart pub global activate dartdoc

# 生成文档
dartdoc

生成的文档位于 doc/api 目录,可通过浏览器打开查看。

3. flutter_lints(Flutter 项目专用)

Flutter 项目推荐使用 flutter_lints 包,包含了 Flutter 特有的检查规则:

dev_dependencies:
  flutter_lints: ^6.0.0
# analysis_options.yaml
include: package:flutter_lints/flutter.yaml


六、综合实践:规范的代码示例

下面是一个符合 Dart 规范的代码示例,包含了命名、格式、注释等最佳实践:

/// 处理用户相关的网络请求
///
/// 封装了用户注册、登录、信息更新等 API 调用,
/// 内部使用 [_dio] 进行网络请求,使用 [_logger] 记录日志。
class UserApiClient {
  final Dio _dio;
  final Logger _logger;

  /// 创建 [UserApiClient] 实例
  ///
  /// [dio]:已配置的 Dio 实例,不能为空
  /// [logger]:日志工具实例,不能为空
  UserApiClient({required Dio dio, required Logger logger})
    : _dio = dio,
      _logger = logger;

  /// 用户登录
  ///
  /// [username]:用户名(非空)
  /// [password]:密码(非空,长度至少 6 位)
  /// 返回:包含登录结果的 [Future<LoginResponse>]
  /// 可能抛出 [NetworkException] 或 [ApiException]
  Future<LoginResponse> login({
    required String username,
    required String password,
  }) async {
    try {
      _logger.i('用户登录', 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('登录失败', error: e);
      throw NetworkException.fromDioError(e);
    } catch (e) {
      _logger.e('登录处理失败', error: e);
      throw ApiException('登录过程中发生错误');
    }
  }

  // 其他方法...
}

对以上代码的规范点解析:

  1. 类名 UserApiClient 使用大驼峰,清晰表达功能
  2. 私有变量 _dio_logger 以下划线开头
  3. 方法名 login 使用小驼峰,动词开头
  4. 详细的文档注释,说明功能、参数、返回值和异常
  5. 合理的空行分隔逻辑块
  6. 函数参数使用命名参数(required 明确必填项)
  7. 异常处理清晰,使用自定义异常