company_banner

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



    Чуть больше года назад я присоединился к команде «Ситимобил» в качестве 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 и практически полностью обновленное приложение.

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

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



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



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



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

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

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

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

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

    Mail.Ru Group

    711,48

    Строим Интернет

    Поделиться публикацией

    Похожие публикации

    Комментарии 22
      +12
      Мне одному первый вариант (до рефакторинга) окон авторизации нравится больше, чем два последующих?
        +1
        Мне тоже, было с изюминкой, а стало «как у всех», переработали в скучный корпоративный стиль.
        Пропала инфа что водителям номер не дадут, и спамить тоже не будут.
          0
          >водителям номер не дадут, и спамить тоже не будут
          Может изменились политики конфиденциальности? Водителю всё ещё номер не даётся, а вот спам может иногда приходить, например.
            0
            Нет, номер телефона используется исключительно для авторизации. Текст был убран, так как более полная информация была описана в условиях оферты
              +2
              Которую никто практически не открывает?

              Если оставили бы эту инфу там где она была то лично мне было бы спокойнее давать вам мой номер телефона, хоть этот текст внизу юридически вас вроде бы ни к чему не обязывает ( могу ошибаться, не юрист ).
          +1
          Согласен, на первый вариант приятно посмотреть. Даже первый рефакторинг приятней чем то, что получилось в итоге.
            0
            Спасибо за конструктиврную критику, будем учитывать в нашей работе. Однако дизайн-гайды платформ постоянно развиваются и меняются. После прошедшего Google I/O как раз произошли большие перемены в плане дизайна, а удобство — это отчасти дело привычки, которая, возможно, еще не успела сформироваться
            0
            На первой фото «после» и вовсе выглядит как после взрыва нейтронной бомбы ;)
              +5
              Первый делал Лебедев www.artlebedev.ru/citymobil/app/process.
                0
                Крутой дизайн был, я презентацию прочел на одном дыхании и захотелось прямо приложение поставить, чтобы увидеть всю эту красоту, странно почему от него ушли?
                  0
                  Не все устраивало в той реализации, не конкретно на экране авторизации, а на других
                0
                +1
                0
                К сожалению, тогда удалось найти не так много полезной литературы.

                Может у кого есть примерный список?
                В любом случае Мартин Фаулер актуален на все времена martinfowler.com/books/refactoring.html
                  0

                  «Эффективная работа с унаследованным кодом», Майкл К. Физерс

                  +5
                  По поводу КДПВ скажу, что пугающе смотрится 14 полосная дорога в центре города.
                    0
                    Там 8+7 полос, итого 15

                    Фото


                      0
                      А, ну тогда не страшно image
                      0
                      Тут она ещё свободная. По-настоящему пугающей она становится, когда все эти полосы забиты глухой пробкой в обе стороны :)

                      Ну а «толкучку» на картинке «до» хорошо описал Гиляровский. Там было душевно, но могли и обдурить.
                      0
                      TheSecond а какой вариант MVP вы используете, самописный или готовое решение (Moxy и.т.д.)?
                      Ну и если самописное, то соответственно как отрабатываете ЖЦ?
                        0
                        В нашем проекте используется самописный MVP. ЖЦ обрабатываем так, что разделяем данные, которые относятся к бизнес-логике и к состоянию UI. Данные бизнес-логики хранятся в репозиториях, которые имеют свой ЖЦ и не зависят от экранов. А данные относящиеся к состоянию UI сохраняем в бандлах и в некоторых специфичных случаях в retain фрагментах.
                        0
                        MVP — шаблон проектирования пользовательского интерфейса (Model-View-Presenter).

                        С моей точки зрения допущены серьезные ошибки в определении:
                        1) MVP — это архитектурный паттерн для построения архитектуры приложений с пользовательским интерфейсом, а не для проектирования пользовательского интерфейса.
                        2) MVP можно использовать и в программном обеспечении без пользовательского интерфейса.
                          0
                          Спасибо за замечание, но, пожалуй, ключевая фраза —
                          С моей точки зрения
                          По поводу того, чем является MVP — архитектурой или лишь паттерном presentation-слоя сломано много копий, я придерживаюсь 2 трактовки. Согласен, приведенная мной формулировка не самая удачная. Но под пользовательским интерфейсом подразумевается не только тот интерфейс, который представлен на экране пользователя смартфона, а интерфейс в более широком понимании — канал, по которому пользователь взаимодействует с приложением, все возможные способы получения информации от него и передачи ему

                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                        Самое читаемое