提升 Flutter 代码质量的技巧与实践

Ducafecat
22 min readOct 15, 2024

--

提升 Flutter 代码质量的技巧与实践

视频

https://youtu.be/0ZxBj-lG9Z8

https://www.bilibili.com/video/BV1jztoeDEeB/

前言

原文 提升Flutter代码质量的技巧与实践

本文总结了Flutter开发中的编码技巧与最佳实践,帮助开发者提升代码质量和应用性能,无论是初学者还是经验丰富的开发者都能从中受益。

这些技巧和规则只是对你的编码提供建议,并不是限制你发挥。

参考

  1. Flutter 官方文档
  2. Dart 编程语言指南
  3. Flutter Community(Flutter 社区资源)
  4. Stack Overflow Flutter 话题

正文

遵循 DRY 原则

DRY(不要重复自己)是软件开发中的一个基本原则,旨在减少代码的重复。

**示例:

// Instead of
double calculateArea(double width, double height) {
return width * height;
}
double calculateVolume(double width, double height, double depth) {
return width * height * depth;
// Use
double calculateArea(double width, double height) {
return width * height;
}
double calculateVolume(double width, double height, double depth) {
return calculateArea(width, height) * depth;
}

使用 enum 用于固定常量集

枚举是表示一组固定常量的好方法,可以使你的代码更易读和更易于维护。

示例:

enum Status {
active,
inactive,
pending,
}
// Usage
Status userStatus = Status.active;

避免使用晦涩的缩写

使用描述性名称使代码更易读。

示例:

// Instead of
void calc() {}
// Use
void calculateTotal() {}

使用 StreamBuilder 实现实时数据

StreamBuilder 是一个根据与 Stream 的最新交互快照来构建自身的 widget。

示例:

class RealTimeWidget extends StatelessWidget {
final Stream<int> _stream = Stream.periodic(Duration(seconds: 1), (count) => count);
@override
Widget build(BuildContext context) {
return StreamBuilder<int>(
stream: _stream,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
return Text('Count: ${snapshot.data}');
}
},
);
}
}

使用 FutureBuilder 处理异步数据

使用 FutureBuilder 显示异步数据。

示例:

class DataWidget extends StatelessWidget {
Future<String> fetchData() async {
await Future.delayed(Duration(seconds: 2));
return 'Data';
}
@override
Widget build(BuildContext context) {
return FutureBuilder<String>(
future: fetchData(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
return Text('Data: ${snapshot.data}');
}
},
);
}
}

根据项目需求使用适当的状态管理

选择最适合您项目需求的状态管理解决方案。

示例:

// For simple state management, use Provider
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}

// For complex state management, use Bloc
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0);
@override
Stream<int> mapEventToState(CounterEvent event) async* {
if (event is Increment) {
yield state + 1;
}
}
}

使用关键小部件进行高效更新

键帮助 Flutter 识别哪些小部件需要更新。

示例:

class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
key: ValueKey(items[index].id),
title: Text(items[index].name),
);
},
);
}
}

避免嵌套的 try-catch 块

保持错误处理简单以提高可读性。

示例:

try {
// Code that might throw an exception
} catch (e) {
// Handle exception
}

将布局与逻辑分离

将 UI 布局与业务逻辑分离,使您的代码更易于维护。

示例:

// Layout
class LoginScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: LoginButton(),
),
);
}
}
// Logic
class LoginButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
// Handle login
},
child: Text('Login'),
);
}
}

使用配置文件来管理常量

将常量存储在配置文件或常量类中,以便更方便地管理它们。

示例:

const String API_URL = 'https://api.example.com';

避免使用过于复杂的 Widgets

保持小部件简单和专注。

示例:

// Instead of
class ComplexWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Title'),
Expanded(
child: ListView(
children: [
ListTile(title: Text('Item 1')),
ListTile(title: Text('Item 2')),
],
),
),
ElevatedButton(onPressed: () {}, child: Text('Button')),
],
);
}
}
// Use
class SimpleWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Title'),
ListView(
children: [
ListTile(title: Text('Item 1')),
ListTile(title: Text('Item 2')),
],
),
ElevatedButton(onPressed: () {}, child: Text('Button')),
],
);
}
}

对于非变量状态使用 Final

使用 final 用于那些初始化后不应更改的变量。

示例:

final String userName = 'John Doe';

使用 Widgets 用于 UI 组件

将 UI 组件封装成可重用的小部件以促进代码复用。

示例:

class MyButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ElevatedButton(onPressed: () {}, child: Text('Press Me'));
}
}

使用构造函数初始化列表

在类中的构造函数中使用初始化列表以提高性能和可读性。

示例:

class User {
final String name;
final int age;

// Instead of initializing fields within the constructor body
User(String name, int age) : name = name, age = age;
}

函数名称应为动词

函数应描述操作以清楚传达它们的功能。

示例:

// Instead of
void nameCheck() {}
// Use
void checkName() {}

避免使用 assert 进行数据验证

使用断言进行调试,而不是在生产环境中进行数据验证。

示例:

// Instead of
assert(user != null, 'User cannot be null');
// Use
if (user == null) {
throw ArgumentError('User cannot be null');
}

使用 Try-Finally 处理资源

确保使用 try-finally 代码块正确管理资源。

示例:

// Using await and try-finally for resource management
Future<void> readFile() async {
final file = File('example.txt');
try {
final contents = await file.readAsString();
print(contents);
} finally {
// Ensure the file is properly closed or resources are cleaned up
}
}

使用类型注解

明确标注类型以提高可读性和可维护性。

示例:

// Instead of
var name = 'Flutter';
// Use
String name = 'Flutter';

使用依赖注入

使用依赖注入来促进组件之间的松耦合。

示例:

class AuthService {
void login(String username, String password) {}
}
class LoginScreen {
final AuthService _authService;
LoginScreen(this._authService);
}

保持相关代码行靠近

将相关的代码行放在一起,以便于理解和维护。

示例:

class Order {
final List<Item> items;
Order(this.items);
double calculateTotal() {
return items.fold(0, (total, item) => total + item.price);
}
}

避免使用魔术数字

硬编码的值可能会令人困惑。使用命名常量以提高清晰度。

示例:

// Instead of
final padding = 16.0;
// Use
const double kPadding = 16.0;
final padding = kPadding;

安全处理空值

使用空感知运算符来安全地处理空值。

示例:

// Instead of
if (user != null) {
print(user.name);
// Use
print(user?.name);

使用自定义异常处理特定错误

创建自定义异常以实现更好的错误处理。

示例:

class CustomException implements Exception {
final String message;
CustomException(this.message);
@override
String toString() {
return 'CustomException: $message';
}
}

使用描述性名称为变量命名

变量应描述其用途。

示例:

// Instead of
var n = 100;
// Use
var maxUsers = 100;

对于不可变类使用 const 构造器

使用 const 构造器用于不可变的类。

示例:

class Point {
final double x;
final double y;
  const Point(this.x, this.y);
}

类应该小

类应该只有一个职责。

示例:

// Instead of
class User {
String name;
String address;
void login() {}
void logout() {}
// Use
class User {
String name;
}
class AuthService {
void login() {}
void logout() {}
}

对于有状态的 UI,请使用 Stateful Widgets

使用 StatefulWidget 当 UI 需要动态更改时。

示例:

class Counter extends StatefulWidget {
@override
_CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('$_count'),
ElevatedButton(
onPressed: () {
setState(() {
_count++;
});
},
child: Text('Increment'),
),
],
);
}
}

节制使用 const 关键字用于小部件

使用 const 关键字用于 widgets 有助于 Flutter 通过减少重建和提高 widget 树的效率来优化性能。

示例:

// Instead of
Widget build(BuildContext context) {
return Text('Hello, World!');
}
// Use
Widget build(BuildContext context) {
return const Text('Hello, World!');
}

避免过长的方法

将长方法拆分成更小、更易管理的部分。每个方法应该只执行一个任务。

示例:

// Instead of
void processOrder(Order order) {
validateOrder(order);
calculateTotal(order);
updateInventory(order);
sendConfirmationEmail(order);
// Use
void processOrder(Order order) {
validateOrder(order);
calculateTotal(order);
updateInventory(order);
sendConfirmationEmail(order);
}
void validateOrder(Order order) {
// Validation logic
}
void calculateTotal(Order order) {
// Calculation logic
}
void updateInventory(Order order) {
// Inventory update logic
}
void sendConfirmationEmail(Order order) {
// Email sending logic
}

对于复杂的部件,请使用建造者模式

使用构建者模式将复杂对象的构建与其表示分离。

示例:

class ComplexWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
buildHeader(),
buildContent(),
buildFooter(),
],
);
}
Widget buildHeader() {
return Text('Header');
}
Widget buildContent() {
return Text('Content');
}
Widget buildFooter() {
return Text('Footer');
}
}

指定参数和返回类型

始终为函数参数和返回类型指定类型。

示例:

// Instead of
void add(a, b) {
return a + b;

// Use
int add(int a, int b) {
return a + b;
}

避免噪音/冗余词

保持名称简明扼要。

示例:

// Instead of
void getUserName() {}
// Use
void fetchUser() {}

保持函数简短

一个函数应该只做一件事情,这样更便于测试和维护。

示例:

// Instead of
void manageOrder(Order order) {
if (order.isValid()) {
order.calculateTotal();
order.updateInventory();
order.sendConfirmationEmail();
}
// Use
void manageOrder(Order order) {
if (order.isValid()) {
calculateTotal(order);
updateInventory(order);
sendConfirmationEmail(order);
}
}
void calculateTotal(Order order) {
// Calculation logic
}
void updateInventory(Order order) {
// Inventory update logic
}
void sendConfirmationEmail(Order order) {
// Email sending logic
}

避免过于复杂的状态管理

选择最符合您需求的简单状态管理解决方案。

示例:

// Use Provider for simple state management
class Counter with ChangeNotifier {
int _count = 0;
  int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}

分离 UI 和业务逻辑

保持 UI 和业务逻辑分离以提高可维护性。

示例:

// UI
class CounterScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<CounterBloc, int>(
builder: (context, count) {
return Text('$count');
},
);
}
}
// Business Logic
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0);
}

使用 asyncawait 用于异步代码

简化异步代码使用 asyncawait

示例:

Future<void> fetchData() async {
try {
final response = await http.get('https://api.example.com/data');
// Process response
} catch (e) {
print('Error: $e');
}
}

类名应该是名词

类名应该代表实体。

示例:

// Instead of
class DoSomething {}
// Use
class User {}

避免硬编码路径

使用配置文件或环境变量。

示例:

// Instead of
final apiUrl = 'https://api.example.com';
// Use
final apiUrl = dotenv.env['API_URL'];

使用单一职责原则

每个类应该只有一个职责。

示例:

// Instead of
class User {
String name;
String address;
void login() {}
void logout() {}
// Use
class User {
String name;
}
class AuthService {
void login() {}
void logout() {}
}

避免使用硬编码字符串

使用常量或本地化进行字符串处理。

示例:

// Instead of
final title = 'Welcome';
// Use
const String kTitle = 'Welcome';
final title = kTitle;

对于长列表使用 ListView

使用 ListView 以高效地显示长列表。

示例:

class LongList extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: 100,
itemBuilder: (context, index) {
return ListTile(
title: Text('Item $index'),
);
},
);
}
}

避免使用临时变量

消除不必要的临时变量。

示例:

// Instead of
final tempResult = calculate();
final finalResult = tempResult * 2;
// Use
final finalResult = calculate() * 2;

避免阻塞主线程

将繁重的任务卸载到单独的隔离环境中以防止 UI 冻结。阅读这篇文章。

示例:

Future<void> fetchData() async {
final data = await compute(fetchDataInBackground, url);
}
Future<String> fetchDataInBackground(String url) {
// Perform heavy computation
return data;
}

使用 isis not 进行类型检查

使用 isis not 操作符检查类型。

示例:

// Instead of
if (widget.runtimeType == Text) {
// Do something
// Use
if (widget is Text) {
// Do something
}

避免深度嵌套

深层次嵌套的代码难以阅读和维护。

示例:

// Instead of
if (x) {
if (y) {
doSomething();
}
// Use
if (x && y) {
doSomething();
}

使用 ?? 作为默认值

使用空值感知操作符 ?? 用于默认值。

示例:

String name = userName ?? 'Guest';

通过异常提供上下文

使错误信息具有信息量,以帮助调试。

示例:

try {
// Code that might throw an exception
} catch (e) {
print('Error: $e');
}

智慧地使用 setState 方法

最小化 setState 的范围以提高性能。

示例:

// Instead of
setState(() {
_counter++;
_someOtherState = true;
});
// Use
setState(() {
_counter++;
});

遵循既定的代码编写标准

遵循社区认可的编码标准和风格指南。

示例:

// Follow Dart's effective code style
void main() {
runApp(MyApp());
}

小结

在快速发展的移动应用开发领域,掌握Flutter的编码技巧和最佳实践是提升开发者技能的关键。本文提供了一系列实用的编码法则,旨在帮助Flutter开发者提高代码质量和应用性能。无论是新手还是经验丰富的开发者,通过遵循这些最佳实践,都能在Flutter开发的道路上更进一步,编写出干净、高效且可维护的代码。确保您的应用符合这些编码规范,将为您的开发工作带来显著的提升。

感谢阅读本文

如果有什么建议,请在评论中让我知道。我很乐意改进。

© 猫哥 ducafecat.com

end

--

--

Ducafecat
Ducafecat

No responses yet