🙄

【Flutter】Effective Dartから頻繁に意識するガイドをピックアップ

に公開

はじめに

皆さん、こんにちは。

Flutterでアプリ開発を行う際、使用言語である Dart の効果的な書き方を知っているかどうかで、コードの品質や保守性は大きく変わります。そのための公式ガイドが Effective Dartです。

今回はよく使うガイドをピックアップしたので、かいつまんで参考にしてください。

https://dart.dev/effective-dart

Effective Dartの構成

要点

  • 個別のガイドは5つの接頭語により開始される
    • DO: 必須
    • DON'T: 禁止
    • PREFER: 推奨
    • AVOID: 非推奨
    • CONSIDER: 要検討

Effective Dartは、ただの「おしゃれなコードの書き方」集ではありません。Dartのコードを読みやすくバグが少なく、そしてチームの誰もが理解しやすいようにするためのガイドを提供するものです。

ガイドラインの中には「絶対に守るべきこと」もあれば、「できればそうした方が良いこと」もあります。

ガイドラインの「重み」を示す5つの接頭語(プレフィックス)

個別のガイドラインは、そのルールがどれだけ重要かを示すために、必ず5つの接頭語(プレフィックス) のいずれかから始まります。

接頭語 意味する重要度 比喩
DO 最も高い(必須) 法律、絶対的な命令
DON'T 非常に高い(禁止) 法律で禁止されている行為
PREFER 中程度(推奨) ベストプラクティス、推奨ルート
AVOID 中程度(非推奨) 避けるべき悪習、回り道
CONSIDER 最も低い(検討) 提案、個人の裁量に委ねる助言

1. DO:常に従うべき最も重要な実践方法

これは「常に従うべき、絶対的なルール」です。まるで法律のように、これに反する正当な理由はほとんどありません。

例えば、「DO use lowercase with underscores for named parameters.(名前付き引数にはアンダースコア付きの小文字を使用する)」といったものです。

コードの一貫性(統一感)を保つために最も高い重要度を持ちます。

2. DON'T:ほとんどの場合、避けるべき良くないアイデア

これは「ほとんどの場合、避けるべきこと」つまり禁止事項です。

そのままではバグの原因になったり、コードが読みにくくなったりする可能性が非常に高いため、非常に高い重要度で避けなければなりません。

3. PREFER:従うことが望ましいが、例外もあり得る実践方法

これは「推奨される実践方法」です。普段はこれに従うべきですが、「そうしない方が理にかなっている状況」がまれに存在する場合もあります。

重要度は中程度ですが、このガイドラインを無視する場合は、その決定がコードに与える影響を完全に理解していることが必要になります。

4. AVOID:「PREFER」の逆で、避けることが望ましいこと

これは「PREFER」の対にあたるもので、「すべきではないが、まれに正当な理由がある」ことです。

基本的にはしない方が良い行為や書き方ですが、まれに例外的な理由があれば許容されます。重要度は「PREFER」と同じ中程度です。

5. CONSIDER:状況や好みに応じて従うか検討する実践方法

これは「検討する」レベルの提案です。状況やチームの前例、個人の好みによって、従うかどうかを検討します。

重要度は最も低いため、柔軟性があり、絶対的な強制力はありません。

1. Style Guide(スタイルガイド)

要点

  • コードのレイアウトと構成に関するガイド
  • 次の項目で記載されている
    • 識別子 (Identifiers)
    • 順序 (Ordering)
    • 書式設定 (Formatting)
  • 利用頻度の高いガイドをピックアップ
    • クラスなど型はUpperCamelCase
    • ファイルやフォルダはlowercase_with_underscores
    • 変数などその他はlowerCamelCase
    • 3文字以上の略語は大文字で始める
    • 未使用の引数には_を使用する

コードのレイアウトと構成に関するガイド

Style Guideの目的は「誰が見ても読みやすく、理解しやすい一貫性(統一感)のあるコード」を実現することです。

次の項目で記載されている

主にコードのレイアウトと構成、そして「名前の付け方」に関するルールを定めています。具体的には、次の3つの視点から整理されています。

  1. 識別子 (Identifiers): クラス、変数、関数などの「名前の付け方」のルール。
  2. 順序 (Ordering): クラス内の要素(変数、メソッドなど)を「どの順番で書くか」のルール。
  3. 書式設定 (Formatting): インデント(字下げ)や改行、行の最大文字数といった「見た目の整え方」のルール。

利用頻度の高いガイドをピックアップ

Style Guideの中でも、特に「識別子(名前の付け方)」に関する以下のルールは、Dartコードを書く上で毎日使う非常に重要な作法です。

1. クラスや型は UpperCamelCase を使用する

クラスや列挙型(enum)、型定義など、「型」を表す名前には、UpperCamelCase(アッパー・キャメルケース)を使います。これは、単語の区切りを大文字で表現し、先頭も大文字にする記法です。

  • 良い例: class MyFlutterApp
  • 悪い例: my_flutter_app

2. ファイルやフォルダは lowercase_with_underscores を使用する

ファイル名やディレクトリ(フォルダ)名には、lowercase_with_underscores(全て小文字で、単語間をアンダースコア _ でつなぐ)を使います。これにより、ファイルシステム上での互換性が高まり、ファイルを探しやすくなります。

  • 良い例: user_profile_screen.dart
  • 悪い例: UserProfileScreen.dart

3. 変数や関数などその他は lowerCamelCase を使用する

ほとんどの変数、関数、メソッド、名前付き引数など、「型」以外の要素には、lowerCamelCase(ローワー・キャメルケース)を使います。これは、単語の区切りを大文字で表現しますが、先頭は小文字にする記法です。

  • 良い例: calculateTotalAmount
  • 悪い例: calculate_total_amount

4. 3文字以上の略語は全体を大文字で始め、大文字で区切る

「HTML」「URL」のような3文字以上の略語(頭文字)を使う場合、略語全体を大文字で始め、次の単語を大文字で区切ります。

  • 良い例: HttpRequest, UrlConverter
  • 悪い例: HTTPRequest, URLConverter

5. 未使用の引数には _ を使用する

メソッド(関数)の引数リストの中に、コード内で一切使用しない引数がある場合、その引数名にアンダースコア (_) を使用します。これにより、コードの読み手に対して「この引数は意図的に使っていませんよ」というメッセージを明確に伝えることができます。

例: void listener(Object sender, String _message) { ... }

参考

https://dart.dev/effective-dart/style

AIによる整理

次の形式でAIにまとめてもらった内容です。

- セクション
	- 接頭語
		- ガイド
  • Identifiers (識別子)
    • DO
      • 型をUpperCamelCaseを使用して命名する。
      • 拡張機能(extensions)をUpperCamelCaseを使用して命名する。
      • パッケージ、ディレクトリ、およびソースファイルをlowercase_with_underscoresを使用して命名する。
      • インポートのプレフィックスをlowercase_with_underscoresを使用して命名する。
      • その他の識別子をlowerCamelCaseを使用して命名する。
      • 3文字より長い頭字語や略語を単語のように大文字で始める。
    • PREFER
      • 定数名にlowerCamelCaseを使用することを推奨する。
      • 未使用のコールバックパラメータにワイルドカードを使用することを推奨する。
    • DON'T
      • プライベートではない識別子に、先頭のアンダースコアを使用しない。
      • プレフィックス文字を使用しない。
      • ライブラリ名を明示的に指定しない。
  • Ordering (順序)
    • DO
      • dart:インポートを他のインポートの前に配置する。
      • package:インポートを相対インポートの前に配置する。
      • すべてのインポートの後に、エクスポートを個別のセクションで指定する。
      • セクションをアルファベット順にソートする。
  • Formatting (書式設定)
    • DO
      • dart formatを使用してコードを整形する。
      • すべてのフロー制御ステートメントに波括弧({})を使用する。
    • PREFER
      • 行の長さを80文字以下にすることを推奨する。
    • CONSIDER
      • コードをフォーマッタが使いやすいように変更することを検討する。

2. Documentation Guide(ドキュメンテーションガイド)

要点

  • コメント内に何を含めるべきかに関するガイド
  • 次の項目で記載されている
    • Comments (一般的なコメント)
    • Doc comments (ドキュメンテーションコメント)
    • Markdown (マークダウン)
    • Writing (記述法)
  • 利用頻度の高いガイドをピックアップ
    • Docコメントは一文の要約で記述する
    • 複数文章で説明するには先頭の一文のあとに空行を入れる
    • 関連機能は[ 関数名など ]で参照する
    • 引数などの説明は文章にする(@paramなどは使わない)
    • マークダウンが利用可能だが、過度に利用しない
  • DocコメントはVSCodeの補完時にTips表示される。つまり呼び出す側に対して仕様を伝えるドキュメントとしてのコメント。

コメント内に何を含めるべきかに関するガイド

Documentation Guideの目的は、「コードが何をするのか、どう使うべきか」を、コードを読まなくても理解できるようにすることです。

Dartでは、クラスや関数の直前に書かれた特別なコメント(Docコメント)が、単なるメモで終わらない、非常に重要な役割を果たします。

VSCodeなどの開発ツールでFlutterコードを書いているとします。関数名にマウスカーソルを合わせた瞬間、小さなポップアップ(Tips)が表示され、「この関数は何をして、どんな値が必要か」という説明が出てきますよね。このポップアップに表示されるのが、まさにDocコメントの内容です。

Docコメントが充実していると、開発者はコードを追うことなく、その場で関数の使い方を理解できます。

次の項目で記載されている

ドキュメントの品質の統一され、誰が書いても分かりやすい説明書きの観点で次の4つの項目から構成されています。

  • Comments (一般的なコメント): コードの途中に書く、自分やチームメンバーへのメモ書き。
  • Doc comments (ドキュメンテーションコメント): クラスや関数などの**「公開された機能」**に付ける、外部向けの公式な説明書き。
  • Markdown (マークダウン): ドキュメントを分かりやすく整形するために使用する簡易的な記法(太字やリストなど)。
  • Writing (記述法): 説明文をどのように、どのような言葉で書くかという文章作成のルール。

