2022/6/2/Dart/EffectiveDart/DesignPart3

Contents

DO make your == operator obey the mathematical rules of equality.

<<パート2へ


Types

When you write down a type in your program, you constrain the kinds of values that flow into different parts of your code. Types can appear in two kinds of places: type annotations on declarations and type arguments to generic invocations.

プログラム中に型を記述すると、コードのさまざまな部分に流れ込む値の種類を制限することができます。型は、宣言の型アノテーションとgeneric呼び出しの型引数の2種類の場所に現れることがあります。

 

Type annotations are what you normally think of when you think of “static types”. You can type annotate a variable, parameter, field, or return type.

型アノテーションは、通常「静的な型」を考えるときに思い浮かべるものです。変数、パラメータ、フィールド、戻り値の型にタイプアノテーションを付けることができます。

 

In the following example, bool and String are type annotations. They hang off the static declarative structure of the code and aren’t “executed” at runtime.

下記のサンプルでboolとStringがタイプアノテーションです。それらは静的な宣言構造として存在しており、ランタイムで(実行時に)それらが「実行される」訳ではありません。

bool isEmpty(String parameter) {
  bool result = parameter.isEmpty;
  return result;
}

 


A generic invocation is a collection literal, a call to a generic class’s constructor, or an invocation of a generic method.

ジェネリック呼び出しは、コレクションリテラル、ジェネリッククラスのコンストラクターの呼び出し、またはジェネリックメソッドの呼び出しです。

 

In the next example, num and int are type arguments on generic invocations.

下記のサンプルで、numとintはgeneric発動の型引数です。

 

Even though they are types, they are first-class entities that get reified and passed to the invocation at runtime.

reify : 具象化する、具体化して考える

それらは型ですが、同時にそれらは、実行時に具体化されて呼び出しに渡されるファーストクラスのエンティティです。

var lists = <num>[1, 2];
lists.addAll(List<num>.filled(3, 4));
lists.cast<int>();

 

We stress the “generic invocation” part here, because type arguments can also appear in type annotations:

私たちは「generic invocation」の部分を強調しています。なぜなら型引数はタイプアノテーションでも現れるからです。

List<int> ints = [1, 2];

 

Here, int is a type argument, but it appears inside a type annotation, not a generic invocation.

ここで、intは型引数ですが、タイプアノテーションの中に現れています。generic invocationの中ではありません。

 

You usually don’t need to worry about this distinction, but in a couple of places, we have different guidance for when a type is used in a generic invocation as opposed to a type annotation.

通常、この違いについて心配する必要はありませんが、いくつかの場所で、型アノテーションではなく、generic呼び出しで型が使用される場合のガイダンスが異なります。


Type inference

Type annotations are optional in Dart. If you omit one, Dart tries to infer a type based on the nearby context.

タイプアノテーションはDartではオプショナル(必須ではない、してもしなくてもよい)です。

 

Sometimes it doesn’t have enough information to infer a complete type.

時には完全な型を推測するのに十分な情報を得られない場合もあります。

 

When that happens, Dart sometimes reports an error, but usually silently fills in any missing parts with dynamic.

その場合、時にはエラーをレポートする時もありますが、通常はdynamic型と推測して実行を続けます。

 

The implicit dynamic leads to code that looks inferred and safe, but actually disables type checking completely.

暗黙的なdynamic型での推測は、推測が成功して安全に見えるコードにつながりますが、実際には型チェックを完全に無効にします。

 

The rules below avoid that by requiring types when inference fails.

以下のルールは、推論が失敗したときに型を要求することによってそれを回避します。

 

The fact that Dart has both type inference and a dynamic type leads to some confusion about what it means to say code is “untyped”.

Dartには型推論とdynamic型の両方があるという事実は、「コードが”untyped”である」と言うことの意味について混乱を招きます。

 

Does that mean the code is dynamically typed, or that you didn’t write the type?

それは「コードが動的に型付けされる」という意味ですか?

それとも「あなた(プログラマー)が型を指定していない」という意味ですか?

 

To avoid that confusion, we avoid saying “untyped” and instead use the following terminology:

この混乱を避けるために、私たちは「untyped」と表現するのを避けて、その代わりに下記の用語を使います。

  • If the code is type annotated, the type was explicitly written in the code.

コードが”type annotated”である、ということは、型が明示的に示されている(明示的に型付けされている、タイプアノテーションが書かれている)ことです。

  • If the code is inferred, no type annotation was written, and Dart successfully figured out the type on its own. Inference can fail, in which case the guidelines don’t consider that inferred.

コードが”inferred“である、ということは、タイプアノテーションが書かれておらず、Dart自身で型を特定(推測)することに成功した、ということです。型推論は失敗する可能性があります。その場合、ガイドラインはinfferedとは認識しません。

  • If the code is dynamic, then its static type is the special dynamic type. Code can be explicitly annotated dynamic or it can be inferred.

コードが”dynamic“である、ということは、その静的な型が特別なdynamic型である、ということです。コードが明示的にdynamicとアノテーションされることもあるし、または推測される可能性もある。

 

In other words, whether some code is annotated or inferred is orthogonal to whether it is dynamic or some other type.

つまり、「あるコードがアノテーションされているか推論されているか」は、「あるコードがdynamicかそれ以外の型か」とは無関係です。

↑これはどういう意味でこう言っているのか微妙。dyanmicと型宣言されているのに他の型として推測されることもある、ということか?そんなことあるか?

現時点では無くても、型推測が進化してそのうちそうなる可能性はあるが。

そうではなく、「タイプアノテーションの結果dynamic型になる場合もあるしそれ以外の型になる場合もある。型推測の結果dynamic型になる場合もあるしそれ以外の型になる場合もある。」ということを説明しているのだと思われる。

Inference is a powerful tool to spare you the effort of writing and reading types that are obvious or uninteresting.

型推論は、明白または興味のないタイプの書き込みと読み取りの労力を節約するための強力なツールです。

 

It keeps the reader’s attention focused on the behavior of the code itself.

型推論は、読み手がコードの振る舞い(挙動)を理解することに集中することを助けます。

 

Explicit types are also a key part of robust, maintainable code.

明示的な型付け(タイプアノテーション)も堅牢で保守性の高いコードにとっての重要な部分です。

 

They define the static shape of an API and create boundaries to document and enforce what kinds of values are allowed to reach different parts of the program.

APIは静的な形状を定義し、どのような種類の値がプログラムの異なる部分に到達することが許されるかを文書化し強制する境界を作成する。

 

Of course, inference isn’t magic. Sometimes inference succeeds and selects a type, but it’s not the type you want.

もちろん型推測は魔法ではありません。時には型推測が成功し型が選択されたがねそれがあなたの期待している型とは違うこともあります。

 

The common case is inferring an overly precise type from a variable’s initializer when you intend to assign values of other types to the variable later. In those cases, you have to write the type explicitly.

よくあるケースは、変数のイニシャライザから過度に正確な型を推論し、後でその変数に他の型の値を代入するつもりである場合である。このような場合は、明示的に型を記述する必要がある。

 

The guidelines here strike the best balance we’ve found between brevity and control, flexibility and safety. There are specific guidelines to cover all the various cases, but the rough summary is:

このガイドラインは、簡潔さと制御性、柔軟性と安全性の間で、私たちが見つけた最良のバランスをとっています。様々なケースをカバーするための具体的なガイドラインがありますが、大まかな概要は次の通りです。

 

Do annotate when inference doesn’t have enough context, even when dynamic is the type you want.

例えあなたの望む型がdynamic型でも、型推測が十分なコンテキストを持っていない場合は型アノテーションしましょう。

Don’t annotate locals and generic invocations unless you need to.

必要な場合以外で「ローカル変数の型アノテーション」と「generic呼び出し」はやめましょう。

Prefer annotating top-level variables and fields unless the initializer makes the type obvious.

イニシャライザによって型が明確な場合以外は、トップレベル変数とフィールドは型アノテーションしましょう。


DO type annotate variables without initializers.

初期化されてない変数はタイプアノテーションしてください。

Linter rule: prefer_typing_uninitialized_variables

The type of a variable—top-level, local, static field, or instance field—can often be inferred from its initializer. However, if there is no initializer, inference fails.

トップレベル、ローカル、staticなフィールド、あるいはインスタンスフィールド、などの変数の型は、初期化の式によって型推測できることが多いです。しかし、初期化の式がないと型推測が失敗します。

//good
List<AstNode> parameters;
if (node is Constructor) {
  parameters = node.signature;
} else if (node is Method) {
  parameters = node.parameters;
}

 

//bad
var parameters;
if (node is Constructor) {
  parameters = node.signature;
} else if (node is Method) {
  parameters = node.parameters;
}

 


DO type annotate fields and top-level variables if the type isn’t obvious.

フィールドとトップレベル変数の型が明確でない時はタイプアノテーションしましょう。

Linter rule: type_annotate_public_apis

Type annotations are important documentation for how a library should be used. They form boundaries between regions of a program to isolate the source of a type error. Consider:

タイプ注釈は、ライブラリの使用方法に関する重要なドキュメントです。 これらは、プログラムの領域間に境界を形成して、型エラーの原因を分離します。

//bad
install(id, destination) => ...

Here, it’s unclear what id is. A string? And what is destination? A string or a File object? Is this method synchronous or asynchronous? This is clearer:

これではidの型は何なのか、destinationの型は何なのか、わかりません。String型でしょうか、File型のオブジェクトでしょうか?このメソッドは同期関数でしょうか、非同期関数でしょうか?不明瞭ですね。


//good
Future<bool> install(PackageId id, String destination) => ...

(上記のようにタイプアノテーションがあれば明確)


In some cases, though, the type is so obvious that writing it is pointless:

しかし時には、型が明確なのでわざわざ明示的にタイプアノテーションしてもあまり意味が無い状況もあります。

