Как решить проблему копипаста.
От: зиг Украина  
Дата: 12.03.12 20:02
Оценка:
Есть у меня один класс, назовем его A, extends X.

Этот самый А процессит некие файлы, несколько раз занося попутно какие-то результаты в бд, периодически что-то оттуда доставая и т.д.. В общем там довольно сложный процесс, и в нем есть метод process, на несколько экранов.

Нам понадобилось иметь класс B, который делает почти что то же самое, НО не совсем то. Тоже точно так же все процессит, но чуууточку по-другому.

Навставлять в A в метод process каких-то условий, чтобы вот эти вот отличия учитывались — не получится. Точнее, может и получится, но тогда код будет ужасный, он станет не 3 экрана, а 5, т.к. там вовлекутся структурные изменения (новые циклы добавятся, и т.д.).
Можно просто в класс B скопипастить метод из A, провести все изменения и вуаля. Будем иметь 2 почти одинаковых класса, с общим алгоритмом. Измененяем в одном, забываем про второй, ну и т.д. прелести.

Я не пойму, как же красиво такое сделать??!

Если попробовать сделать вначале копипаст, а потом провести рефакторинг, так чтобы весь общий код например был в абстрактном родителе X... — это тоже плохо, в таких случаях всегда страдает читабельность. Был метод на 3 экрана, но зато понятна последовательность была алгоритма — тут будет 5 экранов размазанных уже по нескольким классам — и все это в погоне за избавлением от копипасты...

Может какие-то паттерны есть под это дело?
Re: Как решить проблему копипаста.
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 12.03.12 20:13
Оценка: +2
Здравствуйте, зиг, Вы писали:

зиг>Может какие-то паттерны есть под это дело?


Сначала разбить большой метод на маленькие, не более одного экрана размером. А потом уже делать параметры, которые процессинг настраивают.
Re: Как решить проблему копипаста.
От: AlexNek  
Дата: 12.03.12 20:17
Оценка:
Здравствуйте, зиг, Вы писали:

зиг>Есть у меня один класс, назовем его A, extends X.


зиг>Этот самый А процессит некие файлы, несколько раз занося попутно какие-то результаты в бд, периодически что-то оттуда доставая и т.д.. В общем там довольно сложный процесс, и в нем есть метод process, на несколько экранов.


зиг>Нам понадобилось иметь класс B, который делает почти что то же самое, НО не совсем то. Тоже точно так же все процессит, но чуууточку по-другому.

...
А чем не нравиться просто виртуальный метод process?
Cообщение написано в << RSDN@Home 1.2.0 alpha 5-AN-R8 rev. 13227>>
Re[2]: Как решить проблему копипаста.
От: зиг Украина  
Дата: 12.03.12 20:23
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>Здравствуйте, зиг, Вы писали:


зиг>>Может какие-то паттерны есть под это дело?


G>Сначала разбить большой метод на маленькие, не более одного экрана размером. А потом уже делать параметры, которые процессинг настраивают.


эта разбивка уменьшит читаемость.
потом, там в зависимости от параметра — добавляется большой цикл, т.е. оборачивается большая часть кода этим циклом
как такое разбить?
Re: Как решить проблему копипаста.
От: jhfrek Россия  
Дата: 12.03.12 20:36
Оценка: +1
Здравствуйте, зиг, Вы писали:

зиг>Этот самый А процессит некие файлы, несколько раз занося попутно какие-то результаты в бд, периодически что-то оттуда доставая и т.д.. В общем там довольно сложный процесс, и в нем есть метод process, на несколько экранов.

зиг>Нам понадобилось иметь класс B, который делает почти что то же самое, НО не совсем то. Тоже точно так же все процессит, но чуууточку по-другому.
...
зиг>Может какие-то паттерны есть под это дело?

это не вопрос, это поиск оправданий для нежелания делать рефакторинг.

в этом методе есть куски, которые можно вынести в отдельные функции. После переноса станут ясны взаимосвязи — параметры функций. Попутно общее методы могут уйти в другие классы. Отличающиеся куски можно будет обернуть в виртуальные методы и проблема будет решена.
Re[3]: Как решить проблему копипаста.
От: RomikT Германия  
Дата: 12.03.12 20:41
Оценка:
Здравствуйте, зиг, Вы писали:

зиг>эта разбивка уменьшит читаемость.

