Дублирование кода без очевидной абстракции

14

Сталкивались ли вы когда-нибудь со случаем дублирования кода, когда, глядя на строки кода, вы не могли бы разместить в нем тематическую абстракцию, которая точно описывает ее роль в логике? И что вы сделали для этого?

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

Как вы думаете, это лучший способ решить что-то вроде этого?

EpsilonVector
источник

Ответы:

18

Иногда дублирование кода является результатом «каламбура»: две вещи выглядят одинаково, но это не так.

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

Рефакторинг необходим, когда нужно изменить то, что вы считали стабильным (например, этот вызов API всегда будет принимать два аргумента).

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

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

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

Если ваш дублированный код имеет несколько возвращаемых значений (и поэтому вы не можете сделать простой метод извлечения), то, возможно, вам следует создать класс, содержащий возвращаемые значения. Класс может вызывать абстрактный метод для каждой точки, которая варьируется между двумя фрагментами кода. Затем вы сделаете две конкретные реализации класса: по одной для каждого фрагмента. [Это, по сути, шаблон проектирования «Шаблонный метод», который не следует путать с концепцией шаблонов в C ++. В качестве альтернативы, то, на что вы смотрите, может быть лучше решено с помощью шаблона «Стратегия».]

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

Макнейл
источник
4

Когда вы сталкиваетесь с такой ситуацией, лучше подумать о «нетрадиционных» абстракциях. Может быть, у вас много дублирования внутри функции, и факторизация простой старой функции не очень подходит, потому что вам нужно передать слишком много переменных. Здесь отлично работает вложенная функция в стиле D / Python (с доступом к внешней области видимости). (Да, вы могли бы сделать класс для хранения всего этого состояния, но если вы используете его только в двух функциях, это уродливый и многословный обходной путь для отсутствия вложенных функций.) Может быть, наследование не совсем подходит, но Mixin будет хорошо работать. Может быть, вам действительно нужен макрос. Может быть, вам стоит подумать о шаблонном метапрограммировании, рефлексии / самоанализе или даже генеративном программировании.

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

dsimcha
источник
+1 за множество отличных техник, сжатых в стесненных условиях. (Ну, было бы +1 для описанных методов тоже.)
Macneil
3

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

Тоби
источник
2

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

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

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

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

Huperniketes
источник