Category: maven

Как жить на Legacy проекте

Несколько хороших и обязательных к просмотру лекций про то как разрабатывать и сопровождать старые легаси проекты.
Кстати тут нужно сказать что legacy — это переводится не просто как «наследство» а как некий дар, например когда бабушка передаёт внучке кольцо. Авторы термина хотели заложить какое-то позитивное значение 🙂

На эту тему почти на всех конференциях рассказывает Виктор Полищук:

Примечательна также классификация которую Виктор использует:

What is…

  • Very old projects, had been written long long ago.
  • The Highlander: Old mature projects, had been written ages ago. Optimization required.
  • Paragon: Die Hard Nobody knows why it is still alive.
  • Fashion or market victims: Nobody wants this sh%t anymore.
  • Problem Child: Nobody loves it. Sentenced Customer tired of supporting it, everything is up to you.
  • Apathetic: Nobody cares

What is usually NOT…

  • Badly written projects which are not in production.
  • Low quality designed projects.
  • Small projects.
  • Every project you don’t understand.

legacy code - переписать это говно мамонта

Виктор Полищук — Все что вы хотели знать о Legacy-коде, но стеснялись спросить

Виктор Полищук — Legacy: как победить в гонке

И этот доклад вдохновил ребят из ДатаАрта сделать свой доклад:

И продолжение «Travel, Legacy and SWAT. Q&A Session»

Презентация Michael Feather, автора книги Работа с унаследованным кодом:

Дмитрий Миндра: Refactoring Legacy Сode

Virtual JUG: Testing and Refactoring Legacy Code

Сергей Немчинский — Как создаются и поддерживаются Enterprise.
Очень хороший доклад где раскрывается суть легаси проектов, как они возникают и какая у них специфика.

Статья Практика рефакторинга в больших проектах в сотый раз о вечном. Для тех кто ещё халтурит и не прочёл замечательную книгу Working Effectively with Legacy Code

Ну и несколько интересных статей на окололегасиную тематику:
* Что иметь в виду при переписывании программного обеспечения
* Читайте код, с остальным справится компилятор
* Рентабельный код
* Поиск и чтение унаследованного кода

Небольшой конспект с добавлениями моих мыслей.
1. На легаси проекте всегда есть несколько параллельных подходов и технологий. В разное время были попытки и не всегда законченные сделать что-то правильно не выкидывая старого. Например если у вас был старый API и вы решили сделать новый — то у вас есть новый АПИ но клиенты всё равно используют старый потому что в нём были фичи которые вы в новый переносить не будете или ещё не решили как его сделать правильно. Точно также если у вас был JSF и вы решили переписать на Spring MVC то теперь у вас половина кода на Спринге и половина на JSF.
На моём текущем проекте живут одновременно чисто сервлеты, чисто JSP, JSF, JSP + Tiles, немного Velocity, Spring 3.0.7 и мы даже не можем перейти на Спринг 3.1, куча контролеров на Tapestry 4 (к которому уже даже документации не найдёшь), и Tapestry 5.0 который по сути совсем другой фреймворк и мы не можем даже переписать на Tapestry 5.3, просто потому что уже никто его не знает.
Всё это живёт одновременно и активно используется. Всё находится в полупереходном состоянии потому что пока начали переписывать и делать новые ошибки, вдруг выяснилось что чего-то сделать на новом практически невозможно и вообще уже наделали новых ошибок что уже и переписывать так пожалуй не стоит. Всё это устарело и затраты на обновление больше чем на переписывания с ноля.
Из-за версионного просака обновится невозможно. И иногда нужно просто смириться что это останется таким навсегда (привет Кобол программистам).
2. На лагаси проекте очень многое можно просто выкинуть. Очень много нуждается в оптимизации. Но как понять что можно менять а что нет? Как найти проблему? Т.е. здесь на первый план выходит и самое принципиальное это знать как используется программа: вам нужен мониторинг, вам нужно сборка и визуализация логов (logstash+elastic+kibana), или статистика юзаджей, вам нужен доступ к базе на продакшене, вам нужен доступ ко всем конфигам а то и вовсе к серверам. Насколько только это возможно. Именно с этого и следует начинать первым делом.
Допустим ты ковыряешься в коде и видишь кучу проблем связанных с тем что у вас регистрозависимые (case sensitive) логины пользователей, т.е. User и user это два разных пользователя. Потому что дальше ваша система шлёт эти юзернеймы через АПИ в другую систему которая их не поддерживает. И ты должен иметь возможность прямо сейчас посмотреть в конфигах включена ли эта возможность, посмотреть в базе как много пользователей с дублицироваными юзернеймами. Нужно погрепать логи как много пользователей столкнулось с этими проблемами.
Нужно поискать по истории ревизий кода когда добавлялся этот функционал и зачем. Нужно расспросить всех коллег кто что-то знает про это.
Т.е. на легаси проекте для того чтобы сделать какое либо изменение порой требуется настоящее и полноценное исследование, расследование, с допросами и большой археологической работой по изучения пластов кода и поиском то ишью трекеру.
Какая нибудь заброшенная страничка на Вики тебя может реально выручить. Отсюда следует следующий пункт…
3. Всё что только можно нужно записывать и потом никогда не выбрасывать. Желательно любые обсуждения и митинги потом записывать хотя бы тезисно и рассылать по почте, и её, корпоративную почта нужно беречь и лелеять. Возможно даже вам следует хранить логи и бекапы бд баз хотя бы за год. «Чем больше бумаги тем чище жопа». И не бойтесь что создаёте мусор — археологи по мусорным кучам восстанавливают знание о целых эпохах цивилизаций.
Просто поймите — даже самые очевидные для вас вещи следует записывать потому что команда совершенно точно поменяется полностью. Это всё равно что представьте что послезавтра весь ваш код, всю вики, все письма, весь репозиторий просто передадут на разработку в другую компанию.
4. На Вики писать и перечитывать\переписывать\обновлять статьи это абсолютно обязательная работа ничуть не меньшая чем писать код. Тикеты в ишью трекере периодически нужно проходить и перечитывать. Реально очень многие проблемы, уж поверьте, давно известны и даже были попытки их исправить. Это нудное занятие вам сэкономит кучу времени. Но читать их следует только после того как вы уже хоть немного разобрались с проектом, иначе вы не поймёте о чём там вообще идёт речь.
Дело в том что на легаси проекте из-за большого числа джира тикетов по закону больших чисел вы практически наверняка там точно есть практически все баги и проблемы, и вы можете достаточно точно оценить ситуацию с разными компонентами системы.
В совокупности с письмами из сапорта и логами это даёт хорошую картину на весь проект. Это важная метрика которая вам поможет вам понять размер трагедии.
5. На любом легаси проекте почти наверняка есть огромные проблемы со сборкой проекта. Обычно она не тривиальная, требуется ANT и Maven конкретной версии, собирается проект почти наверняка только под шестую джаву, требуются ручные действия. Билд долгий, тесты blinking (то падают то нет) и очень много из низ вообще отключены через @Ignore. Есть самодельно пропатченные версии библиотек. И это вам очень сильно повезёт если проект мавенизирован и зависимости вообще известны и доступны с Maven Central репозитория.
Из-за того что у вас много либ (а их наверняка много) а у вас нет возможности дать каждой свой classpath, у вас точно есть куча проблем с конфликтующими или дублирующимися зависимостями.
И для успешной жизни на проекте вам следует опустится вот это вот болото Мавена и стабилизировать билд. Это второе по важности после логов и мониторинга и метрик что следует сделать.
6. Любой код без тестов автоматически становится легаси. Ваша задача любой ценой покрыть код тестами. На каждый баг — покрываете то место тестами. Вплоть до драконовского правила не принимать новый код без тестов хотя и без фанатизма. Да, вам придётся замокать пол мира, написать кучу фикстур и стабов, написать долгие интеграционные тесты которые пол базы загрузят, но без этого вообще никак. Когда создавалось множество легаси проектов тогда тестирование ещё не было принято как промышленная практика или фреймворки которые были тогда не очень были рассчитаны на то что их нужно будет как-то тестировать. Сами тестовые библиотеки и техники с тех пор прошли большой и мучительный путь, и в каждом фреймворке уже изначально идёт куча вспомогательных классов и стабов и документации о том как тестировать.
Поэтому на легаси проекте это совершенно нормальная практика когда функционал написанный\исправленный за час вы в три раза дольше покрываете тестами. Постепенно самое гадкое вы всё таки покроете тестами а ваша архитектура всё таки начнёт постепенно становится лучшей для тестирования. Это трудозатратный путь, но он оправдан и рано или поздно принесёт плоды, просто верьте.
7. Дальше нужно улучшить логирование — подчистить лог от огромных стектрейсов и пофиксить то что их продуцирует. Дописать логи везде где только нужно, все входящие и выходящие реквесты в АПИ тоже логировать. При этом нужно сделать так чтобы логирование имело смысл по максимуму. Также в логах у вас почти наверняка будет множество секьюрной информации: пароли пользователей и ключи от АПИ, номера кредитных карт или что нибудь ещё такое чего там не должно быть. Представьте что ваши логи взломали хакеры — вы должны максимально им усложнить жизнь.
8. Улучшить мониторинг — через JMX пробросить все важные метрики: количество активных сессий или реквестов. Повключать всякие опции JVM для мониторинга. Об этом Виталий Тимчишин рассказывал в клубе анонимных программистов презентация, видео (часть 1), видео (часть 2) но лучше новую инфу поискать, особенно про FlightRecorder.
9. Билд нужно ускорить и улучшить насколько это возможно. У вас должен быть самый лучший CI tool — TeamCity. Собираться в нём должно всё что только может быть собрано.
10. Вам нужно ввести версионирование БД и db migration tool — DBMaintain, FlywayDB, LiquiBase.
11. Грамотно разнести тесты как в Гугле поделить их по времени выполнения. Тут ключевой момент в том что если для TDD разработки ваш тест должен отрабатывать быстро (максимум 15 секунд) иначе она не приживётся. Сам билд в CI тоже нужно ускорить как только можно (запуск тестов в параллели, использовать in-memory file system).
12. У вас совершенно точно будут множество проблем с базой данных — конекшен пул будет забиваться, будет множество проблем с уровнем изоляции транзакций, запросы будут долго тупить, N + 1 проблемы, данные будут неконсистентными итд. Эти проблемы нужно чётко методично разруливать — обновить либу Tomcat JDBC Connection Pool, загнать всю работу с базой строго в рамки DAO, самые используемые и статические данные закешировать и сделать так чтобы кеш можно было сбросить через JMX или JavaMelody.
13. Саму базу тоже как правило следует подчистить и хорошенько нормализировать. Делать это нужно очень аккуратно и осознано и обязательно основываясь на реальной статистике. Для PostgreSQL есть утилита pg_stats. По ней можно определить как данные используются и сравнив с кодом можно понять свойства данных: как часто они изменяются, как часто они инсертятся или они read only. Где нужно натянуть индексы. Это очень принципиально потому что даёт нормальный прирост скорости и вообще от данных следует отталкиваться.
14. Дальше вам следует возвести китайскую стену между старым кодом и новым, как рассказывал Витя Полищук. У вас должно быть чёткое понимание что в этой части системы мы пытаемся создать островок удачи и везения, где всё прилично и правильно и после этого перетащить на этот остров весь функционал из плохого кода. Этот островок может быть совершенно отдельным модулем\микросервисом отделённым чётким API и интерфейсами. И жёстко следить за тем чтобы в нём был порядок.
У меня например на проекте например даже уволили сотрудника за то что он не стал переписывать старый функционал а за уши притянул его скопипастив код в островок удачи. Теперь нам оставшимся сцыкотно там гадить.
15. Всё что только можно резать на модули, модули выделять в микросервисы которые общаются по простому протоколу типа REST или JSON-RPC.
У вас наверняка есть часть системы и код который никогда уже не перепишешь и всё что можно с этим сделать — только выделить эту гангрену в отдельный модуль.
16. Memento mori — вам следует честно и сразу думать даже о новом функционале как о выброшенном позже и заранее думать о том как его убрать. Микросервисы как раз неплохо решают эту проблему.
17. Стоит максимально улучшить процесс деплоя — для начала достичь zero downtime но конечно в идеале вообще continuous deployment, но даже не мечтайте 🙂 Даже без zero downtime ваш цикл комит — старт сервера должен быть максимально маленьким. Суть в том что если вы начнёте что-то менять то вам придётся постоянно выкатывать хотфиксы которые фиксят баги после ваших фиксов багов. Если вы начнёте наводить порядок на легаси проекте, да даже просто что-то делать особо ничего не трогая, всё равно у вас будут всплывать проблемы и их нужно будет фикисть по горячему.
18. Если у вас есть внешний API то вам следует проксировать его через фасад где чётко вести статистику кто его дёргает и как. Далее если ваш АПИ будет меняться вам следует самим сделать SDK где вы его сами заимплементите. Всё таки это относительно редкость когда клиенты вашего АПИ используют что-то другое кроме PHP, Java или JavaScript, ну или что там у них. Поэтому вы можете сами написать или попросить клиентов поделится с вами их обвёртками над вашим АПИ и сделать его своим официальным. Далее вы можете менять и экспериментировать с АПИ при этом каждый раз выкатывать новую версию SDK. Т.е. это вам даёт дополнительный рычаг гибкости.
Кроме того попытаться заимплементить ваше собственное АПИ очень хорошо поможет вам понять как мучаются ваши клиенты и заметить частые ошибки.
19. После этого можете пробовать постепенно обновлять ваши библиотеки зависимостей на новые версии, тщательно и внимательно просматривая их чейнджлоги и ища всякие баги которые есть с этой версией. Реально в интернете вы уведите много релевантной информации. Первым делом обновлять следует тестовые библиотеки, особенно моки (EasyMock, Mockito) потому что они изменяют байткод. Проверить что тесты проходят как и ранее и не сломались.
Далее следует обновлять сами плагины Мавена — это тоже довольно опасная операция как оказалось на практике.
После этого утильные либы: Apache Commons Lang, Apache Commons Collections, Guava, JodaTime, Jackson, итп.
Потом можно попробовать проайпдейтить Quartz который в основном используется для джоб и другие либы типа Apache POI чья поломка для пользователей пройдёт почти незаметно.
Потом нужно проадейтить те либы которые делают инструментацию байткода и всякую магию: Hibernate и куча всего другого. Это самое злобное.
После этого можно пробовать перейти на новую джаву.
После перехода на новую джаву можно выкинуть JodaTime и JodaMoney и кучу другого хлама. И обновить остальные либы типа Спринга.

Продолжение следует

Реклама

[LinkSet] Compatibility

Theoretical part

 

Articles from Oracle

http://JEP 223: New Version-String Scheme

 

Six kinds of compatibility

Each Joda-Time relase has descriptions of incompatible changes categorized by six kinds:

Compatibility between 2.8 and 2.9
———————————
Build system — Yes
Binary compatible — Yes
Source compatible — Yes
Serialization compatible — Yes
Data compatible — Yes
— DateTimeZone data updated to version 2015g
Semantic compatible — Yes

When binary compatibilities are broken then Majour Version changed. For example v2.0 changelist

Explanation from  Stephen Colebourne, author of Joda-Time:

Build system

Not part of compatibility, just a fact about the build system in use

Example:  in v2.2

 - Ant build removed. Build only on Maven now.

Also, I think, it may be changing in artifact coordinates: groupId, artifactId or even changed repository. Maybe some changes in manifest, for example required dependencies. Or added OSGI manifest info. Maybe some classes was repackaged. Or old artifact was built with ANT and doesn’t contains a Maven’s pom.xml manifest like horrible Xerex library  and then was mavenized.

It also may be recompilation with optimizations or without debug information.

But I think that changing in packaging may be a separate kind of compatibility change.

Binary compatible and Source compatible

Whether the whole API is binary compatible or source compatible. See this document

