Pull to refresh

Comments 22

Критерий «количество усилий на поддержку файла с кодом» мне кажется частным случаем в оценке технического долга и слабым аргументом для менеджера с которым придется эту проблему обсуждать.

Более существенно количество паразитных трудозатрат (отношение объема нового кода к объему переписанного кода, объем регрессионного тестирования) при добавлении фич. В продукте с большим техническим долгом внедрение фич вызывает непропорционально большие трудозатраты и чем дольше копится этот долг, тем выше сложность/цена внедрения нового функционала.
Не совсем понимаю разницу между «усилиями на поддержку» и «паразитными трудозатратами» применительно к конкретному файлу при добавлении нового функционала. Разве это не одно и то же?
Я имел в виду, что оценка основанная на "файлах" это частный случай (взгляд программиста), а "продукт" (взгляд менеджера) — это не только код, но и тестирование, и интеграционные решения. Например, изменив тип аргумента в одном из методов web-сервиса я произведу малозаметные, с точки зрения «файла» изменения, с чудовищными последствиями для «продукта» — типа труднообнаружимых ошибок (если мы не побеспокоились заранее об интеграционных тестах) и необходимости доработок в приложениях интегрирующихся с нашим API. Этот пример не удачен с точки зрения темы рефакторинга, но он показывает что объем трудозатрат при внедрении фичи для продукта в целом может слабо коррелироваться с количеством измененных файлов или строк кода. Т е сама методика оценки объема технического долга, основанная на подсчете количества изменений в коде и частоте изменений в файлах может быть верной для некоторых продуктов, а для других продуктов эта методика не даст реальной картины.

При взшляде со стороны продукта правильными метриками могут служить:


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

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

Согласен. Но все же уточню.


  • Я не совсем точно выразился. Под фичей я понимал, скорее, подсистему.
  • Ошибка в совсем другом месте. Тут можно возразить, что возможно, в таком случае надо рефакторить обе подсистемы — раз они так связаны, что ошибка в одной проявляется в другой. Но в моей практике обычно все же это сигнализировало о проблеме именно в той подсистеме, в которой ошибка и появлялась (хотя и необязательно именно та самая фича).

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

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


Грубо (псевдокод):
function someComplex(a, b, c) {
  // some very complex code 
}

function someComplexWithSimpleCase (a, b, c) {
  if (a === 0 ) {
    // some simple code
  } else {
    someComplex(a, b, c);
  }
}

function adminComplex(user, a, b, c) {
  if (user !=== 'admin' ) {
    if (a === 0) {
        // another simple code
    } else {
      someComplexWithSimpleCase(a, b, c);
    }
  } else {
  // the same very complex code
 } 
}
Люто-бешенно плюсую.

Пример: у нас есть огромный монстр на несколько десятков тысяч строк кода, где внутри ant вызывает maven через bash, а тот в свою очередь обратно ant с помощью десятка захардкоженных паролей (и это, на минуточку, банк). Этот код творил сам Сатана.

Он не меняется, но является стандартом. Все, что он делает — «автоматизирует» работу maven, curl и прочих простых инструментов. Он никому совершенно не нужен.
Все, чем мы заняты круглые сутки — мы привинчиваем свой maven так, чтобы он делал свою работу, но передавал этому монстру пустышку и якобы тот работал тоже.
При этом монстра никто не меняет. Он по всем метрикам идеальный код…

Я вчера уволился к черту со словами: «я не могу больше делать непрофессиональную работу». Ничего не изменилось. Идеальный монстр все равно не был признан техническим долгом. Он будет там, даже когда человечество сбежит из-за него на Марс.
Тоже поделюсь наблюдением: имеется некий продукт с большим количеством не документированных классов и частой сменой команды разработчиков. Новые разработчики не имея времени и желания исследовать чужой код, при внедрении фич, лепили собственный код, дублирующий уже имеющийся функционал. Потребность в рефакторинге очевидна, но предложенная метрика, основанная на частоте изменений в файлах, ничего подозрительного не покажет.

