рефакторинг с тестами или без? [re: unit-тесты для БД]

6yrop

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

kokoc88

без автотестов рефакторинг нельзя делать?
Зависит от того, что ты подразумеваешь под этим словом. Я перемалываю более 100% всего написанного кода, это нереально без юнит тестов.

6yrop

Зависит от того, что ты подразумеваешь под этим словом.
это да. Я могу рефакторить два дня и не внести ни одной баги. Без автотестов.

6yrop

Без автотестов.
точнее даже вообще ни разу не побывав в рантайме.

kokoc88

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

6yrop

ну :) ... я наверное немного загибать стал :) но для иллюстрации своей позиции сойдет и так :)

6yrop

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

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

kokoc88

нет, не внесу.
Либо внесёшь, либо делаешь что-то тривиальное и/или 100% автоматизированое.
зачем я буду тратить много времени, если я уверен за свой стиль работы с кодом?
За тем, что без тестирования код без ошибок можно написать только с огромными временными затратами. Уж это-то известно любому практикующему программисту, даже если он никогда не писал автотестов.

6yrop

Либо внесёшь, либо делаешь что-то тривиальное и/или 100% автоматизированое.
нетривиальное. процент автоматизации близок 100%. В этом и фишка, что в C# рефакторинг почти весь автоматизирован.
За тем, что без тестирования код без ошибок можно написать только с огромными временными затратами. Уж это-то известно любому практикующему программисту, даже если он никогда не писал автотестов.

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

kokoc88

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

Я уже написал, что в моём понимании означает нетривиальный рефакторинг. Для примера: замена switch/case на State, замена ручной проверки типов на Visitor, расслоение класса для восстановления SRP, и так далее. Без тестов даже extract method с последующей полировкой может внести ошибку в код.

6yrop

Я уже написал, что в моём понимании означает нетривиальный рефакторинг.
может я что-то пропустил, но ты написал только "Я перемалываю более 100% всего написанного кода". Это не тянет на пояснение понятия.
 
замена switch/case на State

где здесь можно ошибиться? вероятность ошибки крайне мала. Аккуратненько переносишь кусочки кода, да и всё. Ну это при условии, если сам switch был грамотно написан. А если не грамотно, то это уже не рефакторинг, а переписывание говнокода.
 
замена ручной проверки типов на Visitor
тоже самое
 
расслоение класса для восстановления SRP
тут тоже самое, только еще проще, только кнопки в ReSharper-е нажимай
 
Без тестов даже extract method с последующей полировкой может внести ошибку в код.

Это как? :shocked:
В общем “большой” рефакторинг разбивается на мелкие шажки, в которых практически сложно ошибиться. Просто соблюдаешь эту процедуру и не делаешь лишних телодвижений. Ну еще я часто чикинюсь (желательно в feature branch).

kokoc88

Аккуратненько переносишь кусочки кода, да и всё.

И либо тратишь много времени, либо делаешь ошибку.
тут тоже самое, только еще проще, только кнопки в ReSharper-е нажимай

Тут на порядки сложнее, и кнопок в ReSharper-е для этого, конечно же, не хватает.
В общем “большой” рефакторинг разбивается на мелкие шажки, в которых практически сложно ошибиться. Просто соблюдаешь эту процедуру и не делаешь лишних телодвижений.
Точно так же выглядел бы аргумент про программирование в целом: большая программа просто разбивается на мелкие шажки, в которых практически сложно ошибиться. Просто соблюдаешь эту процедуру и пишешь код без ошибок.

kokoc88

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

6yrop

Немного провокаций :p
Для примера: замена switch/case на State, замена ручной проверки типов на Visitor, расслоение класса для восстановления SRP
Пока складывается впечатление, что юнит тесты нужны:
любителям динамических языков;
ООП-шникам;
работникам с говнокодом.

6yrop

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

kokoc88

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

Dasar

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

kokoc88

доказательство правильности работы кода всегда надежнее работает, чем тестирование
И поэтому нигде на практике не реализовано хоть сколько-нибудь сложной верификации. Мы это уже обсуждали, не вижу смысла опять поднимать эту тему, если она практически непригодна.
при рефакторинге есть такая штука, что правильность каждого изменения можно доказать - доказав, что данное маленькое изменение не меняет поведение всей программы.
Во-первых, это никак не спасёт тебя от появления ошибок в коде: маленькое изменение можно сделать неправильно, если оно не автоматизируется. Во-вторых, я привёл пример рефакторинга, который ты либо практически не верифицируешь, либо не выполнишь маленькими шагами за разумное время.
имхо, тестирование помогает только от замыления глаза (глупые описки, опечатки и т.д.) + системное тестирование помогает избежать ситуации: когда представление разработчика не совпадает с реальным миром.
Тестирование помогает находить реальные ошибки, быстрее разрабатывать код, выполнять рефакторинг, избегать регрессии (одинаковый по функциональному эффекту баг может быть вызван разными причинами).
всё остальное лучше делать через доказательство правильности работы кода.

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

6yrop

Шурик очень-очень круто кодит и при этом не делает ошибок. С этим можно было бы согласиться, если бы он при этом признавал, что всё время тестирует результат этого кодирования.
Эээ, как бы "очень круто кодит" и тратит много времени на тестирование это как раз противоположности!
Видимо, ты не в курсе что такое "очень круто кодит".

kokoc88

Эээ, как бы "очень круто кодит" и тратит много времени на тестирование это как раз противоположности!
Ты не прочитал часть фразы "не делает ошибок".

Dasar

