Система типов и null safety
Система типов Dart и то, что меняется, когда относиться к ней как к инструменту
проектирования, а не как к формальности. Строгая (sound) null safety означает,
что компилятор способен доказать: значение не равно null — но только если ему
это позволить, а большинство кодовых баз за первый же месяц разменивают эту
гарантию на !.
Почему это важно. Любое состояние, которое разрешают ваши типы, но запрещает продукт, — это баг, ожидающий подходящего пользователя. В модели, где одновременно могут быть истинны «доставлено» и «дата доставки не задана», рано или поздно так и произойдёт.
Что важно понимать
- Что обещает тип и правда ли это обещание
- Почему значение nullable — оно действительно необязательное, ещё не загружено или создаётся лениво
- Где
!— это доказательство, которое есть у вас и нет у компилятора, а где — просто надежда - Можно ли вообще выразить недопустимую комбинацию полей
- Во что обходится
dynamicи где он появляется, не спросив
Ключевые темы
Система типов
- Строгая типизация и что даёт её строгость
- Обобщённые типы (generics) и код, который обобщён потому, что иначе нельзя
- Вариантность в одном предложении: почему в Dart
List<Dog>— этоList<Animal>и какой проверкой во время выполнения за это приходится платить dynamicпротивObject?— второй заставляет посмотреть, что внутри
Null safety
- Nullable- и non-nullable-типы, и
lateкак обещание, за которое платят во время выполнения - Null-aware операторы и то, какие из них прячут проблемы
!— четыре честных применения и множество нечестных- Миграция кодовой базы и почему уродливое промежуточное состояние временно
Моделирование типами
- Sealed-классы и исчерпывающий
switch - Записи (records) для значений, которые ходят вместе, но не заслуживают отдельного класса
- Сопоставление с образцом и деструктуризация
- Перечисления с полями и методами вместо разбросанных цепочек
if - Недопустимые состояния, которые невозможно выразить, — вместо проверок на каждом шагу
Уровни
| Уровень | Как это выглядит |
|---|---|
| Junior | Пишет корректные аннотации типов. Устраняет ошибки с null, иногда через !. |
| Middle | Осознанно применяет обобщённые типы и sealed-классы. Может объяснить, почему поле nullable, — или убирает эту nullability. |
| Senior | Проектирует модели, в которых неверные состояния не компилируются. Использует исчерпываемость так, чтобы новый вариант ломал ровно тот код, который нужно изменить. |
Практика
Для начала
-
Уберите восклицательные знаки Возьмите файл, где встречается несколько
!, и перестройте код так, чтобы большинство из них стали не нужны. -
Обоснуйте каждый nullable Пройдите по модели и для каждого nullable-поля напишите, почему оно такое. Те, на которые ответа нет, исправьте.
-
Замените строку перечислением Найдите статус, который хранится как
String, сделайте из него enum и дальше идите за компилятором.
Глубже
-
Смоделируйте конечный автомат Замените класс с полями
isLoading,errorиdataна sealed-класс, в котором существуют только допустимые сочетания. -
Используйте исчерпываемость намеренно Добавьте новый вариант в sealed-тип и дайте компилятору показать все места, которые обязаны его обработать.
-
Напишите настоящий обобщённый код Вынесите повторяющийся паттерн в обобщённую функцию или класс с осмысленным ограничением типа.
Проверьте себя
- Какие nullable-поля в вашем проекте такие только потому, что загружаются позже?
- Когда вы пишете
!, что вы знаете такого, чего не знает компилятор? - Какие сочетания полей разрешает ваша модель, но запрещает продукт?
- Где
dynamicпопал в код без вашего решения? - Как найти все места, которые придётся поменять при добавлении нового варианта?
- Когда обобщение делает код переиспользуемым, а когда — нечитаемым?
Материалы
- The Dart type system — как устроена строгость типизации, включая разделы про обобщённые типы и вывод типов, которые почти никто не читает, а потом неверно ставит диагноз.
- Sound null safety — официальное
объяснение: анализ потока управления и то, во что на самом деле обходится
lateво время выполнения. - Patterns — деструктуризация, switch-выражения и исчерпываемость. Именно эта возможность делает моделирование на sealed-классах практичным, а не многословным.
- Effective Dart: Design — рекомендации самой команды языка по типам в публичных API. Сжато, и к каждому пункту приложено обоснование.
- Making illegal states unrepresentable — написано про F#, но это самая ясная формулировка идеи, и она напрямую переносится на sealed-классы в Dart.