Irbisa · Шпаргалка

Линты, анализатор и ревью кода

analysis_options, наборы линтов, dart fix, dart format, custom_lint, аннотации, ревью PR

Middle-разработчик · 21 вопрос

  1. 01

    Что такое analysis_options.yaml и что в нём можно настроить?

    analysis_options.yaml лежит в корне пакета и настраивает анализатор Dart: какой набор правил наследуется, какие дополнительные линты включены, с какой строгостью сообщается каждая диагностика, какие файлы пропускаются и какая ширина строки у форматтера.

    include: package:flutter_lints/flutter.yaml analyzer:  exclude:    - "**/*.g.dart"    - "**/*.freezed.dart"…
  2. 02

    Чем отличаются dart analyze, dart fix и dart format?

    dart analyze сообщает о проблемах, не трогая код, dart fix применяет автоматические исправления, которые анализатор знает для этих проблем, а dart format переписывает пробелы и переносы строк в единый официальный стиль.

    dart analyze                  # report only; infos do not faildart analyze --fatal-infos    # what CI should runflutter analyze               # infos are already fatal here dart fix --dry-run            # preview automated fixes, grouped by ruledart fix --apply --code=prefer_const_constructors…
  3. 03

    Если включён flutter_lints, что flutter analyze сообщит об этом виджете и как исправить каждую проблему?

    class Greeting extends StatelessWidget {  Greeting({this.name});  final String? name;   @override  Widget build(BuildContext context) {    print('build $name');    return Column(children: [      Container(height: 16, child: const Text('Hi')),      Container(child: Text(name ?? 'guest')),      const Icon(Icons.star, color: Color(0xFF00)),    ]);  }}

    Шесть info: нет key, конструктор мог бы быть const, вызов print, Container, который служит лишь SizedBox, Container, который ничего не делает, и цвет со слишком коротким hex.

  4. 04

    Чем отличаются lints, flutter_lints и very_good_analysis и как выбрать один из них?

    Все три представляют собой пакеты с готовым набором правил, который подключается через include: lints содержит базовый набор от команды Dart, flutter_lints добавляет поверх него правила для Flutter и приходит с flutter create, а very_good_analysis даёт гораздо более строгий сторонний набор.

    # Flutter app default, generated by flutter createinclude: package:flutter_lints/flutter.yaml # Pure Dart package:   include: package:lints/recommended.yaml# Strict third-party:  include: package:very_good_analysis/analysis_options.yaml…
  5. 05

    Как изменить строгость линта и какие правила стоит повышать до error?

    В секции analyzer: errors: любой код диагностики можно перевести в error, warning, info или ignore.

    include: package:flutter_lints/flutter.yaml analyzer:  errors:    use_build_context_synchronously: error   # crash after an await    unawaited_futures: error                 # error with no catch to reach…
  6. 06

    Какие линты во Flutter ловят настоящие баги, а не стиль, и что предотвращает каждый из них?

    Небольшая группа: линты, которые ловят всегда ложные сравнения, сломанное равенство, future без await, BuildContext после того, как виджет уже исчез, и секреты, напечатанные в лог устройства.

    // Always false, and it compilesfinal ids = <int>[1, 2, 3];const raw = '2';final found = ids.contains(raw); // collection_methods_unrelated_typefinal same = raw == 2; // unrelated_type_equality_checksfinal fixed = ids.contains(int.parse(raw)); // right…
  7. 07

    Найдите баг: при ответе 404 экран профиля остаётся на индикаторе загрузки, а в консоли появляется необработанная ошибка.

    enum ApiFailure { notFound, server } Future<User> fetchUser(http.Client client, Uri url) async {  final res = await client.get(url);  if (res.statusCode == 404) throw ApiFailure.notFound;  if (res.statusCode != 200) throw ApiFailure.server;  return User.fromJson(jsonDecode(res.body) as Map<String, dynamic>);} Future<void> loadProfile(Uri url) async {  try {    profile.value = ProfileLoaded(await fetchUser(client, url));  } on Exception catch (e) {    profile.value = ProfileError(e);  }}

    ApiFailure представляет собой обычный enum, а не Exception, поэтому on Exception catch его не ловит.

  8. 08

    Чем отличаются // ignore:, // ignore_for_file: и analyzer: exclude: и что из этого допустимо в PR?

    // ignore: глушит одно правило в одной строке, // ignore_for_file: глушит его во всём файле, включая код, который напишут позже, а exclude: полностью убирает файлы из анализа вместе с ошибками.

    // wrong in hand-written code: every lint in this file, including future ones// ignore_for_file: type=lint // right: one rule, one line, and a reason the reviewer can check// ignore: avoid_dynamic_calls -- payload shape is validated in decode()String title(dynamic json) => json['title'] as String;…
  9. 09

    Что делают @override, @immutable, @protected, @visibleForTesting и @mustCallSuper и что на самом деле их проверяет?

    Это метаданные, которые читает анализатор: @override из dart:core, остальные из package:meta, который реэкспортирует foundation во Flutter.

    import 'package:flutter/foundation.dart'; // re-exports package:meta annotations @immutableclass Filter {  const Filter(this.tags);  final List<String> tags; // passes must_be_immutable, yet tags.add() works…
  10. 10

    Найдите баг: DevTools показывает в памяти на один ChatViewModel больше каждый раз, когда пользователь открывает и закрывает чат.

    abstract class ViewModel {  final _subs = <StreamSubscription<Object?>>[];  void watch<T>(Stream<T> s, void Function(T) f) => _subs.add(s.listen(f));  Future<void> dispose() => Future.wait([for (final s in _subs) s.cancel()]);} class ChatViewModel extends ViewModel {  ChatViewModel(ChatRepository repo) {    watch(repo.messages(), messages.add);  }  final messages = <Message>[];  Timer? _typing;   @override  Future<void> dispose() async => _typing?.cancel();}

    ChatViewModel.dispose переопределяет базовый метод, не вызывая super.dispose(), поэтому подписки, созданные через watch, никогда не отменяются.

  11. 11

    Как переименовать публичный метод в пакете, от которого зависят другие приложения, не сломав им сборку?

    Добавьте новое имя, оставьте старое как метод-прокладку с @Deprecated('Use X instead') и удалите его только в следующей мажорной версии.

    class UserApi {  // The new name holds the only implementation.  User parseUser(String body) => User.fromJson(jsonDecode(body) as Map<String, dynamic>);   @Deprecated('Use parseUser instead. Will be removed in 3.0.0.')  User decodeUser(String body) => parseUser(body); // forwards, never copies…
  12. 12

    Как поддерживать единое форматирование в команде и что изменилось в dart format с Dart 3.7?

    Пусть решает инструмент: все запускают dart format, CI падает на любом диффе, и никто не спорит о вёрстке кода на ревью.

    # analysis_options.yaml, read by the IDE, the CLI and CI:#   formatter:#     page_width: 100#     trailing_commas: preserve   # Dart 3.8+: keep commas as written # CI gate: exit 1 if any file would change…
  13. 13

    Что должно быть в pre-commit хуке, а что в CI, и почему хук не может быть главной проверкой качества?

    Проверкой служит CI, а хук остаётся удобством: хуки лежат в .git/hooks, который не клонируется, а git commit --no-verify пропускает их намеренно.

    # lefthook.yml: committed; `lefthook install` wires .git/hooks after a clonepre-commit:  commands:    format:      glob: "*.dart"      run: dart format {staged_files}…
  14. 14

    dart analyze падает в CI на коде, который IDE показывает чистым. Анализатор тот же. Что отличается?

    Инструмент тот же, отличаются входные данные.

  15. 15

    Как найти неиспользуемый код, зависимости и ассеты во Flutter-проекте и чего не найдёт ни один инструмент?

    Анализатор доказывает что-то только в пределах одной библиотеки: неиспользуемые импорты, приватные члены и мёртвые ветки.

  16. 16

    Этот State проходит анализатор и свой виджет-тест. Что вы отметите на код-ревью?

    class _OrderPageState extends State<OrderPage> {  final _note = TextEditingController();  Order? _order;  @override  void initState() {    super.initState();    _load();  }  Future<void> _load() async {    final order = await OrderRepository().fetch(widget.orderId);    setState(() => _order = order);  }  @override  Widget build(BuildContext context) =>      Column(children: [Text(_order?.title ?? 'Loading'), TextField(controller: _note)]);}

    Пять проблем, которые не ловит ни один линт: контроллер не освобождается, ошибка загрузки не обрабатывается, setState может выполниться после того, как виджет исчез, репозиторий создаётся внутри виджета, а новый orderId игнорируется.

  17. 17

    Половину PR написал ИИ-ассистент. Что вы проверите перед апрувом, помимо того, что ловят flutter analyze и тесты?

    Планка та же, что для любого PR, но с прицелом на слабые места сгенерированного кода: непроверенные зависимости, код вне соглашений проекта, заглушённые ошибки и проверки безопасности, а также тесты, которые проходят при любом коде.

    // All three pass `flutter analyze` with flutter_lints; none should be approved. HttpClient trustingClient() => HttpClient()  ..badCertificateCallback = (cert, host, port) => true; // accepts any certificate Future<void> onSave(OrderRepository repo, Order order) async {…
  18. 18

    Как вы проводите код-ревью во Flutter-команде: что ищете помимо анализатора и как формулируете комментарии?

    Форматирование и линты отдайте инструментам, а человеческое внимание тратьте на то, чего они не видят: стейт и жизненный цикл, пути ошибок, границы слоёв и тесты.

  19. 19

    Как превратить повторяющийся комментарий на ревью в собственное правило линтера и как заставить его работать в CI?

    Напишите плагин анализатора, который обходит разрешённое AST и сообщает диагностику.

    // Rules package, built on custom_lint_builder.// CI must run it separately: dart run custom_lintPluginBase createPlugin() => _HouseLints(); class _HouseLints extends PluginBase {  @override…
  20. 20

    Ревьюер хочет, чтобы CI падал, когда файл длиннее 300 строк или цикломатическая сложность функции больше 10. Вы согласитесь?

    Используйте метрики как сигнал в pull request, а не как жёсткий барьер: число говорит, какой код стоит прочитать, но не говорит, хорош ли он, а жёсткий порог начинают обходить.

  21. 21

    Строгий набор правил в приложении на 200 000 строк выдаёт 4 000 замечаний. Как внедрить его без одного неревьюабельного диффа?

    Включайте поэтапно: сначала dart fix, затем оставьте оставшиеся правила видимыми, но неблокирующими для старого кода и блокирующими для кода, который трогает PR, и разбирайте долг по одному правилу небольшими PR.

    # 1. The mechanical share, in its own commit, with no manual editsdart fix --dry-rundart fix --apply                      # or --code=<rule> to keep it reviewablegit commit -am "chore: dart fix --apply (machine-generated)" # 2. Rank what is left; in --format=machine field 3 is the diagnostic code…