前几节课中,我们学习了错误处理与日志,掌握了让程序更健壮的核心技巧。今天我们将关注代码质量的基础保障 ——代码规范与静态检查。规范的代码不仅能提升团队协作效率,还能减少潜在错误;而静态检查工具则能在代码运行前帮我们发现问题,是高质量代码的重要保障。
一、为什么需要代码规范?
在单人项目中,代码风格可能只影响你自己;但在团队协作中,混乱的代码风格会导致:
- 阅读他人代码时需要额外的 “风格转换” 成本
- 代码审查效率低下,争论集中在格式而非逻辑
- 版本控制中出现大量无意义的 “格式修改” 提交
- 隐藏真正的逻辑错误(格式混乱掩盖了代码问题)
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)空格使用
- 关键字(
if、for、return等)后加空格 - 逗号
,后加空格 - 操作符(
+、=、==等)前后加空格 - 函数参数列表中,参数之间加空格
示例:
// 正确的空格使用
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 的基础:
- 添加依赖:
dev_dependencies:
lints: ^6.0.0
- 在
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('登录过程中发生错误');
}
}
// 其他方法...
}
对以上代码的规范点解析:
- 类名
UserApiClient使用大驼峰,清晰表达功能 - 私有变量
_dio、_logger以下划线开头 - 方法名
login使用小驼峰,动词开头 - 详细的文档注释,说明功能、参数、返回值和异常
- 合理的空行分隔逻辑块
- 函数参数使用命名参数(
required明确必填项) - 异常处理清晰,使用自定义异常