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

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


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

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

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

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

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

в этом методе есть куски, которые можно вынести в отдельные функции. После переноса станут ясны взаимосвязи — параметры функций. Попутно общее методы могут уйти в другие классы. Отличающиеся куски можно будет обернуть в виртуальные методы и проблема будет решена.
Re: Как решить проблему копипаста.
От: Banch  
Дата: 13.03.12 10:04
Оценка: +1
Сначала предлагаю почитать следующие книги:

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

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

После них, думаю, ответ появиться.
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[5]: Как решить проблему копипаста.
От: b-3 Россия  
Дата: 17.03.12 07:40
Оценка: :)
Здравствуйте, зиг, Вы писали:

зиг>вот-вот, как вот не путать? где тонкая грань — когда надо отрефакторить и запихнуть похожее в один класс с вирт методами, — а когда иметь эту самую легальную копипасту?

зиг>заранее ведь и не скажешь, будет дальнейшая эволюция или нет.

"Будет эволюция или нет" в разработке ПО необходимо знать. Не только значение (набор требований), но и его производную по времени (насколько конкретные требования подвержены изменениям в средней перспективе).
Должен быть человек, который осмысливает вопросы архитектуры приложения в целом. Должны быть стратегии контроля качества ПО, которые регулируют количество инструментальных классов и степень фреймворкописательства в проекте. В тех 15% случаев, когда копипаста не является следствием ранее случившегося нарушения азбучных истин разработки ПО и/или ранее написанного говнокода — именно стратегии должны давать ответ на вопрос, что лучше — скопипастить код и удвоить поддержку, или написать "универсальный" класс, утроив время на разработку конкретного модуля.
Разумеется, это не отменяет тех 85% случаев, когда копипаста устраняется "по букварю", поэтому вам здесь в основном предлагают почитать букварь.

Мой самый большой грех копипаста был в размере 1200 строк. Некая программа использовала монструозную древовидную структуру данных, которую, как выяснилось слишком поздно, потребуется нетривиальным образом трансформировать. Ну я накатал паттерн обсервер под одну задачу,и уже тогда было страшно. Потому вылезла вторая задача по трансформации дерева, а потом и третья задача, и встала перспектива копипасты кода. Я тогда долго думал, как же придать этой логике универсальность... и пришёл к пониманию, что для этого необходим аппарат уровня XSLT с громоздкими шаблонами, либо функциональщина с полиморфными лямбдами и функциями высшего порядка. В итоге было решено, что это оверкилл, и один обсервер стал тремя похожими обсерверами. До сих пор за тот код стыдно, хоть вроде и поступил рационально, взвешено. Утешаю себя рассуждениями про меньшее зло.
Забанен с формулировкой "клинический дисидент".
Как решить проблему копипаста.
От: зиг Украина  
Дата: 12.03.12 20:02
Оценка:
Есть у меня один класс, назовем его A, extends X.

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

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

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

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

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

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

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


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


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

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

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


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


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


эта разбивка уменьшит читаемость.
потом, там в зависимости от параметра — добавляется большой цикл, т.е. оборачивается большая часть кода этим циклом
как такое разбить?
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[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]: Как решить проблему копипаста.
От: 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 почти одинаковых класса, с общим алгоритмом. Измененяем в одном, забываем про второй, ну и т.д. прелести.

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


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

Проблема копипасты в том, что код дублируется и его становится сложнее поддерживать.
Если вы начнете только ради избавления от нее городить паттерны/наследование/шаблонные методы/стратегии и проче-прочее-прочее, то код станет сложным для понимания и, как следствие, его тоже будет непросто поддерживать.
Re[2]: Как решить проблему копипаста.
От: SkyDance Земля  
Дата: 16.03.12 05:26
Оценка:
L>Проблема копипасты в том, что код дублируется и его становится сложнее поддерживать.

Есть эмпирическое правило: если код копи-пастится более одного раза (т.е. присутствует в 3х местах), это 100% кандидат на немедленный рефакторинг.

Но если гарантированно заведомо ясно, что код больше никогда-никогда не будет скопи-пасчет — почему б и не оставить. Особенно если там не три экрана, а десять строк.

Вообще, надо тоже понимать, что копи-паст в некотором объеме неизбежен, особенно при работе с синтаксически неудобными штуками.
Re[3]: Как решить проблему копипаста.
От: maxkar  
Дата: 16.03.12 13:15
Оценка:
Здравствуйте, зиг, Вы писали:

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


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


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

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

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

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

Не следует путать ситуацию со случаем "просто алгоритм". В этом случае проблем с "изменили в одном месте — нужно менять в другом" просто нет. Так как назначение алгоритмов различно, они и меняются различно. Это соображение по различной дальнейшей эволюции дает и "легальную" копипасту в ряде случаев. Поддержка различных форматов данных и протоколов обмена, например. Особенно, если часть из этих форматов/протоколов еще развиваются. Попытка запихнуть "похожие" алгоритмы в один класс с настройками может выглядеть очень плохо (а может и не выглядеть, зависит от конкретной ситуации).
Re[4]: Как решить проблему копипаста.
От: зиг Украина  
Дата: 16.03.12 19:43
Оценка:
Здравствуйте, maxkar, Вы писали:


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


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


вот-вот, как вот не путать? где тонкая грань — когда надо отрефакторить и запихнуть похожее в один класс с вирт методами, — а когда иметь эту самую легальную копипасту?
заранее ведь и не скажешь, будет дальнейшая эволюция или нет.
Re[5]: Как решить проблему копипаста.
От: dudkin  
Дата: 16.03.12 19:47
Оценка:
Здравствуйте, зиг, Вы писали:

зиг>заранее ведь и не скажешь, будет дальнейшая эволюция или нет.


