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

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

Старый код работает


На самом деле, старый код очень полезен. Упрощенное определение «унаследованного кода» может быть таким:

Унаследованный код – это код, который приносит столько пользы, что с издержками на его обслуживание можно мириться.

Эта формулировка настолько тривиальна, что кажется бессмысленной; поэтому получившееся определение сильно усложняется. Но я часто нахожу, что это позволяет по делу рассматривать унаследованные системы, с которыми мы взаимодействуем, потому что воспитывает смирение в подходе к вещам, имеющим долгую историю.

Польза может быть очевидной не сразу. Не все могут даже согласиться, что в этом есть польза. Мы можем даже подумать, что организация «зря» решила сохранить унаследованную систему. Но системы редко становятся унаследованными, если наследовать нечего. Если вы не уверены, что можете сформулировать, в чем заключается это наследование – продолжайте формулировать.

Наследие книги «Эффективная работа с унаследованным кодом»


Одной из книг, определивших порядок обслуживания унаследованных систем в компании «8th Light», стала работа Майкла Физерса «Эффективная работа с устаревшим кодом», вышедшая в 2004 году. Физерс отстаивает свое небесспорное определение унаследованного кода:

С моей точки зрения, унаследованный код — это просто код без тестов. Такое определение далось мне горьким опытом. Что же должны делать тесты для выявления неудачного кода? [...] С помощью тестов мы можем быстро и под полным контролем изменить поведение нашего кода. А без них мы на самом деле не знаем, становится ли наш код лучше или хуже.

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

Мы, компания 8th Light, полностью согласны с этим. Мы были сторонниками подхода «сначала тестирование» с момента основания компании. Мы видели, как хорошо структурированный комплект тестов не только помогает увереннее судить о поведении кода, но и стимулирует эволюцию кода так, чтобы он становился понятнее и более удобен в поддержке. Когда мы запускаем новую работу, мы задействуем проектирование через тестирование, чтобы помочь этим проектом войти в эффективный цикл.

Сила книги Физерса в ее прагматизме. Он не тратит времени на обсуждение исходной посылки, что отсутствие тестов является проблемой (цитата выше взята с клапана оригинальной обложки!). Вся книга посвящена тому, что делать, когда эффективность цикла оставляет желать лучшего.

Большая часть книги представляет собой сборник общих проблем и решений (например, «Я повсюду меняю один и тот же код», «этот класс слишком велик, и я не хочу, чтобы он разрастался»). Книга завершается каталогом безопасных, в основном механических изменений, которые могут быть внесены в отсутствие тестов, чтобы закрепиться, что помогает обойти проблему курицы и яйца, когда «не протестированный код имеет тенденцию быть нетестируемым вовсе».

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

Книга устранила оправдание того, что система была «непроверяемой», продемонстрировав множество способов добавления тестов. Это также обеспечило подстраховку и несколько отбросило идею работы с унаследованными системами. Таким образом, на протяжении всей нашей истории мы могли исследовать положительные коннотации слова «наследование» без особого страха. Наши консультанты даже говорят о своем унаследованном коде как о предмете гордости.

Сила унаследованного кода и глупость простого переписывания


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

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

На самом деле существует некоторая теоретическая основа для такого подхода к выживанию в отрасли. Эффект Линди предполагает, что для некоторых артефактов, в частности, технологических идей, будущая долговечность пропорциональна прошлой долговечности. Ожидается, что идеи, которые существуют дольше, будут существовать и дальше. Это эквивалентно распределению времени жизни по Парето, что выглядит как правдоподобная и экономная модель, которая могла бы применяться к срокам службы программных систем:

image

Если эффект Линди действительно применим к продакшену, мы должны рассматривать факт долговечности системы как свидетельство ее практической ценности. Это кажется не просто реалистичным, но даже очевидным. Несмотря на отсутствие тестов, «эта система», которая обслуживает клиентов в течение 15 лет, имеет гораздо более высокие требования к надежности и пригодности для конкретных целей, чем все, что мы внедрили бы сегодня – исключительно благодаря ее истории успеха.

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

Отчасти соблазн переписывания заключается в том, что гораздо проще создавать что-то с чистого листа, чем осмыслить и безопасно изменить существующую систему. Было бы неплохо сосредоточиться на хорошо понятной проблеме, не отвлекаясь на посторонние задачи и ограничения, не так ли? Опыт подсказывает, что переписывание кода к такому результату не приводит, но, если бы это было достижимо, то позволило бы избежать, казалось бы, ненужной работы.

Мы можем не в полной мере учесть стоимость переписывания, потому что всегда есть некоторые «неизвестные неизвестные» о существующей системе. Тем важнее практиковать предложенный Физерсом подход к непротестированному коду: первое, что нужно сделать с ценным не протестированным кодом, – это выяснить, как быстрее всего охватить его тестами.

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

