Является ли кодирование методом копирования и вставки когда-либо приемлемым?

StackOverflow https://stackoverflow.com/questions/401856

  •  03-07-2019
  •  | 
  •  

Вопрос

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

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

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

Это было полезно?

Решение

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

Другие советы

Задайте этот вопрос о ваших функциях

"если это небольшое требование изменится, придется ли мне менять обе функции, чтобы удовлетворить его?"

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

Средний вариант - использовать макрос, если у вас есть такой доступный.

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

Re - это когда-либо приемлемое сокращение:

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

Повторные сегменты, которые выглядят похожими, но не совсем одинаковыми:

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

Например, если у вас есть сегмент кода, который отличается двумя сегментами - diff #1 и diff # 2.И в diff #1 у вас может быть diff1A или diff1B, а для diff # 2 у вас могут быть diff2A и diff2B.

Если diff1A и diff2A всегда вместе, а diff1B и diff2B всегда вместе, то diff1A и diff2A могут содержаться в одном командном классе или одной абстрактной шаблонной реализации, а diff1B и diff2B - в другой.

Однако, если существует несколько комбинаций (т.е.diff1A & diff2A, diff1A & diff2B, diff1B & diff2A, diff1B & diff2B ), то вы можете переосмыслить свою функцию, поскольку она, возможно, пытается справиться со слишком большим количеством обязанностей самостоятельно.

Повторять инструкции SQL:

Использование логики (if-else, циклы) для динамического построения вашего SQL приводит к потере удобочитаемости.Но создание всех вариантов SQL было бы трудно поддерживать.Так что остановитесь на полпути и используйте SQL-сегменты.Извлеките общие элементы в виде сегментов SQL и создайте все варианты SQL с этими сегментами SQL в качестве констант.

Например:

private static final String EMPLOYEE_COLUMNS = " id, fName, lName, status";

private static final String EMPLOYEE_TABLE = " employee";

private static final String EMPLOYEE_HAS_ACTIVE_STATUS = " employee";

private static final String GET_EMPLOYEE_BY_STATUS =
  " select" + EMPLOYEE_COLUMNS + " from" + EMPLOYEE_TABLE + " where" + EMPLOYEE_HAS_ACTIVE_STATUS;

private static final String GET_EMPLOYEE_BY_SOMETHING_ELSE =
  " select" + EMPLOYEE_COLUMNS + " from" + EMPLOYEE_TABLE + " where" + SOMETHING_ELSE;

В кодовой базе моей компании у нас есть серия примерно из 10 больших неровных SQL-инструкций, которые имеют высокую степень общности.Все утверждения имеют общее ядро или, по крайней мере, ядро, которое отличается всего на одно-два слова.Затем вы могли бы сгруппировать 10 утверждений в 3 или 4 группы, которые добавляют общие дополнения к ядру, опять же, возможно, с одним или двумя разными словами в каждом дополнении.В любом случае, думайте о 10 операторах SQL как о наборах на диаграмме Венна со значительным перекрытием.

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

Код умен в том смысле, что ни один из SQL никогда не повторяется.Если вам когда-нибудь понадобится изменить предложение в SQL, вы изменяете его только в одном месте, и все 10 операторов SQL изменяются соответствующим образом.

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

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

Преимущество наличия понятного SQL-кода и всего в одном месте, вероятно, перевесило бы недостатки вырезания и вставки SQL-кода.

Как предлагает Мартин Фаулер,

сделай это один раз, прекрасно.

сделайте это дважды, начнет пахнуть.

сделайте это трижды, на этот раз, чтобы рефакторинг.


Редактировать:отвечая на комментарий, скажу, что источником этого совета является Дон Робертс:

Три удара - и вы выполняете рефакторинг.

Мартин Фаулер описывает это в Рефакторинг глава 2, раздел "Правило трех" (стр. 58).

АБСОЛЮТНО НИКОГДА..

:)

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

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

Недавно у меня были функции add() и edit() в PHP-скрипте.Они оба делали практически одно и то же, но функция edit() выполняла запрос UPDATE вместо запроса INSERT.Я просто сделал что-то вроде

function add($title, $content, $edit = false)
{
    # ...
    $sql = edit ? "UPDATE ..." : "INSERT ...";
    mysql_unbuffered_query($sql);
}

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

  1. Хороший код - это код многократного использования.
  2. Не изобретайте велосипед заново.
  3. Примеры существуют не просто так:чтобы помочь вам учиться, а в идеале и лучше кодировать.

Должны ли вы скопировать и вставить?Какая разница!Что важно, так это почему вы копируете и вставляете.Я не пытаюсь ни к кому здесь относиться философски, но давайте подумаем об этом практически:

Это из-за лени?"Бла-бла-бла, я делал это раньше...Я меняю только несколько имен переменных..сделано".

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

Это потому, что ты не понимаешь?"Черт..Я не понимаю, как работает эта функция, но мне интересно, сработает ли она в моем коде .. " Возможно!Это может сэкономить вам время в тот момент, когда вы испытываете стресс из-за того, что у вас крайний срок - 9 утра.и ты смотришь красными глазами на часы, около 4 часов утра.

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

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

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

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

#define RETPOS(E) do { if ((E) > 0) then return; } while(0)

но это цитирование необходимо.Я часто буду использовать препроцессор C, несмотря на его недостатки, потому что он не добавляет другой элемент в набор инструментов и поэтому не требует изменения процесса сборки или Make-файлов.

Я рад, что эта статья помечена как субъективная, потому что это, безусловно, так и есть!Это слишком расплывчатый пример, но я бы предположил, что если у вас достаточно дублированного кода, вы могли бы абстрагировать эти разделы и сохранить разные части разными.Смысл отказа от копирования-вставки в том, чтобы в конечном итоге у вас не получился код, который сложен в обслуживании и хрупок.

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top