деолектика, панимаш!
надо писать программы так чтобы никто не мог разобраться но и сам не запутаться
copy modify or create new
От: AlexNek  
Дата: 17.03.12 14:04
Оценка:
Здравствуйте, b-3, Вы писали:

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


b-3>Мой самый большой грех копипаста был в размере 1200 строк.

...
b-3>Утешаю себя рассуждениями про меньшее зло.
Похоже проблема копипасты все же есть. Вот и мы сейчас на работе дискутируем скопировать и модифицировать старую часть или начать ее "разрабатывать с нуля" по образу и подобию.
А именно. Есть два устройства и два плагина к ним. Один уже давно есть, другой требуется реализовать.
При разработке первого плагина учитывалось наличие в будующем нескольких плагинов, поэтому многие части которые казались общими вынесены в базовые классы. Также имется достаточное количество интерфейсов и протоколов взаимодействия с главным приложением.
Например, в старом было
public string GeName {get {return "Name1";} }
...
  ReadPart1();

А в новом будет
public string GeName {get {return "Name2";} }
...
  if (ReadPart1()) ReadPart2();

(Код исключительно для примера)
Кода в плагине относительно много, примерно половину старого кода можно сразу выбросить, она специфична исключительно для данного устройства. Остальное можно будет скорректировать, и вполне возможно, что найдутся общие части, которые все же видимо не следует рефакторить.
Например было
  WritePart1();
  WritePart2();

А стало
  WritePart1();
  WritePart2();
  WritePart3();

Но, вполне вероятно, что в третьем плагине будет в этом месте
  WriteAbc();
  WriteCde();

Но основной вопрос все же другой. Писать новый плагин полностью с нуля, посматривая на старый или скопировать нужные части старого и модифицировать их постепенно?
Cообщение написано в &lt;&lt; RSDN@Home 1.2.0 alpha 5-AN-R8 rev. 13227&gt;&gt;
Re: copy modify or create new
От: jhfrek Россия  
Дата: 17.03.12 14:55
Оценка:
Здравствуйте, AlexNek, Вы писали:

AN>Есть два устройства и два плагина к ним.


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

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

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

А если один плагин у вас делает write(), а другой showondisplay(), то какой смысл их объединять, пусть даже набор команд в обоих методах будет похожий

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


я добавляю постепенно, покрывая добавленную функциональность тестами. Так проще выделять общие методы — при написании придется осознать что пишешь и вспомнить что означал старый код. Так меньше вероятности ошибиться и забыть что-то удалить. Так процесс разработки более естественен и не происходит перегрузки мозга от попыток охватить все скопированное.
Re[2]: copy modify or create new
От: AlexNek  
Дата: 17.03.12 15:31
Оценка:
Здравствуйте, jhfrek, Вы писали:

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


AN>>Есть два устройства и два плагина к ним.


J>у меня есть код, решающий дифференциальные уравнения и в нем есть процедура сортировки пузырьком. Мне нужно написать парсер текстов, в котором понадобиться сортировка пузырьком.


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


J>Я к тому, что копипастить/не копипастить — не определяет архитектуру. Если у вас метод write() — это принципиально присущий всем плагинам метод, да еще и имеющий общую часть, то конечно нужно эту общую часть вынести в базовый класс.

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

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


J>я добавляю постепенно, покрывая добавленную функциональность тестами. Так проще выделять общие методы — при написании придется осознать что пишешь и вспомнить что означал старый код.

J>Так меньше вероятности ошибиться и забыть что-то удалить.
Это фактически главный козырь у сторонников писать с нуля. Тесты то также уже есть их можно также модифицировать.
J>Так процесс разработки более естественен и не происходит перегрузки мозга от попыток охватить все скопированное.
А зачем охватывать все? Двигаемся все равно постепенно.

При таком способе (пишем с нуля) достаточно сильно возрастает время разработаки и уменьшается "преемственность кода". (когда одинаковые действия будут исполнятся по другому), при этом велика вероятность напоротся на забытые камни. Ну типа при возврате строки будем возвращать текст, тогда как в старом плагине это строка берется, например из ресурсов.
Кроме этого при копировании достаточно быстро получается рабочий прототип, пусть и выдающий кривые данные. В этом случае другие члены команды могут работать не с простейней заглушкой, а с чем-то более реальным.
Cообщение написано в &lt;&lt; RSDN@Home 1.2.0 alpha 5-AN-R8 rev. 13227&gt;&gt;
Re[3]: copy modify or create new
От: jhfrek Россия  
Дата: 17.03.12 17:52
Оценка:
Здравствуйте, AlexNek, Вы писали:

J>>Я к тому, что копипастить/не копипастить — не определяет архитектуру. Если у вас метод write() — это принципиально присущий всем плагинам метод, да еще и имеющий общую часть, то конечно нужно эту общую часть вынести в базовый класс.

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

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

J>>Так меньше вероятности ошибиться и забыть что-то удалить.

AN>Это фактически главный козырь у сторонников писать с нуля. Тесты то также уже есть их можно также модифицировать.

ну да

J>>Так процесс разработки более естественен и не происходит перегрузки мозга от попыток охватить все скопированное.

AN>А зачем охватывать все? Двигаемся все равно постепенно.

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

AN>При таком способе (пишем с нуля) достаточно сильно возрастает время разработаки и уменьшается "преемственность кода". (когда одинаковые действия будут исполнятся по другому),


а это главный козырь у сторонников из противоположного лагеря

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

AN>при этом велика вероятность напоротся на забытые камни. Ну типа при возврате строки будем возвращать текст, тогда как в старом плагине это строка берется, например из ресурсов.

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

при создании с нуля тоже получается рабочий прототип, выдающий правильные данные, просто не все или не всеми способами.
Re[5]: Как решить проблему копипаста.
От: maxkar  
Дата: 17.03.12 20:06
Оценка:
Здравствуйте, зиг, Вы писали:

зиг>вот-вот, как вот не путать?

А вот для ответа на этот вопрос нужна "модель" приложения. И "высокоуровневая" семантика действий. Например:
payer.withdrawMoney(amount);
receiver.addMoney(amount);

это перевод денег от одного участника к другому (две строчки вместе). И это действие просится в отдельный метод transferMoney. Именно потому, что это одно и то же действие в рамках "глобальной" модели приложения. Возможная эволюция — уведомление уполномоченных органов о больших переводах между двумя физическими лицами. Вынос метода очень поможет. В этом случае достаточно будет реализовать уведомление только в одном месте. Копипасту этого фрагмента найти будет сложно — и withdrawMoney, и addMoney могут использоваться по-отдельности. А в местах парного использования между ними может быть еще какой-то код (например, добавили логирование для отладки да так там и оставили). Так что ни поиск вызовов, ни поиск по тексту работать не будут.

Второй пример (где-то в драйвере устройства, уровень сообщений, транспортный уровень (потоки) не показан):
public byte[] toProtoMessage(AppMessage message) {
  final byte[] payload = toPayload(message);
  final byte[] checksum = calculateChecksum(payload);
  return formatOutputMessage(payload, checksum);
}

В этом случае семантика — формирование сообщения для конкретного протокола. Если вся остальная работа с устройством ведется только посредством интерфейса драйвера, действие одно и хорошо изолировано. Потому что действие в данном случае "сформировать пакет передачи данных для устройства Х". В другом устройстве тот же код будет делать действие "сформировать пакет передачи данных для устройства Y". Дальше возможны два варианта. Первый — изменение протокола (например, можно добавлять пакет мониторинга (время компьютера, например)). Вполне естественно, что изменение локально и будет выполнено только для одного устройства. Второй — изменение высокоуровневых требований. Но в этом случае разработчик сначала найдет интерфейс драйвера и уже затем будет править каждый драйвер под новую функциональность. В некоторых случаях ему все равно придется пересматривать соответствующие протоколы. Так что он заметит "похожий" код и исправит его. Если это будут делать несколько программистов, они могут внести похожие правки в похожие классы (допустим, каждый из них копипасту не видит). В этом случае если правки корректны (прошло тестирование), тоже все будет нормально. Код может отличаться все больше и больше, но это не страшно до тех пор, пока драйверы делают "разные" действия (работают с разными устройствами).

зиг>где тонкая грань — когда надо отрефакторить и запихнуть похожее в один класс с вирт методами, — а когда иметь эту самую легальную копипасту?

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

Если пример не придумывается но есть сомнения, лучше все равно сделать рефакторинг. Так что "легальная копипаста" остается только в тех случаях, где она "определенно допустима" и не принесет проблем.

Есть эвристика, которую можно учитывать. Предположим, ваш код поддерживает другой программист. Он читал описание архитектуры, руководящую документацию, но в коде пока ориентируется плохо. И ему нужно сделать какое-то изменение. Вопрос — найдет ли он и второе место, которое нужно изменить? Если копипаста встречается в реализациях одного и того же интерфейса, реализации которого глобально заменимы по всей программе (т.е. весь код работает только в этих абстракциях!), тогда, вероятно, все хорошо. В этом случае программист дойдет до вызова метода интерфейса и пойдет править все реализации. Оба требования (интерфейс и возможность глобальной замены) обязательны! Абстрактные классы с наследованием обычно (ладно, только мной, но все же...) воспринимаются как "специализированные" версии, заточенные в рамках шаблона под одну задачу. В этом случае разработчик найдет одну реализацию (в наиболее вероятном сценарии) и исправит только ее (совершенно не очевидно, что будут "похожие" специализации). Необходимость глобальной заменимости нужна в случае вызовов вроде doSomethingGeneric(new ConcreteImplementation1(), p1, p2). Здесь обобщенный метод (работающий с интерфейсом) получает конкретную реализацию, создающуюся "по-месту". Программист может решить, что достаточно поправить только эту реализацию (так как она отвечает за функциональность в найденном сценарии) и все.

И еще. Предложенный рефакторинг мне не нравится. Наследование здесь в большинстве случаев лишнее. Можно вынести все эти абстрактные методы в интерфейс. Необходимый контекст передавать через параметры или callback'и (если функции должны изменять состояние "родителя"). Возможно, еще и кандидата в абстрактного родителя стоит переделать. Или разобрать на несколько статических методов. Или, например, сделать его конструктор непубличным (создавать экземпляры статическими методами). Еще лучше, если "родитель" реализует какой-то интерфейс. Тогда его можно сделать и не видимым публично (пакетная видимость), предоставив создание экземпляров через статические методы (которые возвращают "интерфейс", а не конкретный тип). Последний вариант хорош тем, что позволяет в дальнейшем производить достаточно глобальные изменения не затрагивая публичный интерфейс (например, один класс может быть заменен на несколько, для нескольких вариантов может начать использоваться "общая" обертка, реализующая интерфейс и т.п.).

зиг>заранее ведь и не скажешь, будет дальнейшая эволюция или нет.

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

Также нужно предполагать, что изменения будет делать человек, плохо ориентирующийся в коде (и, соответственно, не представляющий о существовании копипасты). Выше уже описывал, что есть некоторые особенности работы с кодом, и эти особенности могут влиять на вероятность нахождения всех необходимых мест. Так что выбор копипаста/рефакторинг будет зависеть и от критичности кода, и от необходимости копипасты. В идеале даже одна копипаста одного и того же "семантического" действия должна рефакториться (если при этом образуется другое, никак не связное с первым действие — копипаста легальна).
Re: copy modify or create new
От: maxkar  
Дата: 17.03.12 20:33
Оценка:
Здравствуйте, AlexNek, Вы писали:

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