Serialization compatible

Whether the serialization format is compatible with the previous version.

Data compatible

Whether the data, time-zone data in this case, has changed.

For example: some time zone was changed or even removed. If your database stores old timezone id and application trying to create a date object with this timezone id you’ll get an exception.

Semantic compatible

Whether there is some other semantic change. This is relevant when the classes and methods have not changed, but the meaning of a method has.

See section Serialization incompitables below

For example:  v2.0 has a lot of semantic changes

Previously, DateTimeZone.forID matched time zone names case-insensitively, now it is case-sensitive

I think it always hard to separate semantic changes when dealing with bugs. For example, JDK has bug when using null as key in Map

And that was a question is this a bug or feature.

Another one example, is Apache Commons Lang library. In version 3 the methods StringUtils.isAlpha(), isNumeric() and isAlphanumeric() now all return false when passed an empty String. Previously they returned true.

Semantic looks like correlated with behavior compatibility. When changed not signatures but they flow or contract.

Also it’s often situation when behavior was just undefined by contract. For example old class Vector doesn’t declare behavior about removing elements during iteration.  That’s why was created new class ArrayList and Vector was accepted as Superseded.
So, deprecation, as any changes in contract may be also some kind of semantic change.

 

Serialization incompitables

Compatible incopatibility

Also I think that it should be some kind of «reverse compatibility» that means a contract usage. For example, I saw an issue when subclass of HashMap doesn’t follow a contract. This change was compatible in all ways but all clients become incompatible. How to predict it, I don’t know.

Each of developer has experience when you can’t change something in your API because there is some clients that abuse it or implemented incorrectly. So it should have it’s own classification and developers should think about this kind of reverse incompatibility.

Complexity compatibility

I mean the complexity of program in wide sense: algorithm speed, it’s derivate and consumption of resources (memory, i/o, CPU, disk space), and even source code beautification and structural changes like refactoring. You know,  some algorithms may use more memory but use low processor.

For example, in some early versions of 64 bit JDK your application may fail with OutOfMemory error. This was change absolutely compatible in all categories mentioned before.

Another one sample is when new version of program is working more slowly than previous.

It may not change a contract and flow or anything else may it’s also may be changes that requires an attention.

 

Tool for checking API and BPI

Service provider interface (SPI)

https://stackoverflow.com/questions/2954372/difference-between-spi-and-api
https://en.wikipedia.org/wiki/Service_provider_interface

Also interesting

Why do I hate Maven?

Мавен упорно позиционирует себя как билд тул, хотя на самом деле он ни что иное как менеджер пакетов и конфигураций.
Т.е. через мавен я не могу через мавен ставить например ява пакеты как программы, а не как библиотеки.
Не могу например grails или какой нибудь sdk скачать и установить.
Когда я работаю с мавеном у меня постоянно всплывают воспоминания и дебиановском пакетном менеджере.
Мавену явно нужно было брать с ниго пример, там очень много крутых вещей в документации таится о которой не все линуксоиды знают (я помню меня сильно впечатлил строгость формата changelog файла).

Во вторых мне не нравится что мавен является не эволюционным продолжением анта, а конкурирующим с ним продуктом.
В итоге нельзя эволюциооно обновлять свой антовский проект — только сразу на мавен, только хардкор. Для слабоков смиловались и запилили ант плагин к мавену, что вообщем хоть как то, но выручает.Отсюда кстати и поползли холивары.
Документация к мавену ужасна. Только гугл, только stack-overflow.
Кодировка исходников и версия джавы под которую они написаны прописывается неявным образом:

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <jdk>1.6</jdk>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <configuration>
                    <encoding>${project.build.sourceEncoding}</encoding>
               </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>${jdk}</source>
                    <target>${jdk}</target>
                    <compilerVersion>${jdk}</compilerVersion>
                </configuration>
            </plugin>
        </plugins>
    </build>

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

Упаси бог написать вам свой java agent — они не поддерживаются на уровне конфигурации мавена. И даже плагина ещё нет.
В итоге для прогона юнит тестов библиотеки javaagent приходится изголятся таким образом:

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <forkMode>pertest</forkMode>
                    <argLine>-javaagent:${project.build.directory}${file.separator}${project.build.finalName}.${project.packaging}</argLine>
                    <workingDirectory>${project.build.directory}</workingDirectory>
                </configuration>
            </plugin>

И при первом прогоне оно не работает, посокольку джарника ещё нет, а мы уже к нему обращаемся.
Но как ни странно стадия тестов от этого не валится и он таки билдитcя. Мрак.

И так же тут всплывает ещё одна проблема: в пропертях мавена нет пути к сбилдженому артефакту.
Приходится самому её имитировать:
${project.build.directory}${file.separator}${project.build.finalName}.${project.packaging}

Кстати сами проперти в отдельный файл никак не вынесеш, как это сделано в анте.

settings.xml явно не самая удобная вещь. И непонятно почему репозитории не перенесены туда.

Не до конца понятный build lifecycle:

  • clean и site запускается только вручную, по сути это самостоятельные таргеты а пункты лайфцайкла.
  • install это совсем не то что что и install в make.
  • deploy тоже у всех ассоциируется немного с другим процессом.

С версииированием артифактов всё тоже не всегда хорошо. Дописывать alpha beta к релиз пакетам геморно (через плагин). А ведь это вполне стандартная система версиирования.

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

UPD
Интересная статья Maven is broken by design