neodart

Neo-Dart,或称“新的”Dart,是一系列推荐的包和原则,它们打破了传统的约定(“我们总是这样做的”) ,并倾向于变革而不是为了稳定而稳定。

提示:重要的是,没有人应该感到被迫遵循 neo-Dart 的约定。

我们贡献这些是为了帮助他人理解构建 Dart/Flutter 应用和库所涉及的一些权衡,但您可以自由地拥有自己的观点。

此外,这些指南中的部分或全部可能不适用于短暂的原型、周末项目,或那些不需要特别健壮或由他人贡献的项目。

用法

Neo-Dart 是一种约定,您可以通过阅读(并选择性地贡献)这个存储库来“使用”它。

但是,如果您想使用一套非常主观的 lints 来代表这里使用的约定

# Add neodart as a development dependency.
dart pub add --dev neodart

# analysis_options.yaml
include: package:neodart/neodart.yaml

这些规则包含 include: package:lints/core.yaml,然后在它们之上包含额外的更严格的 lints,始终附带理由/解释。

贡献

要做出贡献,只需打开一个 issue(重大讨论)或一个 pull request(小改动或无需讨论的改动,例如拼写错误)。您的请求不保证会被接受,但总体上欢迎对指南的修改。

截至 2022-07-05,github.com/matanlurey 是唯一的决策者,并希望能够扩展。

要求

  1. 您的请求必须有一个充分记录的理由,而不仅仅是个人偏好。
  2. 您的请求必须可以用书面英语进行交流。
  3. 形式为“但是 X 语言/框架/库…”的请求是不够的。

原则

以下是 neo-Dart 包和代码的原则

  • 不要使用:在任何非实验性代码中都不应出现。
  • 避免:在非实验性代码中应极少出现;有充分的文档说明。
  • 考虑:应在适当时予以考虑。
  • 倾向于:除非有充分的文档说明,否则应是默认选项。

不要使用 dart:mirrors

强制执行。理想情况下应该有一个 lint,所以待定。

运行时反射在 Dart 1.x 中从未真正完全完善,而在现代 Dart (2.x) 中,它被降级为三等公民,不再支持 Dart AOT(即大多数 Flutter 二进制文件)或任何用于 Web 的 Dart 程序。

此外,即使对于命令行 Dart JIT 程序,mirrors 也故意不再被支持或更新以跟上新的语言范例或原始类型。

因此,不要使用 dart:mirrors,即使是用于测试。运行时反射经常给包的新用户和维护者带来困难,并且会变得过于聪明。总有更好的方法。

不要使用或覆盖 noSuchMethod

强制执行。理想情况下应该有一个 lint,所以待定。

dart:mirrors 有些相似的是 noSuchMethod,这是一个用户可实现的“万能”方法,用于 Dart 类中未实现的成员。在现代 Dart 中,它主要用于存根(此类尚未实现或故意不实现)或模拟(例如使用 package:mockito)。

与 mirrors 一样,noSuchMethod 在语言中很大程度上没有跟上,并且具有大多数开发人员注意不到的性能(运行时和代码大小)成本。

几乎总有比覆盖 noSuchMethod 更好的方法。

不要进行动态调用

强制执行:(通过可选的 lint)avoid_dynamic_calls

在 Dart 1.x 中,每次调用都是动态调用,类型签名仅用于(非常有限的)静态分析。从 Dart 2.x 开始,大多数调用都是静态的,动态调用仅限于(隐式或显式)类型为 dynamic 的对象。

动态调用是完全可以避免的技术债务,它禁用了任何有用的静态分析,使编译器工作更辛苦(并且在大多数情况下生成更慢、更不紧凑的代码),并且越来越无意中发生。

请使用

  • Object? 结合类型检查(即 is)和类型转换。
  • 类型推断,包括方法泛型。
  • 强类型自定义对象,而不是依赖于 foo.someMethod()

注意:对类型为 FunctionFunction.apply 的调用也是动态调用。

不要使用模拟

强制执行:(部分通过 lint)avoid_implementing_value_types,但需要更多;待定。