Скорее всего, второй подход будет лучше, но нужно попробовать и посмотреть на результат. Возможные проблемы:
  1. Код плагинов окажется слишком различным. В этом случае проще будет писать с нуля.
  2. Код плагинов окажется слишком похожим. При этом будут какие-то мелкие детали все же будут отличаться (нужно еще, чтобы при чтении кода такие детали ловить было сложно). Здесь уже на практике нужно смотреть, позволит ли увеличение времени на разработку сократить время на тестирование.
  3. При разработке какие-то изменения будут забыты (граничные случаи и т.п.). Чем больше граничных случаев, тем больше вероятность ошибки.

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

Если "база" похожа, стоит взять ее. Как раз из-за соображений протестированности (существующая база уже работает). Радикально другое решение вы строить не собираетесь, так что смысла писать код "второй раз" не имеет смысла. Еще и ценный опыт получите — насколько база подходит под "разные" задачи. Если вдруг появятся третье, четвертое и пятое устройства, вы уже будете знать, чего не хватает в существующем решении и как его лучше модифицировать для большей универсальности. А вот решение о том, как именно писать отдельные плагины, можно оставить разрабочтикам, ответственным за эти плагины. Они выберут ту методологию, с которой комфортнее работать лично им.
Re[4]: copy modify or create new
От: AlexNek  
Дата: 17.03.12 21:52
Оценка:
Здравствуйте, jhfrek, Вы писали:

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



AN>>при этом велика вероятность напоротся на забытые камни. Ну типа при возврате строки будем возвращать текст, тогда как в старом плагине это строка берется, например из ресурсов.

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

J>при создании с нуля тоже получается рабочий прототип, выдающий правильные данные, просто не все или не всеми способами.

Не всегда это может получится также быстро. Кроме того, многие ньюансы вероятно просто забылись. Уже пройденную дорогу нужно будет пройти еще раз.
А так, копируем старый плагин, меням пару идентификаторов, подсовываем ему старое устройство. И ГВИ уже показывает что у нас устройство "Б", а не "А". Теперь делаем фейк данные, ... и новый плагин готов для использования в разработке. Кстати, просто выдать данные в "пустом" плагине не так уж и просто, приложение ожидает строго определенную последовательность событий генерируемую "рабочим" плагином.
Cообщение написано в &lt;&lt; RSDN@Home 1.2.0 alpha 5-AN-R8 rev. 13227&gt;&gt;
Re[2]: copy modify or create new
От: AlexNek  
Дата: 17.03.12 23:09
Оценка:
Здравствуйте, maxkar, Вы писали:

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


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


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

M>

    M>
  1. Код плагинов окажется слишком различным. В этом случае проще будет писать с нуля.
    Этого не может быть в принципе — скелет будет всегда одинаковым. Так специально спроектировано.
    M>
  2. Код плагинов окажется слишком похожим. При этом будут какие-то мелкие детали все же будут отличаться (нужно еще, чтобы при чтении кода такие детали ловить было сложно). Здесь уже на практике нужно смотреть, позволит ли увеличение времени на разработку сократить время на тестирование.
    Ну если устройства принадлежат к одной группе, то различий будет действительно немного.
    M>
  3. При разработке какие-то изменения будут забыты (граничные случаи и т.п.). Чем больше граничных случаев, тем больше вероятность ошибки.
    А почему это будет специфично именно для модификации? Для нового кода будет абсолютно тоже самое, при том что можно забыть то, что уже было реализовано.
    M>

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


M>Если "база" похожа, стоит взять ее. Как раз из-за соображений протестированности (существующая база уже работает).

M>Радикально другое решение вы строить не собираетесь, так что смысла писать код "второй раз" не имеет смысла.
Если бы все так думали. Только согласно какой "умной книжки" код всегда следует писать с нуля маленьким порциями и не сверху вниз, а исключительно снизу вверх.
M>Еще и ценный опыт получите — насколько база подходит под "разные" задачи.
Слишком больших открытий не ожидается, так как концепт уже был отработан в предыдущей программе, которая не использовала плагины.
M>Если вдруг появятся третье, четвертое и пятое устройства, вы уже будете знать, чего не хватает в существующем решении и как его лучше модифицировать для большей универсальности.
M>А вот решение о том, как именно писать отдельные плагины, можно оставить разрабочтикам, ответственным за эти плагины. Они выберут ту методологию, с которой комфортнее работать лично им.
А вот этого не следует делать. Все плагины должны использовать один концепт и один скелет. Иначе, когда будет десяток плагинов никто в них не разберется, они все будут разными.
Cообщение написано в &lt;&lt; RSDN@Home 1.2.0 alpha 5-AN-R8 rev. 13227&gt;&gt;
Re[2]: copy modify or create new
От: AlexNek  
Дата: 17.03.12 23:13
Оценка:
Здравствуйте, maxkar, Вы писали:

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


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


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

M>

    M>
  1. Код плагинов окажется слишком различным. В этом случае проще будет писать с нуля.
    Этого не может быть в принципе — скелет будет всегда одинаковым. Так специально спроектировано.
    M>
  2. Код плагинов окажется слишком похожим. При этом будут какие-то мелкие детали все же будут отличаться (нужно еще, чтобы при чтении кода такие детали ловить было сложно). Здесь уже на практике нужно смотреть, позволит ли увеличение времени на разработку сократить время на тестирование.
    Ну если устройства принадлежат к одной группе, то различий будет действительно немного.
    M>
  3. При разработке какие-то изменения будут забыты (граничные случаи и т.п.). Чем больше граничных случаев, тем больше вероятность ошибки.
    А почему это будет специфично именно для модификации? Для нового кода будет абсолютно тоже самое, при том что можно забыть то, что уже было реализовано.
    M>

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


M>Если "база" похожа, стоит взять ее. Как раз из-за соображений протестированности (существующая база уже работает).

M>Радикально другое решение вы строить не собираетесь, так что смысла писать код "второй раз" не имеет смысла.
Если бы все так думали. Только согласно какой "умной книжки" код всегда следует писать с нуля маленьким порциями и не сверху вниз, а исключительно снизу вверх.
M>Еще и ценный опыт получите — насколько база подходит под "разные" задачи.
Слишком больших открытий не ожидается, так как концепт уже был отработан в предыдущей программе, которая не использовала плагины.
M>Если вдруг появятся третье, четвертое и пятое устройства, вы уже будете знать, чего не хватает в существующем решении и как его лучше модифицировать для большей универсальности.
M>А вот решение о том, как именно писать отдельные плагины, можно оставить разрабочтикам, ответственным за эти плагины. Они выберут ту методологию, с которой комфортнее работать лично им.
А вот этого не следует делать. Все плагины должны использовать один концепт и один скелет. Иначе, когда будет десяток плагинов никто в них не разберется, они все будут разными.
Cообщение написано в &lt;&lt; RSDN@Home 1.2.0 alpha 5-AN-R8 rev. 13227&gt;&gt;
Re[5]: copy modify or create new
От: jhfrek Россия  
Дата: 18.03.12 07:54
Оценка:
Здравствуйте, AlexNek, Вы писали:

J>>при создании с нуля тоже получается рабочий прототип, выдающий правильные данные, просто не все или не всеми способами.

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

вот именно поэтому мне вариант с нуля больше нравится — вспоминание ньюансов позволяет меньше ошибаться и дает идеи для рефакторинга

AN>А так, копируем старый плагин, меням пару идентификаторов, подсовываем ему старое устройство. И ГВИ уже показывает что у нас устройство "Б", а не "А". Теперь делаем фейк данные, ... и новый плагин готов для использования в разработке. Кстати, просто выдать данные в "пустом" плагине не так уж и просто, приложение ожидает строго определенную последовательность событий генерируемую "рабочим" плагином.


ха, ну раз все так просто, то конечно переделывайте существующий.

Просто я сталкивался со случаем, когда все было просто только внешне — класс по чтению и обработке файла. Функция Открыть() внутри которой вызывается ОбработатьДанныеТипаА(). Копируем, меняем идентификатор ОбработатьДанныеТипаА() на заглушку ОбработатьДанныеТипаВ() — наш новый тип, — компилируем, запускаем, все работает. Добавляем остальные методы — тоже работает, но после добавление какого-то из методов начинает слетать. Откатываем — прекращает слетать, накатываем еще раз — не слетает, запускаем несколько раз — не слетает. Снова продолжаем добавлять — через сколько-то методов опять получаем слет, откатываем, все еще слетает, потом прекращает... Короче пара дней была убита на поиск бага, при редко воспроизводящихся слетах непонятно в какой функции.

Оказалось какой-то деятель, вставил нотификацию файл_открыт не в метод Открыть(), а в метод ОбработатьДанныеТипаА(). Причем эта нотификация вызывалась косвенно, порождая пакет других нотификаций, приводящих в конечном итоге к инициализации данных вообще в другом куске кода, о котором никто даже не вспомнил.
Re[6]: copy modify or create new
От: AlexNek  
Дата: 18.03.12 11:13
Оценка:
Здравствуйте, jhfrek, Вы писали:

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


J>>>при создании с нуля тоже получается рабочий прототип, выдающий правильные данные, просто не все или не всеми способами.

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

J>вот именно поэтому мне вариант с нуля больше нравится — вспоминание ньюансов позволяет меньше ошибаться и дает идеи для рефакторинга

Очень не уверен, что можно вспомнить ньюансы не видя старого кода. Да даже если и смотреть фиг бывает вспомнишь почему именно так сделано (если коммент забыли). Рефакторинга чего? ведь все по новому делаем. А вот когда страое приспосбливаешь к новому, вот именно тогда и возникает море ньюансов.

AN>>А так, копируем старый плагин, меням пару идентификаторов, подсовываем ему старое устройство. И ГВИ уже показывает что у нас устройство "Б", а не "А". Теперь делаем фейк данные, ... и новый плагин готов для использования в разработке. Кстати, просто выдать данные в "пустом" плагине не так уж и просто, приложение ожидает строго определенную последовательность событий генерируемую "рабочим" плагином.


J>ха, ну раз все так просто, то конечно переделывайте существующий.

Мне в принипе было просто интересно узнать различные мнения по данному поводу. Так как "сверху" уже поступила противополжная директива.
Cообщение написано в &lt;&lt; RSDN@Home 1.2.0 alpha 5-AN-R8 rev. 13227&gt;&gt;
Re[7]: copy modify or create new
От: jhfrek Россия  
Дата: 18.03.12 15:42
Оценка:
Здравствуйте, AlexNek, Вы писали:


J>>вот именно поэтому мне вариант с нуля больше нравится — вспоминание ньюансов позволяет меньше ошибаться и дает идеи для рефакторинга

AN>Очень не уверен, что можно вспомнить ньюансы не видя старого кода.

почему не видя? новый код пишется по образу и подобию старого

AN>Да даже если и смотреть фиг бывает вспомнишь почему именно так сделано (если коммент забыли).


если не вспомнишь, то не добавляем — зачем нам неизвестно что делающий код

AN>Рефакторинга чего? ведь все по новому делаем. А вот когда страое приспосбливаешь к новому, вот именно тогда и возникает море ньюансов.


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

AN>>>А так, копируем старый плагин, меням пару идентификаторов, подсовываем ему старое устройство. И ГВИ уже показывает что у нас устройство "Б", а не "А". Теперь делаем фейк данные, ... и новый плагин готов для использования в разработке. Кстати, просто выдать данные в "пустом" плагине не так уж и просто, приложение ожидает строго определенную последовательность событий генерируемую "рабочим" плагином.

J>>ха, ну раз все так просто, то конечно переделывайте существующий.
AN>Мне в принипе было просто интересно узнать различные мнения по данному поводу. Так как "сверху" уже поступила противополжная директива.

да и я не настаиваю на своей правоте — так, размышляю и делюсь своим опытом.
Re[8]: copy modify or create new
От: AlexNek  
Дата: 18.03.12 16:28
Оценка:
Здравствуйте, jhfrek, Вы писали:

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



J>>>вот именно поэтому мне вариант с нуля больше нравится — вспоминание ньюансов позволяет меньше ошибаться и дает идеи для рефакторинга

AN>>Очень не уверен, что можно вспомнить ньюансы не видя старого кода.

J>почему не видя? новый код пишется по образу и подобию старого

Это как?На одном экране старый код, на другом новый, используя визуальное копирование?

AN>>Да даже если и смотреть фиг бывает вспомнишь почему именно так сделано (если коммент забыли).


J>если не вспомнишь, то не добавляем — зачем нам неизвестно что делающий код

То бишь лучше опять ждать аналогичного теста, который выявит старую/решенную проблему?

AN>>Рефакторинга чего? ведь все по новому делаем. А вот когда страое приспосбливаешь к новому, вот именно тогда и возникает море ньюансов.


J>методов. В старом коде есть метод который читает данные, пихает их в массив, сортирует его и потом как-то массив обрабатывает. Нам надо написать аналог метода для нового кода. Мы внимательно смотрим на старый метод, вникаем в него, понимаем что у нового кода будет отличатся только обработка.

J>Значит сначала рефакторим старый код — выделяем из него функцию обработки,
где будет находится этот "старый код"
J>делаем ее виртуальной и в новом коде только лишь переопределяем эту функцию.
А откуда в новом возьмутся старые части?

AN>>>>А так, копируем старый плагин, меням пару идентификаторов, подсовываем ему старое устройство. И ГВИ уже показывает что у нас устройство "Б", а не "А". Теперь делаем фейк данные, ... и новый плагин готов для использования в разработке. Кстати, просто выдать данные в "пустом" плагине не так уж и просто, приложение ожидает строго определенную последовательность событий генерируемую "рабочим" плагином.

J>>>ха, ну раз все так просто, то конечно переделывайте существующий.
AN>>Мне в принипе было просто интересно узнать различные мнения по данному поводу. Так как "сверху" уже поступила противополжная директива.

J>да и я не настаиваю на своей правоте — так, размышляю и делюсь своим опытом.

Я также не знаю правильного ответа, но что то мне подсказывает, что делать все с нуля не пользуя старый код будет далеко не оптимальным решением.
Cообщение написано в &lt;&lt; RSDN@Home 1.2.0 alpha 5-AN-R8 rev. 13227&gt;&gt;
Re[9]: copy modify or create new
От: jhfrek Россия  
Дата: 18.03.12 16:52
Оценка:
Здравствуйте, AlexNek, Вы писали:

J>>почему не видя? новый код пишется по образу и подобию старого

AN>Это как?На одном экране старый код, на другом новый, используя визуальное копирование?

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

AN>>>Да даже если и смотреть фиг бывает вспомнишь почему именно так сделано (если коммент забыли).

J>>если не вспомнишь, то не добавляем — зачем нам неизвестно что делающий код
AN>То бишь лучше опять ждать аналогичного теста, который выявит старую/решенную проблему?

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

AN>>>Рефакторинга чего? ведь все по новому делаем. А вот когда страое приспосбливаешь к новому, вот именно тогда и возникает море ньюансов.

J>>методов. В старом коде есть метод который читает данные, пихает их в массив, сортирует его и потом как-то массив обрабатывает. Нам надо написать аналог метода для нового кода. Мы внимательно смотрим на старый метод, вникаем в него, понимаем что у нового кода будет отличатся только обработка.
J>>Значит сначала рефакторим старый код — выделяем из него функцию обработки,
AN>где будет находится этот "старый код"

старый код — это обработчик файлов типа А, новый код — это обработчик файлов типа Б, который нам надо написать по образу и подобию обработчика файлов типа А

J>>делаем ее виртуальной и в новом коде только лишь переопределяем эту функцию.

AN>А откуда в новом возьмутся старые части?

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

Это дольше, чем скопировать и переименовать, но это надежнее и приятнее в программировании

J>>да и я не настаиваю на своей правоте — так, размышляю и делюсь своим опытом.

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

зависит от старого кода, в примере который я рассказал написание с нуля вышло бы быстрее чем последующий поиск бага в скопированном
Re[10]: copy modify or create new
От: AlexNek  
Дата: 18.03.12 22:44
Оценка:
Здравствуйте, jhfrek, Вы писали:

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


J>>>почему не видя? новый код пишется по образу и подобию старого

AN>>Это как?На одном экране старый код, на другом новый, используя визуальное копирование?

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

Какая вероятность того что эти два кода будут достаточно сильно отличаться?

AN>>>>Да даже если и смотреть фиг бывает вспомнишь почему именно так сделано (если коммент забыли).

J>>>если не вспомнишь, то не добавляем — зачем нам неизвестно что делающий код
AN>>То бишь лучше опять ждать аналогичного теста, который выявит старую/решенную проблему?

J>нет, лучше потратить время и разобраться что старый код делает