> И поэтому нигде на практике не реализовано хоть сколько-нибудь сложной верификации.
если ты про автоматизированную - то да, это достаточно дорогая и сложная штука, и применяется только где нужна сверх надежность.
но доказательство правильности работы кода человеком применяется сплошь и рядом.
например, правильность написания многопоточных приложений почти невозможно протестировать.
но можно доказать, что во-первых, все переменные доступные из нескольких тредов защищены локами, а во-вторых - что локи не могут попасть в dead-lock.
конечно, при этом используются не строгие формы доказательств, и может даже правильно это называть словом "показывается".
т.е. разработчик показывает сам себе (и окружающим) почему он считает, что его код будет работать правильно всегда.
зы
если брать "хороший запах кода", то он тоже в большинстве своем направлен на то, чтобы упростить "показывание" того, что код будет работать правильно.
на это же направлено и Code Review.
> Тестирование помогает находить реальные ошибки, быстрее разрабатывать код, выполнять рефакторинг, избегать регрессии (одинаковый по функциональному эффекту баг может быть вызван разными причинами).
согласен, и я не против тестирования.
я лишь хочу отметить, что тестирование имеет смысл применять совместно с доказыванием (показыванием) что код будет работать правильно.
> Доказать правильность работы каждой строчки кода в любом сложном проекте практически нереально, хотя бы из-за временных затрат
а это и не надо.
локальная опечатка быстро себя проявит на тех же системных или регрессионных тестах.
доказывать необходимо правильность взаимодействия нескольких участков куда

Maurog

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

kokoc88

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

kokoc88

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

Dasar

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

kokoc88

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

Bibi

ты, наверное, настолько крутой, что с тобой в команде никто работать не хочет?

6yrop

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

Bibi

*шутка про int overflow*

6yrop

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

kokoc88

Если при рефакторинге мы не меняем изменяемые состояния (забавный каламбурчик то такой рефакторинг легко разбивается на маленькие шаги, которые легко сделать без ошибок.
Ключевой момент заключается в том, что "легко сделать без ошибок" не означает "всегда делается без ошибок".
А какой вид рефакторинга SRP или еще какая хрень это не важно, если выполняется условие неизменности изменяемого состояния.

При чём тут состояние: ты запросто можешь сделать ошибку в control flow.

6yrop

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

не представляю каким образом это можно сделать. Всё ж почти к последовательности ReSharper-овских кнопок сводится.

kokoc88

Вопрос лишь в том какое их количество допустимо в конкретном проекте.
Я без понятия, сколько их допустимо в твоих проектах; но в моих я стараюсь свести это количество к нулю. И автотесты этому, очевидно, очень хорошо помогают.
не представляю каким образом это можно сделать. Всё ж почти к последовательности extract method/parameter сводится.
Я ещё раз повторю: кроме автоматического рефакторинга есть ещё куча всяких вещей, которые посложнее. Тебе что, действительно не приходит в голову мысль, что при расслоении класса методы оригинала могут сильно изменяться? Или что в двух методах может быть общая часть, которую захочется выделить? Или ещё куча всего, что не связано с автоматизированными паттернами в ReSharper?

6yrop

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

общий кусок выдели в метод, в чем проблема? Уже сейчас решарпер может при выноси параметра делать лямбды. Я писал им на форум, обещали сделать еще более общую форму такого рефакторинга. Детали могу вспомнить, если надо.
Или ещё куча всего, что не связано с автоматизированными паттернами в ReSharper?

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

kokoc88

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

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

Ответь мне ещё раз на вопрос, ты понимаешь, что рефакторинг автоматизируется максимум процентов на 10? Или ты под словом "рефакторинг" понимаешь только автоматизированные в ReSharper паттерны? А то мне начинает казаться, что тема в очередной раз слита: ничего нового ты не написал, зато уже два раза повторил сказанное.

6yrop

очередной раз слита
после таких слов .... .... пойду спать

kokoc88

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

6yrop

А ты никогда не замечал общее в двух методах, когда это общее можно выделить только с изменением control flow?
Главное что это можно сделать маленькими простыми шажками. В общем, тут самое время тебе привести код.
 
А про то, что при расслоении классов в каком-то методе не будут доступны все поля, используемые ранее?

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

6yrop

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

это я могу прокомментировать так:
1. Решарпер развивается, в книгах отражена инфа, относящаяся к 3-10 летней давности.
2. Если у человека есть время на написание книги, то у него меньше времени на поддержания формы профессионального программиста. Т.е. книги пишутся людьми, которые находятся слегка на расстоянии от кода.

kokoc88

Главное что это можно сделать маленькими простыми шажками. В общем, тут самое время тебе привести код.
Блядь, да при чём тут размер "шажков"? Я тебе в третий раз говорю, что следуя твоим утверждениям, можно сделать вывод о том, что все программы пишутся без ошибок, потому что они точно так же разбиваются на эти твои "маленькие шажки". Возражения есть?
А readonly поля, пришедшие в аргументе конструктора, и просто параметры метода это почти одно и тоже; в том смысле, что одни в другие (и обратно) преобразовываются маленькими простыми шажками.
Какой бред. Во-первых, ты не сможешь написать сложную программу с полностью immutable состоянием на императивном языке; то есть в рамках нашей дискусии поля любые, а не только readonly. Во-вторых, readonly поля могут быть mutable. В третьих, дублирование полей в двух классах может вполне себе нарушать DRY (о котором ты сам недавно писал и поэтому может быть неприемлемым и потребует более серьёзных изменений в коде.

kokoc88

я там не нашел таких.
Ты вообще на них не посмотрел.
Решарпер развивается, в книгах отражена инфа, относящаяся к 3-10 летней давности.
Я не знаю, какой у тебя ReSharper, а в моём около трёх десятков пунктов в меню рефакторинга. В то время как в книгах около 80 паттернов, а на просторах интернета - более сотни. И это только паттерны, а ведь они и близко не перекрывают весь спектр вносимых в код изменений.
Если у человека есть время на написание книги, то у него меньше времени на поддержания формы профессионального программиста. Т.е. книги пишутся людьми, которые находятся слегка на расстоянии от кода.

Если у человека есть время только на кодинг, он так и останется кодером. Тебе следовало бы почитать книжки. Может быть, после этого тебя можно будет считать разработчиком, а не кодером. Да и как можно вести с тобой дискуссию, если ты не признаёшь авторитетов, кроме себя. Извини, но тебе пока что далеко до звания "Великий Непревзойдённый Разработчик", и по этой причине вполне логично было бы аппелировать к третьим лицам.

6yrop

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

kokoc88

можно еще к коду. В предыдущем посте я запросил у тебя код? Кода не будет?
Какой именно код тебя интересует и зачем? Открой книгу Фаулера - будет тебе одновременно и ссылка на третье лицо, и код. Я не вижу смысла копировать оттуда текст только для того, чтобы поспорить с упёртым кодером, который считает себя гением.

6yrop

Какой именно код тебя интересует
код который демонстрирует вот это
А ты никогда не замечал общее в двух методах, когда это общее можно выделить только с изменением control flow?

зачем?

я покажу последовательность шажков для рефакторинга. Требования к шажкам: автоматические или простое перемещение текста с добавлением скобочек и т.п.

kokoc88

я покажу последовательность шажков для рефакторинга. Требования к шажкам: автоматические или простое перемещение текста с добавлением скобочек и т.п
Интересно, ты троллишь или просто не понимаешь, что это ничему не поможет? Давай лучше вернёмся к твоему изначальному утверждению: ты можешь делать рефакторинг два дня подряд, не запуская проект и никак не тестируя его. При этом ты сказал, что не сделаешь ни одной ошибки.
Я согласен, что можешь, но при двух ограничениях: либо ты будешь держаться в рамках исключительно автоматизированного рефакторинга; либо на что-нибудь простое ты потратишь слишком много времени, потому что тебе придётся всё время интерпретировать код в голове. С этими ограничениями такой рефакторинг становится фактически бесполезным.
Юнит тесты это всё серьёзно упрощают, потому что во-первых, позволяют делать более сложный рефакторинг, который не автоматизирован; во-вторых, позволяют в десятки раз сокращать затрачиваемое на кодирование время.
Ты пытаешься возражать в каком-то не ясном мне ключе, ссылаясь на то, что сложный рефакторинг ты разобьёшь на мелкие части. В четвёртый раз повторю, что это не спасёт тебя от ошибок: даже автоматические тесты всего лишь сводят к близкому к нулю значению их количество.

kokoc88

я покажу последовательность шажков для рефакторинга.
Кстати, какой замечательный способ троллинга: надо сначала сказать, что пишешь код три дня подряд "маленькими шажками" абсолютно без ошибок. В ответ на охуевающе-недоумевающие сомнения окружающих в твоей гениальности, предлагаешь задать простую кодерскую задачу, чтобы показать, как закодить её "маленькими шажками", абсолютно без ошибок! :grin: :grin: :grin:

6yrop

либо на что-нибудь простое ты потратишь слишком много времени, потому что тебе придётся всё время интерпретировать код в голове.
не надо интерпретировать код в голове. Надо всего лишь проверять следующее. Каждый шажок это переход кода из состояния n в состояние n+1. Надо гарантировать, что состояние n тождественно состоянию n+1.
Это как в алгебре. Пусть есть уравнение:
a+b+c=0
если мы переносим b вправо, напишем знак минус и уберем ноль
a+c=-b,
то получим тождественное уравнение.

kokoc88

Каждый шажок это переход кода из состояния n в состояние n+1. Надо гарантировать, что состояние n тождественно состоянию n+1.
Мы можем это проверить, только интерпретируя код в голове.

Phoenix

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

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

kokoc88

Надо всего лишь проверять следующее. Каждый шажок это переход кода из состояния n в состояние n+1. Надо гарантировать, что состояние n тождественно состоянию n+1.
Хорошо, уговорил. Вот тебе код, проведи рефакторинг к паттерну Visitor, заменив enum на полиморфные типы и отделив операции Calculate и Derivative от этих типов. При этом ты должен провести этот рефакторинг своими "мелкими шажками", полностью доказав тождественность преобразований каждого "шажка" в обе стороны. При этом ты не имеешь права аппелировать к третьим лицам, и должен доказывать все эти шажки на математических аксиомах.
Чтобы не возникало недопонимания, я сразу скажу, что этот рефакторинг весьма хорошо разжёван в некоторых статьях; причём разжёван он именно до "маленьких шажков", где после каждого такого шажка получается компилируемый рабочий код. Единственное отличие от того, что должен сделать Шурик (а я уверен, что он всё равно спишет из статьи) заключается в том, что в статьях рекомендуют использовать юнит тесты. Я всего лишь хочу показать, насколько этот рефакторинг может быть сложным и длительным, если делать его по шагам; а так же указать на вероятность возникновения потенциальных ошибок в "маленьких шажках". С автотестами этот рефакторинг делается за 10 минут.
        enum NodeType
{
CONSTANT,

/// <summary>
/// Переменная X (для простоты задачи других переменных нет).
/// </summary>
VARIABLE_X,

ADD,

SUB,

MULTIPLY
}

class Node
{
readonly Node left;

readonly Node right;

readonly double value;

readonly NodeType type;

/// <summary>
/// Создаёт бинарное выражение.
/// </summary>
public Node(Node left, Node right, NodeType type)
{
this.left = left;
this.right = right;
this.type = type;
}

/// <summary>
/// Создаёт выражение типа константы.
/// </summary>
public Node(double value)
{
this.value = value;
type = NodeType.CONSTANT;
}

/// <summary>
/// Создаёт выражение типа переменной X.
/// </summary>
public Node
{
type = NodeType.VARIABLE_X;
}

/// <summary>
/// Вычисляет выражение.
/// </summary>
/// <param name="variableX">Значение переменной X.</param>
public double Calculate(double variableX)
{
switch (type)
{
case NodeType.ADD:
return left.Calculate(variableX) + right.Calculate(variableX);
case NodeType.SUB:
return left.Calculate(variableX) - right.Calculate(variableX);
case NodeType.MULTIPLY:
return left.Calculate(variableX)*right.Calculate(variableX);
case NodeType.CONSTANT:
return value;
case NodeType.VARIABLE_X:
return variableX;
default:
throw new InvalidOperationException;
}
}

/// <summary>
/// Вычисляет производную выражения.
/// </summary>
public Node Derivative
{
switch (type)
{
case NodeType.ADD:
return new Node(left.Derivative right.Derivative NodeType.ADD);
case NodeType.SUB:
return new Node(left.Derivative right.Derivative NodeType.SUB);
case NodeType.MULTIPLY:
var addLeft = new Node(left.Derivative right, NodeType.MULTIPLY);
var addRight = new Node(right.Derivative left, NodeType.MULTIPLY);
return new Node(addLeft, addRight, NodeType.ADD);
case NodeType.CONSTANT:
return new Node(0);
case NodeType.VARIABLE_X:
return new Node(1);
default:
throw new InvalidOperationException;
}
}
}

kokoc88

На каждом маленьком шажке её поведение не меняется.
Нет, на каждом "маленьком шажке" её поведение меняется таким минимальным образом, чтобы это приближало нас к требуемому конечному решению. При этом шажок ну такой маленький, что сделать в нём ошибку просто нереально!1

Phoenix

рефакторинг делается "без изменения поведения", а написание новой с изменением.

kokoc88

проведи рефакторинг к паттерну Visitor
Вот результат, который меня интересует:
        interface INode
{
T ProcessBy<T>(INodeVisitor<T> visitor);
}

interface INodeVisitor<T>
{
T OnConstant(double val);

T OnVariableX;

T OnAdd(INode left, INode right);

T OnSub(INode left, INode right);

T OnMul(INode left, INode right);
}

abstract class BinaryNode : INode
{
protected readonly INode left;

protected readonly INode right;

protected BinaryNode(INode left, INode right)
{
this.left = left;
this.right = right;
}

public abstract T ProcessBy<T>(INodeVisitor<T> visitor);
}

class Add : BinaryNode
{
public Add(INode left, INode right) : base(left, right)
{
}

public override T ProcessBy<T>(INodeVisitor<T> visitor)
{
return visitor.OnAdd(left, right);
}
}

class Sub : BinaryNode
{
public Sub(INode left, INode right) : base(left, right)
{
}

public override T ProcessBy<T>(INodeVisitor<T> visitor)
{
return visitor.OnSub(left, right);
}
}

class Mul : BinaryNode
{
public Mul(INode left, INode right) : base(left, right)
{
}

public override T ProcessBy<T>(INodeVisitor<T> visitor)
{
return visitor.OnMul(left, right);
}
}

class Constant : INode
{
readonly double val;

public Constant(double val)
{
this.val = val;
}

public T ProcessBy<T>(INodeVisitor<T> visitor)
{
return visitor.OnConstant(val);
}
}

class VariableX : INode
{
public T ProcessBy<T>(INodeVisitor<T> visitor)
{
return visitor.OnVariableX;
}
}

class Calculate : INodeVisitor<double>
{
readonly double variableX;

public Calculate(double variableX)
{
this.variableX = variableX;
}

public double OnConstant(double val)
{
return val;
}

public double OnVariableX
{
return variableX;
}

public double OnAdd(INode left, INode right)
{
return left.ProcessBy(this) + right.ProcessBy(this);
}

public double OnSub(INode left, INode right)
{
return left.ProcessBy(this) - right.ProcessBy(this);
}

public double OnMul(INode left, INode right)
{
return left.ProcessBy(this)*right.ProcessBy(this);
}
}

class Derivative : INodeVisitor<INode>
{
public INode OnConstant(double val)
{
return new Constant(0);
}

public INode OnVariableX
{
return new Constant(1);
}

public INode OnAdd(INode left, INode right)
{
return new Add(left.ProcessBy(this right.ProcessBy(this;
}

public INode OnSub(INode left, INode right)
{
return new Sub(left.ProcessBy(this right.ProcessBy(this;
}

public INode OnMul(INode left, INode right)
{
var addLeft = new Mul(left.ProcessBy(this right);
var addRight = new Mul(right.ProcessBy(this left);
return new Add(addLeft, addRight);
}
}

kokoc88

рефакторинг делается "без изменения поведения", а написание новой с изменением.
Смотря что понимать под изменением. Замену алгоритма O(N) на O(LogN) или улучшение функциональности пользовательского интерфейса можно тоже считать рефакторингом. Но даже если и не считать, с чем я не намерен сейчас спорить, то моё утверждение всё равно остаётся в силе, если принимать на веру, что в "маленьких шажках" невозможно ошибиться.
А если не принимать это на веру, то становится очевидно, что вероятность появления ошибки возрастает вместе с количеством этих "маленьких шажков", даже если каждый отдельно взятый из них ничего не портит. Нам всего-то нужно либо ошибиться в одном из них, либо перепутать их порядок.

6yrop

Поля конечно readonly, но они инициализируются не во всех конструкторах. Так пишут только плохие ООП-шники. Интересно рассматривать рефакторинг кода без таких детсадовских косяков.

kokoc88

Поля конечно readonly, но они инициализируются не во всех конструкторах. Так пишут только плохие ООП-шники.

Если бы везде были только хорошие ООП-шники, то понятие рефакторинга никогда бы не появилось.
Интересно рассматривать рефакторинг кода без таких детсадовских косяков.
Либо пиши код, либо мы засчитываем тебе очередной слив.

Phoenix

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

kokoc88

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

kokoc88

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

6yrop

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

6yrop

Хорошо, уговорил. Вот тебе код, проведи рефакторинг к паттерну Visitor, заменив enum на полиморфные типы и отделив операции Calculate и Derivative от этих типов. При этом ты должен провести этот рефакторинг своими "мелкими шажками", полностью доказав тождественность преобразований каждого "шажка" в обе стороны. При этом ты не имеешь права аппелировать к третьим лицам, и должен доказывать все эти шажки на математических аксиомах.
Вот последовательность шажков. Пощу в pdf поскольку неохота переносить форматирование:

interface INode
{
T ProcessBy<T>(INodeVisitor<T> visitor);
}

interface INodeVisitor<T>
{
T CONSTANTNode(double value);
T VARIABLE_XNode;
T ADDNode(INode left, INode right);
T SUBNode(INode left, INode right);
T MULTIPLYNode(INode left, INode right);
}

class Derivative : INodeVisitor<INode>
{
public INode CONSTANTNode(double value)
{
return new CONSTANTNode(0);
}

public INode VARIABLE_XNode
{
return new CONSTANTNode(1);
}

public INode ADDNode(INode left, INode right)
{
return new ADDNode(left.ProcessBy(this right.ProcessBy(this;
}

public INode MULTIPLYNode(INode left, INode right)
{
var addLeft = new MULTIPLYNode(left.ProcessBy(this right);
var addRight = new MULTIPLYNode(right.ProcessBy(this left);
return new ADDNode(addLeft, addRight);
}

public INode SUBNode(INode left, INode right)
{
return new SUBNode(left.ProcessBy(this right.ProcessBy(this;
}
}

class Calculate : INodeVisitor<double>
{
private readonly double variableX;

public Calculate(double variableX)
{
this.variableX = variableX;
}

public double ADDNode(INode left, INode right)
{
return left.ProcessBy(this) + right.ProcessBy(this);
}

public double CONSTANTNode(double value)
{
return value;
}

public double MULTIPLYNode(INode left, INode right)
{
return left.ProcessBy(this) * right.ProcessBy(this);
}

public double SUBNode(INode left, INode right)
{
return left.ProcessBy(this) - right.ProcessBy(this);
}

public double VARIABLE_XNode
{
return variableX;
}
}

class CONSTANTNode : INode
{
readonly double value;

public CONSTANTNode(double value)
{
this.value = value;
}

public T ProcessBy<T>(INodeVisitor<T> visitor)
{
return visitor.CONSTANTNode(value);
}
}

class VARIABLE_XNode : INode
{
public T ProcessBy<T>(INodeVisitor<T> visitor)
{
return visitor.VARIABLE_XNode;
}
}

class ADDNode : BinaryNode
{
public ADDNode(INode left, INode right) : base(left, right)
{
}

public override T ProcessBy<T>(INodeVisitor<T> visitor)
{
return visitor.ADDNode(left, right);
}
}

class SUBNode : BinaryNode
{
public SUBNode(INode left, INode right) : base(left, right)
{
}

public override T ProcessBy<T>(INodeVisitor<T> visitor)
{
return visitor.SUBNode(left, right);
}
}

class MULTIPLYNode : BinaryNode
{
public MULTIPLYNode(INode left, INode right) : base(left, right)
{
}

public override T ProcessBy<T>(INodeVisitor<T> visitor)
{
return visitor.MULTIPLYNode(left, right);
}
}

abstract class BinaryNode: INode
{
protected readonly INode left;

protected readonly INode right;

protected BinaryNode(INode left, INode right)
{
this.left = left;
this.right = right;
}

public abstract T ProcessBy<T>(INodeVisitor<T> visitor);
}

6yrop

конечно это всё гораздо легче воспринимается, если показать в действии.

6yrop

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

6yrop

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

6yrop

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

kokoc88

Вот последовательность шажков. Пощу в pdf поскольку неохота переносить форматирование:
Пока что ответ не принимается: нет ни обоснования каждого шага, ни кода после каждого шага, в документе куча детских ошибок.
Ты сам-то прочитал свой PDF? Например:
1. "Строчка return new VARIABLE_XNode(1); небилдиться." - нигде выше в документе не написано, как эта строчка появилась. Вообще весь документ страдает от этого.
2. "Заменяем на return new CONSTANTNode(1); Она опять не билдится, но на это не обращаем внимания" - твои "маленькие шажки", особенно когда ты кинулся на спасательный круг их "тождественности", не могут приводить к некомпилируемому результату.
3. "Красным подсветится не верный тип у возвращаемого значения BinaryNode." - я не понял, откуда взялось возвращаемое значение у класса? Может быть, не стоило в спешке выплёвывать документ, в котором столько детсадовских ошибок и неточностей?
4. "Там где конструктор вызывался с третьим параметром подсветиться красным; переносим значение третьего параметра в название конструктора." - это не является "тождественным маленьким преобразованием".
5. "С помощью решарпера сгенерировали методы в интерфейсе INodeVisitor." - опять исправление некомпилируемоего кода, которое очевидно не является "тождественным маленьким преобразованием".
6. "Вырезаем метод qwe и переносим в класс Derivative." - и код опять не компилируется.
7. "по Alt+Enter добавляем в метод параметры left и right." - опять-таки, перестаёт компилироваться класс ADDNode, при чём нигде не сказано, что ты с этим сделал сейчас (введя значения по умолчанию) или потом (добавив их руками).

kokoc88

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

kokoc88

затролишь
А вот как я бы затроллил:
Это как в алгебре. Пусть есть уравнение:
a+b+c=0
если мы переносим b вправо, напишем знак минус и уберем ноль
a+c=-b,
то получим тождественное уравнение.
Пусть есть уравнение "a+b+c=0", которое надо привести к "a+c=-b". Сначала поделим "c" на ноль "a+b+c/0=0", при этом выражение теряет смысл, но это не важно, потому что мы вынесем "b" направо "a+c/0=0-b". Теперь прибавим корень из -1 к правой части выражения: "a+c\0=0-b+√-1", после чего умножаем часть "c/0" на ноль, что даёт обратно "c": "a+c=0-b+√-1". Выражение до сих пор не очень осмысленно, но мы сейчас прибавим корень из -1 и к левой его части, при этом ноль справа уйдёт: "a+c+√-1=-b+√-1". Теперь надо просто вычесть корень из -1 из обеих его частей, вуаля: "a+c=-b".

6yrop

Пока что ответ не принимается: нет ни обоснования каждого шага, ни кода после каждого шага, в документе куча детских ошибок.
Итак, решение задачи есть в документе. Осталось только проблема твоего понимания этого документа. Как обычно это бывает, время затрачиваемое на понимание того или иного вопроса сильно зависит от того настроен ли человек на то, чтобы понять или настроен против; разница времен бывает на порядки. Если настрой “против”, то это займет месяца, я такого себе позволить не могу. Пока у тебя проявляется настрой “против”, но я напишу еще несколько постов в надежде, что ты поменяешь свой настрой.
Проиграй по шагам то, что написано в документе, там, где не очень ясно написано, пости сюда, я опишу аккуратнее/подробнее.
Как и в прошлый раз, я предполагаю, что разговариваю с достаточно опытным C# программистом и пользователем ReSharper-а. Если, как в прошлый раз, есть какие-то пробелы в твоих знаниях (в прошлый раз ты не знал как билдиться ASP.NET сайт то прошу постараться как можно раньше сообщить об этом (в этом состоит одно из проявлений положительного настроя на понимание).

6yrop

Пусть есть уравнение "a+b+c=0", которое надо привести к "a+c=-b". Сначала поделим "c" на ноль "a+b+c/0=0", при этом выражение теряет смысл, но это не важно, потому что мы вынесем "b" направо "a+c/0=0-b". Теперь прибавим корень из -1 к правой части выражения: "a+c\0=0-b+√-1", после чего умножаем часть "c/0" на ноль, что даёт обратно "c": "a+c=0-b+√-1". Выражение до сих пор не очень осмысленно, но мы сейчас прибавим корень из -1 и к левой его части, при этом ноль справа уйдёт: "a+c+√-1=-b+√-1". Теперь надо просто вычесть корень из -1 из обеих его частей, вуаля: "a+c=-b".
Я так понимаю это аналогия твоего текущего понимания моего документа? Если, да, то ты в корне ошибаешься, в документе, как раз, только нужная инфа в краткой форме.

kokoc88

Итак, решение задачи есть в документе.
Решения задачи нет в документе, потому что в нём нету последовательности "маленьких тождественных шагов", перепутаны или отсутствуют важные события, нету кода для демонстрации происходящего. Я предлагаю тебе либо закончить этот балаган, либо написать документ по-нормальному. Во втором случае ты просто подтвердишь мою правоту: время или ошибки.
Проиграй по шагам то, что написано в документе, там, где не очень ясно написано, пости сюда, я опишу аккуратнее/подробнее.
Сначала напиши шаги так, чтобы после каждого шага код компилировался и работал правильно. Иначе теряется твой единственный на данный момент аргумент: "маленькие тождественные шажки".
Если, как в прошлый раз, есть какие-то пробелы в твоих знаниях
Ты уже в который раз наезжаешь на мои знания, причём настолько забавно, как будто ребёнок пытается что-то себе доказать. Я бы рекомендовал тебе сначала заняться своими, например ты мог бы научиться писать документы; или почитать книжки и от кодерства перейти к разработке.

6yrop

Сначала напиши шаги так, чтобы после каждого шага код компилировался и работал правильно. Иначе теряется твой единственный на данный момент аргумент: "маленькие тождественные шажки".
Такого утверждения я не делал. Разбитие на шажки, как ты видишь, иерархическое. На этапе мелких шажков код может не компилироваться. Как пример: в нашем примере допускается, что код компилируется и работает только на рутовых шагах 1, 2, 3. Разбитие на мелкие шажки нужно лишь для того, чтобы человек не сделал ошибки. Не сделать ошибки человеку помогает компилятор/решарпер. Когда нам встречается достаточно крупный шажок, который мы не можем сделать без ошибок, мы ищем/придумываем/знаем с опытом, какой маленький шажок нам надо сделать, чтобы решарпер с помощью подсветок ошибок/предупреждений проконтролировал нас на протяжении всего крупного шажка. Кроме компилятора еще помогают туду. И, возможно, еще какие-то фичи среды разработки. Короче, если проиграешь пример, то будут более конкретные вопросы. Это тот случай, когда надо идти от частности, чтобы понять общую суть (о способы понимания обсуждались, кажется, в разделе Society).

6yrop

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

6yrop

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

6yrop

что из этой гипотезы нет досадных исключений.
если есть, то нас спасает Undo или TFS :)

kokoc88

Такого утверждения я не делал.
Какого утверждения ты не делал? Сначала ты просто писал про "маленькие шажки" и всё. Потом был наезд от -а на их тождественность, и ты под этим наездом подписался. Если ты делаешь просто какие-то "маленькие шажки", то я в пятый раз повторяю: почему бы тебе сразу не сказать, что ты пишешь код вообще без ошибок.
Разбитие на мелкие шажки нужно лишь для того, чтобы человек не сделал ошибки. Не сделать ошибки человеку помогает компилятор/решарпер.

Ты утверждал, что можешь делать эти "мелкие шажки" без ошибок два дня подряд, вообще без тестирования. Пока что это необоснованный выпендрёж.
Как пример: в нашем примере допускается, что код компилируется и работает только на рутовых шагах 1, 2, 3.
А вот в книжках этого не допускается: там почему-то код компилируется, работает и проходит тесты на каждом маленьком шаге.
На этапе мелких шажков код может не компилироваться.
Тогда ты не можешь обосновать свою позицию. Из чего следует, что ты либо вообще не можешь два дня делать рефакторинг без какого-то вида тестирования; либо делаешь при этом кучу ошибок, которые потом приходится разгребать.

Dasar

вот последовательность минимальных шажков, после каждого пункта код успешно компилируется и решает задачу не хуже исходного кода
учим тип Node быть полиморфным
добавляем интерфейс INode с функциями Calculate/Derivate
заменяем тип Node на INode в полях, конструкторах и функциях
методы Calculate и Derivative делаем виртуальными
поля в Node делаем protected
выносим Constant в отдельный класс
добавляем класс Constant отнаследованный от Node. конструктор делаем public Constant(double val) : base(val) { }
перекрываем Constant.Calculate копируя функционал из Node.Calculate
перекрываем Constant.Derivative копируя функционал из Node.Derivative
по коду заменяем создания константы через Node на создание через Constant
удаляем поддержку Constant из Node: добавляем protected constructor Node(NodeType переносим поле value в Constant,
удаляем конструктор из Node для Constant, удаляем ветку constant из Calculate/Derivative
аналогично добавлению Constant добавляем типы Add, Sub, Mul, VariableX
методы Node.Calculate, Node.Derivative и класс Node делаем абстрактными
классы Constant, VariableX напрямую наследуем от INode
убираем enum NodeType
класс Node переименовываем в BinaryNode
переносим функциональность из Node-ов в Visitor-ы
добавлем интерфейс INodeVisitor
добавляем метод INode.ProcessBy и реализуем его в наследниках
добавляем класс Calculate и реализуем его, копируя в него последовательно методы Calculate из наследников Node меняя сигнатуру,
и заменяя в теле вызовы Calculate на ProcessBy
добавляем класс Derivative, аналогично классу Calculate
прибиваем методы INode.Calculate/Derivative и их реализацию

Dasar

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

kokoc88

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

kokoc88

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

Конечно, можно. Не знаю, можно ли это показать по твоим шагам, потому что в них тяжело вникнуть. Но у Фаулера это всё детально расписывается в виде двух или трёх независимых паттернов. И при этом он всё равно рекомендует использовать тестирование.
так же все эти пункты можно выполнять без юнит-тестов, но один большой полный регрессионный тест, конечно, стоит прогонять для избежания опечаток
Ты вместо Шурика подтвердил истинность моего высказывания. Чтобы во время даже очень простого рефакторинга приблизить вероятность ошибки к той, которая получается при наличии тестирования, мы должны потратить очень много времени. При наличии автоматического тестирования многие маленькие пункты можно пропускать, продолжительное время содержать код в некомпилируемом состоянии, и так далее; фактически, при меньших временных затратах, получая достаточно низкую вероятность ошибиться.
В частности, я отрефакторил свой же пример за несколько минут: сначала написал юнит тесты, в этом примере очень легко сделать почти 100% покрытие; затем выполнил рефакторинг за два глобальных шага, при этом код не компилировался от начала и до конца.
Кстати, вы писали свои "маленькие шаги" уже после того, как я дал вам готовый результат (кстати, он вообще рабочий или нет? :grin: :grin: :grin: )

Dasar

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

kokoc88

тесты на реальных задачах обеспечивают покрытие в гомеопатических дозах
На реальных задачах автоматические тесты обеспечивают как минимум покрытие функциональности. Я ещё раз сошлюсь на Фаулера (кстати, на кого ссылаешься ты?). Он не отрицает, что при использовании юнит тестов есть deminishing returns и что нельзя сделать 100% покрытие, перечисляя каждый int 2^32 раза. Но тем не менее в книге утверждается, что если не обращать внимания на теоретиков, а заниматься практикой, то вполне можно достичь оптимального, качественного и разумного результата.
В этом случае, как постоянно практикующий разработчик, я могу лишь полностью согласиться с Фаулером. Автоматические тесты позволяют не только быстрее делать рефакторинг, но и быстрее реализовывать и расширять функциональность кода программы, избегать регрессии, улучшать понимание кода разработчиками.

Dmitriy82

Кстати, а каковы причины ставить в корень такой иерархии интерфейс, а не класс?

kokoc88

Кстати, а каковы причины ставить в корень такой иерархии интерфейс, а не класс?
А какой класс ты предлагаешь поставить в корень этой иерархии?

Dmitriy82

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

kokoc88

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

6yrop

кстати, он вообще рабочий или нет?
Мой код рабочий, если изначальный код с enum-ами был рабочий. Кстати, это, наверное, даже наиболее важное преимущество такого стиля работы с кодом: тебе не обязательно вникать, что код делает. Я не смотрел, как там что у тебя вычислялось и derivative-тилось. Обычно схема такая. Перед программистом ставится конкретная задача. Он смотрит некоторое время в код, если всё хорошо, то просто решает задачу. Если на текущий код задача ложиться криво, то требуется рефакторинг. Тот стиль рефакторинга, который я показал, позволяет свести к минимуму требуемый программисту объем сведений о том, что делает код. Код просто преобразовывается по формальным правилам. А вот если тесты нельзя поправить без специальных знаний о функциональности кода, то это порождает целый ряд вопросов: где эту информацию брать, если из документов, то кто-то должен поддерживать документы в актуальном состоянии; при чтении документов возникают вопросы, кто-то должен на них отвечать и т.д. и т.п. Да, понять, что требуется рефакторинг можно на неполных знаниях.
P.S. Мы же про апликухи типа опердени, а там, чем меньшим объемом инфы надо обмениваться между людьми, тем лучше.

kokoc88

Мой код рабочий, если изначальный код с enum-ами был рабочий.
Извини, но это уже неважно. Рабочий пример только у , а твой документ не состоит из тождественных шагов. Пока ты это не исправишь, я не вижу смысла обсуждать поставленную мною задачу: её решил , но не решил ты.
А вот если тесты нельзя поправить без специальных знаний о функциональности кода

Из этой фразы следует, что ты не разбираешься в том, что такое атоматическое тестирование. Из чего следует, что ты не сделал ни одного проекта, хотя бы на 10% покрытого автотестами. Из этого, а так же из того, что ты не признаёшь авторитетов следует, что вести с тобой конструктивный спор вообще бессмысленно. Как и в споре про Java, ты пытаешься рассуждать о вещах, которые тебе не известны и не опробованы тобою на практике.

ava3443

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

6yrop

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

6yrop

Из чего следует, что ты не сделал ни одного проекта, хотя бы на 10% покрытого автотестами.
В 2005-2006 годах я был таким же фанатам unit-тестов как ты сейчас. Со временем пришло более здравое понимание.

Bibi

опердень у людей в головах!

kokoc88

В 2005-2006 годах я был таким же фанатам unit-тестов как ты сейчас.
Тогда странно, что ты при этом не прочитал ни одной книжки. Странно, что ты не смог в этой теме предложить ни одного аргументированного минуса автоматического тестирования, хотя их достаточно много. Странно, что ты всё время аргументировал тем, какой ты крутой, включая и этот последний пост.
Со временем пришло более здравое понимание.
Видимо, к тебе пришло понимание, что ты делаешь что-то не так, но ты сам, упиваясь своей крутизной, не смог в этом разобраться. Поэтому и прекратил развиваться в этом направлении.

6yrop

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

kokoc88

Но вот в его известной книге про Архитектуру корпоративных приложений, есть такой наивняк в коде (могу указать, если надо что практикующим программистом его уж никак не назовешь.
У него много чего есть, и минорные ошибки в статьях, и сомнительные паттерны типа "Split Loop". Но кроме Фаулера тесты описаны и у других авторов, которых я упоминал. Фаулер был приведён в качестве аргументов про полезность тестирования для рефакторинга. К сожалению, его, а не твоя, книга считается каноническим примером по рефакторингу, а поэтому более чем глупо с твоей стороны наезжать на его работу. Ты ещё Пушкина стихи поучил бы писать. (Это гипербола, если вдруг чувство юмора кого-то опять подведёт.)

6yrop

Тогда странно, что ты при этом не прочитал ни одной книжки.
мне вроде хватило Интернета (библиографический список не виду :)).
Странно, что ты не смог в этой теме предложить ни одного аргументированного минуса автоматического тестирования, хотя их достаточно много.

а у меня хотя бы намеренье такое было? (Кстати, посмотри первый пост первоначального треда.)
Странно, что ты всё время аргументировал тем, какой ты крутой, включая и этот последний пост.

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

Вряд ли, поскольку в других многих направления я замечаю свое развитие со временем, вряд ли это является каким-либо исключением.

kokoc88

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

да, чё там это ж всё очевидно практикующему девелоперу

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

В 2005-2006 годах я был таким же фанатам unit-тестов как ты сейчас. Со временем пришло более здравое понимание.

Вряд ли, поскольку в других многих направления я замечаю свое развитие со временем, вряд ли это является каким-либо исключением.

Видимо, является, и не только это.

6yrop

В первом посте ты сказал, что по твоему опыту невозможно писать тесты для каких-то приложений, и что их стоимость существенно увеличивается. Кроме ссылок на самого себя ты никак больше не аргументировал это заявление.
да, где мое намеренье на развертывание этой аргументации?
Так вот, в статически типизированных языках широкий класс изменений можно вносить
так, что вероятность появления багов крайне мала.
да, чё там это ж всё очевидно практикующему девелоперу
Я могу рефакторить два дня и не внести ни одной баги. Без автотестов.
точнее даже вообще ни разу не побывав в рантайме.
В 2005-2006 годах я был таким же фанатам unit-тестов как ты сейчас. Со временем пришло более здравое понимание.

кх, это заявления гения?
Видимо, является, и не только это.

Мне бросить заниматься программированием?

6yrop

Мне бросить заниматься программированием?
хотя ведь это ты же не понял мою последовательность шажков. А она же работает. Я ее два раза проделывал, первый раз и когда описывал.

kokoc88

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

Твою последовательность шажков я понял, и написал про неё развёрнутый ответ: поставленную задачу ты не решил.

6yrop

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

kokoc88

Я прокомментировал твой ответ: ты не понял мое решение. Я бы даже сказал, что не пытался понять. Оно работает.
Речь не идёт о том, работает что-то или нет. Речь идёт о том, что твоё решение не является доказательством того, что ты вообще умеешь делать тождественные шаги при рефакторинге. Шаги, приводящие к некомпилируемому результату не являются тождественными. Глобальные шаги не являются маленькими.

6yrop

Речь идёт о том, что твоё решение не является доказательством того, что ты вообще умеешь делать тождественные шаги при рефакторинге. Шаги, приводящие к некомпилируемому результату не являются тождественными. Глобальные шаги не являются маленькими.
Мой ответ на это уже был:
Я уже много кратно про себя отмечаю, что ты повторяешь свои вопросы опять и опять (некоторые по-моему более 5 раз).

kokoc88

Я уже много кратно про себя отмечаю, что ты повторяешь свои вопросы опять и опять (некоторые по-моему более 5 раз).
Я повторяю их только тогда, когда кто-то не может меня понять с первого раза. Знаешь, как детей учат газговаривать. Давай ещё раз: твои шаги не являются тождественными. Глобальные шаги не являются маленькими. Из этого следует, что ты вообще не умеешь делать рефакторинг без тестирования и без рантайма.

6yrop

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

kokoc88

у взрослого дяди плохо с элементарной логикой
Слив засчитан.

6yrop

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

6yrop

Слив засчитан.
твой, да :)

kokoc88

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

6yrop

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

kokoc88

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

6yrop

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

kokoc88

у меня больше настолько, что различие уже качественное.
Да что ты говоришь, а в дверях он у тебя не застревает? За облака не задевает?

Dmitriy82


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

kokoc88

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

Dmitriy82

Попробовал заменить. Получилось
abstract class Node {
abstract public T ProcessBy<T>(INodeVisitor<T> visitor);
}

вместо
interface INode {             
T ProcessBy<T>(INodeVisitor<T> visitor);
}

Также добавились модификаторы override, а абстрактный метод ProcessBy в BinaryNode стал не нужен.
Что ты имел в виду, я не понял.

kokoc88

Что ты имел в виду, я не понял.
Прочитай своё предположение, после чего посмотри на ключевое слово "abstract" перед методом ProcessBy.
Оставить комментарий
Имя или ник:
Комментарий: