发布时间:2019年5月22日 - 8分钟阅读
Flutter和Flutter Web正在引起大量的讨论,这是理所当然的,他们正在推动UI开发的边界。Flutter是用Dart编写的,而Dart刚刚在 "UI as Code "的旗帜下获得了许多功能,这些功能将给每个Flutter开发者的日子带来欢乐。这是一个令人兴奋的时代。
但是,等等! 不是所有的事情都应该快速发展。有时候,一丝不苟、挑剔、快速、细心,或者--我敢说--学究气,是有好处的。所以,在Dart的package:pedantic中,我们一直在慢慢地收集一个精确正确的lints列表,你可以应用到你的代码中。
当然,为了检查lints,你需要一个linter。Dart的linter就内置在Dart分析器中,这意味着它的146个lints(在我写这篇文章的时候)在你想要的任何地方都可以使用:在命令行上,在你的预提交中,以及在你的IDE中。作为一个Dart(或Flutter)开发者,你有数百个lints在你的指尖;唯一的问题是决定启用哪些lints。
这是个比看起来更棘手的问题。如果你只想使用package:pedantic推荐的lints,那么无需继续阅读;只需遵循这些说明即可。
还在听我说吗?很好,现在,让我们来研究一下为什么要使用package:pedantic推荐的lints。现在,让我们来研究一下为什么你不能直接启用所有146个lints并开始编码。我们将从最简单的原因开始,然后逐步深入。然后,我们将看看到底是如何评估一个linter是否会被包含在package:pedantic中的,通过一个在执行上特别麻烦的linter的例子,并以一些关于如何参与的提示来结束。
过时的lints
有些lints已经没有意义了。一个例子是super_goes_last,它要求如果super出现在初始化器的列表中,就把它放在最后。实际上,这是一个非常有用的线程,以至于在Dart 2中,它成为了语言的一个要求,所以这个线程不再需要了。
// super_goes_last; now included in Dart 2, no need for a lint.
View(Style style, List children)
: super(style), // LINT
_children = children {
禁用的lints
有些lints实际上并不打算用于公共消费。特别是,一些为Flutter SDK而设计的lints直接与Dart风格指南相矛盾,比如always_specify_types;过度指定类型意味着你失去了类型推理的好处。一个例子是 always_specify_types;过度指定类型意味着你失去了类型推理的好处。风格指南仔细解释了如何在类型注释和类型推理之间取得良好的平衡。
// always_specify_types; do not use; breaks with recommended style!
var foo = 10; // LINT
final bar = new Bar(); // LINT
const quux = 20; // LINT
昂贵的lints
评估lints涉及到对一个复杂的数据结构--你的源代码--进行任意计算。你怎么知道它们不会拖累你呢?当然,在添加lints时,我们会尽一切努力保持它们的效率;但也会有意外发生。library_prefixes lint有一个性能上的bug,只在非常不明显的情况下才会出现;当然,现在已经修复了。
// library_prefixes; performance issue was fixed, now good to go!
import 'dart:math' as Math; // LINT
import 'dart:json' as JSON; // LINT
import 'package:js/js.dart' as JS; // LINT
冗余lints
大多数棉絮的计算速度非常快,但它们并不完全免费。所以,一个lint需要发挥它的作用,它需要提供足够的价值。例如,我们拒绝了 empty_statements lint,理由是使用 dartfmt 会使空语句很容易被发现。它们不太可能被误写,所以这个lint是多余的。
// empty_statements; considered redundant with dartfmt.
if (complicated.expression.foo()) ; // LINT
贪婪的lints
有些lints不够精确,无法强制执行。例如,在Dart中省略局部变量的类型是好的风格,但只是在大多数时候。这是一个建议,而不是一个硬性规则。这使得相应的lint omit_local_variable_types过于严格,无法在所有地方执行。
// omit_local_variable_types; too strict. Local variable types
// are good style where they improve readability.
void myMethod() {
MyType bar = expression.methodCall().otherMethodCall(); // LINT
}
有意见的lints
一些lints将代码推向了一个并没有错的方向,但却很不寻常。一个例子是prefer_final_locals,它要求尽可能地将局部变量声明为final。这是一些开发者喜欢的风格,但这不是大多数Dart开发者喜欢的风格,所以默认情况下,lint应该是关闭的。
// prefer_final_locals; inconsistent with common style.
void myMethod() {
var label = 'foo'; // LINT
}
评估所有lints
看起来,即使考虑到所有这些,一个对Dart有深入了解的人应该能够坐下来,与146个可用的亚麻名单,并产生一个推荐的亚麻名单,没有太多麻烦。
但这不是我们所发现的,这实在是一个太大的任务。就像每一个lints都可以对你的代码进行任意的评估一样,决定一个特定的lints是否既正确又有用,几乎是一个任意的难题。
处理困难问题的一个好方法是要求提供硬数据。因此,在考虑每个int时,我们首先对其性能进行基准测试,并收集目前Google内部Dart代码中所有违反int的信息。
这些数字给了我们一个很好的讨论起点。
例如,如果Google的所有Dart代码只包含5个违反lint的行为,那么每一个都最好是一个严重的bug;否则,这个lint不太可能发挥它的作用。recursive_getters lint是一个罕见的例子,它能捕捉到极少的严重问题;一个调用自己的getter是一个等待发生的栈溢出。
// recursive_getters; definitely not what you meant to write!
int get field => field; // LINT
如果另一方面,我们发现了很多违反lint的情况,那么问题就会转过来:lint会改变很多开发者正在做的事情,那么我们是否确定它是一种改进,无论是整体还是每个单独的例子?如果lint会让一小部分情况变得更糟,我们能证明这一点吗?或者,也许,我们可以改进lint?
unrelated_type_equality_checks lint就是一个很好的例子。这条线程要求,在允许你比较两个对象之前,它们必须是兼容的静态类型。因此,你不允许检查3和foo是否相等;我们认为,因为一个是int,另一个是String,所以这个问题根本没有意义。
// unrelated_type_equality_checks; or, don't ask stupid questions!
void someFunction() {
var x = '1';
if (x == 1) print('surprise!'); // LINT
}
这听起来不错,但它不正确,原因有二。
它在理论上是失败的,因为 implements;一个对象可以有多个类型,所以两个静态看起来不相关的对象可能在运行时实现了相同的类型,并且完全可以进行比较。
// unrelated_type_equality_checks; objects _can_ hold surprises.
void checkForSurprise(Foo foo, Bar bar) {
if (foo == bar) print('surprise!'); // LINT
}
abstract class Foo {}
abstract class Bar {}
class Baz implements Foo, Bar {}
void main() {
var baz = Baz();
checkForSurprise(baz, baz);
}
它在实践中是失败的,因为operator==是由每个类的作者来实现的,没有任何东西强迫他们遵循它的契约。我们特别发现,package:fixnum中的Int64和Int32允许与int进行比较,但只有当int在==的右手边时,才允许进行比较。
所以,数据显示了lint的三种结果:大量正确的发现(抓到的bug),少量不正确的发现是由于运行时类型兼容,而静态类型不兼容,以及相对较多的不正确发现是由于Int64与int比较。
我们做了什么?我们改进了lint:它现在知道Int64(和Int32),并允许你像以前一样将其与int进行比较。这就留下了极少数由于静态类型不完整而导致的误报;我们选择重构,必要时使用casts,使它们符合lint的要求。
有了这些改变,我们能够达成共识,强制执行unrelated_type_equality_checks。
这里的 "我们 "指的是 "所有关心Dart lints的Google开发者的集合"。Google的任何一个正在编写Dart的人都可以参与到这个过程中来,而且很多人都在做,所以我们得到了很多的意见--特别是当一个lints是有争议的时候。
如果和当对一个lint达成共识时,接下来发生的事情就是提议执行这个lint的人清理Google内部所有的Dart代码,以通过这个lint。在这个过程中,有时我们会学到一些新的东西;如果我们遗漏了一些东西,会使清理工作成为一个突破性或困难的改变,清理工作通常会暂停。现在我们有足够多的lints要处理,我们可以直接跳过这种情况,以后再来处理。
一旦一处lints被成功清理,它就会在presubmit时被强制执行,防止Google内部代码再出现任何违规行为,并在package:pedantic的下一个版本中发布。
unawaited_futures lint是一个更难的案例。这条线程解决了过去非常常见的开发者抱怨:忘记await一个Future,导致不可预测的运行时行为和片状测试。
// unawaited_futures; catching accidentally asynchronous behaviour.
Future<void> doSomething() => ...;
Future<void> doSomethingElse() => ...;
void main() async {
doSomething(); // LINT
doSomethingElse(); // LINT
}
但是,lint是有问题的,因为我们知道有些情况下,你确实想启动一个Future,然后不等它完成就继续。其中一个例子是日志记录:通常情况下,知道日志记录会在某个时刻完成,而不需要等待它的完成是可以的。
lint提供了巨大的价值,但不可能使其正确。我们讨论了很久的最佳路径。Dart的lints可以通过写// ignore.<lint_name>来忽略。<lint_name>在前面一行,所以强制执行一个lint对于开发者来说,实际上从来没有阻塞。但是,我们真的不想训练开发人员写ignore,我们强制执行的大部分lints总是正确的,永远不应该被忽略。
这个讨论实际上是导致最初创建package:pedantic的原因。我们想提供一种规范的方式来表示:"我知道unawaited_futures lint,但它并不适用于这里。" 现在这就是package:pedantic中的unawaited方法。在发布了这一点之后,我们更新了 unawaited_futures lint 的消息和文档,将其指向 unawaited,现在我们处于一个我们希望的合理的好位置:我们有了一个你有时可能需要关闭的 lint,以及一个规范的、可读的方法来关闭它。
// unawaited_futures; say `unawaited` if that's what you wanted.
Future<void> doSomething() => ...;
Future<void> doSomethingElse() => ...;
void main() async {
unawaited(doSomething());
unawaited(doSomethingElse());
}
这足以让大家达成共识,强制执行unawaited_futures lint。
一步步走向完美的Dart linting。
这个过程还在继续。我们现在已经启用了25个lints,还有8个lints被明确禁止,所以我们还没有完成146个lints的四分之一。当然,人们还在不断地添加新的线程--因为线程的设计使得添加新的线程很容易,所以这是一个移动的目标。
我很希望能够挥动魔杖,提供一份完美的亚麻名单;希望这篇文章已经解释了为什么这是不可能的,而且我们正在努力。
最后,如果已经存在您希望尽快启用的lints,欢迎您使用pedantic问题跟踪器来告知我们;我们在考虑下一步要解决的问题时,会考虑到这些问题。遗憾的是,由于Google内部代码是决定启用哪些lints的关键,我们无法让整个过程透明化,但我们的目标是在GitHub的讨论中尽可能地开放。特别是,我们可以尽可能地提供反馈,说明哪些内容有可能被纳入package:pedantic,以及何时被纳入。
如果你对贡献新的lints或改进现有的lints感兴趣,请在GitHub上参与进来吧
通过( www.DeepL.com/Translator )(免费版)翻译