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


15

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

Нужно ли мне:

  • Не касаться тех частей кода, которые мне не нужны, а написать свой патч, используя новые языковые функции, которые облегчают написание патча, но не используются в аналогичных ситуациях где-либо еще в модуле? (Это решение я бы выбрал интуитивно.)
  • Игнорировать тот факт, что прошли годы, и отразить стиль, который используется в остальной части кода при написании моего патча, и вести себя так, как будто я выполнял ту же задачу много лет назад? (Это решение я бы посчитал глупым, но, учитывая тот факт, что каждый, кто говорит о «хорошем коде», суетится над поддержанием последовательности любой ценой, возможно, это то, что я должен сделать.)
  • Обновить весь модуль, чтобы использовать более новые языковые конструкции и соглашения? (Это, вероятно, лучшее решение, но может потребовать много времени и энергии, которые лучше потратить на другую задачу.)

1
@JerryCoffin Я не собираюсь продолжать спорить в комментариях: я разместил на мета, где мы можем провести надлежащее обсуждение.

Ответы:


10

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

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

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

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


1
Я частично не согласен. Мы должны следовать принципу открытого закрытия. Таким образом, мы должны стараться изолировать новый код настолько, насколько мы можем, и в то же время написать новый код с использованием самой последней возможной языковой конструкции.
Ананд Вайдья

3
@AnandVaidya: это ИМХО нереалистичное ожидание, поскольку оно предполагает, что старый код следует за OCP или может быть легко изменен, чтобы следовать за OCP, что редко бывает или просто не стоит усилий.
Док Браун

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

2
@AnandVaidya: когда вы не имеете в виду OCP, вы не должны называть это так. Я предполагаю, что вы на самом деле имеете в виду то, что Фезер называет написанием нового кода в методе sprout , поэтому можно ограничить новый стиль кода новым отдельным методом. Когда это возможно и разумно, вы правы, это нормально. К сожалению, этот подход не всегда применим, по крайней мере, если вы хотите сохранить старый код СУХИМ.
Док Браун

@DocBrown: я не думаю, что принцип открытия / закрытия ограничен объектно-ориентированным программированием. У вас могут быть модули, в которые вы добавляете новые процедуры, но не изменяете существующие, то есть сохраняете семантику существующего кода в неизменном виде и, если вам нужна новая функциональность, вы пишете новый код.
Джорджио

5

В значительной степени код и то, как он выглядит, не имеет значения . То, что делает код , имеет значение.

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

Если вы не можете гарантировать эту стабильность (у вас нет этих тестов), то оставьте ее в покое.

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

Конечно, ничто не помешает вам создать набор таких тестов в готовности к такому упражнению ...


4

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

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

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

Для третьего подхода очевидная стоимость - это просто сделать гораздо больше работы. Менее очевидной ценой является то, что вы можете сломать вещи, которые работали. Это особенно вероятно, если (как это часто бывает) старый код имеет неадекватные тесты, чтобы гарантировать, что он продолжает работать правильно. Очевидное преимущество состоит в том, что (при условии, что вы делаете это успешно) у вас есть хороший, блестящий новый код. Наряду с использованием новых языковых конструкций, у вас есть возможность реорганизовать базу кода, которая почти всегда дает улучшения сама по себе, даже если вы все еще использовали язык в точности так, как он существовал на момент написания, - добавьте новые конструкции, которые делают работа легче, и это вполне может быть крупной победой.

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

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

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

Если требования изменились, вам также может понадобиться посмотреть, какие изменения происходят, и что является причиной этих изменений. Например, предположим, что вы модифицировали Git, чтобы заменить использование SHA-1 на SHA-256. Это изменение требований, но сфера его действия четко определена и довольно узка. После того, как вы настроили хранение и правильное использование SHA-256, вы вряд ли встретите другие изменения, которые повлияют на остальную часть кода.

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

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


1

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

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


1

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

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

Подводя итог: ваше окончательное решение должно основываться на анализе «затрат» / «выгод» вашего конкретного случая.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.