Connect with us

Разработка

50 вопросов о System Design, ответы на которые должен знать каждый Senior iOS-разработчик: часть 1

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

Опубликовано

/

     
     

Эта статья открывает серию из пятидесяти вопросов по системному проектированию и архитектуре, которые часто встречаются на собеседованиях Senior iOS-разработчиков. Вместо заучивания готовых ответов вы узнаете, как опытные инженеры рассуждают при решении таких задач.

Часть 1. Архитектура приложения: вопросы 1–5

Вопрос 1. Как бы вы спроектировали архитектуру крупного iOS-приложения?

Почему интервьюеры задают этот вопрос

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

Сильный ответ показывает, что вы думаете о масштабируемости, тестировании, модульности и разделении ответственности.

Ответ уровня старшего разработчика

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

Типичная архитектура выглядит так:

Presentation Layer
│
├── SwiftUI Views / UIKit ViewControllers
├── ViewModels
│
Domain Layer
│
├── Use Cases
├── Business Logic
│
Data Layer
│
├── Repository
├── Network Service
├── Local Database
│
Infrastructure
│
├── API Client
├── Cache
├── Analytics
├── Keychain

У каждого слоя должна быть одна зона ответственности.

Слой представления должен только отображать данные.

Доменный слой содержит бизнес-правила.

Слой данных определяет, откуда получать данные: с сервера или из локального хранилища.

Инфраструктурный слой отвечает за переиспользуемые службы.

Такое разделение упрощает тестирование и изменение приложения.

Пример из реального проекта

Представим, что вы разрабатываете приложение интернет-магазина. Многие начинающие разработчики вызывают API напрямую из ViewController.

ProductViewController
↓
API Call
↓
Update UI

На первых этапах это работает. Через шесть месяцев данные о товарах понадобятся ещё одному экрану. В результате одна и та же сетевая логика будет скопирована в несколько ViewController. Постепенно каждый экран начнёт реализовывать собственную работу с сетью.

Вместо этого лучше добавить репозиторий:

ViewModel
↓
ProductRepository
↓
Network Service

Теперь все экраны используют одну реализацию. При изменении конечной точки достаточно обновить код в одном месте.

Распространённая ошибка

Многие кандидаты отвечают: «Я использую MVVM».

Этого недостаточно. Интервьюеры хотят узнать:

  • почему вы выбрали MVVM;
  • как взаимодействуют слои;
  • где находится бизнес-логика;
  • где выполняются сетевые запросы;
  • как всё это тестируется.

Дополнительные вопросы

  • Почему не MVC?
  • Где должна находиться логика проверки данных?
  • Должна ли ViewModel знать о сетевом слое?
  • Где следует разместить аналитику?

Вопрос 2. Почему бы вы выбрали бы MVVM вместо MVC?

Почему интервьюеры задают этот вопрос

Почти каждый iOS-разработчик знаком с MVC. Интервьюеры хотят понять, знаете ли вы ограничения этого подхода и можете ли определить, когда стоит выбрать другую архитектуру.

Ответ уровня старшего разработчика

MVC хорошо подходит для небольших приложений. Однако по мере роста проекта ViewController часто начинает отвечать за слишком большое количество задач.

Он загружает данные. Проверяет ввод. Преобразует модели. Обновляет представления. Выполняет переходы между экранами. В итоге такие классы разрастаются до тысяч строк.

Эта проблема широко известна как Massive View Controller — чрезмерно большой контроллер представления. MVVM решает её, добавляя ViewModel между представлением и моделью.

View
↓
ViewModel
↓
Repository
↓
API

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

Пример из реального проекта

Предположим, экран входа должен обрабатывать:

  • формат адреса электронной почты;
  • требования к паролю;
  • запрос к программному интерфейсу;
  • состояние загрузки;
  • сообщения об ошибках.

В MVC вся эта логика часто оказывается внутри ViewController.

В MVVM:

LoginViewModel
✓ Validation
✓ Login API
✓ Error Handling
✓ Loading State
✓ Navigation Events

ViewController только биндит ViewModel.

Распространённая ошибка

Некоторые разработчики переносят сетевую логику непосредственно в ViewModel. Это создаёт новую проблему. ViewModel становится жёстко связана с сетевым слоем.

Лучше использовать следующую схему:

ViewModel
↓
Repository
↓
Network Service

Теперь сетевой слой легко заменить моком во время тестирования.

Дополнительные вопросы

  • Может ли ViewModel стать слишком большой?
  • Где должна находиться навигация?
  • Должна ли ViewModel знать UIKit?
  • Должна ли ViewModel знать SwiftUI?

Вопрос 3. Что такое чистая архитектура и когда её стоит использовать?

Почему интервьюеры задают этот вопрос

Многие разработчики указывают чистую архитектуру в резюме. Интервьюеры хотят понять, действительно ли вы применяли её на практике или только посмотрели обучающий материал.

Ответ уровня старшего разработчика

Чистая архитектура отделяет бизнес-правила от деталей реализации.

Её цель состоит в том, чтобы основная бизнес-логика не зависела от таких средств, как UIKit, SwiftUI, Core Data или сетевые библиотеки.