模拟,或创建假装实现类型的短期代理对象,在测试相关对象之间的某些契约时可能会有用;例如,您可能希望确保 pet.eat(bowl)调用 bowl.grab(...) 方法一次,并带有特定参数(宠物的嘴大小?)。

但是,模拟通常被过度使用。典型的应用程序和库通常不需要模拟,并且最好是使用真实对象(尤其是对于值类型的不可变对象),以及/或使用或贡献伪造——有意用于测试的子类型。

避免代码生成

强制执行;不是硬性规则,易于在代码审查中发现。

作为不使用 dart:mirrors 的后续,不要使用代码生成。代码生成可能是一个强大的工具,但几乎总是被过度使用,并且在 Dart 生态系统中(截至 2022-07-05)支持不佳。

为什么避免代码生成

  • 没有强大的工具支持;build 生态系统被认为是“尽力而为”,Dart 团队本身并不广泛使用它;诸如分析器之类的工具不理解代码生成的概念。

  • 代码生成通常会产生在代码库中永远不允许的模式,但这些模式被隐藏或模糊在“生成的代码”之后。仅仅因为它被生成,并不意味着代码不应该被很好地理解并且质量良好。

  • 代码生成太慢,无法成为大多数严肃开发人员周期的组成部分;通常比运行 Dart 代码要慢得多,尤其是在 VM 中。

但是,此规则是避免,而不是不要使用;定向使用代码生成(例如,生成 API 客户端或进行 JSON 序列化)允许的,但应谨慎使用;尝试在不使用代码生成的情况下开发您的功能或库,并在决定代码生成之前缩小模式。

注意:有一些有前途的语言工作正在进行中,形式为,这可能允许在未来放宽此规则,但即使使用宏,您也需要谨慎对待过度使用。

在开发库时避免重量级依赖

强制执行;不是硬性规则,易于在代码审查中发现。

依赖关系不是邪恶的,它们让您无需花费时间重新发明轮子即可创建任何非平凡的应用程序(在安全、隐私、性能或底层计算方面尤其重要)。

但是,在为包添加非开发依赖项时要谨慎,因为您添加的每个依赖项都会使用户升级和维护您的库变得越来越困难。您甚至应该考虑在将符号从其他包导入到您的库时使用 show

import 'package:other/other.dart' show ExplicitThingIAmUsing;

例如,在创建假设的 number_parser 类时,使用现有的数字格式化、字符串解析或介于两者之间的库可能很诱人。但是,问问自己是否真正需要依赖项中的许多确切功能,或者是否可以使用更小(私有)的定制实现。

总而言之:一般来说,尽量遵守YAGNI(您不需要它)原则。

避免创建可以或应该被扩展或实现的类

强制执行部分通过分析器(@sealedfactory)。

这是厚颜无耻地从Effective Java(第 17 条)复制的

为继承而设计和记录,否则禁止继承。

在 Dart 中,除了文档之外,实现此目的的机制有限(例如,/// Do not extend this class),但有一些建议是

  • 使用 package:meta 中的 @sealed;禁止大多数类型的继承

    import 'package:meta/meta.dart';
    
    @sealed
    class Animal {
      final String name;
    
      Animal(this.name);
    }
  • 确保您所有的公共构造函数都是 factory 构造函数

    class Animal {
      final String name;
    
      // Factory constructors cannot be called/used by super-classes.
      factory Animal(String name) = Animal._;
    
      // Having a private constructor prevents `extends` and `with` (mixins).
      Animal._(this.name);
    }

避免在公共 API 中使用可变对象

强制执行。理想情况下应该有一个 lint,所以待定。

完全避免可变状态和 API 是不可取的;归根结底,所有的不变性都是一种抽象。

但是,对于公共 API,请设计为不可变,除非需要可变性(例如,出于性能或正确性原因)。避免修改提供给您的对象(除非明确说明您将修改它们)

/// Removes elements from [names] that match our explicit name filter.
void removeExplicit(Set<String> names) {
  // OK: Documented in the public API that "names" will be mutated.
}

GitHub

查看 Github