Официальный анонс Dart 3.9 здесь. А вот два новых линта, которые там не упомянули.
switch_on_type
Есть такой странный способ проверить тип переменной — switch по runtimeType:
class A {} void main() { switch (variable.runtimeType) { case A: print('A'); default: print('Something else'); } }
Проблема в том, что обычно мы хотим, чтобы подклассы A тоже попадали в первый кейс. Иначе мы не сможем подменять объекты, и полиморфизм, как учили в школе, не будет работать. Это большая проблема, потому что:
Объекты могут создаваться в коде, который мы не контролируем, поэтому имплементации могут поменяться без нашего ведома.
Нельзя делать моки для тестов, потому что пакеты типа mocktail и mockito работают через подмену имплементации.
И даже если вы полностью контролируете код, ни к чему создавать такое место, про обновление которого всегда надо помнить, если добавите подкласс.
Чтобы люди так не делали, добавился линт. Он заставляет переписать такой switch через паттерны:
switch (variable) { case A(): print('A'); default: print('Something else'); }
Теперь, если сделать B extends A, он тоже будет покрываться паттерном A(), и все моки будут работать.
Ладно, а как отделить A от B extends A? Надо сначала отбросить всю ветку наследования, которая начинается с B:
switch (variable) { case B(): // Соответствует B и всем его подклассам. break; case A(): // Соответствует A и всем подклассам, кроме B и его подклассов. print('A'); default: print('Нечто совершенно иное'); }
Безопасные типы — мой выбор!
unnecessary_unawaited
Когда вызываем функцию, которая возвращает Future, обычно мы хотим дождаться результата. Иначе выполнение продолжится раньше, чем случится нужный нам эффект:
Future<void> save() { /* ... */ } void fn() { save(); // LINT: discarded_futures close(); // Э, мы ещё не сохранили! }
Поэтому ещё в Dart 2.18 сделали линт discarded_futures, который это помечает.
Чтобы он не срабатывал, нужно или дождаться результата через await, или обернуть вызов в unawaited():
Future<void> save() { /* ... */ } Future<void> fn() async { unawaited(save()); // Это глупо. await save(); // Так-то лучше. close(); }
Однако, есть асинхронные функции, результат которых:
Почти никогда не нужен, поэтому глупо утомлять разработчиков таким линтом.
Иногда всё‑таки нужен, поэтому они всё равно возвращают
Future.
Обычно это логирование, освобождение ресурсов и тому подобное:
Future<LogMessage> log(String message) { ... } void fn() { unawaited(log('Message')); // Результат логирования обычно неважен. }
И вот, чтобы это упростить, в пакет meta добавили аннотацию @awaitNotRequired, чтобы делать функции, которые не триггерят линт, если их не ждать:
@awaitNotRequired Future<LogMessage> log(String message) { ... } void fn() { log('Message'); // Прекрасно! }
А если всё‑таки хочется дождаться результата:
@awaitNotRequired Future<LogMessage> log(String message) { ... } Future<void> fn() async { await log('Message'); // Никаких проблем. }
Но если вы уже расставили везде unawaited(), то он теперь лишний. И новый линт поможет вам найти такие места:
@awaitNotRequired Future<LogMessage> log(String message) { ... } void fn() { unawaited(log('Message')); // LINT: unnecessary_unawaited }
Более старые линты
Если вы пропустили мои предыдущие статьи, то вот:
В Dart 3.3 не было новых линтов.
Как подключить новые линты
Прочитайте эту статью о том, как подключить эти правила вручную.
Или можете использовать в своём проекте мой пакет total_lints, в котором включено большинство правил линтера. Я использую его, чтобы не повторять одну и ту же конфигурацию между своими проектами.
Не пропускайте мои статьи, добавляйтесь в Телеграм‑канал: ainkin_com
Русские переводы реже и с задержкой здесь: ainkin_com_ru
