主题
开发文档
本文档整合了代码规范和常见问题,为日常开发提供参考。
1. 代码规范
1.1 文件命名规范
Dart 文件:小写蛇形命名法
✅ 正确:ocr_repository.dart, vocabulary_notebook_page.dart
❌ 错误:OCRRepo.dart, vocab_notebook.dart目录命名:小写蛇形命名法,复数形式表示集合
features/
├── photo_recognition/
├── vocabulary_filtering/
└── statistics/1.2 命名规范
类名:大驼峰命名法
dart
✅ 正确:class VocabularyNotebookPage {}
❌ 错误:class vocabulary_notebook_page {}变量和方法:小驼峰命名法
dart
✅ 正确:
String vocabularyName;
void addVocabulary() {}
String _privateField; // 私有成员前缀下划线
❌ 错误:
String Vocabulary_Name;
void AddVocabulary() {}常量:小驼峰或全大写蛇形
dart
const defaultRetryCount = 3; // 推荐
const MAX_RETRY_COUNT = 3; // 也可枚举:enum 本体大驼峰,值小驼峰
dart
enum SourceType {
book,
article,
other,
}1.3 文件结构顺序
dart
// 1. 导入部分(分组)
import 'package:flutter/material.dart'; // Flutter 库
import 'package:riverpod/riverpod.dart'; // 第三方库
import 'package:reading_vocab_helper/core/...'; // 项目内部
// 2. 类定义
/// 类文档注释
class MyClass {
// 3. 成员变量
final String _privateField;
// 4. 构造函数
MyClass(this._privateField);
// 5. 公共方法
void publicMethod() {}
// 6. 私有方法
void _privateMethod() {}
// 7. 内部类/枚举
enum _InternalEnum {}
}1.4 注释规范
文档注释:使用 ///
dart
/// 词汇实体类
///
/// 代表一个单词及其学习信息
class Vocabulary {
/// 计算下次复习日期
///
/// 使用 SM-2 算法计算
/// [rating] 用户评分 (1-4)
/// [currentInterval] 当前间隔天数
/// 返回下次复习的间隔天数
int calculateNextInterval({
required int rating,
required int currentInterval,
}) {
// 实现...
}
}行内注释:使用 // 解释复杂逻辑
dart
// SM-2 算法核心公式
// EF' = EF + (0.1 - (3 - q) * (0.08 + (3 - q) * 0.02))
final newEF = easinessFactor + (0.1 - (3 - rating) * (0.08 + (3 - rating) * 0.02));1.5 格式规范
代码格式化:
bash
dart format . # 格式化所有代码
dart format --output=none --set-exit-if-changed . # 仅检查行长度:
- 推荐:80 字符
- 硬性限制:120 字符
缩进:2 空格(不使用 Tab)
1.6 Dart 最佳实践
使用 const 构造函数:
dart
✅ 正确:const Card(child: Text('Hello'));
❌ 错误:Card(child: Text('Hello'));使用 Null Safety:
dart
String? nullableName;
String nonNullableName = 'Larry';
void printName(String? name) {
print(name ?? 'Unknown');
}使用扩展方法:
dart
extension StringExtension on String {
bool get isNotBlank => isNotEmpty;
}
// 使用
if (text.isNotBlank) { ... }1.7 Riverpod 规范
Provider 命名:
- 后缀 Provider:
vocabularyProvider - 后缀 Notifier:
vocabularyNotifier
使用 riverpod_generator:
dart
@riverpod
class VocabularyFiltering extends _$VocabularyFiltering {
@override
FutureOr<FilteringResultEntity> build() {
return FilteringResultEntity.initial();
}
Future<void> filterVocabularies(List<String> words) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
final repository = ref.read(vocabularyFilteringRepositoryProvider);
return await repository.filterVocabularies(words: words);
});
}
}1.8 Git 提交规范
提交消息格式:
<type>(<scope>): <subject>
<body>
<footer>类型(type):
feat: 新功能fix: Bug 修复docs: 文档更新style: 代码格式(不影响功能)refactor: 重构test: 测试相关chore: 构建/工具链相关
示例:
feat(ocr): 添加百度 OCR 支持
- 集成百度 OCR API
- 添加 API 配置管理
- 实现错误处理和重试
Closes #1231.9 i18n / 国际化开发规范
状态:i18n 迁移已全部完成(1045 个 key,EN/ZH 双语;ja 为部分翻译,84 个)。以下规范适用于所有新功能开发。
⚠️ 存量债:2026-08-03 实测 1045 个 key 中有 295 个已无代码引用(28%),迁移完成时记录的「0 未用」不变量已失效。新增 key 不受影响,但全量清扫需独立立项(
/i18n-check)。
核心原则
所有用户可见的 UI 文本必须使用 ARB key,禁止硬编码字符串。
dart
// ✅ 正确
final l10n = AppLocalizations.of(context);
Text(l10n.commonCancel)
// ❌ 错误
Text('Cancel')
Text('取消')什么必须国际化
| 必须国际化 | 不需要国际化 |
|---|---|
| 按钮文字、标签、标题 | 代码注释(中英文均可) |
| 提示信息、错误消息(用户可见的) | AppLogger 日志消息 |
| 对话框文字 | Failure 类内部错误消息 |
| SnackBar / Toast 文字 | Notifier/Provider 内部错误(无 BuildContext) |
| 空状态提示、引导文字 | 技术调试参数名(开发者工具) |
| Tooltip 文字 |
ARB Key 命名规范
格式:模块前缀 + 描述(camelCase)
commonCancel — 跨模块共享(3+ 模块使用)
notebookPageTitle — 模块级
notebookDeleteConfirm — 具体场景已有前缀:
| 前缀 | 模块 | 示例 |
|---|---|---|
common | 跨模块共享 | commonCancel, commonSave |
app | 应用级 | appTitle |
books | 书籍管理 | booksAddBook |
cefr | CEFR 级别 | cefrA1Name |
dev | 开发者工具 | devDebugSettings |
excluded | 排除词库 | excludedAddWord |
filter | 词汇过滤 | filterConfirmTitle |
notebook | 词汇笔记本 | notebookPageTitle |
photo | 拍照识别 | photoCapture |
settings | 设置 | settingsLanguage |
shared | 共享页面 | sharedAboutTitle |
source | 阅读来源 | sourceListTitle |
translation | 翻译 | translationLookup |
vocabTest | 词汇测试 | vocabTestTitle |
welcome | 欢迎页 | welcomeMe |
后缀约定:
Title— 页面/对话框标题Label— 表单标签Hint— 输入提示Message— 提示/通知消息Action— 按钮操作文字Error— 错误消息
新增 ARB Key 流程
bash
# 1. 在 app_en.arb 中添加 key(按字母序插入)
"notebookNewFeature": "My new feature",
# 2. 在 app_zh.arb 中添加对应翻译
"notebookNewFeature": "我的新功能",
# 3. 生成代码
flutter gen-l10n
# 4. 在 Dart 代码中使用
final l10n = AppLocalizations.of(context);
Text(l10n.notebookNewFeature)带参数的字符串(ICU Message Format)
json
// app_en.arb
"commonWordCount": "{count} words"
// app_zh.arb
"commonWordCount": "{count} 个单词"dart
// 使用
l10n.commonWordCount(42) // "42 words" / "42 个单词"Domain 层适配器模式
Domain 层没有 BuildContext,不能直接调用 l10n。使用适配器类桥接:
dart
// lib/config/cefr_descriptions.dart
class CefrDescriptions {
static String name(BuildContext context, CefrLevel level) {
final l10n = AppLocalizations.of(context);
switch (level) {
case CefrLevel.a1: return l10n.cefrA1Name;
// ...
}
}
}已有的适配器类:
CefrDescriptions— CEFR 级别名称和描述FontSizeDescriptions— 字体大小标签和描述OcrModeDescriptions— OCR 模式名称和描述RecommendationDescriptions— 复习分组推荐提示文案(v58)
⚠️ v58 教训:
GroupRecommendationService曾直接返回拼好的英文串 ('Due: 15 words'),RecommendationCard原样渲染 → 中文界面显示英文。 Domain 层要输出「带数值的文案」时,返回结构化语义(枚举 + 数值)而不是 拼好的字符串,否则 adapter 无法在不反解字符串的前提下本地化。
当前支持的语言
| 语言 | ARB 文件 | 状态 |
|---|---|---|
| English | lib/l10n/app_en.arb | ✅ 模板语言 |
| 中文 | lib/l10n/app_zh.arb | ✅ 完成 |
添加新语言
只需 3 步,不需要改任何 Dart 代码:
bash
# 1. 复制模板创建新的 ARB 文件
cp lib/l10n/app_en.arb lib/l10n/app_ja.arb
# 2. 修改 @@locale 并翻译所有 value
# "@@locale": "ja"
# 3. 重新生成
flutter gen-l10nFlutter 会自动发现新的 ARB 文件并生成对应的 app_localizations_ja.dart。
常见错误
dart
// ❌ 硬编码文字
Text('No data')
// ❌ 拼接字符串
Text('Found $count words')
// ❌ const + l10n(l10n 不是编译时常量)
const Text(l10n.commonCancel)
// ✅ 使用 l10n key
Text(l10n.commonNoData)
// ✅ 使用 ICU 参数
Text(l10n.commonWordCount(count))
// ✅ 去掉 const
Text(l10n.commonCancel)2. 常见问题
2.1 开发环境问题
Q1: Flutter SDK 找不到
错误信息:
Flutter SDK not found解决方案:
bash
# 检查 Flutter 是否已安装
flutter --version
# 确保 Flutter 已添加到 PATH
export PATH="$PATH:/path/to/flutter/bin"Q2: 依赖安装失败
错误信息:
Could not resolve all packages解决方案:
bash
flutter clean
flutter pub cache repair
flutter pub get
# 如果还是失败
flutter upgradeQ3: iOS 模拟器启动失败
解决方案:
bash
# 列出可用模拟器
xcrun simctl list devices
# 启动指定模拟器
xcrun simctl boot "iPhone 15 Pro"
# 重启模拟器
sudo xcrun simctl shutdown all2.2 构建问题
Q4: Android 构建失败
解决方案:
bash
flutter clean
flutter pub upgrade
flutter build apk --releaseQ5: iOS 构建失败(CocoaPods)
解决方案:
bash
cd ios
rm -rf Pods Podfile.lock
pod install
# 如果还是失败
sudo gem install cocoapods
pod install --repo-update2.3 运行时问题
Q6: 应用启动白屏/黑屏
问题描述:应用启动后长时间白屏或黑屏
解决方案:
- 检查
main.dart中的runApp()是否正确 - 检查是否有阻塞的初始化代码
- 使用
FutureBuilder延迟加载
dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}Q7: Hot Reload 不生效
解决方案:
bash
# 1. Hot Restart(Shift+R)
# 2. 完全重启
flutter run
# 3. 清理后重新运行
flutter clean && flutter run2.4 OCR 问题
Q8: OCR 识别准确率低
问题描述:识别结果不准确或为空
解决方案:
- 图片质量:确保图片清晰、光线充足
- 图片预处理:添加灰度化、二值化、降噪
- 裁剪区域:只识别文字区域,避免干扰
- 多引擎尝试:如果一个引擎失败,尝试另一个
图片预处理示例:
dart
Future<Uint8List> preprocessImage(String imagePath) async {
final image = img.decodeImage(await File(imagePath).readAsBytes())!;
// 灰度化
final grayscale = img.grayscale(image);
// 调整对比度
final contrast = img.adjustColor(grayscale, contrast: 1.2);
return Uint8List.fromList(img.encodePng(contrast));
}Q9: 百度 OCR 调用失败
错误信息:
Invalid API Key or Quota exceeded解决方案:
- 检查
.env文件中的 API Key 是否正确 - 确认百度 OCR 配额是否用完
- 检查网络连接
- 使用备用 OCR 引擎(Google ML Kit)
2.5 数据库问题
Q10: SQLite 数据库锁定
错误信息:
DatabaseException: database is locked解决方案:
dart
// ✅ 正确:使用单例
final database = await openDatabase(
path,
version: 1,
singleInstance: true, // 确保单例
);
// ❌ 错误:多次打开数据库
final db1 = await openDatabase(path);
final db2 = await openDatabase(path); // 错误!Q11: 数据库迁移失败
错误信息:
Migration failed: table already exists解决方案:
dart
void _createVocabulariesTable(Database db) async {
await db.execute('''
CREATE TABLE IF NOT EXISTS vocabularies (
id TEXT PRIMARY KEY,
word TEXT NOT NULL UNIQUE,
...
)
''');
}2.6 状态管理问题
Q12: Riverpod Provider 未找到
错误信息:
StateError: Could not find the Provider解决方案:
dart
void main() {
runApp(
ProviderScope( // 必须包裹
child: MyApp(),
),
);
}
class MyApp extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// ✅ 正确
final state = ref.watch(vocabularyProvider);
// ❌ 错误:直接访问
// final state = vocabularyProvider;
}
}Q13: 状态更新不触发 UI 刷新
问题描述:修改状态后 UI 没有更新
解决方案:
dart
// ✅ 正确:重新赋值
class VocabularyNotifier extends StateNotifier<VocabularyState> {
VocabularyNotifier() : super(VocabularyInitial());
void loadVocabularies() {
state = VocabularyLoading(); // 重新赋值
// ...
state = VocabularyLoaded(data);
}
}
// ❌ 错误:修改内部
// state.list.add(item); // 不会触发更新2.7 OpenCV 问题
Q14: Mat 对象内存泄漏
问题描述:应用内存持续增长,最终崩溃
解决方案:使用 MatScope 自动管理
dart
// ✅ 正确:使用 MatScope
MatScope.run(() {
final gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY);
final edges = cv.Canny(gray, 50, 150);
// ... 处理逻辑
// 所有 Mat 对象在作用域结束时自动释放
});
// ❌ 错误:忘记释放
final gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY);
// ... 忘记调用 gray.dispose()Q15: 边缘检测超时或崩溃
问题描述:findContours 检测到过多轮廓,导致超时或崩溃
解决方案:
dart
// 1. 图像预缩放
final maxDim = max(image.width, image.height);
if (maxDim > 1200) {
final scale = 1200.0 / maxDim;
cv.resize(image, image, Size.zero, fx: scale, fy: scale);
}
// 2. 高斯模糊降噪
cv.gaussianBlur(image, image, (5, 5), 1.5);
// 3. 限制轮廓数量
final sortedContours = contours.toList()
..sort((a, b) => cv.contourArea(b).compareTo(cv.contourArea(a)));
final topContours = sortedContours.take(50).toList();2.8 性能问题
Q16: 应用启动慢
问题描述:冷启动时间 > 3 秒
解决方案:
- 延迟初始化非关键组件
- 使用 Isolate 处理耗时操作
- 优化数据库查询(添加索引)
- 减少首页加载的数据量
dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 只初始化关键组件
await initCriticalServices();
runApp(MyApp());
// 延迟初始化其他组件
Future.microtask(() async {
await initNonCriticalServices();
});
}Q17: 数据库查询慢
问题描述:查询耗时 > 500ms
解决方案:
- 为高频查询字段建立索引
- 使用
LIMIT限制结果数量 - 避免
SELECT *,只查询需要的字段 - 使用批量操作代替逐条操作
sql
-- 添加索引
CREATE INDEX idx_notebook_next_review ON notebook_entries(next_review_date);
CREATE INDEX idx_vocabulary_word ON vocabulary_items(word);
-- 优化查询
SELECT id, word, next_review_date
FROM notebook_entries
WHERE next_review_date <= date('now')
LIMIT 20;Q18: 内存占用过高
问题描述:应用内存峰值 > 250MB
解决方案:
- 及时释放图片资源
- 使用图片缓存(cached_network_image)
- 限制列表项数量(使用分页)
- 避免重复创建大对象
dart
// ✅ 正确:及时释放
final image = await decodeImageFromList(bytes);
try {
// 使用 image
} finally {
image.dispose(); // 确保释放
}
// ✅ 正确:使用缓存
CachedNetworkImage(
imageUrl: url,
memCacheWidth: 200, // 限制缓存大小
)2.9 测试问题
Q19: 单元测试失败
问题描述:测试运行失败
解决方案:
bash
# 运行所有测试
flutter test
# 运行单个测试文件
flutter test test/domain/entities/vocabulary_test.dart
# 查看详细输出
flutter test --verbose
# 更新 golden 文件
flutter test --update-goldensQ20: Widget 测试崩溃
问题描述:Widget 测试时找不到 Provider
解决方案:
dart
testWidgets('should display vocabulary list', (tester) async {
await tester.pumpWidget(
ProviderScope( // 必须包裹 ProviderScope
child: MaterialApp(
home: VocabularyListPage(),
),
),
);
await tester.pump();
expect(find.text('Hello'), findsOneWidget);
});最后更新:2025-12-28 更新频率:遇到新问题时添加 文档状态:v1.0