Страницы

суббота, 4 июня 2011 г.

Переход на юникод 1: Поиск стратегии.

Обещанная статья о переводе большой программы с Delphi 6 на Delphi 2010 (для Delphi 2009 и Delphi XE (2011), ситуация будет аналогичной). Материала получилось довольно много, поэтому я разобью его на несколько постов.

Дано

Большое приложение, с большой базой данных и большой историей. Приложение, которое начали разрабатывать ещё на Delphi 3, потом портировали на Delphi 6. Теперь надо ввести поддержку юникода, и собрать в Delphi 2010. Приложение использует кучу пакетов как самописных, так и от сторонних производителей. Проект большой, комментариев в коде практически нет. Юнит-тесты написаны только для очень маленькой части кода общих библиотек.

Что надо получить?

Работающую версию программы с поддержкой юникода для D2010. Из-за большого числа клиентов, крайне желательно, чтобы обновлению программы (включая базу данных) с неюникодной версии на юникодную происходило по возможности безболезненно. В идеале сделать так, чтобы с одной и той же базой данных можно было работать как из уникодной версии программы так и из Ansi-версии.

Необходимо поддерживать и развивать существующую версию (на Delphi 6), исправляя найденные баги и расширяя функционал. А баги будут на сто процентов. Всё-таки баги в программах появляются также ожидаемо как глисты у собак летом.

Стратегии перехода

Стратегии для перехода на Юникод:

  1. Сделать так, чтобы один и тот же код мог собираться как в Delphi 6, так и в Delphi 2010 и чтобы программа работала стабильно и без ошибок.
  2. Начать проект с нуля только на Delphi 2010, частями перенося код из версии для D6.
  3. Сделать копию стабильной версии проекта и вести работу по переходу на юникод на копии, оставляя нетронутой стабильную версию для Delphi 6.

Плюсы и минусы стратегий

Вариант 1. Общий код для Delphi 6 и Delphi 2010.

  • + Одна база кода. Не придётся исправлять одну и ту же ошибку и Delphi 6 и в Delphi 2010.
  • + Все нововведения, одновременно доступны как в версии для D6 так и в версии для D2010
  • + Юнит-тесты достаточно написать один раз, а собирать и запускать их можно как в Delphi 6, так и в Delphi 2010.
  • - Есть риск, что после какого-либо изменения, нестабильной окажется либо одна из веток программы
  • - После каждого изменения кода необходимо собирать и тестировать программу в 2х вариантах. Без юникода и с. Впрочем, до релиза юникодной версии достаточно тщательно проверять только неюникодную версию.
  • - Придётся обновить библиотеки, стабильно работавшие в Delphi 6, только для того, чтобы использующий их код можно было скомпилировать в Delphi 2010.
  • - Сложности, если в версии для Delphi 6 и в версии для Delphi 2010 придётся использовать разные версии компонентов. Например, если в D6 в коде используется RxLib, а в Delphi 2010 вместо них JVCL. Придётся писать дополнительный код, для поддержания такой работы.
  • - Придётся тащить с собой весь устаревший код или переписывать.

Вариант 2. Начать с нуля.

  • + Можно избавится от устаревшего кода.
  • + Можно не разбираться в неизвестном коде.
  • - Придётся досконально разобраться в бизнес логике программы, которая, скорее всего, нигде не задокументирована.
  • - Высок риск потерять большую часть функционала, из-за недостаточного понимания всех нюансов работы программы. Риск увеличивается, при частом использовании обработчиков событий.
  • - Новый проект всегда будет работать не так как раньше.
  • - Придётся вести 2 версии кода.

Вариант 3. Отдельные ветки для D6 и D2010.

  • + Можно избавиться от устаревшего кода.
  • + При переходе не придётся заботится об обратной совместимости с Delphi 6.
  • + Не придётся обновлять библиотеки в версии для Delphi 6. Достаточно будет установить последние версии только для Delphi 2010.
  • - Старые ошибки придётся исправлять в двух версиях программы. Как показывает практика, рано или поздно случается так, что какие-то из ошибок забывают исправить в одной из версий программы. И через какое-то время их приходится исправлять повторно.
  • - Нововведения, придётся вводить также в двух разных версиях программ.

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

Второй вариант часто выбирают новички, когда не могут разобраться в существующем коде. Как подсказывает мне мой опыт, в случае отсутствия чёткой спецификации системы, этот способ ведёт к тому, что программа теряет большую часть функционала. Наиболее затратный по времени способ.

