Использование wildcard-переменной _ в Dart 3

от автора

С выходом Dart 3 в язык были добавлены значительные нововведения, включая рекорды, паттерн-матчинг и модификаторы классов. Данная статья посвящена менее обсуждаемой, но полезной возможности, улучшающей чистоту и выразительность кода — wildcard-переменной, обозначаемой символом _.

Символ _ в определенных контекстах позволяет явно указать на намерение разработчика проигнорировать некоторое значение. Рассмотрим сценарии использования и преимущества этого механизма.

Проблема: Неиспользуемые переменные

Разработчики часто сталкиваются с ситуациями, когда необходимо объявить переменную, значение которой не используется в дальнейшем коде, например:

  1. Параметры колбэков:

    Dart

    future.then((value) { // Параметр 'value' объявлен согласно сигнатуре   print('Операция завершена!');   // 'value' не используется в теле колбэка }).catchError((error, stackTrace) { // Параметр 'stackTrace' может быть не нужен   _logError(error);   // 'stackTrace' не используется }); 
  2. Индекс при итерации (если нужен только элемент):

    Dart

    final List<String> items = ['яблоко', 'банан', 'апельсин']; items.asMap().forEach((index, item) { // 'index' объявляется   print('Фрукт: $item');   // 'index' не используется }); 
  3. Частичная деструктуризация:

    Dart

    (String, int, bool) fetchUserInfo() => ('Alice', 30, true);  final (String name, int age, bool isActive) userInfo = fetchUserInfo(); // Требуется только 'age' final userAge = userInfo.$2; print('Возраст пользователя: $userAge'); // 'name' и 'isActive' объявлены, но не используются 

Во всех перечисленных случаях статический анализатор (линтер) может генерировать предупреждения (unused_local_variable, unused_element). Игнорирование этих предупреждений или использование имен переменных вроде unused не решает проблему по существу и может ухудшать читаемость кода.

Решение: Wildcard _

Wildcard _ представляет собой специальный синтаксис, который информирует компилятор и других разработчиков о том, что значение в данном месте намеренно игнорируется.

Ключевые особенности _:

  1. Не создает переменную (в контексте паттернов): При использовании _ внутри паттернов (деструктуризация, switch, if-case) Dart не создает локальную переменную. Выполняется проверка соответствия значения паттерну, но само значение не сохраняется. Это может потенциально снизить потребление памяти для игнорируемых объектов.

  2. Многократное использование: можно использовать несколько раз в одной области видимости или паттерне без конфликтов имен. Каждый экземпляр функционирует как независимый маркер игнорирования.

  3. Подавление предупреждений линтера: Применение _ для игнорируемых значений является семантически корректным способом избежать предупреждений об неиспользуемых переменных.

Рассмотрим обновленные примеры с использованием _.

1. Игнорирование параметров в колбэках

Dart

// Пример с forEach: items.asMap().forEach((_, item) { // Использование '_' для индекса   // Указывает, что индекс не требуется, используется только значение.   print('Фрукт: $item'); });  // ------  // Пример с Future: Future<String> fetchData() async { /* ... */ return "Data"; } Future<void> saveToDb() async { /* ... */ } Future<void> cacheLocally() async { /* ... */ }  Future<void> processData() async {   // Использование Future.wait, результаты отдельных Future не важны.   await Future.wait([     saveToDb(),     cacheLocally(),   ]).then((_) { // Использование '_' для списка результатов Future.wait     // Сигнализирует, что конкретные результаты не интересуют, важен факт завершения.     print('База данных и кэш обновлены!');   });    final result = await fetchData();   await saveToDb().then((_) { // Результат saveToDb() (void) игнорируется.      print('Данные сохранены, результат $result');   }); } 

Объяснение: В примере с forEach указывает на игнорирование индекса. В примере с Future.wait в then используется для игнорирования списка результатов, так как важно только завершение всех операций.

2. Итерация по Map без ключа (или без значения)

Dart

final Map<String, int> scores = {'Alice': 100, 'Bob': 85, 'Charlie': 92};  // Обработка только значений (очков) print('Обрабатываем очки:'); for (final (_, score) in scores.entries) { // Использование '_' для ключа   // При деструктуризации MapEntry ключ игнорируется.   // Это лаконичнее, чем доступ через entry.value.   print('Получено очков: $score'); }  // Обработка только ключей (имен) print('\nОбрабатываем имена:'); for (final (name, _) in scores.entries) { // Использование '_' для значения   // Аналогично, игнорируется значение.   print('Игрок: $name'); } 

Объяснение: При итерации по scores.entries деструктуризация (key, value) в цикле for-in позволяет с помощью _ сразу отбросить ненужную часть (key или value), фокусируясь на используемых данных.

3. Деструктуризация с игнорированием

_ эффективен при работе со структурами данных, такими как списки и рекорды.

Списка:

Dart

final List<int> coordinates = [10, 20, 30, 40, 50];  // Получение второго и третьего элементов final [_, y, z, ...] = coordinates; // '_' для первого, '...' для остальных после z // Указывает на игнорирование первого элемента и всех после третьего. print('Координаты YZ: ($y, $z)'); // Вывод: Координаты YZ: (20, 30)  // Получение первого и последнего final [first, ..., last] = coordinates; // '...' игнорирует элементы между первым и последним print('Первый: $first, Последний: $last'); // Вывод: Первый: 10, Последний: 50 

Рекордов (Records):

Dart

(int, int, int) getPixelColor() => (255, 170, 187); // RGB  // Получение зеленого (G) и синего (B) каналов final (_, g, b) = getPixelColor(); // '_' для красного (R) канала // Код явно показывает игнорирование красного канала. print('Зеленый: $g, Синий: $b'); // Вывод: Зеленый: 170, Синий: 187 

Объяснение: При деструктуризации _ позволяет пропустить ненужные элементы структуры данных, делая намерение игнорировать часть данных более очевидным.

4. Паттерн-матчинг в switch и if-case

_ является важным элементом синтаксиса паттерн-матчинга, позволяя сопоставлять «любое значение» или служить аналогом default.

Dart

String checkPair(Object record) {   return switch (record) {     // Сопоставление рекорда (пары): первый элемент - int, второй - любой (игнорируется)     (int count, _) => 'Найдено $count элементов.',     // Сопоставление рекорда: первый элемент - любой (игнорируется), второй - String     (_, String label) => 'Найдена метка "$label".',     // Сопоставление по типу, значение не важно     bool _ => 'Это просто булево значение.',     // Wildcard для остальных случаев     _ => 'Неизвестная пара или тип.'   }; }  print(checkPair((10, 'items'))); // Вывод: Найдено 10 элементов. print(checkPair((null, 'User'))); // Вывод: Найдена метка "User". print(checkPair(true));         // Вывод: Это просто булево значение. print(checkPair([1, 2]));      // Вывод: Неизвестная пара или тип.  // ------  // Пример валидации JSON-подобной структуры Map void processJson(Map<String, dynamic> json) {   switch (json) {     // Поиск Map с ключом 'id' (int) и 'data' (любой тип, значение игнорируется)     case {'id': int id, 'data': _}: // Игнорирование значения по ключу 'data'       // Указание на то, что требуется только 'id', а значение 'data' не используется.       // Это может оптимизировать обработку, так как значение 'data' не извлекается.       print('Обрабатываем запись с ID: $id');       handle(id);       break;     case {'error': String message}:       print('Получена ошибка: $message');       break;     default:       print('Неизвестный формат JSON: $json');       throw FormatException('Неверный формат JSON');   } }  void handle(int id) { /* ... */ }  processJson({'id': 123, 'data': {'value': 42}}); // Вывод: Обрабатываем запись с ID: 123 // ... (остальные вызовы processJson и обработка исключения) 

Объяснение: В switch позволяет создавать гибкие паттерны. В case (int count, ) сопоставляется пара, где важен только первый элемент. В примере с JSON case {'id': int id, 'data': _} демонстрирует проверку наличия ключа data без извлечения и использования его значения.

Различие между и variableName

Необходимо различать wildcard и имя переменной, начинающееся с (например, _myPrivateVar).

  • (Wildcard в паттернах): Внутри паттернов является специальной конструкцией, которая не создает переменную и не хранит значение. Попытка чтения из _ в этом контексте приведет к ошибке компиляции.

  • (Вне паттернов): При использовании вне паттернов (например, var = someValue;), формально может вести себя как переменная с именем . Однако такая практика не рекомендуется. Современные анализаторы кода препятствуют чтению значения из такой переменной.

  • variableName: Это обычная переменная. Префикс делает ее приватной для текущей библиотеки (файла), согласно конвенции Dart. Значение такой переменной можно читать и изменять. Линтер выдаст предупреждение, если она не используется.

Рекомендация: Используйте исключительно для обозначения игнорируемого значения. Для переменных, включая приватные, используйте осмысленные имена (internalCounter).

Правило линтера: Правило discard_Namen (включено по умолчанию в рекомендуемых наборах правил для Dart 3) запрещает чтение из переменной с именем _, поощряя использование этого символа строго для игнорирования значений.

Dart

void checkContext(dynamic value) {   // Корректное использование в паттерне:   if (value case [int count, _]) { // Игнорирование второго элемента     print('List starts with count: $count');     // print(_); // Ошибка компиляции   }    // Использование вне паттерна (чтение не рекомендуется):   var _ = value;   // print(_); // Предупреждение линтера `discard_Namen`    // Обычная приватная переменная:   var _internalState = value;   print('Internal state: $_internalState'); // Корректно } 

Преимущества использования _

  1. Повышение читаемости: Код становится яснее, так как намерение игнорировать значение выражено явно.

  2. Четкое выражение намерения: _ служит сигналом о том, что значение не требуется для дальнейшей логики.

  3. Уменьшение «шума»: Сокращается количество неиспользуемых имен переменных.

  4. Поддержка паттерн-матчинга: _ является неотъемлемой частью синтаксиса паттернов.

  5. Совместимость с линтером: Позволяет избежать предупреждений анализатора для намеренно неиспользуемых значений.

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

Заключение

Wildcard-переменная в Dart 3 является полезным инструментом для написания более чистого и поддерживаемого кода. Она позволяет явно указывать на игнорируемые значения в различных контекстах, от колбэков до паттерн-матчинга, улучшая общую читаемость и выразительность кода. Рекомендуется использовать в соответствии с его назначением — для игнорирования значений, не предназначенных для дальнейшего использования.


ссылка на оригинал статьи https://habr.com/ru/articles/901932/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *