Вы не сможете построить дом на зыбучем песке. Так же вы не сможете создать хорошее приложение без архитектуры. В этой статье мы обсудим важность архитектуры приложения и то, как вы можете начать с ней работать.
Если вы когда-либо изменяли одну часть кода вашей программы, а другая часть при этом переставала работать. Или в вашем приложении не было единой структуры, и вам было трудно его расширить. Или же вы в конечном итоге запутались в коде вашего приложения. Все это говорит только об одном – вашему приложению не хватает архитектуры.
Когда в вашем приложении постоянно появляются новые баги
Представим, что в вашем коде есть ошибка. Вы ее исправляете. Однако после этого у вас появляются новые ошибки в других частях кода. Как это вообще возможно?
Технически, приложения состоят из множества небольших компонентов, соединенных вместе. База данных, пользовательский интерфейс, серверная часть и функции приложения.
Каждый из этих компонентов имеет более мелкие компоненты. К примеру, в базе данных есть объекты, которые она сохраняет, код для чтения и записи, код для кэширования данных, код для интерпретации запросов к базе данных и так далее.
Каждый из этих компонентов имеет еще более мелкие компоненты. Механизм кэширования базы данных имеет код для очистки кэша, добавления кэшированных объектов, их удаления, а также код для отслеживания доступной памяти.
Компоненты накладываются поверх других компонентов, составляющих всю систему приложения.
На данный момент проблем может не возникнуть. В большинстве случаев вы все равно работаете только с высокоуровневыми компонентами приложения. Однако и здесь могут возникнуть проблемы.
Допустим, вы создаете приложение со списком фильмов. Ваше приложение сохраняет объекты Movie в базе данных. Фильмы имеют свойства: заголовок, режиссер, актерский состав и т. д.
struct Movie { var title: String var director: String var cast: [String] }
В какой-то момент вы решили добавить новый объект телешоу в ваше приложение. Поскольку фильмы и телешоу очень похожи, вы решаете использовать Movie объекты для сохранения телешоу.
Вы используете функцию, isShow() для обозначения телешоу. Она возвращает true, когда объектом фильма является телешоу.
Это работает хорошо в течение определенного времени. Через год новый разработчик начинает работу над этим приложением. Он добавляют новое свойство episode к телешоу и корректируют isShow() функцию таким образом, чтобы она возвращала true только тогда, когда свойство episode содержит определенный формат шоу.
Внезапно вся база данных ломается. Объекты, которые являются телешоу, отображаются как фильмы, а объекты, которые являются фильмами, вообще не появляются.
Инкапсуляция, абстракция, документация
Что вызвало эту ошибку с функцией isShow()?
Оба разработчика полагались на функцию isShow() в процессе своей работы. Код, который зависел от функции, сломался, потому что его реализация изменилась. Когда вы меняете базовый механизм функции, можете ли вы по-прежнему быть уверены, что код, который зависит от этой функции, все еще работает корректно?
Вы можете утверждать, что правильное документирование зависимостей isShow() функции предотвратило бы эту ошибку. Оглядываясь назад, это кажется разумным решением. Тогда бы новый разработчик знал, для чего функция isShow() была использована.
Хорошая архитектура приложения основана на 3 идеях:
- Инкапсуляция – четкое определение границ компонентов.
- Абстракция – обобщение сложного кода в более простой.
- Документация – документация ожидаемого и конечного результата.
Инкапсуляция означает, что вы четко определяете границы компонента. Своего рода вы создаете «капсулу» для кода. Вы можете использовать компоненты только через формальные функции, которые инкапсулируют данные, например .setTitle(_:) или isMovie().
Логика в объекте Movie должна быть абстрагирована или «скрыта». Вы или другой разработчик не имеете прямого доступа к данным объекта. Абстракция имеет решающее значение в разработке программного обеспечения. С ее помощью вы можете избежать слишком большой сложности при работе с кодом.
Когда «капсула» спроектирована, вам больше не нужно беспокоиться о ее реализации. С помощью тестирования вы можете проверить целостность капсулы и поддерживать ее совместимость с остальным кодом.
Вы также хотите, чтобы компоненты, которые вы определяете и абстрагируете, были расширяемыми. Разумный подход, позволяющий избежать проблемы с функцией isShow() – это создание подкласса Movie типа с типом TVShow. Вы передаете обычные Movie типы существующему коду, а новый код телешоу использует тип TVShow.
Документирование вашего проекта также очень важно. Правильное документирование вашего кода означает добавление комментариев к коду, когда это необходимо. Вы добавляете так называемые описательные имена к функциям, свойствам и классам. Они описывают назначение компонента, ожидаемый и конечный результат.
Управление зависимостями
А как насчет зависимостей? Если ваш код не зависит от другой части вашего кода, вы можете полностью избежать ошибок. На ум приходит несколько примеров, например, когда простая библиотека JavaScript нарушала экосистему NPM и когда Facebook SDK для приложений iOS вызывал массу сбоев.
Если ваш код функционирует независимо, вы, очевидно, не подвержены влиянию сбоя библиотек или фреймворков. Однако насколько это реалистичный подход?
Код неизбежно строится на другом коде и, следовательно, зависит от этого кода. Вы не можете писать код в вакууме и не использовать никаких зависимостей. Правильный выбор здесь – это управление зависимостями и использование инструментов и подходов, которые ограничивают доступ к вашему коду.
- Добейтесь баланса между написанием собственной реализации и использованием стороннего кода (к примеру, нет смысла создавать свою собственную базу данных, когда можно использовать Realm).
- Используйте подходы, чтобы отделить ваш код от зависимостей. Задайте себе вопрос: «Как я могу сделать переход на другую библиотеку в будущем максимально простым?»
- Создайте и запустите тесты, чтобы проверить, функционирует ли ваш фрагмент кода так, как он должен. Это не панацея, но это отличная система раннего обнаружения ошибок и изменений в реализации.
- Проверяйте, поддерживайте и документируйте свою работу. Сделайте зависимости вашего кода явными, как в коде, так и вне его.
Цель состоит не в том, чтобы избежать одной распространенной ошибки, которая ломает сотни приложений. Цель состоит в том, чтобы избежать сотен различных ошибок, которые могут сломать ваше приложение. Вы встраиваете в свои приложения надежность, выбирая правильный подход в проектировании, не избегая при этом использовать различные сторонние компоненты и зависимости.
Будьте гибки в выборе архитиктуры своего приложения
Архитектура приложений постоянно меняется. Например, архитектура приложений VIPER однажды стала популярной среди разработчиков iOS. Теперь, когда есть SwiftUI, становится актуальные декларативное программирование и правильного управления состоянием.
Model-View-Controller (MVC) часто презирается сообществом разработчиков. С помощью MVC легко создавать громоздкие части кода, если вы не следуете определенным принципам. По иронии судьбы, принципы MVC лежат в основе многих других архитектурных моделей.
Вам как разработчику важно освоить архитектуру приложения, шаблоны проектирования и принципы разработки программного обеспечения. Вы также должны не бояться делать ошибки сейчас, чтобы вы могли избежать похожих ошибок в будущем. Ошибки приводят к мастерству, поэтому вы должны опасаться крайностей, таких как «Никогда не используйте Singleton!» или «MVC – это неправильно!» или «Gitflow – это круче всего!»
Когда вы осознаете основы, лежащие в основе многих из этих архитектур, гораздо проще выбрать новую архитектуру, такую как VIPER. Это не работает наоборот. Осваивая программирование самостоятельно, вы получаете понимание, которого вам будет не хватать, если вы будете слушать только «экспертов».
Как разработчик iOS, вы можете выбирать из широкого спектра архитектур, шаблонов проектирования и инженерных принципов.
Создание приложения с разумной архитектурой решает большинство проблем, с которыми могут столкнуться разработчики. Архитектура вашего приложения определяет структуру его компонентов и формализует их взаимодействие друг с другом.
В результате вы получаете приложение, которое:
- Имеет меньше багов и проще в обслуживании.
- Имеет код, который прост для понимания и навигации.
- Код, который легко может быть расширен новыми функциями.
Придерживайтесь того, что работает для других, и что популярно сейчас. Выберите подходящую архитектуру и не бойтесь совершать ошибки. Никогда не поздно начать проектировать свое приложение.
Когда вы реорганизуете свой код, вы переписываете код, который не соответствует вашему архитектурному проекту. В конечном счете, вы будете тратить меньше времени на исправление ошибок, и вас появится больше времени на создание качественных приложений, что означает больше счастливых клиентов или довольного работодателя.