# 风格指南

好的代码有一个非常重要的特点就是拥有好的风格。一致的命名、一致的顺序、以及一致的格式让代码看起来是一样的。这非常有利于发挥我们视力系统强大的模式匹配能力。如果我们整个 Dart 生态系统中都使用统一的风格,那么这将让我们彼此之间更容易的互相学习和互相贡献。它使我们所有人都可以更容易地学习并为彼此的代码做出贡献。

# 标识符

在 Dart 中标识符有三种类型。

  • UpperCamelCase 每个单词的首字母都大写,包含第一个单词。
  • lowerCamelCase 除了第一个字母始终是小写(即使是缩略词),每个单词的首字母都大写。
  • lowercase_with_underscores 只是用小写字母单词,即使是缩略词,并且单词之间使用 _ 连接。

# 强制UpperCamelCase

  • Classes(类名)、 enums(枚举类型)、 typedefs(类型定义)、以及 type parameters(类型参数)应该把每个单词的首字母都大写(包含第一个单词),不使用分隔符。

    class SliderMenu { ... }
    
    class HttpRequest { ... }
    
    typedef Predicate<T> = bool Function(T value);
    
    class Foo {
      const Foo([Object? arg]);
    }
    
    (anArg)
    class A { ... }
    
    ()
    class B { ... }
    

    如果注解类的构造函数是无参函数,则可以使用一个 lowerCamelCase 风格的常量来初始化这个注解。

    const foo = Foo();
    
    
    class C { ... }
    
  • 与类型命名一样,扩展 (opens new window) 的名称也应大写每个单词的首字母(包括第一个单词),并且不使用分隔符。

    extension MyFancyList<T> on List<T> { ... }
    
    extension SmartIterable<T> on Iterable<T> { ... }
    

# 强制lowerCamelCase

一些文件系统不区分大小写,所以很多项目要求文件名必须是小写字母。使用分隔符这种形式可以保证命名的可读性。使用下划线作为分隔符可确保名称仍然是有效的Dart标识符,如果语言后续支持符号导入,这将会起到非常大的帮助。

  • 要在package文件夹源文件 中使用 lowercase_with_underscores 方式命名。

    library peg_parser.source_scanner;
    
    import 'file_system.dart';
    import 'slider_menu.dart';
    

    不要使用如下的方式定义文件名:

    library pegparser.SourceScanner;
    
    import 'file-system.dart';
    import 'SliderMenu.dart';
    
  • 类成员、顶级定义、变量、参数以及命名参数等 除了第一个单词,每个单词首字母都应大写,并且不使用分隔符。

    var count = 3;
    
    HttpRequest httpRequest;
    
    void align(bool clearItems) {
      // ...
    }
    
  • 在新的代码中,使用 lowerCamelCase 来命名常量,包括枚举的值。

    const pi = 3.14;
    const defaultTimeout = 1000;
    final urlScheme = RegExp('^([a-z]+):');
    
    class Dice {
      static final numberGenerator = Random();
    }
    

    很多语言中,都推荐把常量写成大写,而Flutter中不推荐。如下:

    const PI = 3.14;
    const DefaultTimeout = 1000;
    final URL_SCHEME = RegExp('^([a-z]+):');
    
    class Dice {
      static final NUMBER_GENERATOR = Random();
    }
    

    Flutter官方也给出了解释:

    注意: 一开始使用 Java SCREAMING_CAPS 风格来命名常量。我们之所以不再使用,是因为:

    • SCREAMING_CAPS 很多情况下看起来比较糟糕,尤其类似于 CSS 颜色这类的枚举值。
    • 常量常常被修改为 final 类型的非常量变量,这种情况你还需要修改变量的名字为小写字母形式。
    • 在枚举类型中自动定义的 values 属性为常量并且是小写字母形式的。

# 特殊情况

  • 把超过两个字母的首字母大写缩略词和缩写词当做一般单词来对待。

    首字母大写缩略词比较难阅读,特别是多个缩略词连载一起的时候会引起歧义。例如,一个以 HTTPSFTP 开头的名字,没有办法判断它是指 HTTPS FTP 还是 HTTP SFTP。

    为了避免上面的情况,缩略词和缩写词要像普通单词一样首字母大写。

    例外情况 两个字母情况下,类似 IO (input/output) 这样的 缩略词 要全大写。另外,两个字母的 缩写词 比如 ID (identification) 与其他常规单词一样,首字母大写即可: Id

    class HttpConnection {}
    class DBIOPort {}
    class TVVcr {}
    class MrRogers {}
    
    var httpRequest = ...
    var uiHandler = ...
    Id id;
    

    不好的书写风格:

    class HTTPConnection {}
    class DbIoPort {}
    class TvVcr {}
    class MRRogers {}
    
    var hTTPRequest = ...
    var uIHandler = ...
    ID iD;
    
  • 对于未使用的回调参数,更倾向于使用 _, _ _, 等。 有时回调函数的类型签名需要一个参数,但回调的实现并不使用该参数。在这种情况下,将未使用的参数命名为_是一种习惯做法。

    如果函数有多个未使用的参数,使用额外的下划线以避免名称冲突,如_,_ _ 等等。

    这条准则只适用于既是匿名又是本地的函数。

    这些函数通常是在上下文中立即使用的,在这里可以清楚地看到未使用的参数代表什么。

    相比之下,顶层函数和方法声明没有这样的上下文,所以它们的参数必须被命名,以便清楚地知道每个参数的用途,即使它没有被使用。

  • 对于非私有的标识符,不要使用前导下划线。

    Dart在标识符中使用_前导下划线来标记成员和顶层声明为私有。这就训练了用户将前导下划线与这些类型的声明联系起来。看到"_"就会想到 "私有"。

    对于局部变量、参数、局部函数或库的前缀,没有 "私有 "的概念。

    当其中一个名字以下划线开头时,就会向读者发出一个混乱的信号。为了避免这种情况,不要在这些名称中使用前导下划线。