Разбивка практически никогда не уменьшает читаемость, если конечно методы называть не process, solve и doIt а так, что бы по названию было понятно что метод делает.

зиг>потом, там в зависимости от параметра — добавляется большой цикл, т.е. оборачивается большая часть кода этим циклом

зиг>как такое разбить?
Вынести внутренность цикла в метод, а оставшуюся часть переопределить в одном классе с циклом, в другом без.
Re[3]: Как решить проблему копипаста.
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 13.03.12 04:52
Оценка:
Здравствуйте, зиг, Вы писали:

зиг>Здравствуйте, gandjustas, Вы писали:


G>>Здравствуйте, зиг, Вы писали:


зиг>>>Может какие-то паттерны есть под это дело?


G>>Сначала разбить большой метод на маленькие, не более одного экрана размером. А потом уже делать параметры, которые процессинг настраивают.


зиг>эта разбивка уменьшит читаемость.

Неправда. Главное подобрать хорошие имена функциям.

зиг>потом, там в зависимости от параметра — добавляется большой цикл, т.е. оборачивается большая часть кода этим циклом

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

Для начала разбей большой метода на маленькие, а только потом думай как обобщить.
Re[3]: Как решить проблему копипаста.
От: jhfrek Россия  
Дата: 13.03.12 06:03
Оценка:
Здравствуйте, зиг, Вы писали:

зиг>потом, там в зависимости от параметра — добавляется большой цикл, т.е. оборачивается большая часть кода этим циклом


что значит оборачивается часть кода циклом? У вас цикл на goto организован?
Re: Как решить проблему копипаста.
От: Banch  
Дата: 13.03.12 10:04
Оценка: +1
Сначала предлагаю почитать следующие книги:

Приемы объектно-ориентированного проектирования
Э. Гамма, Р. Хелм, Р. Джонсон, Д. Влиссидес

Рефакторинг. Улучшение существующего кода
Фаулер М., Бек К., Брант Д., Апдайк У., Робертс Д.

После них, думаю, ответ появиться.
Re: Как решить проблему копипаста.
От: chrisevans  
Дата: 13.03.12 13:47
Оценка: +2
Иногда даже самый простой рефакторинг может существенно упростить код. И не надо кидаться разбивать метод на кучу маленьких с целью уменьшить количество строк в методе. Лучше начать с выделения основной логики метода и вспомогательной (а если метод состоит из сотен строк кода, то 90% что вспомогательная логика есть). Вспомогательная логика это шаманства, вызванные особенностями библиотек/языков/фреймворков/любых API/кривых архитектур (и в своем коде и в стороннем), и зачастую именно они "утяжеляют" метод и затрудняют работу с основной логикой метода.

Это все банальности, но даже понимая их часто о них забываешь.
Re[2]: Как решить проблему копипаста.
От: chrisevans  
Дата: 13.03.12 13:55
Оценка:
Здравствуйте, chrisevans, Вы писали:

C>Иногда даже самый простой рефакторинг может существенно упростить код. И не надо кидаться разбивать метод на кучу маленьких с целью уменьшить количество строк в методе. Лучше начать с выделения основной логики метода и вспомогательной (а если метод состоит из сотен строк кода, то 90% что вспомогательная логика есть). Вспомогательная логика это шаманства, вызванные особенностями библиотек/языков/фреймворков/любых API/кривых архитектур (и в своем коде и в стороннем), и зачастую именно они "утяжеляют" метод и затрудняют работу с основной логикой метода.


C>Это все банальности, но даже понимая их часто о них забываешь.


В общем, есть метод process, который знает, что некоторые части его логики могут меняться, но не знает как. Есть подклассы, каждый из которых знает как должна меняться его логика для метода process. Значит в метод process эту логику можно передавать.
Re[2]: Как решить проблему копипаста.
От: зиг Украина  
Дата: 13.03.12 19:35
Оценка:
Здравствуйте, chrisevans, Вы писали:

C>Иногда даже самый простой рефакторинг может существенно упростить код. И не надо кидаться разбивать метод на кучу маленьких с целью уменьшить количество строк в методе. Лучше начать с выделения основной логики метода и вспомогательной (а если метод состоит из сотен строк кода, то 90% что вспомогательная логика есть). Вспомогательная логика это шаманства, вызванные особенностями библиотек/языков/фреймворков/любых API/кривых архитектур (и в своем коде и в стороннем), и зачастую именно они "утяжеляют" метод и затрудняют работу с основной логикой метода.


ну а что вот если вот эти шаманства все и так уже вынесены, и там именно идет алгоритм, описание его последовательности?
если алгоритм состоит из N шагов, то все эти N шагов логично иметь в одном методе?
А если нет, то по какой системе их разбить/объединить в несколько более крупных частей? если предположить, что эти действия самодостаточные, независимые и атомарные..? Если вот так вот разбить то начнет страдать читаемость, т.к. будет непонятно что делает метод firstPart содержащий первые 10 допустим шагов

C>Это все банальности, но даже понимая их часто о них забываешь.
Re[3]: Как решить проблему копипаста.
От: MozgC США http://nightcoder.livejournal.com
Дата: 13.03.12 19:55
Оценка:
Здравствуйте, зиг, Вы писали:

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

зиг>если алгоритм состоит из N шагов, то все эти N шагов логично иметь в одном методе?
зиг>А если нет, то по какой системе их разбить/объединить в несколько более крупных частей? если предположить, что эти действия самодостаточные, независимые и атомарные..? Если вот так вот разбить то начнет страдать читаемость, т.к. будет непонятно что делает метод firstPart содержащий первые 10 допустим шагов

C>>Это все банальности, но даже понимая их часто о них забываешь.


На моем опыте в 98% случаев методы больше 2х экранов можно разбить на несколько более простых методов с улучшением ситуации. Но если называть методы firstPart() то конечно читаемость не улучшится

Еще посмотрите паттерн template method, возможно он подойдет к вашей ситуации.

А вообще гадать можно долго, может лучше метод приведете?
Re[3]: Как решить проблему копипаста.
От: jhfrek Россия  
Дата: 13.03.12 20:29
Оценка: +1
Здравствуйте, зиг, Вы писали:

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

зиг>если алгоритм состоит из N шагов, то все эти N шагов логично иметь в одном методе?

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

Для стековой машины это будет 3 осмысленных процедуры: Загрузить делимое(); Загрузить делитель(); Поделить()

зиг>А если нет, то по какой системе их разбить/объединить в несколько более крупных частей? если предположить, что эти действия самодостаточные, независимые и атомарные..? Если вот так вот разбить то начнет страдать читаемость, т.к. будет непонятно что делает метод firstPart содержащий первые 10 допустим шагов


не, лучше Part1 с одним шагом, Part2 с двумя шагами, Part3 с тремя шагами — веселее будет разбираться. Рефакторинг — это же деление большой процедуры случайным образом на части случайного размера.

Какие в баню 10 шагов?

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

for i := 1 to N do
  .... - команды настройки на 2 экрана
  for j := i to N do
    .... - команды настройки на 2 экрана
    if A[i] < A[j] then begin
      X := A[i];
      A[i] := A[j];
      A[j] := X
    end


разберешься ты, что делает эта процедура? Ни за что — суть ее будет размазана по экранам из-за команд настройки

отрефакторим

procedure КомандыНастройкиДляТекущегоЭлемента()
begin
end

procedure КомандыНастройкиПередСравнением()
begin
end

procedure Swap(ItemIndex1, ItemIndex2)
begin
  X := A[ItemIndex1];
  A[ItemIndex1] := A[ItemIndex2];
  A[ItemIndex2] := X
end;

procedure PopItem(CurrentIndex)
begin
  for j := CurrentIndex to N do begin
    КомандыНастройкиПередСравнением();
    if A[CurrentIndex] < A[j] then 
      Swap(A[CurrentIndex], A[j])
  end
end;

for i := 1 to N do begin
  КомандыНастройкиДляТекущегоЭлемента();
  PopItem(i)
end;


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

Нужно поменять возрастающую/убывающую последовательность? Не надо рыскать по коду и искать ключевой if среди десятков прочих — он у тебя сразу виден в процедуре PopItem. Выносим его в функцию Compare и делаем ее параметром или виртуальным членом класса. Получаем настройку поведения, которую ты так жаждешь
Re[3]: Как решить проблему копипаста.
От: chrisevans  
Дата: 14.03.12 08:40
Оценка:
Здравствуйте, зиг, Вы писали:

зиг>Здравствуйте, chrisevans, Вы писали:


C>>Иногда даже самый простой рефакторинг может существенно упростить код. И не надо кидаться разбивать метод на кучу маленьких с целью уменьшить количество строк в методе. Лучше начать с выделения основной логики метода и вспомогательной (а если метод состоит из сотен строк кода, то 90% что вспомогательная логика есть). Вспомогательная логика это шаманства, вызванные особенностями библиотек/языков/фреймворков/любых API/кривых архитектур (и в своем коде и в стороннем), и зачастую именно они "утяжеляют" метод и затрудняют работу с основной логикой метода.


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

зиг>если алгоритм состоит из N шагов, то все эти N шагов логично иметь в одном методе?
зиг>А если нет, то по какой системе их разбить/объединить в несколько более крупных частей? если предположить, что эти действия самодостаточные, независимые и атомарные..? Если вот так вот разбить то начнет страдать читаемость, т.к. будет непонятно что делает метод firstPart содержащий первые 10 допустим шагов

Допустим, что ваш алгоритм получает файл и должен пропустить его контент через какие-то фильтры (регулярные выражения/проверки уровней доступа и т.п.) и выдать результат в какие-нибудь объекты. Вот эти фильтры вы можете давать алгоритму.
Простой пример из жизни: есть пылесос, у него есть труба, основное назначение которой — передача загрязненного воздуха к фильтру и дальше в мешок. Труба говорит: у меня есть круглое отверстие, на которое вы можете насадить устройство для доступа к источнику загрязнения. Трубе не нужно знать об углах, площади, форме ваших насадок, т.к. ее основное назначение — передать воздух. А представьте если бы на трубе были десятки кнопок для такой настройки, которые потребовали бы других материалов и конструкции трубы, которые не имеют отношения к ее основной деятельности — передаче воздуха.
Re[3]: Как решить проблему копипаста.
От: VladD2 Российская Империя www.nemerle.org
Дата: 15.03.12 14:18
Оценка:
Здравствуйте, зиг, Вы писали:

G>>Сначала разбить большой метод на маленькие, не более одного экрана размером. А потом уже делать параметры, которые процессинг настраивают.


зиг>эта разбивка уменьшит читаемость.


Три экрана было читабельным?

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

А три экрана кода — это почти наверняка говнокод.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Как решить проблему копипаста.
От: AlexNek  
Дата: 15.03.12 18:31
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>А три экрана кода — это почти наверняка говнокод.

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

Может паттерн стратегии подойдет?
Cообщение написано в &lt;&lt; RSDN@Home 1.2.0 alpha 5-AN-R8 rev. 13227&gt;&gt;
Re[5]: Как решить проблему копипаста.
От: hardcase Пират http://nemerle.org
Дата: 15.03.12 20:54
Оценка:
Здравствуйте, AlexNek, Вы писали:

AN>Здравствуйте, VladD2, Вы писали:


VD>>А три экрана кода — это почти наверняка говнокод.

AN>Не обязательно.

Вот имеено из-за таких как ты стоит слово почти.
/* иЗвиНите зА неРовнЫй поЧерК */
Re[6]: Как решить проблему копипаста.
От: AlexNek  
Дата: 15.03.12 21:07
Оценка:
Здравствуйте, hardcase, Вы писали:

H>Здравствуйте, AlexNek, Вы писали:


AN>>Здравствуйте, VladD2, Вы писали:


VD>>>А три экрана кода — это почти наверняка говнокод.

AN>>Не обязательно.

H>Вот имеено из-за таких как ты стоит слово почти.

Это бы сгодилось в начале дискуссии.
Сейчас я бы даже сказал, что там почти наверняка не говонокод
Cообщение написано в &lt;&lt; RSDN@Home 1.2.0 alpha 5-AN-R8 rev. 13227&gt;&gt;
Re: Как решить проблему копипаста.
От: Lloyd Россия  
Дата: 15.03.12 21:18
Оценка:
Здравствуйте, зиг, Вы писали:

зиг>Навставлять в A в метод process каких-то условий, чтобы вот эти вот отличия учитывались — не получится. Точнее, может и получится, но тогда код будет ужасный, он станет не 3 экрана, а 5, т.к. там вовлекутся структурные изменения (новые циклы добавятся, и т.д.).

зиг>Можно просто в класс B скопипастить метод из A, провести все изменения и вуаля. Будем иметь 2 почти одинаковых класса, с общим алгоритмом. Измененяем в одном, забываем про второй, ну и т.д. прелести.

зиг>Я не пойму, как же красиво такое сделать??!


А вы уверены, что от нее всегда стоит избавляться любой ценой?

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