AN>>>>Рефакторинга чего? ведь все по новому делаем. А вот когда страое приспосбливаешь к новому, вот именно тогда и возникает море ньюансов.

J>>>методов. В старом коде есть метод который читает данные, пихает их в массив, сортирует его и потом как-то массив обрабатывает. Нам надо написать аналог метода для нового кода. Мы внимательно смотрим на старый метод, вникаем в него, понимаем что у нового кода будет отличатся только обработка.
J>>>Значит сначала рефакторим старый код — выделяем из него функцию обработки,
AN>>где будет находится этот "старый код"

J>старый код — это обработчик файлов типа А, новый код — это обработчик файлов типа Б, который нам надо написать по образу и подобию обработчика файлов типа А


J:Значит сначала рефакторим старый код — выделяем из него функцию обработки,
где будет физически находится этот "старый код" во время рефакторинга?

J>>>делаем ее виртуальной и в новом коде только лишь переопределяем эту функцию.

AN>>А откуда в новом возьмутся старые части?

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

То бишь делаем копию старого кода в которой его рефакторим и проверяем работоспособность.

J>Это дольше, чем скопировать и переименовать, но это надежнее и приятнее в программировании

А что мешает рефакторить старый код сразу на новом месте?

J>>>да и я не настаиваю на своей правоте — так, размышляю и делюсь своим опытом.

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

J>зависит от старого кода, в примере который я рассказал написание с нуля вышло бы быстрее чем последующий поиск бага в скопированном

Так это же просто замечательно что нашли новый баг, его нужно и со старого кода вычистить.
Cообщение написано в &lt;&lt; RSDN@Home 1.2.0 alpha 5-AN-R8 rev. 13227&gt;&gt;
Re[11]: copy modify or create new
От: jhfrek Россия  
Дата: 19.03.12 19:04
Оценка:
Здравствуйте, AlexNek, Вы писали:

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

AN>Какая вероятность того что эти два кода будут достаточно сильно отличаться?

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

AN>J:Значит сначала рефакторим старый код — выделяем из него функцию обработки,

AN>где будет физически находится этот "старый код" во время рефакторинга?
J>>>>делаем ее виртуальной и в новом коде только лишь переопределяем эту функцию.
AN>>>А откуда в новом возьмутся старые части?
J>>так мы ж отрефакторили — общую часть выделили в базовый класс в котором теперь данные читаются, пихаются и сортируются и обрабатываются абстрактным обработчиком, который перекрывается в обоих методах.
AN>То бишь делаем копию старого кода в которой его рефакторим и проверяем работоспособность.

еще раз. Допустим у нас есть файл А, в котором лежит класс ОбработчикФайловТипаА с единственной функцией Обработать() на 3 экрана, которая открывает файл, зачитывает из него данные, извлекает из данных некие важные числа, запихивает их в массив, тут же сортирует этот массив по возрастанию пузырьком и считает среднее арифметическое 5 максимальных чисел в качестве характеристики наших данных. Нам нужно написать обработчик файлов типа Б, который делает все абсолютно тоже самое, но считает среднее арифметическое 5 минимальных чисел.

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

При разработке с нуля мы сначала смотрим на код функции из класса А, анализируем его и понимаем что в новом классе будет отличаться только подсчет среднего арифметического. Выносим этот подсчет в отдельную функцию ПосчитатьХарактеристики() и прогоняем тесты, что бы убедиться что мы ничего не сломали. Заводим файл База, в нем заводим класс БазовыйОбработчик файлов и переносим в него функцию Обработать() целиком. Потом заводим абстрактную ПосчитатьХарактеристики(), делаем ПосчитатьХарактеристики() в классе ОбработчикФайловТипаА виртуальной и снова прогоняем тесты. И только после этого заводим файл Б, с классом ОбработчикФайловТипаБ в котором всего навсего пишем небольшую функцию ПосчитатьХарактеристики()

Человек, который будет писать ОбработчикФайловТипаС, в котором будет считаться среднее геометрическое, скажет нам офигенное спасибо

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

J>>Это дольше, чем скопировать и переименовать, но это надежнее и приятнее в программировании

AN>А что мешает рефакторить старый код сразу на новом месте?

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

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

J>>>>да и я не настаиваю на своей правоте — так, размышляю и делюсь своим опытом.

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

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

J>>зависит от старого кода, в примере который я рассказал написание с нуля вышло бы быстрее чем последующий поиск бага в скопированном

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

не, в старом коде бага не было. Мы его внесли при превращении старого кода в новый
Re[3]: copy modify or create new
От: maxkar  
Дата: 20.03.12 17:36
Оценка:
Здравствуйте, AlexNek, Вы писали:

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


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

AN>А вот этого не следует делать. Все плагины должны использовать один концепт и один скелет. Иначе, когда будет десяток плагинов никто в них не разберется, они все будут разными.

Я имел в виду методологию разработки "снизу вверх" или "сверху вниз". Архитектурные вопросы и вопросы стиля нужно проверять (ревью, например). А вот будет ли функциональность наращиваться "с нуля" или путем допиливания уже существующей здесь не важно до тех пор, пока результат соответствует всем требованиям. Хотя при этом возникает необходимость правильно отслеживать момент "завершения" работы над модулем. Например, в репозиторий не коммитится плагин "в разработке". Или коммитится, но где-то в трекере можно посмотреть статус всех функций (не реализовано, реализовано). Наличие такой поддержки позволяет и разрабатывать плагины "с нуля" (статус можно отобразить корректно). А вот отсутствие "статуса завершенности плагина" автоматически запрещает второй вариант, так как в снапшоте репозитория нельзя будет определить статус готовности плагина. "Работающий" плагин может только делать вид, что работает. А вот "недописанный" плагин явно не будет работать.
Re[4]: copy modify or create new
От: AlexNek  
Дата: 27.03.12 23:51
Оценка:
Здравствуйте, maxkar, Вы писали:

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


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


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

AN>>А вот этого не следует делать. Все плагины должны использовать один концепт и один скелет. Иначе, когда будет десяток плагинов никто в них не разберется, они все будут разными.

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

Результат и так должен соответствовать, а путь к нему имеет самостятельную роль.

M>Хотя при этом возникает необходимость правильно отслеживать момент "завершения" работы над модулем.

Зачем, это и так известно.
M>Например, в репозиторий не коммитится плагин "в разработке". Или коммитится,
M>но где-то в трекере можно посмотреть статус всех функций (не реализовано, реализовано).
Кто будет заниматься этой ерундой (обновленим статуса) и для чего? Все кто имеет точки соприкосновения и так всё знают, а для отчетов есть план работ. А коммитить можно и в бранч, кому нужно тот возмет.
M>Наличие такой поддержки позволяет и разрабатывать плагины "с нуля" (статус можно отобразить корректно).
Наличие такой поддержки никому не нужная работа. А если функции еше нет? Да и причем вообще функции, когда интересует функционал.
M>А вот отсутствие "статуса завершенности плагина" автоматически запрещает второй вариант, так как в снапшоте репозитория нельзя будет определить статус готовности плагина.
Для чего нужен еще какой то статус? Или функционал полностью реализован или нет. Кому будет интересен этот статус и причем статус к типу разработки?
M>"Работающий" плагин может только делать вид, что работает.
Если функционал не готов то и ежу понятно что не все работает правильно, но "база" обеспеченная старым скелетом резко минимизирует необходимость разработки заглушек для сопряженных заданий.
M>А вот "недописанный" плагин явно не будет работать.
В любом случае плагин будет до определенного момента недописанным.
Cообщение написано в &lt;&lt; RSDN@Home 1.2.0 alpha 5-AN-R8 rev. 13227&gt;&gt;
Re[5]: copy modify or create new
От: maxkar  
Дата: 28.03.12 15:17
Оценка:
Здравствуйте, AlexNek, Вы писали:

AN>Для чего нужен еще какой то статус? Или функционал полностью реализован или нет. Кому будет интересен этот статус и причем статус к типу разработки?


Статус может быть интересен другому разработчику, который по какой-либо причине открыл этот код. Например, чтобы взять за основу для похожего плагина/устройства (оно может оказаться проще, чем брать "старый" плагин). Да и для тех же отчетов (которые план работ) проверить проще отсутствие меток TODO/FIXME/STUB, чем вспоминать, было оно до конца доделано или нет.

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


Вот поэтому и будет работать только подход "писать заново". В нем по коду определить, "готов функционал или нет" обычно гораздо проще. "Заглушки" обычно хоть как-то помечаются (теми же TODO/FIXME в комментариях и другими подобными способами). А вот старый скелет такой информации не дает. Вот вы открыли файл, там какие-то методы. Вопрос — это уже полностью рабочий код или нет? Может быть, там старый скелет, еще не переписаный местами. А может, это уже полностью готовый модуль. Или придется искать другого разработчика и спрашивать его о состоянии дел? Или тестировать самому, работает оно до конца или нет? Все же в общем коде в репозитории если что-то не работает/не доделано, это хотелось бы определять практически сразу.
Re[6]: copy modify or create new
От: AlexNek  
Дата: 28.03.12 18:09
Оценка:
Здравствуйте, maxkar, Вы писали:

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


AN>>Для чего нужен еще какой то статус? Или функционал полностью реализован или нет. Кому будет интересен этот статус и причем статус к типу разработки?


M>Статус может быть интересен другому разработчику, который по какой-либо причине открыл этот код.

для чего другому разработчику лезть в незаконченный код?
M>Например, чтобы взять за основу для похожего плагина/устройства (оно может оказаться проще, чем брать "старый" плагин).
звучит сильнее чем фантастика. Если такое и требуется, то не должно планироваться в параллель. А если уж и приспичило, то возмется копия старого плагина и попросятся нужные части от нового как временное решение.
M> Да и для тех же отчетов (которые план работ) проверить проще отсутствие меток TODO/FIXME/STUB, чем вспоминать, было оно до конца доделано или нет.
Так по плану работ и видно доделано или нет.
AN>>Если функционал не готов то и ежу понятно что не все работает правильно, но "база" обеспеченная старым скелетом резко минимизирует необходимость разработки заглушек для сопряженных заданий.

M>Вот поэтому и будет работать только подход "писать заново". В нем по коду определить, "готов функционал или нет" обычно гораздо проще. "Заглушки" обычно хоть как-то помечаются (теми же TODO/FIXME в комментариях и другими подобными способами). А вот старый скелет такой информации не дает.

M>Вот вы открыли файл, там какие-то методы. Вопрос — это уже полностью рабочий код или нет?
Еще не попадалось случая когда команда не знает состояния дел в своем сегменте. Модуль или рабочий или в разработке. Для оперативности у разработчиков есть телефоны и почта.
M>Может быть, там старый скелет, еще не переписаный местами. А может, это уже полностью готовый модуль. Или придется искать другого разработчика и спрашивать его о состоянии дел?
А для кого эти все вопросы? Модуль в разработке, по окончанию разработки начнется более углубленное тестирование. Всегда есть человек который в курсе актуального статуса проекта/модуля.
M>Или тестировать самому, работает оно до конца или нет? Все же в общем коде в репозитории если что-то не работает/не доделано, это хотелось бы определять практически сразу.
Не нужно это определять это и так известно, иначе есть проблема коммуникации в команде.
Cообщение написано в &lt;&lt; RSDN@Home 1.2.0 alpha 5-AN-R8 rev. 13227&gt;&gt;
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.