利用頻度の高いガイドをピックアップ

ここでは、特にDocコメントを書く際に役立つ、利用頻度の高いガイドラインを紹介します。

1. Docコメントは一文の要約で開始する

まず、Docコメントの先頭の一文は、その機能(クラスや関数など)が「何をするのか」を簡潔にまとめた「要約」として記述します。これは、ポップアップ表示などで最も目立つ、最も重要な情報です。

2. 複数文章で説明するには要約の後に空行を入れる

もし、要約だけでは説明しきれない場合は、先頭の一文(要約)のあとに空行を入れ、その後に詳細な説明を続けます。これにより、ドキュメント生成ツールが「どこまでが要約で、どこからが詳細か」を正しく判別できます。

3. 関連機能は [ 関数名など ] で参照する

ドキュメント内で、この機能と関連する別のクラスや関数について言及したい場合、その名前を角括弧 [ ] で囲みます。

例: 「この関数は、データを加工する前に [loadData] を呼び出す必要があります。」

4. 引数などの説明は文章にする(@paramなどは使わない)

他の多くのプログラミング言語にあるような、@param@return のような特別な記号を使った記述(タグ)は使用しません。代わりに、引数(パラメーター)や戻り値の説明も含めて自然な文章で書きます。

例: 「渡された [userId] を使用して、ユーザーのプロフィール情報をデータベースから取得します。」

5. Markdown(マークダウン)が利用可能だが、過度に利用しない

Docコメント内では、Markdownという簡易的な記法(例:太字にする **文字**、リストを作る -)を使って、説明文を分かりやすく整形できます。

ただし、過度に装飾しすぎず、本当に強調したい部分やリスト表示にしたい部分だけに留めることが推奨されています。

参考

https://dart.dev/effective-dart/documentation

AIによる整理

次の形式でAIにまとめてもらった内容です。

- セクション
	- 接頭語
		- ガイド
  • Comments (一般的なコメント)
    • DO
      • コメントは文のように整形する。
    • DON'T
      • ドキュメントのためにブロックコメントを使用しない。
  • Doc comments (ドキュメンテーションコメント)
    • DO
      • /// docコメントを使用してメンバーと型を文書化する。
      • docコメントを単一の文の要約で始める。
      • docコメントの最初の文を独立したパラグラフにする。
      • スコープ内の識別子を参照するためにdocコメント内で角括弧を使用する。
      • パラメータ、戻り値、例外を散文(自然な文章)で説明する。
      • メタデータアノテーションの前にdocコメントを配置する。
    • PREFER
      • 公開APIにはdocコメントを記述することを推奨する。
      • 主な目的が副作用である関数やメソッドのコメントを三人称動詞で始めることを推奨する。
      • 非ブール型の変数またはプロパティのコメントを名詞句で始めることを推奨する。
      • ブール型の変数またはプロパティのコメントを「Whether」に名詞または動名詞句を続けた形で始めることを推奨する。
      • 戻り値が主要な目的である関数やメソッドには、名詞句または非命令形の動詞句を推奨する。
      • ライブラリまたは型のコメントを名詞句で始めることを推奨する。
    • AVOID
      • 周囲の文脈との冗長性を避ける。
    • DON'T
      • プロパティのゲッターとセッターの両方にドキュメントを記述しない。
    • CONSIDER
      • ライブラリレベルのdocコメントを記述することを検討する。
      • プライベートAPIにもdocコメントを記述することを検討する。
      • docコメントにコードサンプルを含めることを検討する。
  • Markdown (マークダウン)
    • PREFER
      • コードブロックにはバッククォートのフェンスを推奨する。
    • AVOID
      • Markdownの過度な使用を避ける。
      • 書式設定にHTMLの使用を避ける。
  • Writing (記述法)
    • PREFER
      • 簡潔さを推奨する。
      • メンバーのインスタンスを参照する際は、「the」の代わりに「this」を使用することを推奨する。
    • AVOID
      • 略語や頭字語は、明白な場合を除き避ける。

3. Usage Guide(使用法ガイド)

要点

  • 言語仕様や挙動に沿った書き方のガイド
  • 次の項目で記載されている
    • ライブラリ (Libraries)
    • Null (Null)
    • 文字列 (Strings)
    • コレクション (Collections)
    • 関数 (Functions)
    • 変数 (Variables)
    • メンバー (Members)
    • コンストラクター (Constructors)
    • エラー処理 (Error handling)
    • 非同期処理 (Asynchrony)
  • 利用頻度の高いガイドをピックアップ
    • libフォルダ以下のインポートは相対インポートを利用する(packageインポートに比べて記述が短い)
    • libフォルダ外(ライブラリのインポートなど)はpackageインポートを利用する(混乱を招く)
    • null許容型の変数の初期化にnullを代入してはいけない
    • bool値の比較で == true のように記述してはいけない
    • ${式}で文字列に変数名を指定して値を埋め込む際は{}を省略する
    • コレクション内の要素の有無の確認に.lengthを利用しないで.isEmpty と .isNotEmptyを使う
      • 特に動的なコレクションは要素数の計算が.length を呼び出した瞬間に行われ時間がかかる
    • forEachではなくfor-inを使う
    • 関数名が必要な場合は関数宣言をする(関数式はコールバックなど名前が不要な場合に利用)
    • ローカル変数には型注釈はつけない
    • ローカル変数でのvar, finalの使い分けはどちらかのルール
      • 1:再代入するもののみvar、それ以外はfinal
      • 2:すべてvarで宣言、ローカル変数ではfinalは使わない
    • クラスのフィールドをそのまま返すgetterを作らない(フィールドの参照もgetterの利用も文法的な区別がないため)
    • 参照可能だが更新不可なフィールドはfinalキーワードで宣言
    • getter/setterは1行のみなら=>で記述する(複数行ならメソッドとする)
    • 外側の const は暗黙的に内側に伝搬するため冗長的に付与しない
    • futureの扱いはthenよりasync/awaitを優先する

言語仕様や挙動に沿った書き方のガイド

Usage Guideの役割は、Dartの言語仕様(文法や仕組み)に沿った、最も「Dartらしい」かつ「高性能」なコードの書き方を教えることです。

次の項目で記載されている

このガイドラインは、ライブラリの構成から、Null安全性(値がない状態を安全に扱う仕組み)、文字列の扱い、非同期処理(時間のかかる処理を待たずに進める仕組み)に至るまで、Dartプログラミングの全域をカバーしています。

  • ライブラリ (Libraries):ファイルのインポート方法など、コードの構造化。
  • Null (Null安全性)null の安全な扱い方。
  • 文字列 (Strings):文字列操作の効率化と可読性向上。
  • コレクション (Collections):リストやマップなど、データの集合体の扱い方。
  • 関数 (Functions):関数の宣言方法や利用シーン。
  • 変数 (Variables):変数の宣言キーワード(var, final)の使い分け。
  • メンバー (Members):クラス内のフィールドやメソッドの構成。
  • コンストラクター (Constructors):オブジェクト生成の作法。
  • エラー処理 (Error handling):例外的な事態への対応方法。
  • 非同期処理 (Asynchrony):時間のかかる処理の効率的な待ち合わせ方。

利用頻度の高いガイドをピックアップ

言語機能を最大限に活用して動作を実装する方法を教えます。ステートメントまたは式に関する内容がここでカバーされます。

1. libフォルダ以下のインポートは相対インポートを利用する(packageインポートに比べて記述が短い)

プロジェクト内のlibフォルダ配下にあるファイルをインポートする際は、import '../path/to/file.dart'; のような相対インポートを使います。

これは、package: 形式で記述するよりもコードが短く、ファイル間の位置関係**が把握しやすくなるためです。

2. libフォルダ外(ライブラリのインポートなど)はpackageインポートを利用する(混乱を招く)

Flutterや外部のライブラリをインポートする際は、必ず import 'package:flutter/material.dart'; のようなパッケージインポートを使います。

これにより、プロジェクトの内部ファイルと外部ライブラリとの区別が明確になり、コードの参照元に関する混乱を防ぎます。

3. Null許容型の変数の初期化にnullを代入してはいけない

Null許容型(例: String? name;)の変数を宣言する際、値を初期化せずに宣言すると、Dartは自動的に null で初期化します。

したがって、String? name = null; のように明示的に null を代入するのは冗長であり、避けるべきです。

4. bool値の比較で == true のように記述してはいけない

ブール値(trueまたはfalse)を保持する変数 flag の真偽を確認する際は、if (flag == true) のように冗長な比較はせず、if (flag)if (!flag) のように直接記述します。

5. 文字列展開($変数名)で波括弧 {} を省略する

文字列の中に変数の値を埋め込む(文字列展開)際、単一の変数名であれば、${変数名} のように波括弧 {} を使わずに、$変数名 の形式で記述します。これにより、コードが簡潔になります。

6. コレクション内の要素の有無の確認に.lengthを利用しないで.isEmpty.isNotEmpty を使う

リストやマップなどのコレクションが空かどうかを確認する際は、.length == 0.length > 0 の代わりに、.isEmpty.isNotEmpty を使います。

特に動的なコレクションでは、.length の呼び出し時に要素数の計算が行われ、パフォーマンスが低下する可能性があるため、より効率的な専用プロパティを使用します。

7. コレクションの反復処理にはforEachではなく for-inを使う

コレクション内の全要素に対して処理を行う場合、多くの言語にある forEach メソッドよりも、Dartのfor (var item in collection) というfor-in ループを使うことが推奨されます。for-in はコードが読みやすく、実行効率が良いことが多いです。

8. 関数名が必要な場合は関数宣言をする

関数名が必要な場合(例:トップレベル関数やクラスのメソッドなど)は、通常の関数宣言(例: void calculate() { ... })を使います。

関数式(アロー関数や匿名関数)は、主にコールバックとして利用するなど、名前が不要な場合に利用を限定します。

9. ローカル変数には型注釈をつけない