# 不要使用前缀字

在编译器无法帮助你了解自己代码的时, 匈牙利命名法 (opens new window) 和其他方案出现在了 BCPL ,但是因为 Dart 可以提示你声明的类型,范围,可变性和其他属性,所以没有理由在标识符名称中对这些属性进行编码。

// 好的示例
defaultTimeout
  
// 错误的做法
kDefaultTimeout

# 包的导入

  • 要把 “dart:” 导入语句放到其他导入语句之前。

    import 'dart:async';
    import 'dart:html';
    
    import 'package:bar/bar.dart';
    import 'package:foo/foo.dart';
    
  • 要把 “package:” 导入语句放到项目相关导入语句之前。

    import 'package:bar/bar.dart';
    import 'package:foo/foo.dart';
    
    import 'util.dart';
    
  • 要把导出(export)语句作为一个单独的部分放到所有导入语句之后。

    import 'src/error.dart';
    import 'src/foo_bar.dart';
    
    export 'src/error.dart';
    

    错误的做法:

    import 'src/error.dart';
    export 'src/error.dart';
    import 'src/foo_bar.dart';
    
  • 要按照字母顺序来排序每个部分中的语句。

    import 'package:bar/bar.dart';
    import 'package:foo/foo.dart';
    
    import 'foo.dart';
    import 'foo/foo.dart';
    

    错误的做法:

    import 'package:foo/foo.dart';
    import 'package:bar/bar.dart';
    
    import 'foo/foo.dart';
    import 'foo.dart';
    

# 格式化

和其他大部分语言一样, Dart 忽略空格。但是,不会。具有一致的空格风格有助于帮助我们能够用编译器相同的方式理解代码。

  • 要使用 dart format 格式化你的代码。

    格式化是一项繁琐的工作,尤其在重构过程中特别耗时。庆幸的是,你不必担心。我们提供了一个名为 dart format (opens new window) 的优秀的自动代码格式化程序,它可以为你完成格式化工作。我们有一些关于它适用的规则的 文档 (opens new window) , Dart 中任何官方的空格处理规则由 dart format 生成

    其余格式指南用于 dart format 无法修复的一些规则。

  • 考虑修改你的代码让格式更友好。

    无论你扔给格式化程序什么样代码,它都会尽力去处理,但是格式化程序不会创造奇迹。如果代码里有特别长的标识符,深层嵌套的表达式,混合的不同类型运算符等。格式化输出的代码可能任然很难阅读。

    当有这样的情况发生时,那么就需要重新组织或简化你的代码。考虑缩短局部变量名或者将表达式抽取为一个新的局部变量。换句话说,你应该做一些手动格式化并增加代码的可读性的修改。在工作中应该把 dart format 看做一个合作伙伴,在代码的编写和迭代过程中互相协作输出优质的代码。

  • 避免单行超过 80 个字符。

    可读性研究表明,长行的文字不易阅读,长行文字移动到下一行的开头时,眼睛需要移动更长的距离。这也是为什么报纸和杂志会使用多列样式的文字排版。

    如果你真的发现你需要的文字长度超过了 80 个字符,根据我们的经验,你的代码很可能过于冗长,而且有方式可以让它更紧凑。最常见的的一种情况就是使用 VeryLongCamelCaseClassNames (非常长的类名字和变量名字)。当遇到这种情况时,请自问一下:“那个类型名称中的每个单词都会告诉我一些关键的内容或阻止名称冲突吗?”,如果不是,考虑删除它。

    注意,dart format 能自动处理 99% 的情况,但是剩下的 1% 需要你自己处理。

    dart format 不会把很长的字符串字面量分割为 80 个字符的列,所以这种情况你需要自己手工确保每行不超过 80 个字符。

    例外:

    • 当情况出现在注释或字符串是(通常在导入和导出语句中),即使文字超出行限制,也可能会保留在一行中。这样可以更轻松地搜索给定路径的源文件。

    • 多行字符串可以包含超过80个字符的行,因为换行符在字符串内是很重要的,把行拆成更短的行会改变程序。

  • 要对所有流控制结构使用花括号。

    这样可以避免 dangling else (opens new window)(else悬挂)的问题。

    if (isWeekDay) {
      print('Bike to work!');
    } else {
      print('Go dancing or read a book!');
    }
    

    这里有一个例外:一个没有 elseif 语句,并且这个 if 语句以及它的执行体适合在一行中实现。在这种情况下,如果您愿意,可以不用括号:

    if (arg == null) return defaultValue;
    

    但是,如果执行体包含下一行,请使用大括号:

    if (overflowChars != other.overflowChars) {
      return overflowChars < other.overflowChars;
    }
    

    错误的写法:

    if (overflowChars != other.overflowChars)
      return overflowChars < other.overflowChars;