Можно даже предположить, что там где часто изменяют одни и те же файлы, фактически происходит рефакторинг, а там, где старый код долго остается неизменным, есть какая то проблема)
Потребность в рефакторинге очевидна

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


А вот если этот дублирующий код изменяться в дальнейшем будет редко (или вообще никогда), то и рефакторить его по большому счету незачем. Ну, разве что из чувства прекрасного.

Очевидно)
  • Повторное написание кода — лишние трудозатраты
  • Дублирование кода — усложнение продукта, ухудшение его понятности
  • Дублирование кода — путь к труднообнаружимым ошибкам, которые сложно устранять (в разных ситуациях работает разный код с одними и теми же данными)

Не-не-не :)


  • Повторный код УЖЕ написан. Оставить его как есть — ноль трудозатрат. Рефакторинг — лишние трудозатраты.
  • Усложнение продукта. Ну и ладно, работает — не трогай :)
  • Путь к ошибкам. Будут ошибки — рассмотрим необходимость рефакторинга. Рефакторинг тоже может быть источником ошибок (я уверен, что нездоровая ситуация, описанная выше, также подразумевает недостаточное покрытие тестами).

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

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


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

Это не так очевидно.


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

В целом мне кажется, что дублирование допустимо, если продублированный код опирается на общий код нижележащего уровня.

Когда я вижу дублирование кода, я занимаюсь рефакторингом) Лично для меня дублирование кода противоестественно, хотя Вашу аргументацию я понимаю.

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

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


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

Вовремя проведенный и удачный рефакторинг предотвратит появление дублирующегося кода

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


не поняли, что это уже реализовано или догадывались, что реализовано, но не нашли

А это даже не всегда определяется качеством кода. Это может быть результатом документации / процессов (или их отсутствия).


Ну и наконец можно дискутировать о вреде дублирующегося кода в общем случае :)

В какой момент у нас имел место технический долг?

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


А это даже не всегда определяется качеством кода.

Одна из основных целей рефакторинга — увеличение самодокументируемости кода.

  • «Повторный код уже написан» — плохо, но еще хуже если его еще раз продублировать (т е написать то же самое в третий, четвертый раз)
  • «работает — не трогай» — каждая новая фича дается все тяжелей, устранение простых, на первый взгляд, багов требует много времени
  • Отсутствие тестов или их низкое качество, также можно отнести к техническому долгу. Автотесты (на прктике) не могут выявить все ошибки. Естественно, что найденную ошибку придется устранить, но никто не гарантирует, что устраняя ошибку в одном месте, тем самым не создаешь ее в другом.

Обо всем этом много уже написано: Роберт Мартин «Чистый код», Мартин Фаулер «Рефакторинг» и т д. Думаю, что в этих книгах можно найти гораздо больше аргументов на тему «почему» надо писать чистый код. Хотя, конечно, все зависит от продукта и установок в команде — если команда слышит что продукт никому не нужен, а самих программистов завтра выгонят, ну и конечно незабываемое — «это надо было сделать вчера», то все это не стимулирует разработчиков писать качественный код. Часто менеджер однодневка своим общением с командой закладывает мину в продукт.
На мой взгляд необходимость рефакторинга — понятие очень субъективное и если какой-то кусок кода вызывает боль у разработчика, то его задача поднять вопрос о рефакторинге этого куска кода.
Количество изменений в системе контроля версий не всегда является корректным показателем.
Я бы скорее смотрел на:
— Количество строк в файле/классе (если больше 2000, то скорее всего нужен рефакторинг)
— Количество условий и циклов (чем больше, тем больше вероятность, что нужен рефакторинг)
— Количество дублирующихся блоков кода (часто идёт вместе с предыдущим пунктом)
— Для добавления нового функционала необходимо исправлять код во многих местах (не всегда очевидных)
— Количество обращений от поддержки на исправление ошибок в одной фиче.
Sign up to leave a comment.

Articles