関数内で一時的に使う変数(ローカル変数)を宣言する際、Dartの型推論機能が初期値から自動で型を判断できるため、型注釈(例: String name = 'Alice';)はつけず、代わりに var または final を使います。

10. ローカル変数での varfinal の使い分けはどちらかのルールに従う

ローカル変数における varfinal の使い方は、チーム内で一貫した以下のどちらかのルールを採用します。

  • ルール1: 再代入するもののみ var を使い、それ以外(値を変更しないもの)は final を使う。
  • ルール2: すべて var で宣言し、ローカル変数では final は使わない(変更の有無はコードの読み取りに任せる)。

11. クラスのフィールドをそのまま返すgetterを作らない

クラスのフィールドの値をそのまま返すだけのシンプルなGetter(例: String get name => _name;)は、冗長なため作らないことが推奨されます。

Dartでは、フィールドの直接参照(instance.name)とGetterの利用は文法的な区別がないため、シンプルにフィールドを公開すれば十分です。

12. 参照可能だが更新不可なフィールドはfinalキーワードで宣言

クラスのフィールドを外部から参照可能にしたいが、一度設定した後は値の更新(再代入)を禁止したい場合、フィールドに final キーワードを付けて宣言します。

これにより、不必要なGetterの作成を避けつつ、不変性(値が変わらないこと)を保証できます。

13. Getter/Setterは1行のみなら=>で記述する

クラスのGetterやSetterの処理が一行で完結する場合は、String get name => _name.toUpperCase(); のようなアロー関数(=>)の形式で簡潔に記述します。

処理が複数行にわたる場合は、通常のメソッド({...})として記述します。

14. 外側の const は暗黙的に内側に伝搬するため冗長的に付与しない

const キーワードは、定数(コンスタント)であることを示します。

const のコレクション(リストやマップなど)を宣言する際、その内側の要素も自動的に const と見なされるため、内側の要素にまで const を重ねて記述するのは冗長であり、避けましょう。

15. future の扱いは .then() より async/await を優先する

時間のかかる非同期処理(Future)の結果を待つ際、.then() メソッドを使ったコールバックチェーン(処理の連鎖)よりも、async および await キーワードを使った記述方法を優先します。

これは、async/await の方が処理の流れが上から下に実行されるように見え、コードの可読性が格段に向上するためです。

参考

https://dart.dev/effective-dart/usage

AIによる整理

次の形式でAIにまとめてもらった内容です。

- セクション
	- 接頭語
		- ガイド
  • ライブラリ
    • DO
      • part of ディレクティブでは文字列を使用する。
    • DON'T
      • 他のパッケージの src ディレクトリ内にあるライブラリをインポートしない。
      • インポートパスが lib の中や外に及ぶことを許可しない。
    • PREFER
      • 相対的なインポートパスを使用することを推奨する。
  • Null
    • DON'T
      • 変数を明示的に null に初期化しない。
      • 明示的なデフォルト値として null を使用しない。
      • 等価演算で true または false を使用しない。
    • AVOID
      • 初期化されているかどうかを確認する必要がある場合は、late 変数を避ける。
    • CONSIDER
      • ヌル許容型を使用するために型プロモーションまたはヌルチェックパターンを検討する。
  • 文字列
    • DO
      • 文字列リテラルを連結するために隣接する文字列を使用する。
    • PREFER
      • 文字列と値を構成するために文字列補間を使用することを推奨する。
    • AVOID
      • 必要のない場合に補間に中括弧を使用することを避ける。
  • コレクション
    • DO
      • 可能な場合はコレクションリテラルを使用する。
      • 型によってコレクションをフィルタリングするために whereType() を使用する。
    • DON'T
      • コレクションが空かどうかを確認するために .length を使用しない。
      • 結果の型を変更する意図がない限り、List.from() を使用しない。
      • 近くの操作で済む場合に cast() を使用しない。
    • AVOID
      • 関数リテラルを持つ Iterable.forEach() の使用を避ける。
      • cast() の使用を避ける。
  • 関数
    • DO
      • 関数を名前にバインドするために、関数宣言を使用する。
    • DON'T
      • ティアオフで済む場合にラムダを作成しない。
  • 変数
    • DO
      • ローカル変数における varfinal の一貫した規則に従う。
    • AVOID
      • 計算できるものを格納することを避ける。
  • メンバー
    • DO
      • 可能な場合は宣言時にフィールドを初期化する。
    • DON'T
      • 不必要にフィールドをゲッターとセッターでラップしない。
      • 名前付きコンストラクターへのリダイレクト、またはシャドウイングを避ける場合を除き、this. を使用しない。
    • PREFER
      • 読み取り専用プロパティを作成するために final フィールドを使用することを推奨する。
    • CONSIDER
      • 単純なメンバーに => を使用することを検討する。
  • コンストラクター
    • DO
      • 可能な場合は初期化仮引数(initializing formals)を使用する。
      • 空のコンストラクター本体には {} の代わりに ; を使用する。
    • DON'T
      • コンストラクターの初期化リストで済む場合に late を使用しない。
      • new を使用しない。
      • const を冗長に使用しない。
  • エラー処理
    • DO
      • プログラム上のエラーに対してのみ Error を実装するオブジェクトをスローする。
      • キャッチした例外を再スローするために rethrow を使用する。
    • DON'T
      • on 句のない catch からのエラーを破棄しない。
      • Error またはそれを実装する型を明示的にキャッチしない。
    • AVOID
      • on 句のない catch を避ける。
  • 非同期処理
    • DO
      • 型引数が Object である可能性がある FutureOr<T> を区別する際に、Future<T> のテストを実行する。
    • DON'T
      • 有用な効果がない場合に async を使用しない。
    • PREFER
      • rawなFutureを使用するよりも async/await を推奨する。
    • CONSIDER
      • ストリームを変換するために高階メソッドを使用することを検討する。
    • AVOID
      • Completer を直接使用することを避ける。

4. Design Guide(デザインガイド)

要点

  • 使いやすく理解しやすいコードを書くためのガイド
  • 次の項目で記載されている
    • Names (名前)
    • Libraries (ライブラリ)
    • Classes and mixins (クラスとMixin)
    • Constructors (コンストラクター)
    • Members (メンバー)
    • Types (型)
    • Parameters (パラメータ)
    • Equality (等価性)
  • 利用頻度の高いガイドをピックアップ
    • getterだとしてもメソッド名はgetから始めない
    • クラスに結びつかないデータや処理はトップレベルに変数や関数として配置する
    • 全てのフィールドがfinalでコンストラクタは初期化のみを行う場合はconstコンストラクタを作成する
    • 可能な限りフィールドやトップレベル変数はfinalで作成する
    • privateなフィールドではgetterがないならsetterを作らず、setterではなくフィールド更新のメソッドを用意する(getterやsetterは公開フィールドのように見えるので、どちらかだけだと混乱を招く)
    • 形推論
      • ローカル変数は基本的に推論を使う
        • dynamic型に推論される場合や不十分な推論になる場合は型注釈をする
      • フィールドやトップレベル変数は型注釈をする
      • 引数には型注釈をする
      • コールバックで使う関数式の引数は推論を使う
      • 意図したdynamic型は型注釈する
    • 可能な限りdynamic型は避け、Object型を検討する(JSONを扱う際は慣習としてMap<String, dynamic> を使う)
    • FutureOr<T>型の戻り値は避け、Future<T>型に統一する

使いやすく理解しやすいコードを書くためのガイド

Design Guideは、使いやすく理解しやすいコードを書くためのガイドです。
このガイドラインに従うことで、作ったクラスやライブラリが、他の開発者にとって、「使い方を迷わない」、「挙動が予測しやすい」、そして「変更しやすい」ものになります。

次の項目で記載されている

このガイドは、以下の要素に関する、公開されるべき機能(パブリックAPI)の設計原則を中心に構成されています。

  • 名前 (Names): クラス、メソッド、変数などの「名前の付け方」に関する設計ルール。
  • ライブラリ (Libraries): 複数のファイルをどのように構成するか。
  • クラスとMixin (Classes and Mixins): 機能のまとまり(クラス)の設計原則。
  • コンストラクター (Constructors): オブジェクト作成の際の設計。
  • メンバー (Members): クラス内の構成要素の設計。
  • 型 (Types): 型の適切な使い方や推論(型を自動で判断すること)のルール。
  • パラメータ (Parameters): 関数やメソッドが受け取る引数の設計。
  • 等価性 (Equality): オブジェクト同士が「等しい」と判断される基準の設計。

利用頻度の高いガイドをピックアップ

1. クラスに結びつかない処理はトップレベルに配置する

特定のクラスに結びついていないデータや関数(処理)は、無理にクラスの中に入れず、ファイルのトップレベル(クラスの外側)に変数や関数として配置しましょう。

例えば、数学的な計算関数などは、特定のクラスのインスタンス(実体)がなくても実行できるため、トップレベルに置く方が自然で使いやすい設計になります。

2. Getterだとしてもメソッド名は get から始めない

値を読み取るためのメソッド(Getter)を定義する場合でも、メソッドの名前を get で始める(例: getUsers())のはやめましょう。

Dartでは、instance.user のように、フィールド(変数)へのアクセスと同じ形式でGetterを呼び出せるため、getUser のように書いてしまうと、フィールドの参照と書き味が変わってしまいます。

3. 可能な限りフィールドやトップレベル変数は final で作成する

クラス内の変数(フィールド)やトップレベルで定義する変数は、後で値を変更する必要がない限りfinal キーワードを付けて宣言しましょう。

不変性(値が変わらないこと)を保証することで、プログラムの予測可能性が高まり、バグの発生を減らすことができます。

4. const コンストラクタを積極的に作成する

クラスの全てのフィールドfinal であり、コンストラクターが単にそれらのフィールドを初期化(値を設定)するだけの機能を持つ場合、そのコンストラクターを const コンストラクタとして作成しましょう。

これにより、そのクラスのオブジェクトを定数(コンスタント)として効率的に生成できるようになり、パフォーマンスの向上に役立ちます。

5. Getter/Setterの片方だけを公開する際は慎重に

プライベートなフィールド(_から始まる変数)に対して、Getter(読み取り専用)だけを提供する、あるいはSetter(書き込み専用)だけを提供する場合は、注意が必要です。

なぜなら、Dartの Getter/Setter は公開フィールドのように見えるため、片方しかないと開発者が「あれ、もう片方はどこだ?」と混乱する原因になります。GetterがないSetterを提供する代わりに、フィールドを更新する専用のメソッド(例: updateValue(newValue))を用意することを検討しましょう。

6. 型推論を賢く利用する

Dartは賢く型を自動で判断(型推論)できます。

  • ローカル変数(関数内の変数)は、基本的に推論に任せて型注釈を省略しましょう。
    • ただし、推論が dynamic 型になったり、意図した型と異なる場合は、明確に型注釈を付けましょう。
  • フィールドやトップレベル変数、引数は、公開される情報であるため、必ず型注釈を付けましょう。これにより、コードの利用者が一目で型を理解できるようになります。

7. dynamic 型は極力避け、Object 型を検討する

どのような型の値でも受け入れてしまう dynamicは、Null安全性の恩恵を失い、バグの原因になりやすいため、可能な限り避けましょう。

型が不明な値を受け入れる必要がある場合は、代わりに Objectを検討します。

8. FutureOr<T> 型は戻り値で避ける

非同期処理の結果を返す型として、FutureOr<T>Future<T>T のどちらかを表す型)は使わないようにしましょう。

非同期関数の戻り値は、常に Future<T> 型に統一することで、非同期処理を扱う際の予測可能性と一貫性が高まります。

参考

https://dart.dev/effective-dart/design

AIによる整理

次の形式でAIにまとめてもらった内容です。

- セクション
	- 接頭語
		- ガイド
  • Names (名前)
    • DO
      • 用語を一貫して使用する。
      • 型パラメータを命名する際には、既存のニーモニック規則に従う。
    • PREFER
      • 最も説明的な名詞を最後に置くことを推奨する。
      • 非ブール値のプロパティまたは変数には名詞句を推奨する。
      • ブール値のプロパティまたは変数には非命令形の動詞句を推奨する。
      • ブール値のプロパティまたは変数には「肯定的」(positive)な名前を推奨する。
      • 主な目的が副作用である関数またはメソッドには命令形の動詞句を推奨する。
      • 値を返すことが主な目的である関数またはメソッドには名詞句または非命令形の動詞句を推奨する。
      • オブジェクトの状態を新しいオブジェクトにコピーする場合は、メソッド名を to___() とすることを推奨する。
      • 元のオブジェクトに裏打ちされた異なる表現を返す場合は、メソッド名を as___() とすることを推奨する。
    • CONSIDER
      • コードが文章のように読めるようにすることを検討する。
      • 名前付きのブール値パラメータについては動詞を省略することを検討する。
      • 関数またはメソッドが実行する作業に注意を向けさせたい場合は、命令形の動詞句を検討する。
    • AVOID
      • 略語を避ける。
      • メソッド名を get で始めることを避ける。
      • 関数またはメソッドの名前にパラメータを記述することを避ける。
  • Libraries (ライブラリ)
    • PREFER
      • 宣言をプライベートにすることを推奨する。
    • CONSIDER
      • 複数のクラスを同じライブラリで宣言することを検討する。
  • Classes and mixins (クラスとMixin)
    • DO
      • クラスが拡張可能かどうかを制御するためにクラス修飾子を使用する。
      • クラスがインターフェースになれるかどうかを制御するためにクラス修飾子を使用する。
    • PREFER
      • mixin class よりも純粋な mixin または純粋な class を定義することを推奨する。
    • AVOID
      • 単純な関数で済む場合に、メンバーが1つだけの抽象クラスを定義することを避ける。
      • 静的メンバーのみを含むクラスを定義することを避ける。
      • サブクラス化を意図していないクラスを拡張することを避ける。
      • インターフェースとして意図されていないクラスを実装することを避ける。
  • Constructors (コンストラクター)
    • CONSIDER
      • クラスがサポートしている場合は、コンストラクターを const にすることを検討する。
  • Members (メンバー)
    • DO
      • 概念的にプロパティにアクセスする操作にはゲッターを使用する。
      • 概念的にプロパティを変更する操作にはセッターを使用する。
    • PREFER
      • フィールドとトップレベル変数を final にすることを推奨する。
    • DON'T
      • 対応するゲッターなしにセッターを定義しない。
    • AVOID
      • オーバーロードを偽装するためにランタイムの型テストを使用することを避ける。
      • 初期化子を持たないパブリックな late final フィールドを避ける。
      • ヌル許容の FutureStream、およびコレクション型を返すことを避ける。
      • Fluentインターフェースを有効にするためだけにメソッドから this を返すことを避ける。
  • Types (型)
    • DO
      • 初期化子がない変数には型アノテーションを付ける。
      • 型が明白でない場合は、フィールドとトップレベル変数に型アノテーションを付ける。
      • 関数宣言には戻り値の型をアノテーションする。
      • 関数宣言にはパラメータの型をアノテーションする。
      • 推論されないジェネリック呼び出しには型引数を記述する。
      • 推論が失敗する代わりに dynamic でアノテーションする。
      • 値を生成しない非同期メンバーの戻り値の型として Future<void> を使用する。
    • PREFER
      • 関数型アノテーションにはシグネチャを推奨する。
      • typedef よりもインライン関数型を推奨する。
      • パラメータには関数型構文を使用することを推奨する。
    • DON'T
      • 初期化されたローカル変数に冗長な型アノテーションを付けない。
      • 関数式の推論されたパラメータの型をアノテーションしない。
      • 初期化仮引数(initializing formals)に型アノテーションを付けない。
      • 推論されるジェネリック呼び出しには型引数を記述しない。
      • セッターに戻り値の型を指定しない。
      • レガシーな typedef 構文を使用しない。
    • AVOID
      • 不完全なジェネリック型を記述することを避ける。
      • 静的チェックを無効にしたい場合を除き、dynamic の使用を避ける。
      • 戻り値の型として FutureOr<T> を使用することを避ける。
  • Parameters (パラメータ)
    • DO
      • 範囲を受け入れるために、包括的な開始パラメータと排他的な終了パラメータを使用する。
    • AVOID
      • 位置指定のブール値パラメータを避ける。
      • ユーザーが前のパラメータを省略したい場合に、省略可能な位置指定パラメータを避ける。
      • 特別な「引数なし」の値を受け入れる必須パラメータを避ける。
  • Equality (等価性)
    • DO
      • == をオーバーライドする場合は hashCode をオーバーライドする。
      • == 演算子が等価性の数学的規則に従うようにする。
    • AVOID
      • 可変クラスに対してカスタムの等価性を定義することを避ける。
    • DON'T
      • == のパラメータをヌル許容にしない。

各ガイドを跨いだまとめ

「Effective Dart」は、異なる視点から構成されているため、特定の技術要素(例:クラス、メンバー)に関するガイドラインが複数のセクションに分散しています。

ソースの内容に基づき、ガイドラインをセクションを横断して主要な技術要素ごとに再編成し、まとめて列挙します。

分類 ガイド内容
コメント ・Docコメントは一文の要約で記述する
・複数文章で説明するには先頭の一文のあとに空行を入れる
・関連機能は[ 関数名など ]で参照する
・引数などの説明は文章にする(@paramなどは使わない)
・マークダウンが利用可能だが、過度に利用しない
命名 ・クラスなど型はUpperCamelCase
・ファイルやフォルダはlowercase_with_underscores
・変数などその他はlowerCamelCase
・3文字以上の略語は大文字で始める
・getterだとしてもメソッド名はgetから始めない
変数・型 ・未使用の引数には_を使用する
・null許容型の変数の初期化にnullを代入してはいけない
・bool値の比較で == true のように記述してはいけない
${式}で文字列に変数を埋め込む際は {} を省略する
・コレクションの要素有無確認は.isEmpty / .isNotEmptyを使う(.lengthは使わない)
・ローカル変数には型注釈をつけない
・ローカル変数でのvar / finalの使い分けはチームルールに従う
  - ①再代入するもののみvar、それ以外はfinal
  - ②すべてvarで宣言しfinalは使わない
・参照可能だが更新不可なフィールドはfinalで宣言
・外側のconstは暗黙的に内側に伝搬するため重複して書かない
・可能な限りdynamic型は避け、Object型を検討(JSONではMap<String, dynamic>
・形推論の方針:
  - ローカル変数は推論を使用
  - フィールド・トップレベル変数・引数は型注釈を付与
  - コールバックの引数は推論
  - 意図的にdynamicを使う場合は注釈する
関数・処理 ・libフォルダ以下は相対インポートを利用
・libフォルダ外(ライブラリ)はpackageインポートを利用
・forEachではなくfor-inを使う
・関数名が必要な場合は関数宣言を使う(関数式はコールバックなどに)
・getter/setterは1行のみなら=>で記述(複数行ならメソッド)
futureの扱いはthenよりasync/awaitを優先
FutureOr<T>の戻り値は避け、Future<T>に統一
クラス ・クラスのフィールドをそのまま返すgetterを作らない(フィールド参照と文法上の違いがない)
・クラスに結びつかないデータや処理はトップレベル変数・関数として配置
・全フィールドがfinalでコンストラクタが初期化のみならconstコンストラクタを作成
・可能な限りフィールドやトップレベル変数はfinalで作成
・privateフィールドでgetterがないならsetterを作らず、更新は専用メソッドで行う

おわりに


Effective Dartは、Dart開発者にとって「読みやすく、間違いにくいコード」を書くための指針です。今回ピックアップしたものも日々のコーディングに直結する重要なルールが多く含まれていました。

ガイドラインは「縛り」ではなく、「共通言語」として活用するものです。自分やチームのコードを見直す際に、Effective Dartを参照する習慣をつけることで、コードの質と開発体験の両方が確実に向上していくでしょう。

Discussion