【Dart/Flutter】lintの導入方法と対応した警告の修正例を全力で公開してみたよ

Dart

概要

どうも、@daiki1003です!

先日、ある程度大きくなった自分のFlutterプロジェクトにlintを入れてみました。
悪い予感はしてましたが、まぁ出てくるわ出てくるわ笑
警告5,000みたいな感じでした。

ただ、僕自身こう言うのをちまちま直していく作業は好きだったりするので
苦もなく全部倒してやりました。

さて、この記事では


「Flutterのプロジェクトでlintを入れたいけどどうしたらいいかわからない…」
「lintを入れると、何を変えさせられるの?」

そんな疑問にお答えしたいと思います。

それでは早速行ってみましょー!

Flutterのlintって何を採用すれば良いの?

公式ではこちらのページに全て載っています。

pedantic_monoの導入

が、今回はmonoさんが下記パッケージを公開してくれていたので、ありがたく使わせていただきました。

GitHub - mono0926/pedantic_mono: [mono edition] Recommended lints for Flutter apps, packages, and plugins to encourage good coding practices.
Recommended lints for Flutter apps, packages, and plugins to encourage good coding practices. - mono0926/pedantic_mono
Dart/Flutter の静的解析強化のススメ
プロジェクトには analysis_options.yaml を常に配置して静的解析を厳しくすることを強くお勧め

どうやら結構厳し目なlintみたいです。

パッケージのインストール方法は下記記事で解説しています。

【超簡単!】Flutterのパッケージのインストール方法を説明するよ!
概要 Flutter(Dart)で画像のクリッピングするコードが書けますか? Flutter(Dart)でhttp通信するコードが書けますか? 大丈夫、書けなくて良いんです。 Flutterでは、色んな人が使いそ...

analysis_options.yamlの設定

analysis_options.yamlを作成し、以下の様に1行追加します。

 
include: package:pedantic_mono/analysis_options.yaml

これだけで、静的解析が始まり警告がわんさか表示されるはずです。

Flutterプロジェクトにlintを入れた際の変更内容を一挙公開!

さて、導入も無事できたところで、修正内容を公開したいと思います。

エラー

こちらは無視して実行することができません。

Missing arguments type for …

BAD

onPressed: () {
  Navigator.of(context).push(
    ...
  );
}

GOOD

onPressed: () {
  Naviagator.of(context).push<void>(
    ...
  );
}

ジェネリックメソッドを呼び出す際は型を明示的に指定してね、というエラーです。

Missing parameter type for map literal.

BAD

Map hoge = {};

GOOD

Map hoge = <String, dynamic>{};

BETTER

final hoge = <String, dynamic>{};

Mapの型が明示的に指定されていないと怒られます。
後述しますがそもそも変数を型名で受け取るのは基本的に良くないとされているので
betterとして用意しました。

Missing parameter type for …

BAD

Future.delayed(...);

GOOD

Future<void>.delayed(...);

The argument type ‘dynamic’ can’t be assigned to the parameter type ‘void Function()’

BAD

class A {
  const A(this.onPressed);

  final Function onPressed;
  
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onPressed: onPressed,
      ...
    );
  }
}

GOOD

class B {
  const A(this.onPressed);
 
  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onPressed: onPressed,
      ...
    );
  }
}

FunctionではなくVoidCallbackを使いました。
型の違いですね。

The argument type ‘dynamic’ can’t be assigned to the parameter type ‘void Function()’

BAD

User user = ModalRoute.of(context).settings.arguments;

GOOD

final user = ModalRoute.of(context).settings.arguments as User;

警告

警告は最悪無視しても実行は出来ます。

SizedBox for whitespaces.

BAD

Container(
  width: double.infinity,
  height: 240,
  child: ...
);

GOOD

SizedBox(
  width: double.infinity,
  height: 240,
  child: ...
);

SizedBoxは実はchildを持っています。
良くある使い方だと、Widget間の余白を作る際にwidthかheightどちらかを指定する形かなと思います。

prefer const over final for declarations.

BAD

class AppColor {
  static final Color hoge = Color.fromRGBO(110, 110, 100, 1.0);
}

GOOD

class AppColor {
  static const Color hoge = Color.fromRGBO(110, 110, 100, 1.0);
}

finalよりconstが良いよと言う警告です。
ざっくり言うとfinalは実行時定数、constはコンパイル時定数です。
コンパイル時に定数にしてしまえるならそっちの方が良いよと言う警告ですね。

Only use double quotes for strings containing single quotes.

BAD

Text(
  "hoge",
)

GOOD

Text(
  'hoge',
)

""ではなく''で文字列を定義しろということです。

`Future` results in async function bodies must be awaited or marked unawaited using `package:pedantic`

BAD

onPressed: () async {
  Navigator.of(context).pushNamed(
    'user',
  );
}

GOOD

onPressed: () {
  Navigator.of(context).pushNamed(
    'user',
  );
}

GOOD

onPressed: () async {
  await Navigator.of(context).pushNamed(
    'user',
  );
}

GOOD

onPressed: () async {
  unawaited(Navigator.of(context).pushNamed(
    'user',
  ));
}

asyncなメソッド内でFutureを返すメソッドにはawaitを付けなさいよ
という警告です。
・そもそもasyncなメソッドにしない
awaitする
unawaitedを明示的に呼び出す
のどれかで対処します。

Avoid async functions that return void.

BAD

void something() async {
  // ...
}

GOOD

Future<void> something() async {
  // ...
}

asyncなメソッドの返り値は必ずFutureにしようねという警告です。

Sort constructor declarations before other members.

BAD

class Hoge {
  final int a;
  final String b;
  const Hoge(this.a, this.b);
}

GOOD

class Hoge {
  const Hoge(this.a, this.b);

  final int a;
  final String b;
}

コンストラクタは一番最初に定義してねという警告です。

Omit type annotations for local variables.

BAD

final String a = 'hoge';
for (int i = 0; i < 10; ++i) {
  ...
}

GOOD

final a = 'hoge';
for (var i = 0; i < 10; ++i) {
  ...
}

ローカル変数を型名で定義するなということですね。
変更しないならfinal、するならvarで定義します。

Prefer const with constant constructors.

BAD

Container(
  child: Text('a'),
)

GOOD

Container(
  child: const Text('a'),
)

ウィジェットは基本的に何度もビルドされます。
ですが、constキーワードをつけておけば
前回のビルド内容を再利用してくれるのでつけれるものは積極的につけていきましょう。

Prefer int literals over double literals.

BAD

SizedBox(
  width: 240.0,
)

Tween(begin: 0.0, end: 1.0)

GOOD

SizedBox(
  width: 240,
)

Tween(begin: 0, end: 1)

例え、double型を受ける引数だったとしても与える値が
整数値なのであれば整数値で与えましょうということです。

Prefer using lowerCamelCase for constant names.

BAD

enum Hoge {
  THIS_IS,
  BAD,
  Enum,
}

GOOD

enum Hoge {
  thisIs,
  good,
  enum,
}

enum値の定義は先頭小文字のキャメルケースで定義します。

Separate the control structure expression from its statement.

BAD

if (somethingNew()) return;

for (final item in items) {
  if (item.isEmpty) continue;

  ...
}

GOOD

if (somethingNew()) {
  return;
}

for (final item in items) {
  if (item.isEmpty) {
    continue;
  }

  ...
}

returncontinueなどを
ifと同じ行に書くなと言う警告です。

Use interpolation to compose strings and values.

BAD

final a = 'My name is ' + name;

GOOD

final a = 'My name is $name';

文字列連結の際に、+ではなく変数埋め込みを使いましょうという警告です。

No default cases.

enum Hoge {
  first,
  second,
}

BAD

int something() {
  switch (hoge) {
    case Hoge.first:
      return 1;
    default:
      return 0;
  }
}

GOOD

int something() {
  switch (hoge) {
    case Hoge.first:
      return 1;
    case Hoge.second:
      return 2;
  }
  throw UnimplementedError();
}

switch文でdefault節を使うなと言う警告です。
新しくHoge.thirdが追加された時にBAD側だと
defaultで制御されてしまうので意図せぬ挙動を起こしやすいからだと思います。

個人的には、switch文を抜けたらUnimplementedErrorで受けるのが好きです。

Cascade consecutive method invocations on the same reference.

BAD

final user = User();
user.age = 18;
user.name = 'ashdik';

GOOD

final user = User()
  ..age = 18
  ..name = 'ashdik';

同じ変数に対しての代入は一つの文で行いましょうという警告です。

Avoid lines longer than 80 characters.

BAD

final hoge = ... // longer than 80 words.

GOOD

final hoge = 
  ...;

1行に80文字以上書いてはいけません。

まとめ

どうでしょうか?
これ以外にもまだまだあるはずです。

頑張って綺麗なコードを書けるようにがんばりましょう 💪

また、こういう細々した作業が苦手な人は
僕が代わりにやることも可能なのでお声がけいただければと思います!

誰かのお役に立てば。

Twitterフォローお願いします

「次回以降も記事を読んでみたい!」
「この辺分からなかったから質問したい!」

そんな時は、是非Twitter (@daiki1003)Instagram (@ashdik_flutter)のフォローお願いします♪

Twitterコミュニティ参加お願いします

Twitterコミュニティ「Flutter lovers」を開設しました!
参加お待ちしております😁

☕️ Buy me a coffee

また、記事がとても役に立ったと思う人は
コーヒーを奢っていただけると非常に嬉しいです!

コメント

タイトルとURLをコピーしました