Dart编码规范
英文版:
effective-dart en
中文版:
effective-dart zh
Flutter编码规范
中文版,没有找到,我用蹩脚的英语翻译了一遍,内容如下, 还请各位看官批评指正
Lazy programming
按需编写代码,不要添加未使用的代码,功能不准备上线的代码就先不要添加
比如临时给PO或者UI看的示例代码,可以新建poc或者spike分支,修改完后打本地包或者模拟器视频截图
不要写临时的解决方案的代码
Write Test,find Bug
不仅仅是覆盖率100%,更要逻辑达到100%, 如三目运算,if/else不同的结果值验证
不要写这样的注释代码: write test laster
Avoid duplicating state
不要复制活动状态
Getters feel faster than methods
简单的数据操作使用get,如获取缓存状态O(1)
复杂耗时的操作使用方法
No synchronous slow work
不要同步执行耗时的操作,比如磁盘或网络存读取大文件,音视频的压缩
Layers
Flutter框架很多类都遵循了单一职责的原则,尽可能使用更简洁的Widget
Avoid interleaving multiple concepts together
避免将多个概念交织在一起,如一个申明为child的widget,那么就不应该在内部再去判断child的类型
每个一个RenderObjects处理一个单一的类容,如处理Cliping和Opacity,而不是在一个RenderObject中同时处理这两个行为
尽量使用不可变的元素代替可变的元素
Avoid secret (or global) state
避免使用全局的变量和状态,最好通过类来管理
Prefer general APIs, but use dedicated APIs where there is a reason
推荐使用全局的API,但如果是一些特殊用途,推荐使用专门的API,例如一些性能问题,如切除圆角,可以使用一个特定的API来完成
Avoid the lowest common denominator
避免使用最小分母, 比如platform方法,只有某个单独的平台可以使用,不建议使用
Avoid APIs that encourage bad practices
字符串操作,解析的代码不是很安全,导致容易被攻击,如js执行脚本
性能差,耗时操作的任务通过Future和Stream返回
Avoid exposing API cliffs
使用定义清晰的API,如插件层的代码调用,注明Android和iOS调用的方法,以及API文档链接,方便查阅
Avoid exposing API oceans
避免底层的API被直接调用,更小的API更容易理解, 可以使用hide关键字进行约束
Solve real problems by literally solving a real problem
通过聆听一个真是用户的想法来来做好一个功能
Get early feedback when designing new APIs
尽早的对给出的API反馈
Only log actionable messages to the console
仅将可操作的消息记录在控制台,设置不同的flags, logLevel, feature name, class Name, function,line ,time
不要随意的打印用户的隐私信息
Plugin compatibility
版本约定, 1.0.0
设定Tag
详细的README介绍
Example
不要使用过期的API
尽可能的兼容低版本
统一的API接口,定义清晰的PlatformInteface
Native平台的方法备注
指定CoderOwner
Workarounds
Avoid abandonware
避免废弃的软件,没有维护和热度不高的开源软件不要随便使用
不要注释代码
Widget libraries follow the latest OEM behavior
针对于小部件Widget可采用新增的方式,而不是从新修改
Factory适配,微小的细节改动,如text增加maxline
新建widget,如broken级别的改动,如SearchBar,布局,边框,按钮行为,图标,交互效果全部改变,则需要新建一个
Documentation (dartdocs, javadocs, etc)
使用dartdoc
来生成Dart Documentation, https://dart.dev/guides/language/effective-dart/documentation#doc-comments
Answer your own questions straight away
避免口头交接,将发生的问题归类会中,以便于后面的人能够直接上手
Avoid useless documentation
避免使用无效的文档注释, 如简单粗暴的对属性变量重复解释
// BAD:
/// The background color.
final Color backgroundColor;
// GOOD:
/// The color with which to fill the circle. Changing the background
/// color will cause the avatar to animate to the new color.
final Color backgroundColor;
Writing prompts for good documentation
// BAD:
/// Note: It is important to be aware of the fact that in the
/// absence of an explicit value, this property defaults to 2.
// GOOD:
/// Defaults to 2.
Leave breadcrumbs in the comments
// GOOD:
/// An object representing a sequence of recorded graphical operations.
///
/// To create a [Picture], use a [PictureRecorder].
///
/// A [Picture] can be placed in a [Scene] using a [SceneBuilder], via
/// the [SceneBuilder.addPicture] method. A [Picture] can also be
/// drawn into a [Canvas], using the [Canvas.drawPicture] method.
abstract class Picture ...
Refactor the code when the documentation would be incomprehensible
如果代码逻辑本省比较难以叙述,让文档变得复杂,可以考虑修改代码结构,帮助理解, 如通知的deeplink的文档描述
Canonical terminology
method 一个类的方法描述
function 一个不在类中定义的函数
parameter 传递给闭包的参数
argument 函数中使用的参数
使用call
进行方法回调
Use correct grammar
类型指定,通过[]
表示一个存在的Object,通过method
,function
,parameter
等同通用的修饰词语来说明它
// BAD
/// [foo] must not be null.
// GOOD
/// The [foo] argument must not be null.
Use the passive voice; recommend, do not require
不要使用you
或者we
等命令的语气,避免过度主观的判定
Provide sample code
提供示例代码,使用{@tool snippet}...{@end-tool}
修饰,这将会让dart-doc来自动生成文档
/// A scrollable list of widgets arranged linearly.
///
/// ...
///
/// {@tool snippet}
/// An infinite list of children:
///
/// ```dart
/// ListView.builder(
/// padding: EdgeInsets.all(8.0),
/// itemExtent: 20.0,
/// itemBuilder: (BuildContext context, int index) {
/// return Text('entry $index');
/// },
/// )
/// ```
/// {@end-tool}
Provide full application samples.
使用全文档示例子
{@tool dartpad --template=<template>} … {@end-tool}
{@tool sample --template=<template>} … {@end-tool}
Provide illustractions, diagrams or screenshots
Use /// for public-quality private documentation
Dartdoc templates and macro
/// {@template <id>}
/// ...
/// {@endtemplate}
// GOOD:
/// {@template flutter.rendering.Layer.findAnnotations.aboutAnnotations}
/// Annotations are great!
/// {@endtemplate
// BAD:
/// {@template the_stuff!}
/// This is some great stuff!
/// {@endtemplate}
Dartdoc-specific requirements
Object使用[]
描述,其它简单类型的数据使用``来描述
// GOOD
/// Creates a foobar, which allows a baz to quux the bar.
///
/// The [bar] argument must not be null.
///
/// The `baz` argument must be greater than zero.
Foo({ this.bar, int baz }) : assert(bar != null), assert(baz > 0);
Coding patterns and catching bugs early
使用assert断言进行描述,尤其是工具类,构造函数
abstract class RenderBox extends RenderObject {
// ...
double getDistanceToBaseline(TextBaseline baseline, {bool onlyReal: false}) {
// simple asserts:
assert(!needsLayout);
assert(!_debugDoingBaseline);
// more complicated asserts:
assert(() {
final RenderObject parent = this.parent;
if (owner.debugDoingLayout)
return (RenderObject.debugActiveLayout == parent) &&
parent.debugDoingThisLayout;
if (owner.debugDoingPaint)
return ((RenderObject.debugActivePaint == parent) &&
parent.debugDoingThisPaint) ||
((RenderObject.debugActivePaint == this) && debugDoingThisPaint);
assert(parent == this.parent);
return false;
});
// ...
return 0.0;
}
// ...
}
使用合适的错误类型,来处理异常
如项目中比较常见的BlocProvider.of
throw FlutterError(
'''
BlocProvider.of() called with a context that does not contain a Bloc/Cubit of type $T.
No ancestor could be found starting from the context that was passed to BlocProvider.of<$T>().
This can happen if the context you used comes from a widget above the BlocProvider.
The context used was: $context
''',
);
Prefer specialized functions, methods and constructors
优先选择合适的便利的构造函数和方法
// BAD:
const EdgeInsets.TRBL(0.0, 8.0, 0.0, 8.0);
// GOOD:
const EdgeInsets.symmetric(horizontal: 8.0);
Minimize the visibility scope of constants
尽可能的缩小变量的作用域范围,优先使用本地变量而不是全局变量
推荐的优先级范围如下:
本地变量 -> 类变量 -> 类所在文件变量 -> 全局变量
Avoid using if chains or ?: or == with enum values
避免enum的选择判断,尽量使用switch进行描述,
Avoid using var and dynamic
尽量避免使用var
和dynamic
,var会导致变量存在被修改的潜在可能性,dynamic
容易导致类型匹配错误.
Avoid using library and part of.
每个类的共有私有关系需要有明确的定义,在对外提供的接口文件中需要有清晰的定义
Avoid using extension.
extension
不利于整理文档,对于类来说,推荐创建新的类或者方法来扩充类容
对于枚举值,个人还是觉得使用extension
比较容易解读一些
Avoid using FutureOr
它会导致API的安全性较差,难以准确判断类型,用于解释dart内部的future描述
///BAD ❌
extension BridgeMethodExtension on BridgeMethod {
/// Calls the [BridgeMethodDelegate] with optinal [args].
///
/// [args] are processed using [arguments] so see there for details.
FutureOr<Object> call([List<Object> args]) {
return delegate(_arguments(args));
}
}
extension BridgeMethodGroupExtension on BridgeMethodGroup {
BridgeMethodDelegate operator [](String methodName) {
final foundMethods = bridgeMethods().where(
(method) => method.name == methodName,
);
assert(
foundMethods.isNotEmpty,
'no bridge method found for name [$methodName]',
);
assert(
foundMethods.length == 1,
'multiple bridge methods found for name [$methodName]',
);
return foundMethods.single.delegate;
}
/// Calls the [BridgeMethodDelegate] with a specific [methodName] with optional [args].
///
/// [args] are processed using [arguments] so see there for details.
FutureOr<Object> call(String methodName, [List<Object> args]) {
return this[methodName](_arguments(args));
}
}
Never check if a port is available before using it, never add timeouts, and other race conditions.
在使用一个端口之前不要去检查它,不要促发超时和其它条件竞争
Avoid mysterious and magical numbers that lack a clear derivation
避免使用缺乏推导的数字,计算过程也写在代码中
// BAD
expect(rect.left, 4.24264068712);
// GOOD
expect(rect.left, 3.0 * math.sqrt(2));
Have good hygiene when using temporary directories
使用临时目录时要注意资源的清除与释放,尤其是在测试ut中, 如果不清除临时文件可能导致其它测试ut失败
使用createTempSync
和tryToDelete
,来处理测试临时文件
Perform dirty checks in setters
对触发其它逻辑执行和状态改变的操作检查setters
属性是否真的需要改变
/// Documentation here (don't wait for a later commit).
TheType get theProperty => _theProperty;
TheType _theProperty;
void set theProperty(TheType value) {
assert(value != null);
if (_theProperty == value)
return;
_theProperty = value;
markNeedsWhatever(); // the method to mark the object dirty
}
Common boilerplates for operator == and hashCode
使用通用的 ==
来判断属性
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
return other is Foo
&& other.bar == bar
&& other.baz == baz
&& other.quux == quux;
}
@override
int get hashCode => hashValues(bar, baz, quux);
Override toString
重写toString
用于诊断
@override
String toString() => '${objectRuntimeType(this, 'NameOfObject')}($bar, $baz, $quux)';
Be explicit about dispose() and the object lifecycle
在dispose的时候释放和类相关的资源.如timer,streamSubscription
Test APIs belong in the test frameworks
测试库只在测试单元中使用,不能集成到正式环境中,避免被滥用
Immutable classes should not have hidden state
不可变的类,不要申明私有属性
Begin global constant names with prefix “k”
全局常量使用k
开头
const double kParagraphSpacing = 1.5;
const String kSaveButtonTitle = 'Save';
const Color _kBarrierColor = Colors.black54;
Avoid abbreviations
避免单词缩写
Avoid anonymous parameter names
避免匿名的参数
// GOOD
onTapDown: (TapDownDetails details) { print('hello!'); },
// BAD
onTapUp: (_) { print('good bye'); },
Naming rules for typedefs and function variables
为函数定义别名,避免直接在方法中使用
typedef ArgumentCallback<T> = void Function(T argument);
///BAD
class Exmaple {
Exmaple(Function(T argument) callback);
}
///BAD
class Exmaple {
Exmaple(ArgumentCallback<T> callback);
}
Spell words in identifiers and comments correctly
采用正确的拼接单词,不要采用谐音,保证单词的准确性,不确定的单词多百度
例如 ‘colors’, 而不是 ‘colorz’.
Capitalize identifiers consistent with their spelling
例如使用toobar
,scrollbar
,因为它们的组合是一个完整的单词,
而appBar
和tabBar
在官方文档中有定义,所以Bar
使用大写
Avoid double negatives in APIs
避免API中使用双重否定语句,如input.disabled = false or widget.hidden = false
Prefer naming the argument to a setter value
?? 没看明白
Qualify variables and methods used only for debugging
调试相关的变量定义,设置固定的前缀_debug
异常bug查找追踪
[JIR-TICKET] {className} {methodName} {NO.} {cutsom string}
Avoid naming undocumented libraries
不要使用库的关键字
Avoid checking in comments that ask questions
不要采用问答的形式来吸引读者的注意
// BAD:
// What should this be?
// This is a workaround.
// GOOD:
// According to this specification, this should be 2.0, but according to that
// specification, it should be 3.0. We split the difference and went with
// 2.5, because we didn't know what else to do.
// TODO(username): Converting color to RGB because class Color doesn't support
// hex yet. See http://link/to/a/bug/123
Comment all // ignores
如非必要,尽量不要写编译警告的代码,并备注原因
foo(); // ignore: lint_code, https://link.to.bug/goes/here
foo(); // ignore: lint_code, sadly there is no choice but to do
// this because we need to twiddle the quux and the bar is zorgle.
Comment all test skips
在极少数的情况下,可能需要跳过某一些测试方法,使用skip指定,并备注原因
Comment empty closures to setState
在setState中对空的实现添加一些文档注释
setState(() app{ /* The animation ticked. We use the animation's value in the build method. */ });
Formatting
提交代码时,执行格式化dartfmt .
,减少手工格式化
Constructors come first in a class
构造函数放在类的开始,帮助读者更快速的理解这个类的结构
Order other class members in a way that makes sense
类的方法,静态变量等属性的排序.
Constructors, with the default constructor first.
Constants of the same type as the class.
Static methods that return the same type as the class.
Final fields that are set from the constructor.
Other static methods.
Static properties and constants.
Mutable properties, each in the order getter, private field, setter, without newlines separating them.
Read-only properties (other than hashCode).
Operators (other than ==).
Methods (other than toString and build).
The build method, for Widget and State classes.
operator ==, hashCode, toString, and diagnostics-related methods, in that order.
Constructor syntax
构造函数定义,如果没有参数传递给super则不需要调用super
构造函数超过2个参数以后
// one-line constructor example
abstract class Foo extends StatelessWidget {
Foo(this.bar, { Key key, this.child }) : super(key: key);
final int bar;
final Widget child;
// ...
}
// fully expanded constructor example
abstract class Foo extends StatelessWidget {
Foo(
this.bar, {
Key key,
Widget childWidget,
}) : child = childWidget,
super(
key: key,
);
final int bar;
final Widget child;
// ...
}
Prefer a maximum line length of 80 characters
// BAD (breaks after assignment operator and still goes over 80 chars)
final int a = 1;
final int b = 2;
final int c =
a.very.very.very.very.very.long.expression.that.returns.three.eventually().but.is.very.long();
final int d = 4;
final int e = 5;
// BETTER (consistent lines, not much longer than the earlier example)
final int a = 1;
final int b = 2;
final int c = a.very.very.very.very.very.long.expression.that.returns.three.eventually().but.is.very.long();
final int d = 4;
final int e = 5;
// BAD (breaks after assignment operator)
final List<FooBarBaz> _members =
<FooBarBaz>[const Quux(), const Qaax(), const Qeex()];
// BETTER (only slightly goes over 80 chars)
final List<FooBarBaz> _members = <FooBarBaz>[const Quux(), const Qaax(), const Qeex()];
// BETTER STILL (fits in 80 chars)推荐这种写法,可以使用逗号分割,自动格式化换行
final List<FooBarBaz> _members = <FooBarBaz>[
const Quux(),
const Qaax(),
const Qeex(),
];
Indent multi-line argument and parameter lists by 2 characters
构造函数多行显示时,锁进2行
Foo f = Foo(
bar: 1.0,
quux: 2.0,
);
Use a trailing comma for arguments, parameters, and list items, but only if they each have their own line.
使用逗号将多个参数分行, list,set,map,function
List<int> myList = [
1,
2,
];
myList = <int>[3, 4];
foo1(
bar,
baz,
);
foo2(bar, baz);
//BAD
List<Quux> myLongList2 = <Quux>[ Quux(1), Quux(2) ];
//GOOD
List<Quux> myLongList2 = <Quux>[
Quux(1),
Quux(2),
];
Prefer single quotes for strings
对于一般的字符串使用单引号, 对于嵌套字符串使用双引号
print('Hello ${name.split(" ")[0]}');
Consider using ⇒ for short functions and methods
使用更短的函数,一行表示
// BAD:
String capitalize(String s) =>
'${s[0].toUpperCase()}${s.substring(1)}';
// GOOD:
String capitalize(String s) => '${s[0].toUpperCase()}${s.substring(1)}';
String capitalize(String s) {
return '${s[0].toUpperCase()}${s.substring(1)}';
}
Use ⇒ for inline callbacks that just return list or map literals
多使用=>
替换只有一行的闭包语句
// GOOD, but slightly more verbose than necessary since it doesn't use =>
@override
Widget build(BuildContext context) {
return PopupMenuButton<String>(
onSelected: (String value) { print('Selected: $value'); },
itemBuilder: (BuildContext context) {
return <PopupMenuItem<String>>[
PopupMenuItem<String>(
value: 'Friends',
child: MenuItemWithIcon(Icons.people, 'Friends', '5 new')
),
PopupMenuItem<String>(
value: 'Events',
child: MenuItemWithIcon(Icons.event, 'Events', '12 upcoming')
),
];
}
);
}
// GOOD, does use =>, slightly briefer
@override
Widget build(BuildContext context) {
return PopupMenuButton<String>(
onSelected: (String value) { print('Selected: $value'); },
itemBuilder: (BuildContext context) => <PopupMenuItem<String>>[
PopupMenuItem<String>(
value: 'Friends',
child: MenuItemWithIcon(Icons.people, 'Friends', '5 new')
),
PopupMenuItem<String>(
value: 'Events',
child: MenuItemWithIcon(Icons.event, 'Events', '12 upcoming')
),
]
);
}
Prefer single line for short collection-if and collection-for
对于简单的if,else
或者for
语句,使用一行表示
// BAD
final List<String> args = <String>[
'test',
if (useFlutterTestFormatter) '-rjson'
else '-rcompact',
'-j1',
if (!hasColor)
'--no-color',
for (final String opt in others)
opt,
];
// GOOD
final List<String> args = <String>[
'test',
if (useFlutterTestFormatter) '-rjson' else '-rcompact',
'-j1',
if (!hasColor) '--no-color',
for (final String opt in others) opt,
];
或者多行显示,使用2个空格分割
// GOOD
final List<String> args = <String>[
'test',
if (useFlutterTestFormatter)
'-rjson.very.very.very.very.very.very.very.very.long'
else
'-rcompact.very.very.very.very.very.very.very.very.long',
'-j1',
if (!hasColor)
'--no-color.very.very.very.very.very.very.very.very.long',
for (final String opt in others)
methodVeryVeryVeryVeryVeryVeryVeryVeryVeryLong(opt),
];
Put spread inside collection-if or collection-for on the same line
级连操作符...
和for,if
语句保持在同一行,增加代码的可读性
// BAD
final List<String> args = <String>[
'test',
if (condA)
...<String>[
'b',
'c',
]
else
'-rcompact',
for (final String opt in others)
...<String>[
m1(opt),
m2(opt),
],
];
// GOOD
final List<String> args = <String>[
'test',
if (condA) ...<String>[
'b',
'c',
] else
'-rcompact',
for (final String opt in others) ...<String>[
m1(opt),
m2(opt),
],
];
Separate the ‘if’ expression from its statement
不要将return
和if
语句不要放在一行,这样会导致容易忽略潜在的代码
// BAD:
if (notReady) return;
// GOOD:
if (notReady)
return;
// ALSO GOOD:
if (notReady) {
return;
}
如果if
和else
有多行,则需要使用{}
// BAD:
if (foo)
bar(
'baz',
);
// BAD:
if (foo)
bar();
else
baz();
// GOOD:
if (foo) {
bar(
'baz',
);
}
// GOOD:
if (foo) {
bar();
} else {
baz();
}
其它
// VERY BAD:
if (foo)
bar();
baz();
// GOOD:
if (foo)
bar();
baz();
// ALSO GOOD:
if (foo) {
bar();
baz();
}
Align expressions
表达式变量位置对齐
// BAD:
if (foo.foo.foo + bar.bar.bar * baz - foo.foo.foo * 2 +
bar.bar.bar * 2 * baz > foo.foo.foo) {
// ...
}
// GOOD (notice how it makes it obvious that this code can be simplified):
if (foo.foo.foo + bar.bar.bar * baz -
foo.foo.foo * 2 + bar.bar.bar * 2 * baz > foo.foo.foo) {
// ...
}
// After simplification, it fits on one line anyway:
if (bar.bar.bar * 3 * baz > foo.foo.foo * 2) {
// ...
}
// BAD:
return foo.x == x &&
foo.y == y &&
foo.z == z;
// GOOD:
return foo.x == x &&
foo.y == y &&
foo.z == z;
// ALSO GOOD:
return foo.x == x
&& foo.y == y
&& foo.z == z;
Prefer += over ++
Use double literals for double constants
指定传入参数的类型如果foo
使用一个double
变量foo(1.0)
//BAD although it can convert to double
void main() {
foo(1);
}
void foo(double value) {
print(value);
}
//GOOD
void main() {
foo(1.0);
}
Features we expect every widget to implement
Flutter framework现在已经基本成熟,新的widget建议增加这些特性
增加accessibility
,辅助功能选项
增加localisation
支持Directionality
,可以任意的进行方向切换
文本缩放至少支持3.0x
为每个member对象添加文档
处理大量数据时不要block ui线程
处理好widget的生命周期事件,确保所有的订阅被及时的关闭.如binding observer,subscribions,timers,controllers
对每个独立的功能进行测试
Use of streams in Flutter framework code
Stream
是一个比较重量级的API
,推荐使用Listenable
的子类(e.g ValueNotifier
或者ChangeNotifier
)
Packages Structure
每个库文件保留一个专门的文件用于导出所有对外暴露的接口
例如rendering.dart
在lib/src/rendering/*.dart
导出所有的公开文件
Import conventions
使用show
,hide
,as
关键字更好的约束库文件,减少dart文件的体积,减少命名冲突
例如import 'dart:ui' show ...
, dart:math as math