Привет, Хабр! Меня зовут Евгений Старков, и я, как и вы, часто сталкиваюсь с техдолгом.

Пришло время рассказать, как я с ним справляюсь! Проблема не нова и встречается в любой компании, связанной с разработкой. Мне, по большей части, техдолг достался «в наследство». Первой задачей в Тензоре было разделение и рефакторинг большой части кодовой базы своей группы. Там попадались и суперклассы, и файлы с количеством строк, перевалившем далеко за 1000, и именование переменных по типу a, b, c.

Когда увидел кодовую базу, которая теперь твоя
Когда увидел кодовую базу, которая теперь твоя

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

Как я себя чувствовал, когда занимался данной задачей
Как я себя чувствовал, когда занимался данной задачей

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

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

  1. сам код (качество, стиль, чистота)

  2. исполнение в рантайме (скорость, оптимальность)

Соберем анамнез. Посмотрим, как было и потом сравним, спасли мы пациента или наоборот, сделали так, что он быстрее уйдет на покой.

Распилим пациента и посмотрим, что у него внутри
Распилим пациента и посмотрим, что у него внутри

Начнем с кода. Как выявить болячки здесь? Проведем диагностику. А в этом нам помогут линтеры, для предварительного диагноза. Начнем с pylint и проверим качество написания кода:

Плохо, но не критично. Тут в файлах заметно, что часть симптомов решили скрыть, отключив линтер:

Мы хотим знать объективную правду, срываем маски и видим, что пока сделали только хуже:

Начнем лечение того, что выявили:

  1. Один файл больше 1000 строк (1847). Решим немного позже, так как можем полечить его, избавившись от других.

  1. Дублирование кода, порой даже в пределах 100 строк. Вот пример:

Плюсом еще такое условие в 2х местах. Выносим в функцию и заменяем:

Создали отдельную функцию
Создали отдельную функцию
И используем уже ее
И используем уже ее

Подобным образом делаем для других повторов. Всего проблем с дублями 15 шт. Файл большой, поэтому выносим такие куски в отдельный файл, который будет хранить вспомогательный функционал. Мы избавились почти от 450 строк.

Результатом трудов стало улучшение состояния - мы делаем коду лучше.

  1. Следующая проблема – использование форматирования строки, вместо f-строк. Данная проблема появилась по причине развития языка и ярко отображает то, что идеальный код со временем может приобрести проблемы. А таких у нас 87.

Эти ошибки исправить просто – меняем формат на f-строку.

  1. Константы, шаблоны запросов и исключения затеряны в самом коде.

Это уменьшает читаемость. Все выносим в отдельные файлы.

  1. Большое количество ветвлений, принимаемых и неиспользуемых аргументов.

Большие функции на 200+ строк. По возможности избавляемся от ветвлений: одну из функций пришлось сделать классом (чтобы уменьшить ветвление), упростить сам код и сделать его лаконичней.

Теперь наш код здоров:

Как я себя чувствовал
Как я себя чувствовал

Но ведь нет предела совершенству. Да и когда мы видим вот такой код с отсутствием у части тайп-хинтингов, один глаз все-таки начинает дергаться.

Поэтому назначаем еще одну диагностику, а именно mypy, и видим следующую картину:

Исправляем, правим типы или добавляем их, так же типизируем принимаемые параметры в функциях и получаем уже приятную глазу картину:

Теперь и читать приятно, и mypy не ругается
Теперь и читать приятно, и mypy не ругается
А ты юзаешь подсказки типов?
А ты юзаешь подсказки типов?

Но откуда же взялись эти проблемы?

Основные причины появления технического долга:

  1. низкая квалификация сотрудников и отсутствие надзора за их исполнением;

  2. отсутствие стандартов написания кода, проектирования функционала;

  3. отсутствие контроля за соблюдением правил, стилистики и архитектуры;

  4. жесткие временные рамки;

  5. плохая взаимосвязь или ее отсутствие между бизнесом и разработкой;

  6. ошибки проектирования или его отсутствие;

  7. изменения требований внутри компаний или переход на новый стек технологий, иногда, обновление сторонних библиотек.

Теперь само написание кода проблем не имеет, но пациент все равно нам всеми силами намекает: «что – то не так!». Простая задачка им выполняется слишком долго, как будто он делает много лишнего.

Идем смотреть, как все работает и видим эти симптомы.

Наш запрос выполняется очень долго, больше половины времени работы всего функционала. Смотрим план, ищем проблему. Оказалась, что нам нужно прочитать несколько записей (часто не больше 10), а мы вычитывали всю базу с кучей Join`ов и фильтраций. Добавляем условие для фильтрации по конкретным идентификаторам и получаем:

После правок SQL запроса
После правок SQL запроса

То есть прирост составил больше 95% во времени и сам запрос теперь занимает не больше 10% тайминга работы всего метода.

Но теперь у нас есть трата временного ресурса на работу языка, занимающая больше 500мс или почти 70% рабочего времени.

Начинаем смотреть и видим не оптимальность.

Здесь начинаем перебор записей и сборку из них другой записи с медленным методом Set, который рекомендуется использовать для установки одной записи. Переделаем на лучший вариант:

Пример локальный, но хорошо демонстрирует суть оптимизации
Пример локальный, но хорошо демонстрирует суть оптимизации

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

Теперь среднее время работы составило не 1800мс, а всего лишь 160мс. То есть мы получили прирост в скорости больше чем на 90%.

Пациент спасен и теперь гордо трудится на радость клиентам, но таких как он, еще много.

Я справился!
Я справился!

Почти все ИТ-компании имеют технический долг, но не у каждой есть с ним сложности. Где же эта тонкая грань?

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

  1. Большие временные затраты

  2. Сложность внедрения новыx фич, которые могут увеличить сам долг.

  3. Снижение мотивации команды и общей комфортной среды внутри.

  4. Выгорание и отсутствие интереса.

Один из основных способов решения – рефакторинг кода (контролируемый процесс улучшения кода или процесса старой кодовой базы без внесения в него новых возможностей).

Существуют и другие способы уменьшения долга:

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

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

  3. Внедрение ревью (желательно многоступенчатого), стандартов написания и стиля кода, развитие команды и совместное обсуждение, встречи. Внедрение систем автоматической проверки кода.

  4. Разработка ТД, схем процессов, данных и архитектуры в целом. Помогает выявить проблемы на основе визуализированных и технических данных. Отличный вариант – проектирование полного бизнес-процесса на этапе создания функционала поможет избежать проблем в будущем при определённом уровне команды, поддержка актуальности документации позволит «новичкам» (не только уровень программирования, но и просто новая команда или сотрудник) вникнуть в процесс быстрее и проще. Но данный способ является больше утопией в современной разработке, чем повседневностью.

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

Подведем итоги

Сам долг не всегда является проблемой или чем-то плохим, он будет всегда. Но мы можем приложить определенные усилия для того, чтобы уменьшить его или избежать в будущем.

Вот и всё. Буду рад здравой критике и ярым обсуждениям.

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


  1. rinace
    30.08.2024 03:46

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

    Что вы делаете когда/если разрабы используют ORM ?


    1. John_SOI Автор
      30.08.2024 03:46

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


  1. anonymous
    30.08.2024 03:46

    НЛО прилетело и опубликовало эту надпись здесь