История рефакторинга приложения «Ситимобил»

в 8:58, , рубрики: android, Блог компании Mail.Ru Group, Проектирование и рефакторинг, разработка мобильных приложений, рефакторинг, ситимобил

История рефакторинга приложения «Ситимобил» - 1

Чуть больше года назад я присоединился к команде «Ситимобил» в качестве Android-разработчика. Привыкал к новому для себя проекту, новым подходам и технологиям. На тот момент у «Ситимобил» уже была довольно длинная история, как и у принятого мной проекта, Android-приложения для заказа такси. Однако, как это часто в таких случаях бывает, код нёс в себе характерные следы старых решений. И сейчас, после успешного рефакторинга кода, хочу поделиться идеями, которые, как я считаю, могут пригодиться тем, кому предстоит рефакторить уже существующий проект. И прежде всего, это может быть полезно небольшим компаниям с небольшими командами разработчиков.

Бизнес часто проверяет свои идеи, направляя на это ограниченные ресурсы, и пытается получить обратную связь, проверить свои гипотезы как можно быстрее. В такие моменты, как правило, качественное продумывание и реализация архитектуры проекта с учетом задела на будущее отходит на второй план. Постепенно проект обрастает новой функциональностью, появляются новые бизнес-требования, и все это сказывается на кодовой базе. «Ситимобил» в этом плане не стал исключением. Проект последовательно разрабатывался несколькими командами в старом офисе, затем, во время переезда, поддерживался и частично переписывался на аутсорсе. Потом начали формировать новую команду, и мне передали работу над проектом.

В то время «разработка» переехала в московский офис, работа кипела — постоянно появлялись новые интересные и амбициозные задачи. Однако legacy всё больше и больше вставляло палки в колеса, и однажды мы поняли, что пришло время больших изменений. К сожалению, тогда удалось найти не так много полезной литературы. Оно и понятно, это познается на опыте, вряд ли можно придумать или найти идеальный рецепт, который работает в 100 % случаев.

Первое, что следует сделать — понять, точно ли вам нужен рефакторинг? Об этом следует подумать, если:

  1. Скорость внедрения новых фич необоснованно низкая, несмотря на высокий уровень специалистов в команде.
  2. Изменения кода в одной части программы могут привести к неожиданному поведению в другой части.
  3. Адаптация новых членов команды затягивается.
  4. Тестирование кода затруднено сильной связностью.

После осознания наличия проблемы следует найти ответы на следующие вопросы:

  1. Что же, собственно, не так?
  2. Что к этому привело?
  3. Что нужно сделать, чтобы такое больше не повторилось?
  4. Как исправлять ситуацию?

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

Изначально проект был написан, в основном, лишь с помощью средств, которые предоставляет само 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 и практически полностью обновленное приложение.
История рефакторинга приложения «Ситимобил» - 2
После рефакторинга изменения функциональности приложения стали проходить значительно легче. Так, недавно мы снова занимались переработкой экранов авторизации.

Изначально они выглядели следующим образом:

История рефакторинга приложения «Ситимобил» - 3

После первой переработки и рефакторинга они стали выглядеть так:

История рефакторинга приложения «Ситимобил» - 4

Теперь же они выглядят так:

История рефакторинга приложения «Ситимобил» - 5

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

Что мы имеем на сегодняшний момент?

Чтобы код был удобен для последующего использования и развития, мы придерживаемся принципа «чистой архитектуры». Я бы не сказал, что у нас канонический Clean, но многие подходы мы переняли. Слой представления написан с использованием паттерна MVP (Model-View-Presenter).

  • Раньше нам приходилось бесконечно обсуждать друг с другом каждый шаг, уточнять, не заденет ли изменение одного модуля функциональность другого. А теперь overhead по переписке значительно снизился.
  • Благодаря унификации отдельных компонентов и фрагментов объём кодовой базы сильно уменьшился.
  • В результате той же унификации и переработки архитектуры, классов стало значительно больше, но теперь в них прослеживается четкое разделение ответственности, что упрощает понимание проекта.
  • Кодовая база разбита на слои, для их разделения и взаимодействия используется фреймворк внедрения зависимостей — Dagger 2. Это уменьшило связность кода и повысило скорость тестирования.

Есть еще много интересных моментов, связанных с проведением рефакторинга legacy-кода. Если читателям будет интересно, напишу о них подробнее в следующий раз. Также буду рад, если вы тоже поделитесь своим опытом.

Автор: TheSecond

Источник

Поделиться

* - обязательные к заполнению поля