Зависимости всегда направлены внутрь:

UI
↓
ViewModel
↓
Use Case
↓
Repository Protocol
↓
Repository Implementation
↓
API/Core Data

Use Case ничего не знает о сетевом взаимодействии. Он выполняет только бизнес-операции.

Например:

LoginUserUseCase
↓
LoginRepository
↓
API

Если завтра изменится API или локальное хранилище, Use Case останется прежним.

Когда её следует использовать?

Чистая архитектура особенно полезна для:

  • банковских приложений;
  • медицинских систем;
  • корпоративных продуктов;
  • крупных облачных сервисов;
  • команд с большим количеством разработчиков;
  • проектов, рассчитанных на многолетнее развитие.

Для простого приложения-калькулятора чистая архитектура обычно избыточна.

Архитектуру следует выбирать с учётом сложности и предполагаемого срока жизни проекта.

Распространённая ошибка

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

Но цель не в этом. Если архитектура создаёт лишнюю сложность и не решает реальную проблему, она не приносит пользы.

Дополнительные вопросы

  • Замедляет ли чистая архитектура разработку?
  • Нужна ли она каждому проекту?
  • Где должны находиться репозитории?
  • Как в неё вписывается внедрение зависимостей?

Вопрос 4. Как сделать архитектуру iOS-приложения масштабируемой?

Почему интервьюеры задают этот вопрос

Масштабируемая архитектура — это не только способность обслуживать больше пользователей. Она также должна поддерживать рост количества функций, разработчиков и бизнес-требований, не становясь сложной в сопровождении.

Интервьюеры хотят понять, думаете ли вы о развитии кодовой базы в будущем.

Ответ уровня старшего разработчика

Масштабируемость достигается за счёт упрощения изменений.

Я придерживаюсь следующих практик:

  • сохраняю независимость слоёв;
  • не размещаю бизнес-логику в представлениях;
  • использую внедрение зависимостей;
  • переиспользую сетевые компоненты;
  • разбиваю крупные функции на небольшие модули;
  • применяю принципы SOLID там, где это оправдано;
  • использую протоколы для снижения связанности;
  • пишу модульные тесты для критически важной бизнес-логики.

По мере роста приложения эти решения помогают естественно встраивать новые возможности в существующую структуру.

Пример из реального проекта

Предположим, что первоначально приложение содержит:

  • логин;
  • дашборд;
  • профиль.

Через год в продукте появляются:

  • платежи;
  • чат;
  • видеозвонки;
  • уведомления;
  • аналитика;
  • автономный режим.

Если всё жёстко связано, каждая новая функция создаёт риск поломки существующих.

Модульная архитектура позволяет командам работать независимо и ограничивает область изменений.

Распространённая ошибка

Многие разработчики связывают масштабируемость с микросервисами или сложными фреймворками.

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

Дополнительные вопросы

  • Как бы вы организовали структуру папок?
  • Как нескольким командам работать над одним приложением?
  • Как уменьшить количество конфликтов при слиянии?
  • Как управлять переключателями функций?

Вопрос 5. Как уменьшить связанность между различными модулями?

Почему интервьюеры задают этот вопрос

Крупные iOS-приложения часто состоят из множества функциональных модулей. Если каждый модуль напрямую зависит от всех остальных, изменения становятся рискованными и трудоёмкими.

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

Ответ уровня старшего разработчика

Я уменьшаю связанность, используя абстракции вместо конкретных реализаций.

Вместо этого:

ProfileViewModel
↓
PaymentManager

я предпочитаю:

ProfileViewModel
↓
PaymentService Protocol
↓
PaymentManager

ViewModel знает только о протоколе.

В дальнейшем можно внедрить любую соответствующую ему реализацию.

Это упрощает тестирование, повышает гибкость и облегчает дальнейшее сопровождение.

Дополнительные приёмы

Я также стараюсь:

  • сохранять однонаправленное взаимодействие;
  • не злоупотреблять синглтонами;
  • передавать между модулями только интерфейсы;
  • использовать внедрение зависимостей;
  • выносить общие вспомогательные средства в отдельные фреймворки;
  • сводить к минимуму импорты между модулями.

Эти практики не позволяют одной функции случайно начать зависеть от другой.

Пример из реального проекта

Предположим, вы решили заменить поставщика аналитики.

Если каждый экран напрямую вызывает Firebase Analytics, придётся изменить сотни файлов.

Вместо этого можно определить абстракцию:

AnalyticsService
↓
FirebaseAnalytics
or
MixpanelAnalytics
or
CustomAnalytics

Теперь при смене поставщика достаточно изменить реализацию, а не каждый экран.

Распространённая ошибка

Многие разработчики создают протокол для каждого класса, даже если существует только одна реализация и нет реальной потребности в абстракции.

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

Дополнительные вопросы

  • Когда следует использовать протоколы?
  • Какие недостатки есть у чрезмерного количества абстракций?
  • Как вы внедряете зависимости?
  • Когда допустимо использовать синглтон?

Заключение

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

Источник

Если вы нашли опечатку - выделите ее и нажмите Ctrl + Enter! Для связи с нами вы можете использовать info@apptractor.ru.
Telegram

Популярное

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: