最佳实践是一套既定的准则,可以提高代码质量、可读性和可靠性。它们确保遵循行业标准,鼓励一致性,并促进开发人员之间的合作。通过遵循最佳实践,代码变得更容易理解、修改和调试,从而提高整体软件质量。
原文 https://ducafecat.com/blog/flutter-best-practices-and-coding-guidelines
https://dart.dev/effective-dart/style
有许多准则和实践可以采用来提高代码质量和应用性能。
Naming convention 命名规范
# Good class ClassName {} extension ExtensionName on String {} enum EnumName {} mixin MixinName{} typedef FunctionName = void Function();
# Bad class Classname { } extension Extensionname on String { } enum Enumname { } mixin Mixinname{} typedef Functionname = void Function();
# Good my_package └─ lib └─ bottom_nav.dart # Bad mypackage └─ lib └─ bottom-nav.dart
# Good import 'package:dio/dio.dart' as dio; #Bad import 'package:dio/dio.dart' as Dio;
# Good int phoneNumber; const pieValue=3.14; // parametrs double calculateBMI(int weightInKg, int heightInMeter) { return weightInKg / (heightInMeter * heightInMeter); } //named parametrs double calculateBMI({int? weightInKg, int? heightInMeter}) { if(weightInKg !=null && heightInMeter !=null){ return weightInKg / (heightInMeter * heightInMeter); } }
# Bad int phone_number; const pie_value=3.14; // parametrs double calculateBMI(int weight_in_kg, int height_in_meter) { return weight_in_kg / (height_in_meter * height_in_meter); } //named parametrs double calculateBMI({int? weight_in_kg, int? height_in_meter}) { return weight_in_kg / (height_in_meter * height_in_meter); }
# Good Color backgroundColor; int calculateAge(Date dob); # Bad Color bg; int age(Date date);
class ClassName { // private variable String _variableName; }
在处理条件表达式时,建议使用 ??
(如果为null)和 ?.
(null aware)运算符,而不是显式的null检查。
??
(如果为空)运算符:
# Bad String? name; name= name==null ? "unknown": name; # Good String? name; name= name ?? "unknown";
?.
(空值安全)运算符:
# Bad String? name; name= name==null? null: name.length.toString(); # Good String? name; name=name?.length.toString();
为了避免潜在的异常情况,在Flutter中建议使用 is
运算符而不是 as
强制转换运算符。 is
运算符允许更安全地进行类型检查,如果转换不可能,也不会抛出异常。
# Bad (person as Person).name="Ashish"; # Good if(person is Person){ person.name="Ashish"; }
Lambda 函数(也称为匿名函数或闭包)是一种无需声明函数名称即可定义的函数。它是一种简洁、灵活的函数编写方式,通常用于需要传递函数作为参数或以函数作为返回值的语言特性中。
在 Dart 和许多其他编程语言中,Lambda 函数可以使用箭头语法或
() {}
语法来定义。例如,在 Dart 中,下面的代码演示了如何使用箭头语法定义一个 lambda 函数:在可以使用 tear-off 的情况下,避免不必要地创建 lambda 函数。如果一个函数只是简单地调用一个带有相同参数的方法,就没有必要手动将调用包装在 lambda 函数中。
# Bad void main(){ List<int> oddNumber=[1,3,4,5,6,7,9,11]; oddNumber.forEach((number){ print(number); }); }
# Good void main(){ List<int> oddNumber=[1,3,4,5,6,7,9,11]; oddNumber.forEach(print); }
# Bad List<int> firstFiveOddNumber=[1,3,5,7,9]; List<int> secondFiveOddNumber=[11,13,15,17,19]; firstFiveOddNumber.addAll(secondFiveOddNumber); # Good List<int> secondFiveOddNumber=[11,13,15,17,19]; List<int> firstFiveOddNumber=[1,3,5,7,9,...secondFiveOddNumber];
class Person { String? name; int? age; Person({ this.name, this.age, }); String toString() { return "name: $name age $age"; } }
# Bad void main(){ final person=Person(); person.name="Ashish"; person.age=25; print(person.toString()); } # Good void main(){ final person=Person(); person ..name="Ashish" ..age=25; print(person.toString()); }
# Bad Column( children: [ isLoggedIn ? ElevatedButton( onPressed: () {}, child: const Text("Go to Login page"), ) : const SizedBox(), ], ),
# Good Column( children: [ if(isLoggedIn) ElevatedButton( onPressed: () {}, child: const Text("Go to Login page"), ) ], ),
() =>
箭头函数。# Bad double calculateBMI(int weight_in_kg, int height_in_meter) { return weight_in_kg / (height_in_meter * height_in_meter); } # Good double calculateBMI(int weight_in_kg, int height_in_meter) => weight_in_kg / (height_in_meter * height_in_meter);
在 Flutter 中,使用
- 输出的信息可能难以区分:在 Flutter 应用程序中,输出的信息可能会与应用程序本身的输出混杂在一起,这可能会导致输出的信息难以区分。
- 输出的信息可能不可靠:
- 输出的信息可能会影响应用程序性能:在某些情况下,输出的信息可能会大量占用应用程序的资源,影响应用程序的性能。
因此,Flutter 推荐使用专门的日志记录库,如
logger
或flutter_bloc
中的BlocObserver
,以便在应用程序中输出可靠、易于区分和可控制的日志。这些库允许您定义输出的日志级别、输出到不同的目标(如控制台或文件)以及格式化日志消息等。例如,使用logger
库,您可以按以下方式输出日志消息:
# Bad # production mode // commented message---main method void main(){ print("print statement"); //..rest of code } void unusedFunction(){ }
# Good # production mode void main(){ //..rest of code }
project/ lib/ providers/ auth_provider.dart models/ user.dart screens/ home_screen.dart login_screen.dart utils.dart constants.dart services.dart main.dart
include: package:flutter_lints/flutter.yaml analyzer: errors: require_trailing_commas: error linter: rules: require_trailing_commas: true prefer_relative_imports: true
# utils.dart import 'package:intl/intl.dart'; String formatDateTime(DateTime dateTime) { final formatter = DateFormat('yyyy-MM-dd HH:mm:ss'); return formatter.format(dateTime); }
# text_input.dart import 'package:flutter/material.dart'; class TextInput extends StatelessWidget { final String? label; final String? hintText; final TextEditingController? controller; final TextInputType keyboardType; final bool obscureText; final String? Function(String?)? validator; final Widget? suffix; const TextInput({ this.label, this.hintText, this.suffix, this.controller, this.validator, this.obscureText = false, this.keyboardType = TextInputType.text, }); Widget build(BuildContext context) { return TextFormField( decoration: InputDecoration( labelText: label, hintText:hintText suffixIcon:suffix, ), controller: controller, obscureText: obscureText, validator:validator keyboardType: keyboardType, ); } }
# Good # validators/ common_validator.dart mixin CommonValidator{ String? emptyValidator(String value) { if (value.isEmpty) { return 'Please enter'; } else { return null; } } } #config/themes colors.dart class AppColors{ static const white=Color(0xffffffff); static const black=Color(0xff000000); } class LoginPage extends StatelessWidget with CommonValidator { const LoginPage({super.key}); Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: AppColors.black, // good title: const Text("Login page"), ), body: Column( children: [ TextInput( label: "email", hintText: "email address", validator: emptyValidator, // good ) ], ), ); } }
#Bad class LoginPage extends StatelessWidget { const LoginPage({super.key}); Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: const Color(0xff000000), // bad title: const Text("Login page"), ), body: Column( children: [ TextInput( label: "email", hintText: "email address", validator: (value) { // bad if (value!.isEmpty) { return 'Please enter'; } else { return null; } }, ) ], ), ); } }
# Bad class LoginPage extends StatefulWidget { const LoginPage({super.key}); State<LoginPage> createState() => _LoginPageState(); } class _LoginPageState extends State<LoginPage> { bool _secureText = true; Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("Login page"), ), body: Column( children: [ const TextInput( label: "Email", hintText: "Email address", ), TextInput( label: "Password", hintText: "Password", obscureText: _secureText, suffix: IconButton( onPressed: () { setState(() { _secureText = !_secureText; }); }, icon: Icon( _secureText ? Icons.visibility_off : Icons.visibility)), ), ElevatedButton( onPressed: () {}, child: const Text("Login")) ], ), ); } }
# Good class LoginPage extends StatelessWidget { const LoginPage({super.key}); Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("Login page"), ), body: Column( children: [ const TextInput( label: "Email", hintText: "Email address", ), const TextInput( label: "Password", hintText: "Password", obscureText: true, ), ElevatedButton( onPressed: () {}, child: const Text("Login")) ], ), ); } } //separate TextFormField Component class TextInput extends StatefulWidget { final String? label; final TextEditingController? controller; final String? hintText; final TextInputType keyboardType; final String? Function(String?)? validator; final bool obscureText; const TextInput({ super.key, this.label, this.hintText, this.validator, this.obscureText = false, this.controller, this.keyboardType = TextInputType.text, }); State<TextInput> createState() => _TextInputState(); } class _TextInputState extends State<TextInput> { bool _secureText = false; void initState() { _secureText = widget.obscureText; super.initState(); } Widget build(BuildContext context) { return TextFormField( decoration: InputDecoration( labelText: widget.label, hintText: widget.hintText, suffixIcon: widget.obscureText ? IconButton( onPressed: () { setState(() { _secureText = !_secureText; }); }, icon: Icon( _secureText ? Icons.visibility_off : Icons.visibility, color: Colors.grey, ), ) : null), controller: widget.controller, validator: widget.validator, obscureText: _secureText, keyboardType: widget.keyboardType, ); } }
# Bad import 'widgets/text_input.dart'; import 'widgets/button.dart' import '../widgets/custom_tile.dart'; # Good import 'package:coding_guidelines/widgets/text_input.dart'; import 'package:coding_guidelines/widgets/button.dart' import 'package:coding_guidelines/widgets/custom_tile.dart'; # Bad void f(int x) { print('debug: $x'); ... } # Good void f(int x) { debugPrint('debug: $x'); } linter: rules: - avoid_empty_else - always_use_package_imports - avoid_print
# Bad class CounterScreen extends StatefulWidget { const CounterScreen({ super.key, }); State<CounterScreen> createState() => _CounterScreenState(); } class _CounterScreenState extends State<CounterScreen> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: const Text("Counter APP"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headlineMedium, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), ); } }
# Good // separte logic from UI // provider state management class CounterProvider with ChangeNotifier { int _counter = 0; int get counter => _counter; void incrementCounter() { _counter++; notifyListeners(); } void decrementCounter() { _counter--; notifyListeners(); } } // UI class CounterScreen extends StatelessWidget { const CounterScreen({ super.key, }); Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: const Text("Counter APP"), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( 'You have pushed the button this many times:', ), Consumer<CounterProvider>( builder: (context, counter, child) { return Text( counter.counter.toString(), style: Theme.of(context).textTheme.headlineMedium, ); }, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: () => context.read<CounterProvider>().incrementCounter(), tooltip: 'Increment', child: const Icon(Icons.add), ), ); } }
pretty_dio_logger
或 dio_logger
这样的日志记录库来记录重要事件或错误。# Good final dio = Dio() ..interceptors.add(PrettyDioLogger( requestHeader: true, requestBody: true, responseBody: true, responseHeader: false, compact: false, )); Future<dynamic> fetchNetworkData() async{ try { // Simulating an asynchronous network call final data= await dio.get('endpoint'); return data; } catch (e, stackTrace) { print('An exception occurred: $e'); print('Stack trace: $stackTrace'); return e; // Perform additional error handling actions } }
# Bad final dio = Dio(); Future<dynamic> fetchNetworkData() { dio.get('endpoint').then((data){ return data; )}.catchError((e) { log.error(e); return e; }); }
flutter_test
这样的测试框架来编写和运行测试。# Good // counter app integartion testing void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('end-to-end test', () { testWidgets('tap on the floating action button, verify counter', (tester) async { app.main(); await tester.pumpAndSettle(); // Verify the counter starts at 0. expect(find.text('0'), findsOneWidget); // Finds the floating action button to tap on. final Finder fab = find.byTooltip('Increment'); // Emulate a tap on the floating action button. await tester.tap(fab); // Trigger a frame. await tester.pumpAndSettle(); // Verify the counter increments by 1. expect(find.text('1'), findsOneWidget); }); }); }
The commit type can include the following: feat – a new feature is introduced with the changes fix – a bug fix has occurred chore – changes that do not relate to a fix or feature and don't modify src or test files (for example updating dependencies) refactor – refactored code that neither fixes a bug nor adds a feature docs – updates to documentation such as a the README or other markdown files style – changes that do not affect the meaning of the code, likely related to code formatting such as white-space, missing semi-colons, and so on. test – including new or correcting previous tests perf – performance improvements ci – continuous integration related build – changes that affect the build system or external dependencies revert – reverts a previous commit # Good feat: button component chore: change login translation # Bad fixed bug on login page Changed button style empty commit messages
以上的编码准则可以帮助您提高编码标准,增强应用性能,并让您更好地理解最佳实践。通过遵循这些准则,您可以编写更清晰、更易维护的代码,优化应用性能,并避免常见的陷阱。
感谢阅读本文
如果我有什么错?请在评论中让我知道。我很乐意改进。
© 猫哥 ducafecat.com
end
Copyright 2023 ducafecat. All rights reserved.
微信: ducafecat, line: ducafecat,京ICP备2021009050号-3