Если вы хотите использовать записи в Dart 3.0 и не чувствовать страшного технического долга, примите во внимание следующее:

typedef ErrorResponse = ({int errorCode, String message});

void main() {
  ErrorResponse? error = request();
}

ErrorResponse? request() {
  return (errorCode: 418, message: 'I\'m a teapot');
}

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

Типоопределение Магия

typedefs, система Dart для определения псевдонимов типов, — очень полезный способ избежать многократного написания очень длинных типов. Они позволяют централизовать определения типов, которые затем можно использовать во всей программе. Если вы хотите изменить структуру или порядок данных, вы можете внести централизованное изменение, которое будет применяться везде. Это может сэкономить время, поскольку вы не будете перескакивать с одной функции на другую по мере изменения типов, особенно для записей.

Использование typedef также может сделать более очевидными другие важные аспекты. Из примера видно, что тип возвращаемого значения, допускающий значение NULL, очень понятен и выделяется. Запрос может не всегда возвращать ошибку и вместо этого иметь нулевой регистр (вы хотели бы уточнить это с помощью комментариев к документации). Также ясно, для чего нужна эта запись, но мы могли бы пойти еще дальше и назвать ее HttpErrorResponse, а также дать ей док-комментарий. Ниже будет эта более подробная версия:

/// An HTTP error with a status code and a message.
typedef HttpErrorResponse = ({int errorCode, String message});

void main() {
  HttpErrorResponse? error = request();
}

/// Returns `null` if the request was successful
HttpErrorResponse? request() {
  return (errorCode: 418, message: 'I\'m a teapot');
}

Когда дело доходит до псевдонимов для записей/кортежей, Dart меня впечатлил. Это все еще то, что C# не реализовал, получив альтернативное решение от C# 10. Забавно, что эти классы записей в C# также обсуждаются для Dart в предложении по классам данных. Поскольку Dart 3.0 опирается на множество различных модификаторов классов, я чувствую, что это подойдет.

Записи вместе с определениями типов в Dart 3.0 составляют отличную пару, которую я хотел бы иметь в других языках. Они дают возможность писать гораздо более лаконичный код без необходимости создания множества классов. Вы также можете оставлять комментарии к typedef на тот случай, если вы забудете, что что-то означает.

Используйте именованные поля

Теперь пришло время для более самоуверенной части: всегда предпочитайте использовать именованные поля для записей. Существуют сценарии, в которых данные достаточно просты, чтобы не требовать именования, но даже добавление простых имен может избавить вас от необходимости думать в будущем. Даже деструктурирование становится намного безопаснее. Смешение x и y может привести к логическим ошибкам, которые легко не заметить и которые трудно отладить.

Что касается позиционных полей, у меня есть еще несколько мыслей по этому поводу. . .

Синтаксис. . .

Синтаксис .$ должен быть одним из самых странных дополнений к синтаксису Dart. В другом месте этот символ появляется при интерполяции строк для включения значений. Единственное, что подходит для .$, это то, что он используется для строк и тот факт, что он уже был допустимым идентификатором, например var $a. Хотя я не уверен, что это правильный идентификатор, что делает его лучшим выбором. Это вполне могло быть .#, что более выразительно.

Одним из явных улучшений добавления записей является предотвращение передачи списков объектов. При доступе к элементам в списках используется известный оператор квадратных скобок ([]). На первый взгляд у вас может возникнуть рефлекторная реакция: Почему они просто не использовали это!. Было некоторое обсуждение того, что это не сработает, и они решили использовать .$0 для доступа к позиционным аргументам. Теперь вы можете заметить что-то немного странное в этом, но позвольте мне сначала добавить несколько других мыслей, которые у меня есть.

Одной из альтернатив использованию [] было бы использование .[]. Я не знаю, насколько это может сломать игру, поскольку я не языковой инженер, но, прежде чем вы слишком много думаете об этом, у этого подхода есть более важная проблема. При использовании этого оператора скобок/синтаксиса языки естественным образом указывают любое число, и даже переменные могут быть переданы для динамического получения элементов (или других типов в случаях ключей для карт). Но если и есть что-то, чего Dart пытается избежать, так это динамические и неизвестные типы. Окончательный выбранный синтаксис гарантирует, что указанное число является допустимым, и Dart может гарантировать возвращаемый тип. Кто знал, что так много можно сказать, когда речь идет только о синтаксических решениях!

Не начиная с нуля

Теперь давайте вернемся к тому, о чем я упоминал ранее: переходим к .$0 для доступа к позиционным полям. Это был синтаксис до тех пор, пока не была открыта проблема GitHub, чтобы пересмотреть это: https://github.com/dart-lang/language/issues/2638. Что мне кажется интересным в этой теме, так это то, как было принято окончательное решение. Уже по первой реакции на проблему видно, что разработчики предпочитают мышление начать с нуля. Но был один разработчик, который воспользовался случаем и сделал небольшой скрейпинг. Они просмотрели пакеты Dart и другие кодовые базы Dart и подсчитали случаи, когда последовательности параметров с одинаковыми именами начинались с единицы по сравнению с нулем. Их результаты довольно подробные: https://github.com/dart-lang/language/issues/2726#issuecomment-1379740674.

Вот их вывод:

Хотя многие последовательности начинаются с нуля, они сильно сконцентрированы в нескольких пакетах, таких как ffigen и realm. Когда вы рассматриваете каждый пакет как один голос за данный стиль, тогда существует гораздо большее количество пакетов, содержащих последовательности, основанные на единице. Если вы посмотрите на них, то увидите, что каждый пакет, основанный на единице, имеет довольно небольшое количество последовательностей. Но таких пакетов много. Это говорит о том, что большинство пользователей, вручную создающих параметры типа и последовательности параметров, предпочитают начинать их с одного.

Основываясь на этом, я думаю, что мы должны начать получать поля позиционных записей тоже с одного.

Представленные результаты и сделанный вывод очень убедительны, но я также думаю, что эта проблема и собранные данные кроются не только в этом. В самом скрипте использовалось следующее RegEx: ^([a-zA-Z_]+)([0–9]+)$. Если вы потратили время на понимание этой тайной магии, вы должны знать, что для этого требуется, чтобы любое слово, на которое вы смотрите, заканчивалось числом.

Когда я программирую, я не ставлю нулевой суффикс в конце каждого имени, которое пишу первым. Обычно это более органично и возникает из-за того, что я сталкиваюсь с неожиданным новым значением, которое требуется. Например:

typedef ErrorResponse = ({int errorCode, String message});

void main() {
  ErrorResponse? response = request();
  ErrorResponse? response1 = request();
}

ErrorResponse? request() {
  return (errorCode: 418, message: 'I\'m a teapot');
}

Я обнаружил, что обычно по умолчанию использую этот стиль, когда итерирую и работаю над проблемой. (Я также знаю, что это не параметры или параметры типа, но именование — это общий процесс). В конечном итоге я считаю ненужным добавлять нулевой регистр.

Возвращаясь к регулярному выражению, он пропустит первый случай имени, в котором в конце нет числа. Это может исказить результаты. Существует также другой случай, когда числа или даже символ $, появляющиеся между символами, приводят к тому, что все имя не совпадает и не включается в набор данных, даже если оно заканчивается цифрой.

Подвести итоги

Хотя мы могли бы углубиться в это подробнее, .$1 ничего не ломает и не делает ничего менее функциональным. Я действительно думаю, что есть лучшие практики для соглашений об именах, чем просто добавление чисел в конец, когда дело доходит до именования переменных, но мы также специально рассматриваем параметры и параметры типа, которые требуют статического именования и надежных гарантий того, к чему они относятся. Что дал Dart 3.0, так это функциональную и гибкую систему для записей.

Надеюсь, вы смогли узнать что-то новое и найти лучшее из того, что может предложить Dart. Есть несколько тем, в которые я мог бы углубиться, например, правильное название, но я не хочу слишком далеко отходить от главного развлекателя этого поста: записей!