Третий вариант хорош тем, что к переходу можно приступать сразу же. Но со временем придётся всё чаще исправлять одни и те же ошибки в двух разных местах. Этот вариант даёт возможность получить результат раньше. Но в случае необходимости сопровождать и развивать и старый и новый код, начнёт требовать в 2 раза больше времени на каждое исправление.

В следующем посте я подробнее остановлюсь на плане перехода и расскажу об опыте обновления сторонних библиотек.

11 комментариев:

  1. Пара комментариев.

    Первый - по предмету статьи. Имхо, реалистичны только первый и третий варианты - для второго варианта как правило нет времени и бюджета. Переписывание "с нуля" - всегда тупиковый вариант. Незнакомый код проще рефакторить или покрыть тестами-спецификациями.

    Второе - не совсем уверен в смысле существования двух параллельных версий. Какая Пользователю разница - д6 или д2010? Для него это при любом раскладе будет win32 приложение! Мы же не говорим о разных платформах - мак, iOS, etc.

    ОтветитьУдалить
  2. > Переписывание "с нуля" - всегда тупиковый вариант.
    Не всегда. Для небольших приложений, при 100% пониманию того, как всё должно работать и излишне замудрённом исходном коде иногда проще переписать заново. Но, согласен, в крупных программах это путь в тупик.

    > Какая Пользователю разница - д6 или д2010?
    Разница в том, что в одной версии есть юникод, а в другой нет. А получить юникодную могут только те пользователи, кто заплатили за апгрейд. Остальные же, при наличии подписки на сопровождение могут требовать только исправления ошибок в приобретённой версии.

    ОтветитьУдалить
  3. 1-й - это Ад. Слишком трудоёмкий. Либо программист должен быть фанатичный.
    2-й как верно подметили тупиковый, не у каждого бизнеса(заказчика) хватит терпения ждать без результатов. Из моего опыта они не доходят до конца, и в итоге через время, у вас 2 проекта новый не работает, старый морально отстал, из-за недостатка внимания.
    3-й ничё так, куча Копипастов, куча ошибок...

    Я бы Вам предложил 4-й вариант (что-то между 2 и 3).
    Правда придётся кой чем пожертвовать..
    Начать разрабатывать с 0 новый exe. Но работать они будут одновременно. (не EXE и DLL а 2 EXE)

    -на 1 клиента 2 коннекта к базе, ...
    -придётся написать некую подсистему взаимо-понимания.
    -если MDI интерфейс, придётся о нём забыть
    +В случае неудачи, Ваши риски малы, у вас всегда рабочая система
    +всё остальное

    Вы берёте свой большой проект, и делите на логич.блоки: A,Б,В1,В2, ... Далее прорабатывайте приоритеты и зависимости. Далее реализовывайте в новом A, в старом удаляете(не заглушки, прям юниты из проекта, чтоб контролировать зависимости) А, реализовывайте Б, в старом удаляете Б, ... . пока старый не помрёт.

    По поводу взаимодействия:
    * ShellExecute с параметрами
    * CreateProcess, WaitForSingleObject
    * CreateFileMapping, MapViewOfFile
    * RegisterWindowMessage, SendMessage

    (пример EMS МенеджСтудио, раз. блоки вызывают(запускают) другие, у них общие настройки, реестр,...)

    ОтветитьУдалить
  4. Выпуск юникодовой версии означает отказ от сопровождения неюникодовой. Тот же MS как-то не сильно сопровождает Win98...

    Так, что, ИМХО, разумно пойти по третьему варианту и затем потихоньку свернуть старую версию...

    ОтветитьУдалить
  5. 2 Анонимный:
    > 1-й - это Ад. Слишком трудоёмкий.
    Вот не соглашёсь. Самое трудоёмкое - это заставить код программы одинаково работать с разными библиотеками в разных версиях. И то, самая трудоёмкая часть - это написать свою прокладку для общения с такими библиотеками, создав иерархию абстрактных базовых классов и сделав конкретную реализацию для конкретной библиотеки. Для меня 1й вариант, выглядит намного проще чем, предложенный вами, 4й вариант.

    >3-й ничё так, куча Копипастов, куча ошибок...
    Копипасты, ошибки. И старая версия отстаёт в развитии, так как ей уделяется всё меньше и меньше внимания.

    > Я бы Вам предложил 4-й вариант (что-то между 2 и 3).
    > Вы берёте свой большой проект, и делите на логич.блоки: A,Б,В1,В2, ...

    Разумеется мы рассуждаем об абстрактном. Но я вообще не представляю как такой подход можно применить в моём конкретном приложении. Слишком много лет его писали разные люди. Слишком много зависимостей внутри кода. Для нормальной работы в таком духе придётся разделить между приложениями кучу внутренних настроек, подключение к БД, транзакции, открытые датасеты. Возможно даже основные формы.
    И сама идея отказа от MDI вызывает у меня ужас. Просто потому, что всё-равно придётся что-то подобное реализовывать самому.

    В этом плане, 1й вариант, выглядит намного проще в реализации.


    Bozhko
    > Выпуск юникодовой версии означает отказ от сопровождения неюникодовой. Тот же MS как-то не сильно сопровождает Win98..
    MS сопровождал свою Win98 ещё лет 5 или 8 после выпуска.
    Ведь есть такая вещь, как гарантия на сопровождение купленной версии. В случае отказа от сопровождения старой версии, клиентам имеющим действующую гарантию на старую версию, придётся предоставлять новую. Слишком щедрый подарок.

    ОтветитьУдалить
  6. >>Но со временем придётся всё чаще исправлять одни и те же ошибки в двух разных местах. Этот вариант даёт возможность получить результат раньше. Но в случае необходимости сопровождать и развивать и старый и новый код, начнёт требовать в 2 раза больше

    Для этого и есть различные CVS. Все равно же переход выполняется в новом бранче, а текущая версия на D6 замораживается на stable. Ни каких копипастов, старая версия только фиксы багов.

    ОтветитьУдалить
  7. > а текущая версия на D6 замораживается на stable.

    Я конечно за, но к сожалению невозможно заморозить версию на 100%. В силу организационных моментов. Что-то всё-равно придётся дописывать и в старой версии, хотя бы просто для того, что с новой фичей удастся сэкономить кучу человекочасов коллег. Просто для того, чтобы клиент был удовлетворён. Это корпоративный сектор. Здесь между моментом покупки и моментом внедрения может пройти год. И через год может вясниться, что какая-то мелочь делает работу с программой невозможной. И эту мелочь придётся исправлять или даже переделывать. Просто для того, чтобы клиент был доволен тем за что выложил кучу денег и не ушёл к конкурентам. Как-то так я это вижу.

    ОтветитьУдалить
  8. >И через год может вясниться, что какая-то мелочь делает работу с программой невозможной.

    Дак заморозка stable не создает не возможность какого то допиливания. Просто это будет проходит в отдельном бранче. Ну и как бы я за третий вариант, и как мне видится оба минуса нивелируется системой контроля версий, при правильном использование: http://www.gnuman.ru/stuff/svn_strateg/. У нас такая же специфика, баг часто обнаруживается спустя год после выпуска фичи, и править нужно именно в предыдущей версии компонента, потому что новая версия задаром клиенту слишком жирно, или просто нет возможно апгрейда. Плюс переход наверняка принесет кучу багов, и какое то продолжительное время не будет стабильной юникодной версии, тогда бы хотелось иметь всегда возможность добавить фичу или пофиксить баг в проверенной стабильной версии.

    ОтветитьУдалить
  9. Если я правильно понял постановку задачи (переход на юникод, а не на Дельфи ХЕ), то могу предложить еще один вариант стратегии - оставаясь в Д-6, сделать юнкод в существующем проекте подключая дополнительные пакеты компонент.
    Например TNT unicode components
    http://www.yunqa.de/delphi/doku.php/products/tntunicodecontrols/index?DokuWiki=4d2c3soj4qnetmsg8ba8s71fv6

    ОтветитьУдалить
  10. cemick:
    > потому что новая версия задаром клиенту слишком жирно
    Ага. =)

    Сколько раз я ни пробовал работать в SVN с ветками, столько раз сталкивался с тем, что не всегда получается корректно перенести изменения из одной ветки в другую (часто случаются такие конфликты, что намного проще скопировать код руками, чем резолвить конфликты). Я сейчас говорю о случае, когда обе ветки редактируются независимо друг от друга. Особенно, если код был подвергнут жёсткому рефакторингу и были затронуты несколько модулей. Ещё хуже дело обстоит если по ходу дела менялись dfm-ки. Но это так... устаревшие впечталения, пары лет тому назад.

    Анонимный:
    Вариант с переходом на TNT вообще не рассматривался. По причине того, что в проекте используется слишком много сторонних библиотек. Я так понимаю, их пришлось бы переводить самим, лезть в исходный код и заменять стандартные классы на TNT аналоги. Это было бы слишком трудозатратно. Да и править чужой код всё ещё развивающихся продуктов - это заранее подписать отказ от всех возможных обновлений.

    ОтветитьУдалить
  11. > 1-й - это Ад. Слишком трудоёмкий. Либо программист должен быть фанатичный.

    Вывод: все разработчики компонентов после смерти попадают в рай, потому что свой ад они уже отбыли на земле :)

    ОтветитьУдалить