//good
const screenWidth = 640; // Inferred as int.

(screenWidthがint型であるのは明確)

 

“Obvious” isn’t precisely defined, but these are all good candidates:

「明確」というのは厳密な定義は無いですが、下記のような状況は明確と言えるでしょう

  • Literals.
  • Constructor invocations.
  • References to other constants that are explicitly typed.
  • Simple expressions on numbers and strings.
  • Factory methods like int.parse(), Future.wait(), etc. that readers are expected to be familiar with.

リテラル

コンストラクタの呼び出し

明示的に型付けされた他のconstantを参照している(代入している)

数値、文字列のシンプルな式

int.parse(), Future.wait()などのFctoryメソッド

などの読者がよく知っていると思われるもの。

 

If you think the initializer expression—whatever it is—is sufficiently clear, then you may omit the annotation. But if you think annotating helps make the code clearer, then add one.

イニシャライザ式が(それが何であれ)十分に明確であると思われる場合は、アノテーションを省略できます。 ただし、アノテーションを付けるとコードが明確になると思われる場合は、アノテーションを追加してください。

 

When in doubt, add a type annotation. Even when a type is obvious, you may still wish to explicitly annotate.

迷ったらタイプアノテーションしてください。仮に型が明確でもあなたは明示的にタイプアノテーションしたいと思うかもしれません。

 

If the inferred type relies on values or declarations from other libraries, you may want to type annotate your declaration so that a change to that other library doesn’t silently change the type of your own API without you realizing.

推論された型が他のライブラリの値や宣言に依存している場合、他のライブラリへの変更によって気づかないうちに自分のAPIの型が変更されないように、宣言に型アノテーションを付けるとよいでしょう。

 

This rule applies to both public and private declarations. Just as type annotations on APIs help users of your code, types on private members help maintainers.

このルールは、パブリック宣言とプライベート宣言の両方に適用されます。 APIの型アノテーションがコードのユーザーを支援するのと同様に、プライベートメンバーの型はメンテナを支援します。


DON’T redundantly type annotate initialized local variables.

初期化されているローカル変数を冗長にタイプアノテーションするのはやめましょう。

Linter rule: omit_local_variable_types

 


DO annotate return types on function declarations.

関数宣言の返り値の型はタイプアノテーションしてください。

Dart doesn’t generally infer the return type of a function declaration from its body, unlike some other languages. That means you should write a type annotation for the return type yourself.

他のいくつかの言語とは違い、Dartは一般的に関数宣言のボディから返り値の型を型推測しません。これはあなた自身が返り値の型を書く必要があることを意味します。

//good
String makeGreeting(String who) {
  return 'Hello, $who!';
}

 

//bad
makeGreeting(String who) {
  return 'Hello, $who!';
}

Note that this guideline only applies to named function declarations: top-level functions, methods, and local functions. Anonymous function expressions infer a return type from their body. In fact, the syntax doesn’t even allow a return type annotation.

このガイドラインはトップレベル関数、メソッド、ローカルの関数などの名前付き関数に関するものであることに注意してください。無名関数は返り値の型をボディから推測します。実際、返り値の型をアノテーションすることはできません。


DO annotate parameter types on function declarations.

関数宣言の引数の型はタイプアノテーションしてください。

A function’s parameter list determines its boundary to the outside world. Annotating parameter types makes that boundary well defined.

関数のパラメーターリストは外界(関数の外)との間の境界を決定します。引数の型をアノテーションすれば、その境界を適切に定義することができます。

 

Note that even though default parameter values look like variable initializers, Dart doesn’t infer an optional parameter’s type from its default value.

たとえ引数のデフォルト値が引数のイニシャライザのように見えても、Dartでは、デフォルト値によってオプショナル引数の型推測はされませんので注意してください。

//good
void sayRepeatedly(String message, {int count = 2}) {
  for (var i = 0; i < count; i++) {
    print(message);
  }
}

 

//bad
void sayRepeatedly(message, {count = 2}) {
  for (var i = 0; i < count; i++) {
    print(message);
  }
}

Exception: Function expressions and initializing formals have different type annotation conventions, as described in the next two guidelines.

例外 : 下記の二つのガイドラインに示す通り、関数式とイニシャライジングフォーマルに関しては別の慣習があります。


DON’T annotate inferred parameter types on function expressions.

関数式の推測された引数の型はタイプアノテーションしないでください。

Linter rule: avoid_types_on_closure_parameters

Anonymous functions are almost always immediately passed to a method taking a callback of some type.

無名関数はほとんどの場合コールバックを引数に取るメソッド即座にに渡されます。

 

 

If the language is able to infer the type you want for a parameter in a function expression, then don’t annotate.

もし関数式の引数の型がDart言語によって推測されるのであれば、あなた自身がアノテーションするのはやめてください。

 

In rare cases, the surrounding context isn’t precise enough to provide a type for one or more of the function’s parameters. In those cases, you may need to annotate.

稀なケースとして、周りの状況から関数の引数の型を推測するための情報が(周囲に)十分に無いは場合には、あなたが自分でアノテーションしないといけません。

 

(If the function isn’t used immediately, it’s usually better to make it a named declaration.)

(もし関数が即座に使われないのであれば、通常は名前をつけて関数を定義した方が良いでしょう。)


DON’T type annotate initializing formals.

イニシャライジングフォーマルをタイプアノテーションしないでください。

Linter rule: type_init_formals

If a constructor parameter is using this. to initialize a field, then the type of the parameter is inferred to have the same type as the field.

イニシャライジングフォーマルを使用している場合、フィールドと同じ型で型推測されますので、タイプアノテーションする必要はありません。

//good
class Point {
  double x, y;
  Point(this.x, this.y);
}

 

//bad
class Point {
  double x, y;
  Point(double this.x, double this.y);
}

 


DO write type arguments on generic invocations that aren’t inferred.

型推測されないgeneric invocationには型引数を書いてください。

Dart is pretty smart about inferring type arguments in generic invocations. It looks at the expected type where the expression occurs and the types of values being passed to the invocation.

Dartは、一般的な呼び出しで型引数を推測することについて非常に賢いです。 式が現れる場所の予想されるタイプと、呼び出しに渡される値のタイプを調べます。

 

However, sometimes those aren’t enough to fully determine a type argument. In that case, write the entire type argument list explicitly.

ただし、型引数を完全に決定するには、これらでは不十分な場合があります。 その場合は、型引数リスト全体を明示的に記述してください。

その場合というか、普通に、型引数がある場合は型引数を指定すれば済む話なので、少なくとも一箇所はどこかに型引数を指定しましょう。

//good

var playerScores = <String, int>{};
final events = StreamController<Event>();

 

//bad
var playerScores = {};
final events = StreamController();

Sometimes the invocation occurs as the initializer to a variable declaration.

変数宣言の初期化としてgeneric invocationが行われる時もあります。

 

If the variable is not local, then instead of writing the type argument list on the invocation itself, you may put a type annotation on the declaration:

ローカル変数でない場合は、generic invocation自体に型引数リストを書き込む代わりに、宣言に型注釈(タイプアノテーション)を付けることができます。

//good
class Downloader {
  final Completer<String> response = Completer();
}

 

//bad
class Downloader {
  final response = Completer();
}

Annotating the variable also addresses this guideline because now the type arguments are inferred.

型引数が推測されますので、タイプアノテーションもこのガイドラインの内容に適合するものです。

左辺のCompleter<String>というタイプアノテーションによって、右辺のgeneric invocationの型引数もString型と型推測されるので、このガイドラインの内容に適合するものである、という話。


DON’T write type arguments on generic invocations that are inferred.

型推測されるgeneric invocationには型引数を書かないでください。

This is the converse of the previous rule. If an invocation’s type argument list is correctly inferred with the types you want, then omit the types and let Dart do the work for you.

これは一つ前のルールの反対です。もしgeneric invocationの型引数があなたの期待する型として正しく推測されるのなら、型引数を省略してDartに推測させましょう。

//good
class Downloader {
  final Completer<String> response = Completer();
}

 

//bad
class Downloader {
  final Completer<String> response = Completer<String>();
}

Here, the type annotation on the field provides a surrounding context to infer the type argument of constructor call in the initializer.

ここ(goodサンプル)では、フィールドの型注釈(左辺のCompleter<String>)は、初期化子のコンストラクター呼び出し(右辺のCompleter())の型引数を推測するための周囲のコンテキストを提供します。


//good
var items = Future.value([1, 2, 3]);

 

//bad
var items = Future<List<int>>.value(<int>[1, 2, 3]);

Here, the types of the collection and instance can be inferred bottom-up from their elements and arguments.

ここで、コレクションとインスタンスの型は、それらの要素と引数からボトムアップで推測できます。

渡されている[1,2,3]によってvalueメソッドの引数の型はList<int>と推測されるし、それによってFutureクラスの型引数もList<int>と推測されるので、プログラマーが書かなくても問題無い。


AVOID writing incomplete generic types.

不完全なgeneric型を書くのは避けましょう。

The goal of writing a type annotation or type argument is to pin down a complete type. However, if you write the name of a generic type but omit its type arguments, you haven’t fully specified the type. In Java, these are called “raw types”. For example:

型注釈または型引数を作成する目的は、完全な型を特定することです。 ただし、ジェネリック型の名前を記述し、その型引数を省略した場合は、型を完全に指定していません。 Javaでは、これらは「raw types」と呼ばれます。 例えば:

//bad
List numbers = [1, 2, 3];
var completer = Completer<Map>();

Here, numbers has a type annotation, but the annotation doesn’t provide a type argument to the generic List. Likewise, the Map type argument to Completer isn’t fully specified.

ここで、numbersはタイプアノテーションされていますが、Listの型引数を指定していません。同様にCompleterの型引数Mapの型引数も指定されていません。

 

