Чуть больше года назад я присоединился к команде «Ситимобил» в качестве Android-разработчика. Привыкал к новому для себя проекту, новым подходам и технологиям. На тот момент у «Ситимобил» уже была довольно длинная история, как и у принятого мной проекта, Android-приложения для заказа такси. Однако, как это часто в таких случаях бывает, код нёс в себе характерные следы старых решений. И сейчас, после успешного рефакторинга кода, хочу поделиться идеями, которые, как я считаю, могут пригодиться тем, кому предстоит рефакторить уже существующий проект. И прежде всего, это может быть полезно небольшим компаниям с небольшими командами разработчиков.
Бизнес часто проверяет свои идеи, направляя на это ограниченные ресурсы, и пытается получить обратную связь, проверить свои гипотезы как можно быстрее. В такие моменты, как правило, качественное продумывание и реализация архитектуры проекта с учетом задела на будущее отходит на второй план. Постепенно проект обрастает новой функциональностью, появляются новые бизнес-требования, и все это сказывается на кодовой базе. «Ситимобил» в этом плане не стал исключением. Проект последовательно разрабатывался несколькими командами в старом офисе, затем, во время переезда, поддерживался и частично переписывался на аутсорсе. Потом начали формировать новую команду, и мне передали работу над проектом.
В то время «разработка» переехала в московский офис, работа кипела — постоянно появлялись новые интересные и амбициозные задачи. Однако legacy всё больше и больше вставляло палки в колеса, и однажды мы поняли, что пришло время больших изменений. К сожалению, тогда удалось найти не так много полезной литературы. Оно и понятно, это познается на опыте, вряд ли можно придумать или найти идеальный рецепт, который работает в 100 % случаев.
Первое, что следует сделать — понять, точно ли вам нужен рефакторинг? Об этом следует подумать, если:
- Скорость внедрения новых фич необоснованно низкая, несмотря на высокий уровень специалистов в команде.
- Изменения кода в одной части программы могут привести к неожиданному поведению в другой части.
- Адаптация новых членов команды затягивается.
- Тестирование кода затруднено сильной связностью.
После осознания наличия проблемы следует найти ответы на следующие вопросы:
- Что же, собственно, не так?
- Что к этому привело?
- Что нужно сделать, чтобы такое больше не повторилось?
- Как исправлять ситуацию?
Практически невозможно построить хороший долгоиграющий проект без закладывания определенной архитектуры. В своем проекте мы решили внедрять «слоеную» архитектуру, которая себя уже хорошо зарекомендовала.
Изначально проект был написан, в основном, лишь с помощью средств, которые предоставляет само Android SDK. Подход, несомненно, рабочий, однако вынуждает писать много шаблонного кода, что сильно тормозит разработку. А учитывая, что сегодня многие привыкли к определенным стекам технологий, адаптация новых разработчиков происходила дольше. Постепенно мы пришли к более удобным технологиям, которые многие знают и ценят, и которые доказали свою надежность и состоятельность:
- MVP — шаблон проектирования пользовательского интерфейса (Model-View-Presenter).
- Dagger 2 — фреймворк для внедрения зависимостей.
- RxJava2 — реализация ReactiveX — библиотеки для создания асинхронных и основанных на событиях программ с использованием паттерна «Наблюдатель», для JVM.
- Cicerone — библиотека, позволяющая упростить навигацию в приложении.
- Ряд специфичных библиотек для работы с картами и локацией.
Очень важно принять общий для команды стиль кода, выработать свод лучших методик. Также следует позаботиться об инфраструктуре и процессах. На новый код лучше сразу же писать тесты, благо информации по этому поводу имеется очень много.
Внутри команды мы стали в обязательном порядке проводить code review, это занимает не так много времени, однако качество кода стало гораздо выше. Даже если вы один в команде, рекомендую работать по Git Flow, создавать merge request'ы и хотя бы проверять их самостоятельно.
Всю «грязную» работу можно делегировать CI — в нашем случае это TeamCity с использованием fastlane. Мы настроили его на сборку feature-веток, прогон тестов и выкладку на внутренний тест. У себя мы отдельно настроили сборки для production/staging-окружения, feature- (их мы называем по номеру задачи с шаблоном TASK#номер_задачи) и релизных веток. Это облегчает тестирование, и если возникает ошибка, мы сразу же знаем, что и где нужно исправить.
После проведения всех предварительных действий принимаемся за работу. Новую жизнь в старом проекте мы начали с создания пакета (cleanarchitecture). Важно не забыть про activity-alias при перемещении точек входа в приложение (a-la ActivitySplash). Если этим пренебречь, то, в лучшем случае, у вас пропадёт иконка в лаунчере, а в худшем — нарушится совместимость с другими приложениями.
<!-- android:name=".SplashActivity" - old launcher activity -->
<!-- android:targetActivity=".cleanarchitecture.presentation.SplashActivity" - new launcher activity -->
<activity-alias
android:name=".SplashActivity"
android:targetActivity=".cleanarchitecture.presentation.SplashActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity-alias>
Как подсказывает опыт, начинать рефакторинг лучше со второстепенных небольших экранов и частей приложения. И к тому моменту, когда придет время перерабатывать самую сложную и объемную часть программы, немалая часть кода уже будет написана для других модулей и может быть переиспользована.
Также перед нами тогда стояла большая задача по полному редизайну приложения, что, временами, выливалось в полное переписывание экранов. Начали с улучшения вспомогательных экранов, готовясь приступить к главному.
После переписывания очередной части приложения мы искали участки кода в старой части приложения и помечали аннотациями @Deprecated и аналогами этих: https://github.com/VitalyNikonorov/UsefulAnnotation. В них мы указывали, что следует сделать при переписывании этой части программы, какая функциональность и где реализована.
/**
* This class deprecated, you have to use
* com.project.company.cleanarchitecture.utils.ResourceUtils
* for new refactored classes
*/
@Deprecated
public class ResourceHelper {...}
После того, как всё было готово к работе над главным экраном, решили 6-8 недель не выпускать новые фичи. Глобальное переписывание мы проводили в собственной ветке, к которой затем добавляли merge request’ы. По окончании рефакторинга получили заветный pull request и практически полностью обновленное приложение.
После рефакторинга изменения функциональности приложения стали проходить значительно легче. Так, недавно мы снова занимались переработкой экранов авторизации.
Изначально они выглядели следующим образом:
После первой переработки и рефакторинга они стали выглядеть так:
Теперь же они выглядят так:
В итоге первая итерация заняла более чем в два раза больше времени, чем вторая. Так как помимо переработки UI пришлось разбираться и в коде бизнес-логики, расположенной там же, хоть в этом не было необходимости, но недостаток был устранен, что сократило время работы над задачей во второй итерации.
Что мы имеем на сегодняшний момент?
Чтобы код был удобен для последующего использования и развития, мы придерживаемся принципа «чистой архитектуры». Я бы не сказал, что у нас канонический Clean, но многие подходы мы переняли. Слой представления написан с использованием паттерна MVP (Model-View-Presenter).
- Раньше нам приходилось бесконечно обсуждать друг с другом каждый шаг, уточнять, не заденет ли изменение одного модуля функциональность другого. А теперь overhead по переписке значительно снизился.
- Благодаря унификации отдельных компонентов и фрагментов объём кодовой базы сильно уменьшился.
- В результате той же унификации и переработки архитектуры, классов стало значительно больше, но теперь в них прослеживается четкое разделение ответственности, что упрощает понимание проекта.
- Кодовая база разбита на слои, для их разделения и взаимодействия используется фреймворк внедрения зависимостей — Dagger 2. Это уменьшило связность кода и повысило скорость тестирования.
Есть еще много интересных моментов, связанных с проведением рефакторинга legacy-кода. Если читателям будет интересно, напишу о них подробнее в следующий раз. Также буду рад, если вы тоже поделитесь своим опытом.
Автор: TheSecond