はじめに
FlutterでCIを行いたいと思い、導入方法を調べていました。(その時の記事はこちら)
CIの中で、静的解析を実行するようにしたのですが、効果的に静的解析をするためにはデフォルトのanalysis_options.yaml
をカスタマイズする必要があります。
Flutterのプロジェクト作成時にanalysis_options.yaml
は自動的に作成されますが、デフォルトの内容だと設定が緩いため、もう少し厳しめに設定した方がチームでコーディングのスタイルが統一しやすくなると思いました。
前置きが長くなりましたが、本記事では、analysis_options.yaml
をカスタマイズするにあたり、Linterのルール一覧を読んで、知らなかったコードの書き方や文法、Widgetがあったので、学んだことを記載します。
analysis_options.yamlとは
備忘録として、analysis_options.yaml
についても調べたことを整理したいと思います。
知っている方は、Linterのルールを読んで学んだことスキップしてください。
analysis_options.yaml
は、コードを静的解析するための設定ファイルです。
静的解析の目的は、コードを実行する前に、記述ミス(閉じカッコがないとか)によるバグや、ソースコードの書き方に違反があったら警告することです。
ソースコードの書き方の違反とは、例えば、以下のようなイメージです。
- 文字列を表す場合はダブルクオートとするというルールがあったとすると、シングルクオートを使っていたら警告する
-
final
を使える場面でvar
として変数定義をしていたら、final
を使うように警告する
analysis_options.yamlの構成
analysis_options.yaml
は三つの構成があります。
- include
- analyzer
- linter
include
すでに定義されている設定を取り込むことができます。
デフォルトだと、include: package:flutter_lints/flutter.yaml
と設定されています。
他の人が作った設定を取り込むこともできて、以下の記事内でおすすめの設定が紹介されているのでご参照ください。
特にこだわりがない場合は、おすすめの設定を取り込んでベースにするのがいいと思います。
analyzer
静的分析をカスタマイズする部分になります。
カスタマイズできるのは下記です。
- 厳密な型チェックを有効にする
- 特定のファイルをチェックの対象外とする
- 特定のルールを無視する
- ルールの重要度を変更する
厳密な型チェックを有効にする
Dartには、デフォルトで型チェックをするようになっていますが、その設定をより厳しくすることができるようです。
特定のファイルをチェックの対象外とする
以下のように、exclude
にパスを定義することで、チェックの対象外とすることができます。
analyzer:
exclude:
- lib/client.dart
- lib/server/*.g.dart
- test/_data/**
特定のルールを無視する
以下のように特定のルールを無視するように設定できます。
analyzer:
errors:
invalid_annotation_target: ignore
例えば、Freezedを使っていると、@JsonSerializable
で警告が出るので、上記の設定のように無視するよう設定できます。
ルールの重要度を変更する
以下のように、ルールごとに警告のレベルを設定できます。
analyzer:
errors:
invalid_assignment: warning
missing_return: error
dead_code: info
以下の記事の説明がわかりやすかったです。
ルールとして設定できるのは、以下の二つのリストみたいでした。
linter
linterのルールを個別に有効・無効の設定する部分になります。
要はこの書き方はOKで、この書き方はNGを設定していく部分です。
例えば、以下のルールがあります。
- 文字列はダブルクオートで定義する
- パッケージのインポートを相対パスで書かないようにする
何が使用できるかは以下にすべてまとまってます。
書き方は以下の二つがあるみたいです。
有効にするルールを定義する方法
linter:
rules:
- always_declare_return_types
- cancel_subscriptions
- close_sinks
ルールごとに有効(true)と無効(false)を定義する方法
linter:
rules:
avoid_shadowing_type_parameters: false
await_only_futures: true
Linterのルールを読んで学んだこと
記事の本題ですが、Linterのルールを読んで学んだことを書いていきます。
prefer_is_not_operator
変数が指定の型かどうかを判定する時、if(str is String)
で判定できますが、指定の型ではないという判定をしたい時、is!
で判定できるみたいです。
final str = "aaaa";
if(str is! int){
print("str is not int");
}
prefer_iterable_whereType
Listなどで、指定の型だけの要素を抽出したい場合、where
ではなくwhereType
を使えます
// 下記は同じ結果になる
iterable.where((e) => e is MyClass);
iterable.whereType<MyClass>();
sized_box_shrink_expand
SizedBoxの書き方に関して、以下のように書き方ができるみたいです。
Widget buildLogo() {
return SizedBox(
height: 0,
width: 0,
child: const MyLogo(),
);
}
// 上記は以下の書き方ができる
Widget buildLogo() {
return SizedBox.shrink(
child: const MyLogo(),
);
}
Widget buildLogo() {
return SizedBox(
height: double.infinity,
width: double.infinity,
child: const MyLogo(),
);
}
// 上記は以下の書き方ができる
Widget buildLogo() {
return SizedBox.expand(
child: const MyLogo(),
);
}
type_literal_in_constant_pattern
例には以下のようなコードが書かれていて、if (x case num)
という書き方を初めて見たので、調べました。
// BAD
void f(Object? x) {
if (x case num) {
print('int or double');
}
}
// GOOD
void f(Object? x) {
if (x case num _) {
print('int or double');
}
}
// BAD
void f(Object? x) {
if (x case int) {
print('int');
}
}
// GOOD
void f(Object? x) {
if (x case const (int)) {
print('int');
}
}
if (x case num _)
に注目します。
まずnumについては、intとdoubleの継承元でした。
つまり、Dart標準の型です。
わかっている情報は、xは変数、numは型となりました。
caseは何かというと、if-case
という文法みたいです。
パターンにマッチすると、ifの{}内で、定義した変数が使えます。(if (x case num _)
の_
の部分が変数)
わかりやすく書き直すと下記になります。
final i = 0;
if(i case num number){
// iがnum型なら、このスコープでnumberという変数として扱える
print(number);
print("i is num");
}
unawaited_futures
こちらのルールの例文で、unawaited
というメソッドを知りました。
Futureのメソッドを明示的に待たないことを表せるメソッドみたいです。
上記のルールは、明示的に待たない部分は、unawaitedを使うというルールになります。
個人的に、有用なルールだと思いました。
実際のプロジェクトで、awaitが必要なところでawaitを書き忘れていて、バグとなった経験があるので、設定しておきたいルールです。
void main() async {
await futurePrint();
print("await futurePrint");
unawaited(futurePrint());
print("unwaited futurePrint");
// 実行すると下記の順番になる
// futurePrint
// await futurePrint
// unwaited futurePrint
// futurePrint
}
Future<void> futurePrint() async {
await Future.delayed(const Duration(milliseconds: 500));
print("futurePrint");
}
unnecessary_lambdas
以下のようにGOODの方を書くようにするルールですが、forEachにメソッドを渡す書き方は見たことがなかったので、短縮できていいなと思いました。
// BAD
names.forEach((name) {
print(name);
});
// GOOD
names.forEach(print);
上手いコード例が見つからなかったのですが、下記のように書けるという感じです。
void main() {
final numStrList = ["1", "2", "3"];
// final numList = numStrList.map((e) => toInt(e)).toList();
final numList = numStrList.map(toInt).toList();
}
int toInt(String str) {
return int.parse(str);
}
use_colored_box
以下のコード例のように、色だけを指定するContainer
の場合、ColoredBox
を使う方が良いみたいです。
// BAD
Widget buildArea() {
return Container(
color: Colors.blue,
child: const Text('hello'),
);
}
// GOOD
Widget buildArea() {
return const ColoredBox(
color: Colors.blue,
child: Text('hello'),
);
}
use_decorated_box
use_colored_box
と同じような感じで、decorationだけを指定するContainer
の場合、DecoratedBox
を使用するほうがいいみたいです。
// BAD
Widget buildArea() {
return Container(
decoration: const BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.all(
Radius.circular(5),
),
),
child: const Text('...'),
);
}
// GOOD
Widget buildArea() {
return const DecoratedBox(
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.all(
Radius.circular(5),
),
),
child: Text('...'),
);
}
use_is_even_rather_than_modulo
数値が奇数か偶数かを調べる場合はisEven
とisOdd
が使える。
if(2.isEven){
print("2は偶数です");
}
if(13.isOdd){
print("13は奇数です");
}
おまけ
Linterのルール一覧を読んで、個人的に設定しておいたほうがよさそうなことを記載しました。
よかったら使ってみてください。
include: package:flutter_lints/flutter.yaml
linter:
rules:
# Error rules
always_use_package_imports: true
avoid_print: true
avoid_relative_lib_imports: true
no_duplicate_case_values: true
# Style rules
always_declare_return_types: true
always_put_control_body_on_new_line: true
always_put_required_named_parameters_first: true
avoid_init_to_null: true
avoid_multiple_declarations_per_line: true
avoid_positional_boolean_parameters: true
avoid_unused_constructor_parameters: true
camel_case_types: true
constant_identifier_names: true
curly_braces_in_flow_control_structures: true
directives_ordering: true
empty_catches: true
empty_constructor_bodies: true
eol_at_end_of_file: true
exhaustive_cases: true
file_names: true
flutter_style_todos: true
library_names: true
library_prefixes: true
lines_longer_than_80_chars: true
matching_super_parameters: true
no_leading_underscores_for_library_prefixes: true
no_leading_underscores_for_local_identifiers: true
no_literal_bool_comparisons: true
non_constant_identifier_names: true
noop_primitive_operations: true
only_throw_errors: true
overridden_fields: true
parameter_assignments: true
prefer_asserts_in_initializer_lists: true
prefer_asserts_with_message: true
prefer_conditional_assignment: true
prefer_const_constructors: true
prefer_const_constructors_in_immutables: true
prefer_const_declarations: true
prefer_const_literals_to_create_immutables: true
prefer_constructors_over_static_methods: true
prefer_contains: true
# 好みによって切り替える
prefer_double_quotes: true
prefer_expression_function_bodies: true
prefer_final_fields: true
prefer_final_in_for_each: true
prefer_final_locals: true
# 好みによって切り替える
# prefer_final_parameters: true
prefer_for_elements_to_map_fromIterable: true
prefer_function_declarations_over_variables: true
prefer_if_null_operators: true
prefer_initializing_formals: true
prefer_inlined_adds: true
prefer_interpolation_to_compose_strings: true
prefer_is_empty: true
prefer_is_not_empty: true
prefer_is_not_operator: true
prefer_iterable_whereType: true
prefer_null_aware_method_calls: true
prefer_spread_collections: true
prefer_typing_uninitialized_variables: true
# 好みによって切り替える(trueにすると大変そう)
public_member_api_docs: true
recursive_getters: true
require_trailing_commas: true
sized_box_for_whitespace: true
sized_box_shrink_expand: true
slash_for_doc_comments: true
sort_child_properties_last: true
sort_constructors_first: true
sort_unnamed_constructors_first: true
type_annotate_public_apis: true
type_init_formals: true
type_literal_in_constant_pattern: true
unawaited_futures: true
unnecessary_await_in_return: true
unnecessary_brace_in_string_interps: true
unnecessary_const: true
unnecessary_constructor_name: true
unnecessary_getters_setters: true
# 好みで切り替える
#unnecessary_lambdas: true
unnecessary_late: true
unnecessary_new: true
unnecessary_null_aware_assignments: true
unnecessary_null_in_if_null_operators: true
unnecessary_nullable_for_final_variable_declarations: true
unnecessary_overrides: true
unnecessary_parenthesis: true
unnecessary_raw_strings: true
unnecessary_string_escapes: true
unnecessary_string_interpolations: true
unnecessary_this: true
unnecessary_to_list_in_spreads: true
use_colored_box: true
use_decorated_box: true
use_is_even_rather_than_modulo: true
use_rethrow_when_possible: true
use_string_buffers: true
use_test_throws_matchers: true
# Pub rules
depend_on_referenced_packages: true
secure_pubspec_urls: true
sort_pub_dependencies: true