In cases like this, Dart will not try to “fill in” the rest of the type for you using the surrounding context. Instead, it silently fills in any missing type arguments with dynamic (or the bound if the class has one). That’s rarely what you want.

このようなケースでDartは周りのコンテキストから指定されていない型を「埋める」ようなことはせず、代わりに暗黙的にdynamic型とします。これはほとんどの場合あなたの期待する挙動ではないでしょう。

 

Instead, if you’re writing a generic type either in a type annotation or as a type argument inside some invocation, make sure to write a complete type:

代わりに、ジェネリック型を「型アノテーション」または「generic invocation内の型引数」として記述している場合は、必ず完全な型を記述してください。

//good
List<num> numbers = [1, 2, 3];
var completer = Completer<Map<String, int>>();

 


DO annotate with dynamic instead of letting inference fail.

型推測を失敗させる代わりにdynamic型でタイプアノテーションしてください。

When inference doesn’t fill in a type, it usually defaults to dynamic. If dynamic is the type you want, this is technically the most terse way to get it. However, it’s not the most clear way.

型推測で型が推測されなかった場合、通常dynamic型とされます。もしdynamic型があなたの望む型なら、それが記述的には最も簡潔な書き方ということになります。しかし、それは最もわかりやすい書き方ではありません。

 

A casual reader of your code who sees that an annotation is missing has no way of knowing if you intended it to be dynamic, expected inference to fill in some other type, or simply forgot to write the annotation.

アノテーションが無い状況でコードの読者は、

=> 書き手はdynamic型を意図してコードを書いたのか、

=> 別の型として推測されることを期待してコードを書いたのか、

=> ただ単にアノテーションを忘れているのか、

どれかを確認する術がありません。

 

When dynamic is the type you want, write that explicitly to make your intent clear and highlight that this code has less static safety.

dynamic型を意図してコードを書いているのなら、それを明示的に記述して、あなたの意図をはっきりさせ、さらにそのコードが静的な安全性の低いコードであることを強調しましょう。

//good
dynamic mergeJson(dynamic original, dynamic changes) => ...

 

//bad
mergeJson(original, changes) => ...

 

 

Note that it’s OK to omit the type when Dart successfully infers dynamic.

Dartが型推測の結果dynamic型であると推測できる箇所でタイプアノテーションを省略することは問題ありません。

//good
Map<String, dynamic> readJson() => ...

void printUsers() {
  var json = readJson();
  var users = json['users'];
  print(users);
}

Here, Dart infers Map<String, dynamic> for json and then from that infers dynamic for users. It’s fine to leave users without a type annotation.

ここでDartは変数jsonの型をMap<String, dynamic>と推測し、その結果変数usersはdynamic型と推測されます。ですからusersのタイプアノテーションが無くても問題ありません。

 

The distinction is a little subtle. It’s OK to allow inference to propagate dynamicthrough your code from a dynamic type annotation somewhere else, but you don’t want it to inject a dynamic type annotation in a place where your code did not specify one.

区別は少し微妙です。 dynamic型の(あなたが行った)型アノテーションからコード全体にdynamic型が型推論を通して伝播することは問題ありませんが、コードでdynamicの型アノテーションが指定されていない場所にdynamic型アノテーションを挿入することは望ましくありません。

挿入する = 型アノテーションが無いことで行われる暗黙的なdyanmic型での推測

 

Before Dart 2, this guideline stated the exact opposite: don’t annotate with dynamic when it is implicit. With the new stronger type system and type inference, users now expect Dart to behave like an inferred statically-typed language. With that mental model, it is an unpleasant surprise to discover that a region of code has silently lost all of the safety and performance of static types.

Dart2より前は、このガイドラインは正反対でした。つまり、

暗黙的な場合はdynamicで型宣言してはいけない、

でした。

新しい強力な型システムと型推測により、現在ユーザーはDartを推測による静的型付け言語として期待します。このメンタルモデルでは、コードがすべての静的型付けの安全性とパフォーマンスを手放すことは望ましいことではありません。

 


PREFER signatures in function type annotations.

The identifier Function by itself without any return type or parameter signature refers to the special Function type.

特に返り値の型も引数の型も指定していないFunctionという識別子は、特別なFunction型を参照します。

 

This type is only marginally more useful than using dynamic. If you’re going to annotate, prefer a full function type that includes the parameters and return type of the function.

このタイプは、dynamic型を使用するよりもわずに便利です。 タイプアノテーションする場合は、関数のパラメーターと戻り型を含む完全な関数型を選択してください。

//good
bool isValid(String value, bool Function(String) test) => ...

 

//bad
bool isValid(String value, Function test) => ...

 

Exception: Sometimes, you want a type that represents the union of multiple different function types. For example, you may accept a function that takes one parameter or a function that takes two. Since we don’t have union types, there’s no way to precisely type that and you’d normally have to use dynamic. Function is at least a little more helpful than that:

例外:複数の異なるFunction型の和集合を表す型が必要な場合があります。 たとえば、「1つのパラメーターを受け取る関数または2つのパラメーターを受け取る関数」を引数として場合受け入れる。 共用体タイプがないため、正確に型を指定する方法はなく、通常はdynamic型を使用する必要があります。 Functionはそれよりも少なくとももう少し役に立ちます。

//good
void handleError(void Function() operation, Function errorHandler) {
  try {
    operation();
  } catch (err, stack) {
    if (errorHandler is Function(Object)) {
      errorHandler(err);
    } else if (errorHandler is Function(Object, StackTrace)) {
      errorHandler(err, stack);
    } else {
      throw ArgumentError('errorHandler has wrong signature.');
    }
  }
}

 


DON’T specify a return type for a setter.

セッターの返り値の型を指定しないでください。

Linter rule: avoid_return_types_on_setters

Setters always return void in Dart. Writing the word is pointless.

Dartでセッターは常にvoidを返します。型指定しても意味がありません。

//bad
void set foo(Foo value) { ... }

 

//good
set foo(Foo value) { ... }

 


DON’T use the legacy typedef syntax.

レガシーなtypedefシンタックスを使わないでください。

工事中🏗

 

 

The new syntax looks like this:

新しいシンタックスは下記の通りです。

//good
typedef Comparison<T> = int Function(T, T);

If you want to include a parameter’s name, you can do that too:

引数名も含めたい場合下記のように書くこともできます。

//good
typedef Comparison<T> = int Function(T a, T b);

The new syntax can express anything the old syntax could express and more, and lacks the error-prone misfeature where a single identifier is treated as the parameter’s name instead of its type.

新しいシンタックスでは古いシンタックスの機能は全て有した上で、古いもののデメリットは無くなりました。typedefの=の後の同じ関数型シンタックスは、型注釈が表示される可能性のあるすべての場所でも許可され、プログラム内の任意の場所に関数型を記述する単一の一貫した方法を提供します

 

The old typedef syntax is still supported to avoid breaking existing code, but it’s deprecated.

古いtypedefシンタックスは既存のコードのためにまだサポートしていますが、非推奨です。

 

 


PREFER inline function types over typedefs.

typedefよりもインライン関数タイプを優先しましょう

 

Linter rule: avoid_private_typedef_functions

In Dart 1, if you wanted to use a function type for a field, variable, or generic type argument, you had to first define a typedef for it. Dart 2 supports a function type syntax that can be used anywhere a type annotation is allowed:

Dart 1では、フィールド、変数、またはジェネリック型の引数に関数型を使用する場合は、最初にtypedeを定義する必要がありました。 Dart 2は、型注釈が許可されている場所ならどこでも使用できる関数型シンタックスをサポートしています。

 

It may still be worth defining a typedef if the function type is particularly long or frequently used. But in most cases, users want to see what the function type actually is right where it’s used, and the function type syntax gives them that clarity.

関数型が特に長い場合や頻繁に使用される場合は、typedef を定義する価値があるかもしれません。しかし、ほとんどの場合、ユーザーはその関数が実際にどのようなものであるかを、それが使用される場所で確認したいと思うものです。

 


PREFER using function type syntax for parameters.

Linter rule: use_function_type_syntax_for_parameters

Dart has a special syntax when defining a parameter whose type is a function. Sort of like in C, you surround the parameter’s name with the function’s return type and parameter signature:

Dartには、型が関数であるパラメーターを定義するときに特別な構文があります。 Cのように、パラメータの名前を関数の戻り値の型とパラメータのシグネチャで囲みます(下記の一つ目のサンプル)。

//Dart2より前の唯一の方法。
Iterable<T> where(bool predicate(T element)) => ...

 

Before Dart 2 added function type syntax, this was the only way to give a parameter a function type without defining a typedef. Now that Dart has a general notation for function types, you can use it for function-typed parameters as well:

上記がDart2よりも前の、typedefを定義せずに引数に関数型をアノテーションする唯一の方法でした。現在はDartは関数型の一般的なノーテーションを持っているので、それを関数型引数にも使うことができます。(下記のgoodサンプル)

//good(このガイドラインで推奨されている方法)
Iterable<T> where(bool Function(T) predicate) => ...

The new syntax is a little more verbose, but is consistent with other locations where you must use the new syntax.

このシンタックスの方が少し冗長かもしれませんが、Dart言語全体としての統一感は高まるのでこちらの方法を推奨します。


AVOID using dynamic unless you want to disable static checking.

静的な型チェックを無効化したい場合を除いて、dynamic型を使うのを避けましょう。

 


DO use Future<void> as the return type of asynchronous members that do not produce values.

値を生み出さない非同期なメンバーの返り値にはFuture<void>型を使ってください。

 


AVOID using FutureOr<T> as a return type.

返り値としてFutureOr<T>型を使うのは避けましょう。

 




Parameters

In Dart, optional parameters can be either positional or named, but not both.

Dartではオプショナルな引数はポジショナルパラメータか、名前付きパラメータのどちらかです。一つの関数で両方同時に使うことはできません。


AVOID positional boolean parameters.

bool型のポジショナル引数は避けましょう。

Linter rule: avoid_positional_boolean_parameters

Unlike other types, booleans are usually used in literal form. Values like numbers are usually wrapped in named constants, but we typically pass around true and false directly.

他の型と違い、bool型は通常リテラルの形で使われます。数値などの値は通常、名前付き定数でラップされますが、通常、trueとfalseを直接渡します。

 

That can make call sites unreadable if it isn’t clear what the boolean represents:

bool値が何を表しているのかはっきりしないと、呼び出し箇所が読みにくく(わかりにくく)なる可能性があります。

 

//bad
new Task(true);
new Task(false);
new ListBox(false, true, true);
new Button(false);

 

Instead, prefer using named arguments, named constructors, or named constants to clarify what the call is doing.

代わりに名前付き引数、名前付きコンストラクタ、あるいは定数を使って、その呼び出しが何をしているのかをはっきりさせましょう。

//good
Task.oneShot(); //名前付きコンストラクタの方がわかりやすい。
Task.repeating();//名前付きコンストラクタの方がわかりやすい。

//↓名前付き引数の方がわかりやすい。
ListBox(scroll: true, showScrollbars: true);

//定数を渡すようにする方がわかりやすい。
Button(ButtonState.enabled);

 

Note that this doesn’t apply to setters, where the name makes it clear what the value represents:

セッターの場合はセッター名が何の値なのかをはっきり表すので問題にはなりません。

//good
listBox.canScroll = true;
button.isEnabled = false;

 


AVOID optional positional parameters if the user may want to omit earlier parameters.

ユーザーが早いほう(左側、あるいは上側)の引数を省略したい可能性がある時は、オプショナルのポジショナル引数は避けましょう。

Optional positional parameters should have a logical progression such that earlier parameters are passed more often than later ones.

オプショナルポジショナルパラメータは早い方の引数(最初の方の引数、左側にある引数、上側にある引数)が遅い方の引数(最後の方の引数、右側にある引数、下側にある引数)よりも頻繁に渡されるように定義されるべきです。

 

Users should almost never need to explicitly pass a “hole” to omit an earlier positional argumenOptional positional parameterst to pass later one. You’re better off using named arguments for that.

ユーザーは、後の引数を渡すために前の位置引数を省略するために、明示的に「穴」を渡す必要はほとんどありません。 それよりも名前付き引数を使用する方がよいでしょう。

//good
String.fromCharCodes(Iterable<int> charCodes, [int start = 0, int? end]);

DateTime(int year,
    [int month = 1,
    int day = 1,
    int hour = 0,
    int minute = 0,
    int second = 0,
    int millisecond = 0,
    int microsecond = 0]);

Duration(
    {int days = 0,
    int hours = 0,
    int minutes = 0,
    int seconds = 0,
    int milliseconds = 0,
    int microseconds = 0});

まず”Optional positional parameters”のように書くと、「Non-optional positional parametersもあるのか?」と思う人がいるんじゃないか、と思ってしまいますが、そんな人はいないんでしょうか笑

Non-optional positional parametersはありません。

positional parameter自体がオプショナルな引数なのでpositional parameterは必ずOptional positional parametersですね。

詳しくはこちらをどうぞ。

どうでもいい話ですが、私自身実際Flutterでアプリを作っている時はポジショナル引数はほとんど使わないので、使い方もうろ覚えになってしまいました。

なのでこのガイドラインを最初に見たときは全くピンと来なかったのですが、調べてみると下記のような話になるようです。一応間違いないと思うのですが、間違いがある場合ご指摘いただけると助かります笑

 

positional parameterが複数ある場合、「あるポジショナル引数を渡した場合、それよりも前のポジショナル引数は必ず渡す必要がある(それよりも前のポジショナル引数を渡さない、ということはできない)」という性質があるようです。(nullable型にしてもnullを渡す必要がある、「渡さない」ということはできない。)

そういう意味で完全なオプショナル引数とは言えず、いわば、「条件付きオプショナル引数」と言えるのではないでしょうか。(一番最後のポジショナル引数のみ完全なオプショナル引数と言える。)

ということでこのガイドラインの説明の通り、あるポジショナル引数を渡した場合、それよりも前のポジショナル引数を省略したくても「渡さない」ということはできないので、何らかの省略を意味する値(普通はintなら0、Stringなら空文字、場合によってはnull、ということになるんでしょうか、ただケースバイケースで別の値にもなり得るような気がします)を渡す必要があります。

それを”hole”と表現しているのだと思います。

そうすればエラーは出なくなるのですが、それよりも名前付き引数にした方がユーザーにとってもハッピーでしょうから、そうしましょう、というルール。

ルールというか、親切で言ってくれているような感じでしょうか。


AVOID mandatory parameters that accept a special “no argument” value.

If the user is logically omitting a parameter, prefer letting them actually omit it by making the parameter optional instead of forcing them to pass null, an empty string, or some other special value that means “did not pass”.

ユーザーが論理的に引数を省略する(したい)ときは、「null、空文字、あるいは他の「具体的な値を渡さないこと」を表す特別な値を渡すことを強制される」よりも「引数がオプショナルなので実際に省略できること」を好みます。

 

Omitting the parameter is more terse and helps prevent bugs where a sentinel value like null is accidentally passed when the user thought they were providing a real value.

引数を省略できる方がより簡潔ですし、間違ってnullを渡した時にエラーが発生するようなバグも防げます。

//good
var rest = string.substring(start);

 

//bad
var rest = string.substring(start, null);

 


DO use inclusive start and exclusive end parameters to accept a range.

範囲(range)を受け取る引数は「startはその値を含む、endはその値を含まない」引数を使ってください。

If you are defining a method or function that lets a user select a range of elements or items from some integer-indexed sequence, take a start index, which refers to the first item and a (likely optional) end index which is one greater than the index of the last item.

もし、整数値で構成された連続値の中から、ユーザーが要素や項目を選択できるようなメソッドや関数を定義する場合は、最初の項目を指す開始インデックスと、最後の項目のインデックスより一つ大きい終了インデックス(おそらくオプション)を指定する方法を定義してください。

 

This is consistent with core libraries that do the same thing.

コアライブラリの慣例なので同じようにしましょう。

//good
[0, 1, 2, 3].sublist(1, 3) // [1, 2]
'abcd'.substring(1, 3) // 'bc'

It’s particularly important to be consistent here because these parameters are usually unnamed. If your API takes a length instead of an end point, the difference won’t be visible at all at the call site.

これらのパラメータは通常無名なので、ここで一貫性を持たせることが特に重要である。もしあなたのAPI(関数)がエンドポイントではなく長さを引数として受け取るように定義した

場合、その違いは呼び出し側では全くわかりません。(エンドポイントの指定なのか長さの指定なのか、必ず定義を確認しないといけなくなる)

(使用する側は定義(使い方)を確認する必要はあるとは思いますが、慣例通りの方がユーザーフレンドリーと言えるでしょう。)




Equality

Implementing custom equality behavior for a class can be tricky. Users have deep intuition about how equality works that your objects need to match, and collection types like hash tables have subtle contracts that they expect elements to follow.

クラスに対して独自(カスタム)の等値性を実装することは時に手の込んだ仕事になることがあります。

ユーザーは、オブジェクトが一致する必要のある同等性がどのように機能するかについて深い直感を持っており、ハッシュテーブルなどのコレクションタイプには、要素が従うことを期待する微妙なコントラクト(決まり)があります。


DO override hashCode if you override ==.

=をオーバーライドするならもhashCodeオーバーライドしてください。

Linter rule: hash_and_equals

The default hash code implementation provides an identity hash—two objects generally only have the same hash code if they are the exact same object.

デフォルトのhash codeの実装は、identity hashを提供します。つまり、二つのオブジェクトは、それらが完全に同一のオブジェクトである場合に同じhash codeを持つことになります。

 

Likewise, the default behavior for == is identity.

同様にデフォルトの==演算子の挙動もidentityに基づきます。

 

If you are overriding ==, it implies you may have different objects that are considered “equal” by your class.

もしあなたが==演算子をオーバーライドすると、あなたのクラスにより、別のオブジェクトを「等しい」とみなす、ということになります。

 

Any two objects that are equal must have the same hash code.

等しい2つのオブジェクトは、同じハッシュコードを持っている必要があります。

==演算子を使った結果trueが返ってくるような二つのオブジェクトは、同じハッシュコードを持っている必要があります。

The keys of a `LinkedHashMap` must have consistent [Object.==] and [Object.hashCode] implementations. This means that the `==` operator must define a stable equivalence relation on the keys (reflexive,symmetric, transitive, and consistent over time), and that `hashCode` must be the same for objects that are considered equal by `==`.

↑(linked_hash_map.dart)

Otherwise, maps and other hash-based collections will fail to recognize that the two objects are equivalent.

そうでないと、Mapや他のハッシュベースのコレクションは、2つのオブジェクトが等価であることを認識できなくなります。

Map型などで、上記が守られていない型をkeyとして使うと、そのMapなどの挙動がおかしくなる可能性がある、ということだと思われる。


DO make your == operator obey the mathematical rules of equality.

==演算子を等値性の数学的規則に従わせてください。

 


AVOID defining custom equality for mutable classes.

mutableなクラスにカスタムの等値性を定義するのは避けましょう。

Linter rule: avoid_equals_and_hash_code_on_mutable_classes

 


DON’T make the parameter to == nullable.

Linter rule: avoid_null_checks_in_equality_operators

==の引数をnullableにしないでください。

The language specifies that null is equal only to itself, and that the == method is called only if the right-hand side is not null.

Dart言語はnullを自分自身とのみ等しくなるように定義しており、==メソッドは右辺がnullでない場合のみ呼び出されます。

 

 

参考

design

カテゴリーDart

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です