Урок 24 из 25 · Месяц 6. Чат и публикация приложений
Мок-интервью и code review
Содержание урока
Техническое собеседование и code review — два навыка, без которых не обходится ни одна работа в команде. На собеседовании вас проверяют, понимаете ли вы, почему код работает, а на code review — умеете ли вы писать код, который удобно читать другим. В этом уроке разберём, как проходит собеседование, частые вопросы на junior и middle Flutter-разработчика с краткими ответами, а также как устроен code review и по какому чек-листу проверять код.
Как проходит собеседование
Обычно техническое собеседование на Flutter-разработчика состоит из нескольких частей:
- Знакомство (5–10 минут) — расскажите о себе и проектах. Подготовьте рассказ на 2 минуты: что делали, на каком стеке, какую задачу решили сами.
- Теория (20–30 минут) — вопросы по Dart, Flutter, управлению состоянием, архитектуре.
- Практика (20–40 минут) — live-coding (написать функцию или виджет), разбор чужого кода или обсуждение вашего тестового задания.
- Ваши вопросы — о команде, проекте, процессах. Задайте хотя бы один: это показывает интерес.
Мок-интервью (mock — «пробный») — репетиция собеседования: ментор или одногруппник играет роль интервьюера. Это лучший способ перестать волноваться и найти пробелы в знаниях.
Вопросы по Dart
Чем final отличается от const?
final — значение задаётся один раз, но может вычисляться во время работы (final now = DateTime.now()). const — значение известно на этапе компиляции, а одинаковые const-объекты — это один объект в памяти. Поэтому const-виджеты не пересоздаются при перерисовке.
Что такое null safety?
Тип String не может быть null, а String? — может. Компилятор заставляет проверить null до использования. Оператор ! говорит «я уверен, что тут не null» — если ошиблись, будет ошибка во время работы.
Чем Future отличается от Stream?
Future — одно значение в будущем, Stream — последовательность значений (см. урок о чате и потоках).
Как Dart выполняет асинхронный код, если он однопоточный?
Через event loop: есть очередь микрозадач (microtask) и очередь событий. await не блокирует поток — функция «встаёт на паузу», а цикл событий выполняет другие задачи. Для тяжёлых вычислений (парсинг огромного JSON) используют Isolate — отдельный поток со своей памятью, например через Isolate.run.
final result = await Isolate.run(() => heavyParse(bigJsonString));
Что такое mixin и extension?
mixin — набор методов, который можно «подмешать» в класс через with, без наследования. extension — добавляет методы к существующему типу, не меняя его ('text'.capitalize()).
Что такое sealed class и зачем он?
Класс, у которого все наследники известны в пределах файла. Компилятор проверяет, что switch обработал все варианты, — удобно для событий и состояний Bloc.
Вопросы по Flutter
Что такое три дерева во Flutter? Widget — неизменяемое описание («чертёж»). Element — живой экземпляр в дереве, связывает виджет и его место. RenderObject — отвечает за размеры, расположение и отрисовку. Виджеты пересоздаются часто и дёшево, а Element и RenderObject по возможности переиспользуются.
Чем StatelessWidget отличается от StatefulWidget?
У Stateless нет изменяемого состояния — он зависит только от параметров. У Stateful есть объект State, который живёт дольше самого виджета и может вызвать setState, чтобы перерисоваться.
Назовите жизненный цикл State.
createState → initState (один раз, подписки и контроллеры) → didChangeDependencies → build (много раз) → didUpdateWidget (родитель передал новые параметры) → dispose (освобождаем ресурсы).
Что такое BuildContext?
Это ссылка на Element — место виджета в дереве. Через него ищут предков: Theme.of(context), context.read<Bloc>().
Зачем нужны Key?
Чтобы Flutter правильно сопоставлял виджеты при перестановке однотипных элементов. Без ключей при удалении элемента из списка состояние (например, текст в поле) может «переехать» к соседу. Для элементов списка используют ValueKey(item.id).
Почему не стоит делать тяжёлую работу в build?
build вызывается очень часто — при каждом setState, смене темы, анимации. Запросы и создание потоков в build приводят к лишним запросам и подтормаживаниям.
Что такое hot reload и hot restart? Hot reload подгружает изменённый код и сохраняет состояние (работает благодаря JIT). Hot restart перезапускает приложение с нуля, состояние сбрасывается. Подробнее — в уроке о JIT и AOT.
Вопросы по состоянию и архитектуре
Чем Bloc отличается от Cubit?
В Cubit вызываете методы (cubit.increment()), в Bloc — отправляете события (bloc.add(Increment())). Bloc многословнее, но даёт историю событий и трансформеры (debounce, отмена предыдущих запросов). Cubit проще для простой логики.
BlocBuilder, BlocListener, BlocConsumer, BlocSelector — в чём разница?
Builder перерисовывает UI, Listener выполняет одноразовые действия (навигация, SnackBar), Consumer — оба сразу, Selector перерисовывает только при изменении выбранной части состояния (урок).
Зачем Equatable в состояниях?
Bloc не выдаёт новое состояние, если оно == текущему. Без Equatable сравниваются ссылки, и одинаковые по содержанию состояния считаются разными.
Расскажите о Clean Architecture. Три слоя: presentation (UI и Bloc), domain (сущности, UseCase, абстрактные репозитории — чистый Dart без Flutter), data (модели, источники данных, реализации репозиториев). Зависимости направлены внутрь, к domain. Плюсы: тестируемость, замена источников данных без переписывания UI.
Что такое DI и зачем GetIt?
Dependency Injection — объект получает зависимости снаружи, а не создаёт сам. GetIt — контейнер (service locator), где регистрируются зависимости: registerLazySingleton, registerFactory. Это позволяет подменять реализации в тестах.
Чем Riverpod отличается от Provider?
Riverpod не зависит от BuildContext, ошибки находятся на этапе компиляции, провайдеры можно комбинировать и переопределять в тестах.
Вопросы по сети и данным
- Как обработать ошибки Dio? Поймать
DioExceptionи посмотретьtype(таймаут, нет соединения, плохой ответ) иresponse?.statusCode; превратить в понятную доменную ошибку. - Где хранить токен? В
flutter_secure_storage(Keychain/Keystore), не вSharedPreferences. - Как обновить истёкший токен? Интерцептор Dio: на 401 обновить токен и повторить запрос.
- Как сделать пагинацию? Хранить номер страницы и флаг
hasReachedMax, догружать при прокрутке к концу, не запускать второй запрос, пока идёт первый.
Как проходит code review
Code review — проверка кода коллегами перед тем, как он попадёт в основную ветку. Процесс обычно такой:
- Вы создаёте ветку (
feature/cart-badge), делаете изменения и открываете Pull Request (PR, в GitLab — Merge Request). - В описании PR пишете: что сделано, зачем, как проверить, скриншоты для UI.
- Ревьюер читает изменения и оставляет комментарии к строкам.
- Вы исправляете или объясняете своё решение, ревьюер одобряет (Approve), код вливается.
Правила хорошего тона:
- PR маленький — до 300–400 строк. Огромный PR невозможно нормально проверить.
- Критикуют код, а не человека: «здесь может быть утечка подписки», а не «ты опять забыл».
- Объясняйте, почему: не «переделай», а «вынеси в UseCase — так проще тестировать».
- Разделяйте важное и вкусовое: префикс
nit:(nitpick, мелочь) означает «можно не исправлять».
Пример ревью
Посмотрим на код, который пришёл на ревью:
class ProductsPage extends StatefulWidget {
@override
_ProductsPageState createState() => _ProductsPageState();
}
class _ProductsPageState extends State<ProductsPage> {
var products = [];
@override
Widget build(BuildContext context) {
Dio().get('https://api.shop.com/products').then((r) {
setState(() => products = r.data);
});
return ListView(
children: products.map((p) => Text(p['title'])).toList(),
);
}
}
Комментарии ревьюера:
- Запрос в
build—setStateвызываетbuild, аbuildснова делает запрос: бесконечный цикл запросов. Загрузку нужно делать вinitStateили, лучше, в Bloc. - Нет обработки ошибок и загрузки — при плохой сети пользователь видит пустой экран.
var products = []— этоList<dynamic>, аp['title']без типов. Нужна модельProduct.fromJson.- Новый
Dio()в виджете — нет общих настроек и интерцепторов; Dio получаем через DI. ListView(children:)для списка с сервера — создаёт все элементы сразу; нуженListView.builder.- nit: нет
constиsuper.keyв конструкторе.
Исправленный вариант в духе курса:
class ProductsPage extends StatelessWidget {
const ProductsPage({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => getIt<ProductsBloc>()..add(ProductsRequested()),
child: BlocBuilder<ProductsBloc, ProductsState>(
builder: (context, state) => switch (state) {
ProductsLoading() => const Center(child: CircularProgressIndicator()),
ProductsFailure(:final message) => Center(child: Text(message)),
ProductsSuccess(:final products) => ListView.builder(
itemCount: products.length,
itemBuilder: (_, i) => ListTile(title: Text(products[i].title)),
),
},
),
);
}
}
Здесь ProductsState — sealed-класс, а ProductsFailure(:final message) — паттерн Dart 3, который сразу достаёт поле из состояния.
Чек-лист для code review
| Область | Что проверить |
|---|---|
| Корректность | Код делает то, что заявлено; учтены пустой список, null, ошибка сети |
| Архитектура | Логика не в виджетах; слои не перепутаны; зависимости через DI |
| Ресурсы | Контроллеры, подписки, таймеры закрыты в dispose/close |
| Производительность | Нет запросов в build; const где можно; ListView.builder для длинных списков |
| Типы | Нет лишних dynamic и !; модели вместо Map |
| Ошибки | try/catch там, где может упасть; пользователь видит понятное сообщение |
| Читаемость | Понятные имена; нет закомментированного кода и print; функции небольшие |
| Безопасность | Нет ключей и токенов в коде; токены не пишутся в логи |
| Проверки | flutter analyze без предупреждений, dart format применён, тесты проходят |
Типичные ошибки
- Заучивать определения без понимания — первый же уточняющий вопрос «а почему?» ставит в тупик.
- Рассказывать о проекте общими словами («делал приложение») вместо конкретики: какая задача, какое решение, что получилось.
- Спорить с ревьюером вместо того, чтобы объяснить своё решение или согласиться.
- Отправлять PR «на 2000 строк» без описания и скриншотов.
- Писать замечания без причины («плохо», «переделай») — автор не поймёт, что именно не так.
Практика
- Запишите свой рассказ о себе на 2 минуты и прослушайте его. Ожидаемый результат: в рассказе есть стек, ваш лучший проект и задача, которую вы решили сами.
- Ответьте вслух, не подглядывая, на 10 вопросов из урока, затем сверьтесь с ответами. Ожидаемый результат: список тем, где ответ был неуверенным, — их повторите.
- Проведите мок-интервью с одногруппником: 20 минут вы спрашиваете, 20 — он. Ожидаемый результат: каждый получает обратную связь — что получилось и что подтянуть.
- Найдите в своём старом проекте 5 проблем по чек-листу и исправьте их отдельным PR с понятным описанием. Ожидаемый результат: PR, который можно отправить ментору на ревью.
- Сделайте ревью PR одногруппника: минимум 3 содержательных комментария с объяснением «почему» и пометьте мелочи как
nit:. Ожидаемый результат: автор понимает, что и зачем исправить.
Итоги
- Собеседование — это знакомство, теория, практика и ваши вопросы; мок-интервью снимает волнение.
- Отвечая, объясняйте «почему», а не повторяйте определения; не знаете — рассуждайте вслух.
- Ключевые темы: Dart (null safety, async, isolates), деревья и жизненный цикл Flutter, Bloc, Clean Architecture, DI, сеть.
- Code review — проверка кода коллегами через Pull Request; PR должен быть маленьким и описанным.
- Комментируйте код, а не человека, объясняйте причину и отделяйте важное от
nit:. - Перед отправкой PR сделайте самопроверку по чек-листу и прогоните
flutter analyze.