Призыв к эпистемологическому смирению


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

Я часто припоминаю притчу о заборе Честертона, чтобы проиллюстрировать важность не дергаться в непривычных ситуациях. При слегка вольной трактовке оригинальной цитаты 1929 года, ее можно понимать так:

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

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

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

— Норман Л. Керт, «Ретроспективы проектов: Руководство для обзоров команд»

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

Поиск ценности в унаследованных системах


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

Если напрямую поговорить со всеми причастными не удается – импровизируйте. Возможно, ваш продукт – это интернет-магазин или клиент-ориентированный SaaS-продукт. В этих случаях существуют более пассивные поведенческие метрики, в частности, аналитика и логи, а также более активные методы, такие как A/B тестирование. Владельцы продуктов отлично разбираются в данной области, чтобы задавать вопросы о приносимой пользе, для обеспечения которой и была разработана система.

Если вы можете получить информацию от людей, которые участвовали в проектировании или разработке унаследованной системы, то это будет потрясающе! Относитесь к этому как к ценной возможности подробнее изучить систему. Полюбопытствуйте о решениях, ограничениях и стимулах, которые привели к созданию того, с чем вы работаете. Но также имейте в виду, что, если существуют способы, при которых существующая система не удовлетворяет потребностям пользователей; входные данные, которые вы получаете, подвержены тем же искажениям, что и исходные.

В унаследованных системах, которые все еще находятся в стадии активного развития, может быть полезно посмотреть на темпы изменений. Какие части системы меняются чаще всего и реже всего? При этом учитывайте, что существует ряд факторов, определяющих скорость изменений:

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

• Медленно меняться может код, о котором никто не заботится, или это может быть «основа», которая выполняет свою простую функцию достаточно хорошо, чтобы ее не нужно было менять так часто.

История контроля версий может быть ценным источником дополнительной информации. Часто вопрос «почему они сделали это таким образом?» о локализованном месте в коде легко решается, если посмотреть на изменение в контексте всего остального, что изменилось вместе с ним. Проявите любопытство к коду и его авторам: вопрос «почему они сделали это таким образом?» всегда следует задавать со 100% искренностью, потому что на него всегда есть искренний ответ.

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

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

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

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

Унаследованный код очень часто плох – его трудно понять, трудно модифицировать или он не соответствует современным стандартам. Но он эволюционировал, чтобы служить бизнес-целям, и потрачены годы на то, чтобы он приносил пользу: независимо от того, нравится код вам или нет, всегда есть, по крайней мере, некоторые отношения, в которых этот код можно считать хорошим.

Комментарии (10)


  1. vadimr
    07.10.2022 14:39

    Унаследованный код очень часто плох – его трудно понять, трудно модифицировать или он не соответствует современным стандартам.

    Ай-яй-яй, кто же эти враги народа, которые вставили плохой код в систему?

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


    1. aamonster
      07.10.2022 14:46
      +6

      "Очередным архитектором" может быть тот же самый, что проектировал это легаси.

      Просто требования потихоньку менялись, появлялись части, взаимодействие с которыми себе даже не представить было, отмирало что-то, диктовавшее определённые решения – но в каждый момент изменения были недостаточно большими, чтобы оправдать полное переписывание.


    1. pharo
      07.10.2022 18:59
      +1

      А, не признак ли этого, что абсолютно любой код для впервые с ним сталкивающимся разработчиком априори является непонятным, а значит плохим с точки зрения его читающего — это ведь не им выстраданный проект? (возможно и через принятие компромиссов реализации и вставки каких то костылей)


  1. Ivan22
    07.10.2022 18:05
    +2

    "Как высосать из старой ИТ шутки "работает - не трогай" статью на 5 страниц


  1. TAZAQ
    08.10.2022 00:33

    Ценность легаси - в месяцах опыта по переписыванию этого добра, причем чаще всего без документации и даже намёка на неё


  1. Racheengel
    08.10.2022 14:01

    У нас несколько продуктов основаны на том самом легаси. И да, он страдает практически от всех перечисленных проблем:

    • Плохая архитектура

    • Низкое быстродействие

    • Практически нереально отлаживать

    • Очень трудно расширять и изменять

    • Нормально компилируется только msvc, поэтому переносимость под вопросом

    • Для вновь пришедших на проект инженеров, пару месяцев вхождения как минимум

    • И т.д.

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


  1. kovserg
    08.10.2022 14:21

    image


  1. aburavcev
    08.10.2022 22:35

    Унаследованный код хранит формализованное знание о предметной области.


    1. Ivan22
      08.10.2022 23:15

      скорее скрывает


    1. Racheengel
      09.10.2022 12:59

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