Dart&Flutter编码规范指北

Flutter 专栏收录该内容
6 篇文章 0 订阅

Dart编码规范

英文版:
effective-dart en

中文版:
effective-dart zh

Flutter编码规范

英文版:
offical-style-guide en

中文版,没有找到,我用蹩脚的英语翻译了一遍,内容如下, 还请各位看官批评指正

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

尽量避免使用vardynamic,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失败

使用createTempSynctryToDelete,来处理测试临时文件

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,因为它们的组合是一个完整的单词,
appBartabBar在官方文档中有定义,所以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

不要将returnif语句不要放在一行,这样会导致容易忽略潜在的代码

// BAD:
if (notReady) return;

// GOOD:
if (notReady)
  return;

// ALSO GOOD:
if (notReady) {
  return;
}

如果ifelse有多行,则需要使用{}

// 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.dartlib/src/rendering/*.dart导出所有的公开文件

Import conventions

使用show,hide,as关键字更好的约束库文件,减少dart文件的体积,减少命名冲突

例如import 'dart:ui' show ..., dart:math as math

  • 0
    点赞
  • 0
    评论
  • 1
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:游动-白 设计师:白松林 返回首页

打赏作者

jiodg45

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值