Дисклеймер
Рубрику Begin я открываю для коллег по ремеслу. Мое ремесло - программирование в широком смысле, а в более узком смысле - это по большей части автоматизация разнообразных процессов выполняемых людьми. В основном они связаны с прикладной частью банковской деятельности. Справедливости ради, надо отметить, что в банках люди занимаются далеко не только, и не столько подсчетом денег, а выполняют массу побочной работы, не связанной напрямую с финансовой или бухгалтерской деятельностью. Ну например, они готовят разнообразные отчеты, взаимодействуют с клиентами, анализируют данные, поддерживают работу оборудования, программного обеспечения и много всего прочего. И, да, работу программистов автоматизации тоже нужно автоматизировать. О чем, собственно, и пойдет речь.
В результате как наблюдения над работой в этой области коллег по цеху в родной организации, так и того как поставлена работа в ряде других смежных организаций формируется некое общее видение проблематики. Возникают некоторые обобщения, понимание того как следует или как не следует поступать в той или иной ситуации. Изучается передовой и не очень опыт опубликованный в открытых источниках, нарабатывается собственный, делаются дидактические выводы. И, по большому счету в целях упорядочения собственных мыслей, я попробую изложить некоторые из них "на бумаге".
Почему Begin? Потому что хоть и опыт кое-какой имеется свой собственный, считать себя специалистом неким образом оригинальным или объективным было бы как минимум самонадеянно. Поэтому все что я предлагаю в этой рубрике это сугубо личный взгляд на вещи или, максимум, некая компиляция немного переработанного чужого опыта, которая пригодится, может быть, начинающим программистам, или тем кто вот сейчас меняет специализацию с какой-то другой области программирования на автоматизацию. Честно сказать, я и сам нахожусь лишь на неком начальном этапе понимания всей глубины проблемы и те выводы и правила, которые устанавливаются в процессе работы, пока проверены только на себе.
Таким образом, рубрика подразумевает скорее призыв к дискуссии и опровержению нижеизложенного, чем констатацию фактического положения дел или какие-то рекомендации, имеющие под собой незыблемую основу. Более того, в основном я попытаюсь изложить некие общие принципы и меньше касаться конкретики, которой в принципе вполне достаточно существующей. Как мне кажется, не хватает именно цельной картины некоторых явлений, проблем с ними связанных и общих путей их решения или избежания.
Сегодня предлагаю поговорить о проблеме устаревания программ, миграции на новые версии языков программирования, и, о том, как и что следовало бы делать, что бы эти действия не превращались в военные.
Постановка
Программы неизбежно развиваются - развиваются технологии, техника, появляются новые концепции и принципы. Эволюционируют и языки программирования вместе с инструментами их применения и приемами использования. Происходит это довольно таки быстро по сравнению с другими областями человеческой деятельности. Если, скажем, поиск и покупка новых оригинальных запчастей для автомобиля десятилетней давности еще не вызывает особых проблем, то сроки сопровождения программ ограничиваются годами, максимум десятком лет, кроме каких-то экзотических случаев.
Связано это прежде всего с тем, что затраты человеческих ресурсов на сопровождение какой-то программы начинают превышать экономическую, в первую очередь, целесообразность ее использования. Иногда участвуют репутационные соображения, которые могут продлить на какое-то время поддержку важного и известного продукта. Никакого принципиального отличия проблемы прекращения поддержки программных продуктов и каких либо других, тех же линеек автомобилей или разъемов питания нет - боль порождает и то и другое. Разница лишь в том, что как-раз в программировании можно значительно эту боль облегчить. Да, программы быстрее устаревают чем стандарты розеток, но и поменять программу значительно проще чем все розетки в стране.
Для перехода от старого к новому часто используется концепция переходников или промежуточных вариантов. Программирование не исключение: обозначение тех или иных устаревших частей интерфейса или функционала - депрекация - один из способов избавиться от пережитков прошлого. Либо, менее радикальным (или, наоборот, более, смотря с чей стороны) способом является обеспечение так называемой обратной совместимости (backward compatibility).
По аналогии с розетками депрекацией является сетевой переходник со стандарта "евро" на "советский". Это как бы сообщение о том, что ваша розетка устарела и ее скоро нужно будет заменить. Проблема переходника в том, что от него рано или поздно придется отказаться в пользу новой розетки - он потеряется или разболтается из-за прямой дополнительной нагрузки. Обратной совместимостью же являются универсальные розетки с сужающейся клеммой, плоским гнездом и широкими отверстиями, в которую можно подключить как старую вилку, так и современную. Проблема совместимых розеток состоит в их изначально низкой надежности соединения - площадь контакта в таком случае будет уменьшена по сравнению и со старым и с новым вариантом, да и сидеть вилка в такой розетке будет слабее. Эти проблемы становятся причиной, мало того что поломок самих розеток и подключаемого оборудования, но и более печальных последствий в виде пожаров.
Вот такие "пожары" нередко возникают и при использовании программ. Понятно, что существуют меры по предупреждению и страхованию от пожаров, но все-таки лучше всего попытаться избежать возможности, а не разгребать последствия. О том как правильно организовать переход или обратную совместимость говорено много и я не тот человек, который разбирается в деталях всех или даже одного конкретного варианта. Но очень часто, если не сказать всегда, я натыкаюсь на примеры того как этого не стоило делать совсем. Складывается впечатление, что люди не задумываются об этой проблеме, ленятся или даже специально поступают так что бы проблема обязательно вызвала максимальное количество последствий. Еще более удивительно то, что занимаются этим, как правило, люди не так давно проклинавшие предшественников точно за то же самое.
Причины
Однако, как я часто подчеркиваю, я верю в человечество и считаю, что большинство негативных явлений происходит вовсе не из чьего-либо злого умысла, а в следствии обстоятельств. Обстоятельства могут быть разные: дефицит времени, некорректная постановка задачи, недостаточная осведомленность, низкая квалификация, и нередко просто плохая организация труда. Куда реже это просто нежелание или несоответствие компетенции.
Если к большинству обстоятельств существуют соответствующие меры противодействия, то к организации труда подобрать ключики пытаются редко. Хотя, и они эти ключики существуют в достаточных количествах. Может быть, проблема как-раз даже в том, что этих ключиков слишком много разных и все они работают в каких-то своих условиях.
Возвращаясь непосредственно к программированию, можно навскидку перечислить десяток разных подходов именно к способу организации выпуска программных продуктов. Существует несколько моделей жизненного цикла продукта, начиная от простейшей каскадной, пришедшей в программирование из классических технологических процессов, заканчивая так называемым экстремальным программированием изобретенным уже программистами для программистов. Не вдаваясь в те или иные плюсы и минусы этих моделей и методологий можно вспомнить старую поговорку: "Что русскому хорошо, то немцу смерть". То есть, да, все они имеют право на жизнь, но обусловлены определенными целями и предназначены для решения конкретных задач и не годятся для других.
Часть ошибок, приводящих к обозначенной проблеме, как нетрудно догадаться, происходит как-раз из-за выбора неадекватной модели или технологического процесса преследуемым целям. Например, модные нынче модели динамического, непрерывного или ускоренного выпуска продуктов как-раз плохо, а часто и совсем не, совместимы с долгосрочным сопровождением и обслуживанием. В программу изначально не закладывается какой-либо срок жизни, а подразумевается его постоянное и непрерывное обновление, до окончания фактической эксплуатации. В некоторых областях программирования это оправдано, например, по экономическим соображениям: нет смысла долго и качественно разрабатывать развлекательное ПО, так как оно зачастую устаревает даже быстрее чем создается.
Несколько по-другому обстоят дела со средствами автоматизации. Программа каким-либо образом оптимизирующая или заменяющая человеческий труд не может устареть по причине технического прогресса или тенденций в моде. Замена ручных рутинных операций на автоматические или полуавтоматические может утратить актуальность лишь при исчезновении необходимости в самом процессе. Очевидно, что распечатывать отчеты и рассылать письма мы будем вынуждены пока существуют получатели этих отчетов и писем. Это как-раз те задачи, при решении которых в полный рост встает вопрос о максимально длительном сроке службы одних и тех же средств. Тут то и начинается основной разброд и шатание.
Кто-то считает, что его поделка вряд-ли достойна использования больше чем несколько раз, а потом кто-нибудь более компетентный обязательно перепишет её, возможно этим человеком будет и он сам немного погодя. Кому-то действительно банально не хватает времени посидеть и хорошенько подумать перед тем как выбрать то или иное решение. Третьи просто вынуждены непрерывно снабжать заплатками старое архитектурное решение, не имея возможности сколь-нибудь основательно разобраться в глубинной проблеме и пересмотреть что-то более радикально. Это в конце концов и приводит к полному скачкообразному переходу на что-то новое альтернативное и прогрессивное, разработанное с нуля, что, по понятным причинам, сопровождается массой других проблем.
Что делать?
Как-правило, что те, что другие, что третьи если не в корне, то сильно ошибаются. Во-первых, как мы прекрасно помним, нет ничего постоянней чем временное. Во-вторых, время, как на первый взгляд кажется, сэкономленное здесь и сейчас, выливается в зря потраченное многократно после. Третье - как прямое следствие первого и второго - является по сути заметанием крошек под скатерть, лечением зубов анальгином, игнорированием более серьезной проблемы в пользу, опять же, сиюминутного облегчения.
На вопрос что же со всем этим делать есть один правильный ответ. Выбрать подходящую модель и методологию, озаботиться продумыванием проекта на стадии технического задания, обосновать выбор той или иной платформы, технологии, компетенций и их конкретных обладателей подходящих для решения задачи. Беда в том, что этот ответ в большинстве случаев является не более чем красивой и теоретической риторикой. Вопреки тому, что "как бы всем прекрасно это известно", на практике выходит все с точностью до наоборот. Приходит заказчик с уже готовым техзаданием, предварительно выбранной средой окружения, с уже имеющимися крайним сроком, и какими-то деньгами, которые напрямую рассчитаны все из тех же сроков, взятых откуда угодно, только не из оценки фактических трудовых затрат.
Далее за работу берется человек, который крайне редко изначально понимает вообще о чем идет речь и начинает разбивать ТЗ на подзадачи, исключительно в меру своего понимания. Подзадачи распределяются уже по конкретным исполнителям не по принципу соответствующей квалификации, а просто по факту меньшей текущей загруженности. Максимум чем может поинтересоваться такой менеджер проекта у исполнителя это насколько быстро тот сможет переучиться из веб-девелопера фронтенеда в бэкенд базиста. Как именно и при помощи каких технологий, языков программирования, фреймворков исполнитель выполнит задачу не интересует ни кого.
Таким образом, с одной стороны обстоятельства фактически не оставляют места для маневра ни одному из участников. Программисту исполнителю дается два варианта: сделать все как надо и потратить на это больше усилий, или действовать опять же по обстоятельствам, а думать будем потом. С другой стороны, отсутствие строго описания задачи дает некую волю при выборе тех или иных инструментов и решений. Именно в этом месте существует ненулевая возможность, если не исправить ситуацию, то как-то минимально в нее вмешаться.
Выбор платформы
Среда, в которой должен будет выполняться будущий продукт или обновленная версия старого, обычно определена заранее и обоснована какими-то причинами часто не связанными с техническими. Потому что просто у заказчика старый сервер на Windows, СУБД на Oracle, а отчеты в Excel и люди привыкли. И если решение новой задачи напрямую не конфликтует с выбранной средой, то переубедить заказчика из любых других соображений зачастую не представляется возможным. Вместе с тем, заказчик, как правило, не предъявляет никаких требований к среде разработки и стеку технологий. Вопрос что же выбрать для того что бы программа прослужила некий минимальный срок, а потом еще и не прекратила свое существование из-за обновления операционной системы или сторонней офисной программы остается на усмотрение исполнителя.
Применительно к сказанному существует довольно странное, по моему убеждению, негласное правило - не брать что-то последнее. Последнюю версию языка программирования или фреймворка, какую-то относительно свежую парадигму или технологию. "Надо делать на PHP 5.6 - он у всех есть", - или: "JDK 1.8 проверен временем, поэтому лучше на нем", - можно услышать из уст довольно серьезных людей. Во-первых, где-то наверняка есть и PHP 3 - всего на свете не предусмотришь. Во-вторых, в том и дело, что Java 8 проверенная временем, показала, что ее пора обновлять и совершенствовать, именно поэтому люди делают новые версии языков.
Выбирая наиболее "ходовую" или "надежную" платформу вы на самом деле лишь сокращаете минимальный срок службы вашего продукта без каких-либо веских оснований - просто потому что так кто-то сказал. Использование самой последней версии платформы или фреймворка только в данный момент может выглядеть излишне прогрессивно, уверяю, всего года через три вам скажут только спасибо. Помимо этого, выбор свежей платформы поможет избежать уже реальных архаизмов в синтаксисе, в использовании устаревших методов и приемов работы. Новая программа должна создаваться для и с помощью всего самого нового. Если же речь идет об обновлении некоторого программного продукта, то так же следует использовать максимально свежие доступные для данной платформы решения. А так же следует максимально изъять устаревшие куски кода, которые через непродолжительное время могут оказаться местом мешающим следующему апгрейду окружения.
Депрекация или обратная совместимость
Что бы программа все-таки как-то работала в устаревшем или не самом прогрессивном текущем окружении существует ряд стандартных приемов. Если возникла необходимость дополнить или переосмыслить старый функционал то есть два пути: добавление новых функций вместо старых, или более хитрая перегрузка некоторых существующих путем инкапсуляции и наследования. Первый путь - депрекация. Оставляем старое как есть, помечаем устаревшим, а новый функционал будет работать исключительно по новым требованиям. Позже, когда наступит полная уверенность в том, что старое больше ни где не используется вырезаем отмерший кусок, что уже никак не повлияет на работоспособность текущего функционала.
Более витиеватый путь - обратная совместимость. В принципе одно другому не мешает, и обратно совместимая функция может быть так же помечена как устаревшая, но это все-таки немного другое. Целью такого апгрейда является не ожидание когда нежелательный кусочек отомрет, а его переделка под новые требования. Мы можем сохранить возможность загружать в программу устаревшие документы в формате Word 2003, но код отвечающий за загрузку изменим таким образом, что функция сперва будет приводить документ к современному формату, а затем уже работать с ним новыми штатными средствами, а не по-старому.
Второй подход усложняет и увеличивает кодовую базу и требует больше времени на отладку и тестирование, однако в итоге отпадает необходимость дублировать те или иные исправления в старом функционале. Имеем всю ту же экономию в долгосрочной перспективе.
Оценка выбора той или иной общей стратегии зависит, опять же, от обстоятельств, тем не менее если существует хотя бы минимальная возможность озаботиться обратной совместимостью вместо прямой депрекации, то следует поступить именно так, пожертвовав каким-то временем сейчас.
Необходимо отметить, что бывают и уходы в крайность. Когда программа теоретически сохраняет совместимость с чем-то настолько архаичным и устаревшим, что уже и при тестировании не удается полноценно проверить такой функционал. C такими опциями, как поддержка dbf файлов, флоппи дисководов и HTML 3 все же стоит прощаться более охотно.
Новинки
Что касается новинок в индустрии в целом. Даже если вы вообще не планируете задействовать новые конструкции языка программирования или фреймворка, как, например, var синтаксис для лямбда-выражений в Java или "строгую типизацию" в PHP, то вы уже можете учесть их при продумывании интерфейсов. Интерфейсы это та часть программы, которая по сути зиждется на понятии совместимость в ее самом прямом и хорошем смысле. Это именно та часть программы, которая не должна меняться никогда. Именно изменения в интерфейсах являются первичной причиной появления новой мажорной версии продукта. И если уж вы снабжаете программу интерфейсом, то мало того что вы должны учесть все современные реалии, но вы должны попытаться спрогнозировать и то что может потребоваться от интерфейса в будущем. И тогда уже нужно не только быть в курсе последних нововведений в выбранную платформу, но и отслеживать тенденции в смежных средах и платформах.
Ни кто не заставляет тут же использовать нововведения только по причине того что они появились. Более того, многие новинки на практике не приживаются и быстро отмирают так и не встретив всеобщей поддержки. Могу подозревать, что лозунги типа "лучшее - враг хорошего" появляются как-раз оттуда. То что ваш код совместим и отвечает всем требованиям и рекомендациям последней версии языка программирования вовсе не означает, что он без него не должен выполняться. Вы можете... нет, вы должны писать сообразно современным представлениям о языке.
Если сказано, что вместо wm_concat следует использовать listagg в качестве агрегатора конкатенации в запросах к Oracle, то вы должны так и поступать, а не изобретать какие-то свои варианты этой функции в версиях СУБД, которые уже прямо её не поддерживают. Если сказано, что модуль mysql будет заменен на аналогичный mysqli то вы должны впредь использовать именно его, а не конструировать сложные структуры обхода в зависимости от версии PHP. (Это, между прочим, как-раз к слову о депрекации и чем она хуже обратной совместимости. По какой причине нельзя было оставить псевдонимы mysql к mysqli мне все еще не очень понятно).
Так же и с интерфейсами. Если вам известно, что в следующей версии языка будет возможна какая-то новая конструкция, например, передача в качестве аргумента функции, типизированной переменной или другой функции, вы можете уже не ограничивать интерфейс исключительно параметрами присутствующими в текущей спецификации языка. Или если готовится к выходу следующая версия какого-то формата обмена данными, то, наверное, разумно оставить абстрактные заглушки к методам реализующим нововведения. Так вы не только зарезервируете в проекте имена методов, но и значительно облегчите судьбу младшим коллегам, которым останется лишь реализовать их, совершенно не задумываясь о том, что это как-то сможет навредить существующему функционалу, и именно так как хотели бы этого вы.
Оговорки
При всем при этом не следует, разумеется, впадать в перфекционизм - разработка идеальной программы занимает вечность, и её у вас в распоряжении, надо полагать, нет. Много приходится слышать простой призыв не делать того о чем вас не просят. Не сказано в ТЗ обеспечивать чистоту и современность исходного кода, значит можно как бы и не особо над этим заморачиваться. Но это, осмелюсь доложить, превратное понимание слов. Когда говорят, что не нужно делать лишнего, то имеют ввиду функционал программы, а не её внутреннюю сущность. Да, не нужно реализовывать импорт помимо указанного в ТЗ 'docx' еще и до кучи 'pdf', 'rtf' и 'mp3' только потому что вы решили, что это может оказаться полезным. Но никто не говорит о том, что бы при реализации импорта вы не применяли свежую библиотеку ООXML, хоть вас об этом и не просят прямо.
Создание проекта с придумывания в начале каких-то сверхуниверсальных интерфейсов тоже может оказаться скорее во вред, чем на пользу. Интерфейсы, если они изначально не прописаны в ТЗ, должны появляться естественным образом при декомпозиции задачи. Придумывая заранее интерфейсы и те же автоматические тесты для экстремального программирования можно наплодить излишнюю сложность реализации или логическое их несоответствие сути реализации. Зачастую уже во время разработки оказывается, что тот или иной параметр интерфейса окажется бессмысленным, а отсутствие другого придется компенсировать сложными обходными конструкциями, как настройки контекста сессии, или, того хуже, отдельными хранилищами промежуточных данных. Однако, уж если интерфейсу быть, то его уже нужно аккуратно и внимательно изучить на предмет пригодности его в будущем.
Как определить срок службы будущей программы вопрос больше риторический. Ответить на него сколь-нибудь адекватно не получается даже у самых продвинутых архитекторов и гуру проектирования. По классам программ, как сказано выше, можно лишь приблизительно оценить то насколько актуальна будет программа через три года, или через десять лет. Программы автоматизации ручного труда человека, как показывает практика, обязаны служить как минимум до того момента пока не придумают аналогичный процесс полностью заменяющий первичный. Программа переводчик с одного языка на другой, например, станет невостребованной только тогда, когда умрет последний человек понимающий один из языков. Ну или когда гугл научится переводить с любого языка на любой лучше любого человека. Даже некоторые игры, рассчитанные в общем от силы лет на пять, иногда приходится обновлять и поддерживать вот уже третий десяток лет.
Обратная совместимость и депрекация остаются открытыми проблемами множества программных, и не только, систем. Найти общее решение попытки делаются и, надо сказать, не без успеха. Более или менее однообразно сейчас обновляются операционные системы, крупные комплексы типа офисных и пакетов или серверных приложений. Все языки программирования так или иначе пытаются балансировать между консервативностью и прогрессом. Кто-то принимает радикальные решения, кто-то остается преданным слову, но так или иначе все движется к единому пониманию чего и как. Мое мнение таково, что и отдельным программистам следует двигаться к этой точке воссоединения и всеобщего благоденствия. Пусть не сразу и не единовременно, но целенаправленно и поступательно. Глядишь, через какое-то время наши потомки будут с ужасом вспоминать те времена, когда программы можно было писать как попало, некачественно и за неприемлемые сроки, и помянут нас добрым словом, а не трехэтажным матом.
End;