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

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

понедельник, 17 января 2022 г.

Инженерные практики могут ускорить тебя в 100х раз

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

Почему я коллекционирую различные ускорялки? Математика проста. Пусть это конкретное решение мне даст +15% в производительности. Какое-то другое решение даст +15%. Еще где-то что-то прочитаю и попробую, снова +15%. Складываем вместе 10 подобных решений и получаем 1.15 ^ 10 = 4.04. То есть 10 подходов ускоряющих тебя всего лишь на 15% в сумме дают + 404%, а это 4х в скорости! Подходов за 10 летний опыт может собраться на порядок больше. И для 50 штук прирост будет уже 1083%, 10x. А и это 15% роста продуктивности это так, самый минимум из того, что новая практика тебе может дать. Часто введение нового подхода сама по себе в разы тебя ускоряет. 

Внимательний читатель заметит, что любой новый подход в разработке облагает автора налогом. Естественно надо закладывать время на суппорт. Часто разработчик принимает решение сам, буду ли заморачиваться с новым кодом и его суппортом или по старинке сделаю, и делает это на основе критерия. Если я скажу, что новый подход даст тебе прирост в производительности в 10 раз, а при этом потребует от тебя всего лишь по 5 минут в день больше времени - дурак не согласится. Но обычно все не так. Времени на реализацию решения надо потратить сегодня 2-3 часа, а потом еще на суппорт решения раз в неделю по 0.5 часа. А прирост будет 10-20%, что уже не так леко прочувствовать, как 2,5 часа. На одной чаше весов - вклад в часах единоразовый с небольшим вниманием, а на другой % прироста производительности. % складываются иначе, чем часы. Копить % выгоднее, чем экономить время. Не всегда, но часто.

Правда, далеко не все дивиденды от инженерных полдходов можно "складывать" как описано выше. Есть независящие инструменты. Ну например я кодирую в Idea (она удобнее), а на сервере у меня все в docker (легче чем инсталить все на host машину). Там на X% ускорился, тут на Y%. Но так как работаю я ИЛИ в идее ИЛИ с докером на сервере, то % берется не от 168 рабочих часах в месяце, а от всего времени в IDE и отдельно всего времени во время обслуживания сервера. Результат складываем и "X% + Y%" дает ((1+X/100)*develop_time + (1+Y/100)*deploy_time). И в этом случае время проинвестированное в разработку и поддержание инструмента стоит оценивать критичнее. Но есть и фундаментальные подходы, скажем как следование принципам BabyStepsRefactoring, CleanCode, UnitTesting, TestFirst. Выгода от использования этих принципов ощущается одновременно и влияет друг на друга, потому формула суммирования будет более вкусной. Возможно не (1+X/100)*(1+Y/100) - это крайность. Но чем более влияния подходов друг на друга, чем чаще они используются, тем ближе мы к пермножению процентов, а не суммированию.

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

Debug - самая сложная и неуправляемая, а потому и дорогостоящая часть в разработке. Во время Debug ты мало что понимаешь - только тыцаешь 2-3 клавиши и смотришь в стек в ожидании инсайта. А без юнит тестов рефакторинг опасен, но если ты и осмелишься - много багов уйдет на прод. А без рефакторинга сложно и за CleanCode следить. Так вскоре техдолг не даст менять сисему как того нужно бизнесу. В какой-то момент исправление 1 баги будет порождать 2 новых. Тогда лучшее, что можно сделать - заморозить его. Ну или покрыть модуля автогенерируемыми юнит тестами, затем взяться за рефакторинг модулей с целью навести порядок в модели и сделать код чуть более clean. Затем выкинуть старые автогенерируемые тесты и написать нормальные, читабельные. Этот долг придется выплатить, если хочется продлить жизнь проекту. И лучше тут пользовать подходом ПрицнипСкаута. Сделать место стоянки после себя чище, чем оно было до тебя. Каждый день плати чуть-чуть времени уделяя этим инструментам: CleanCode + Refactoring + UnitTesting. И проект проживет дольше. 

Это один из примеров связки практик. Есть и другие практики. И тот из нас, кто научится использовать их по максимуму будет производительнее чем тот, кто не будет в 100 раз, в 1000 раз, а может и в 10000 раз. Возьмем студента новичка без опыта разработки - сложная задача может просто его застопорить на недели (если вообще задача будет решена), тогда как опытный миддл сделает ее за два дня. Вот и решай на сколько более производительный тот, кто сделал работу в сравнении с тем, кто не сделал ее. Я же верю в то, что хоть в среднем по индустрии Middle не сильно отличается от Senior (так просто устоялось), их производительность может отличаться в весятки, а то и сотню раз. Вопрос в том, остановился ли Senior в развитии на уровне инструментария Middle и дальше качает только SoftSkills или продолжает поиск инструментов его ускоряющих.

вторник, 6 января 2015 г.

Что лучше "найти все баги" или "найти как можно больше багов"

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

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

Вот стенограмма (конечно же с позволения собеседника):

Один Хороший Программист: Получается лажа(
СанЁк Баглай: так всегда
СанЁк Баглай: потом найдешь как сделать то же другим способом, потом еще одним
СанЁк Баглай: когда будешь уметь сделать это 3мя разными, условно - ты миддл, когда 10ю - сеньйор
СанЁк Баглай: просто экспериментируй
СанЁк Баглай: код это любит
СанЁк Баглай: кстати да, этот вопрос до тебя уже решали
СанЁк Баглай: 1000 других ребят, сидели так же и мучились точно над такой же ситуацией
СанЁк Баглай: может быть кто-то уже описал решение в своем блоге?
СанЁк Баглай: может быть разрабочики фреймворка как-то это продумали в свой версии 1.7 ?
СанЁк Баглай: а че имено не нравится-то? в чем "лажа"?
СанЁк Баглай: код работает, первый этап "make it work" позади
СанЁк Баглай: теперь "make it better"
СанЁк Баглай: сделать код красивше
СанЁк Баглай: так ведь?
Один Хороший Программист: да
Один Хороший Программист: У тебя код работает без изьянов?)
СанЁк Баглай: ты что?! :)
СанЁк Баглай: бажит по черной
СанЁк Баглай: я как ТДД стал использовать потерял то вот чувство, что код может быть безбажный
СанЁк Баглай: никогда_&_ниукого
СанЁк Баглай: всегда есть баги
СанЁк Баглай: недавно на GoQA Спрашивали один филосовский вопрос
СанЁк Баглай: что лучше "найти все баги" или "найти как можно больше багов" ?
СанЁк Баглай: а?
Один Хороший Программист: Найти все баги)
СанЁк Баглай: обоснуй
Один Хороший Программист: Любой баг может привести к непредсказуемым последствиям, так что нужно искать все баги, что бы снизить риск. Ну и если браться за дело так на уж на все 100%
СанЁк Баглай: а как ты будешь знать что 100% наступили?
СанЁк Баглай: вот уже 100% или еще одну багу поймаю и будет 100%? или через час будет 100% ?
СанЁк Баглай: кто скажет "чувак! все 100% можно идти домой! хух!" а?
СанЁк Баглай: а если по дороге домой придумаешь еще один кейс, где точно должна быть бага, или под утро прийдет в голову идея в душе?
СанЁк Баглай: 100% превратятся во сколько, в 99% или 80% ?
Один Хороший Программист: Тогда баг - это не совсем ошибка, а то как можно было бы исправить систему в лучшую сторону?!Получается все баги не отыскать
СанЁк Баглай: все не отыскать
СанЁк Баглай: но к этому стоит стремиться
СанЁк Баглай: стремиться легче, когда ты знешь что еще валом работы
СанЁк Баглай: и тебе предстоит еще найти "ка кможно больше багов"
СанЁк Баглай: а не когда ты решил себе, что все, я сегодня 100% с мыслю "я нашел все баги"
СанЁк Баглай: так же и с кодом, вернее его эстетичностью
СанЁк Баглай: нет предела совершенству
СанЁк Баглай: только ты решаешь когда закончить его улучшать
СанЁк Баглай: когда у тебя 100%
СанЁк Баглай: но тут не так как с багами
СанЁк Баглай: баги - критично
СанЁк Баглай: красивый код - не так сильно
СанЁк Баглай: да его будут потом фукать, и скорее всего в любом случае
СанЁк Баглай: скорее всего длительный проект (длящийся годами) будет легаси и ничего ты с этим не поделаешь
СанЁк Баглай: но что ты можешь сделать - так это стать сеньйором быстрее
СанЁк Баглай: потому что будешь знать как решать то же 10ю разными способами
СанЁк Баглай: а все потому что ты немного поигрался с кодом в игру - сделай его няшнее
СанЁк Баглай: сегодня чуть, завтра чуть, после завтра чуть
СанЁк Баглай: он уже работает, а ты хочешь сделать его красивше
СанЁк Баглай: зная, что идеал недостижим
СанЁк Баглай: но хоть чуточку
СанЁк Баглай: и не слишком долго :) чтобы сроки не профакапить
СанЁк Баглай: по другим таскам
СанЁк Баглай: потому ищи варианты
СанЁк Баглай: не имею права подсказывать
СанЁк Баглай: да и не знаю я если честно сразу ответ - надо включать моцк и думать самому как сделать лучше
СанЁк Баглай: есть инструмент который может помочь
СанЁк Баглай: метафора системы называется
СанЁк Баглай: загугли определение
СанЁк Баглай: из XP ростут ноги
СанЁк Баглай: суть в том, что для любой системы ты находишь аналог в реальном мире
СанЁк Баглай: такое возможно, потому как ООП моделирует реальный мир, и ты там ничего такого не придумаешь, чего бы небыло уже в реальном мире
СанЁк Баглай: просто надо найти, что вот этих пару классов и методов напоминают из реального мира
СанЁк Баглай: может таксист и диспетчер со службой такси
СанЁк Баглай: может дворник с метлой мусором и жеком
СанЁк Баглай: может карбюратор под капотом машины
СанЁк Баглай: может спутник, что вокруг орбиты летает и ретрнслирует инфу
СанЁк Баглай: ХЗ, чо бы ни придумал
СанЁк Баглай: это потом поможет понять как должна развиваться система
СанЁк Баглай: это легче объяснить напарнику, чем вот смотри у меня тут хелпер, а тут баттон, а тут мессаджи в кью...
СанЁк Баглай: и самое главное это включает моцк
СанЁк Баглай: мы мыслим образами, а не кодом
СанЁк Баглай: и даже не словами
СанЁк Баглай: а яркими образами, кодирующими наш опыт
СанЁк Баглай: так что метафоры самое оно
Один Хороший Программист: Ок, спасибо.
СанЁк Баглай: так тебе спасибо
СанЁк Баглай: какой хороший пост в блоге получится :)
СанЁк Баглай: занесло меня чуть, это фрирайтинг, не я
Один Хороший Программист: Кинь потом ссылку на пост)

Как-то так

А когда ты останавливаешься?
Как долго надо полировать свой код?
И еще, нравится ли тебе такой формат постов?

ПриЁм

пятница, 11 апреля 2014 г.

Вебинар по рефакторингу

Ребята привет.

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

Сам по себе рефакторинг опасен. А потому без армии маленьких помощников тут никак. Имя им - тесты. Unit-тесты. Их тоже можно написать как-нибудь. Ну а можно задуматься о б их рефакторинге. "Тесты как документация" - возможно ты слышал раньше это громкое заявление. Рефакторинг в тестах в основном служит этой цели. Как этого добиться? Рассмотрим. 

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

Как это будет происходить?
- start: code review -> WTF -> рефакторинг -> goto start
   (тут буду много рассказывать {и рисовать} почему так, а не иначе, параллельно с кодингом)
- ближе к концу секция вопросов/ответов
   (тут ты сможешь задать вопрос и получить на него ответ)
- и в конце ссылки на полезное чтиво
   (а так же запись вебинара для личного использования)

Язык программирования - Java

Среда разработки - Intellij IDEA

Стоимость вебинара - 70 грн

Продолжительность вебинара - 1,5-2 часа.

От тебя на это время потребуются - компьютер с интернетом, наушники и (возможно) попкорн.

Дата проведения уточняется - в ближайшие неделю-две

Форма предварительной регистрации - >>> вот тут <<< (зарегистрируйся, чтобы быть в курсе) Поторопись! 

Деньги, собранные от участников вебинара пойдут на покупку планшета Samsung Galaxy Note Pro 12.2 мне на день рождения. А нужен он мне для того, чтобы чуть чаще заниматься мультипликацией в блоге. Не так давно я держал подобный планшет в руках и весь наэлектризовался от возможностей, которые он мне открывает.

Ну а 10% от прибыли - уйдет на благотоврительность. 

Как-то так

воскресенье, 9 марта 2014 г.

Рефакторинг, это как уборка дома

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

И в процессе уборки сегодня я понял две штуки. 


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


Вторая мысль (интересно они приходят, вот ты занят чем-то, руками делаешь что-то и оп! появилась мысля, думаешь ее думаешь и оттираешь какую-то какашню, что прилипла где-то куда давно живые организмы не заглядывали...) уже про рефакторинг. Я люблю рефакторить код. Теперь я знаю почему. Потому что Мама с детства привила мне прививку. Сколько себя помню, постоянно если уборка в квартире происходила - она меня в труднодоступные места запускала и говорила что там как делать, по доброму так. Главный критерий помню - тряпку ополоснул, руку засунул далеко далеко, протер там, вытащил - грязно, goto start, пока тряпка не будет чистой. И ведь в самые трднодоступные места заглядывали. Квест прям был - найди пылюку. Малый был - на ус все наматывал. Вот и осталось. А сейчас, как подрос - есть много мест, где прибраться можно - на рабочем столе, у себя в квартире, в шкафу, на балконе (выкинуть нафиг уже этот холодильник), во дворе, в коде. А в коде - это уже рефакторинг. 


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


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


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

 
Ну как-то так :) Навеяло...

вторник, 22 января 2013 г.

Сегодня суперкод - завтра говнокод

Сегодня знакомились с новой группой Java тренинга в КПИ. На этот раз был приглашен в качестве гостя, чем бесконечно рад. Но не об этом сейчас. А о том, что случилось на этой встрече. Цель собрания была познакомиться, сделать коллективно code review, после чего ребята самостоятельно могли бы общаться и решать поставленные задачи. В ходе code review мы зацепили тему Don't Repeat Yourself, а так же магических констант. Не сдержался - показал свой код 10 летней давности. Код все того же фрактального проводника, который писал во времена студенческие. Писал я его на Delphi7. В общем, я его просто тут оставлю и все станет понятно...
type
  TPal = array [0..511] of TColor;
  TSavePalRec = array [1..50] of record
    Color:TColor;
    X:Integer;
  end;
//---------------------------------------------------------------------------------------------
var
    PalProp:record
        mT, mW, mH, mW05:integer;
    end;
    ClkX, ClkY:integer;
    bMovePan, bDouble:boolean;
    SavePalRec:TSavePalRec;
    pan:array [1..100] of record
        x:integer;
        col:TColor;
    end;
    CountPan:integer;
    bDown:boolean;
    bIncDec:boolean;
    RedPos, GreenPos, BluePos:integer;
    RedCount, GreenCount, BlueCount:integer;
    ColArrR, ColArrG, ColArrB:array [0..1024] of byte;
    bMoveR, bMoveG, bMoveB:boolean;
    bmp:TBitMap;
//--------------------------------------------------------------------------------------------- 
 const NumbColor=19;
      ColorArray:array [0..NumbColor - 1] of TColor=($FFFFFF, $00FFFF, $FF00FF, $FFFF00, $0000FF, $FF0000, $00FF00, $C0FFFF, $FFC0FF, $FFFFC0, $C0C0FF, $FFC0C0, $C0FFC0, $C000FF, $00C0FF, $00FFC0, $C0FF00, $FFC000, $FF00C0);
//---------------------------------------------------------------------------------------------
function TForm2.CreateAndSortPanel(X:integer; bRepaint:boolean; cl:TColor):integer;
var i, j, tle, le:integer;
    col, tcol:TColor;
begin
    for i:=1 to CountPan-1 do // определяем после которого маркера будет создаваемый
        if (pan[i].x < X) and (X < pan[i+1].x) then break;

    if i = CountPan then Exit; // если после последнего то выходим

    Result:=i;

    if (pan[i+1].x - pan[i].x) < PalProp.mW+2 then Exit;  // если маркер некуда втиснуть между двумя ближайшими то выходим
    if ((pan[i].x + PalProp.mW+1) > X) or ((pan[i+1].x - PalProp.mW-1) < X) then Exit; // очень близко ставить маркер возле соседнего нельзя

    le:=pan[i+1].x;
    col:=pan[i+1].Col;
    pan[i+1].x:=X;
    pan[i+1].Col:=cl;

    for j:=i+2 to CountPan do begin
        tle:=pan[j].x;
        tcol:=pan[j].Col;
        pan[j].x:=le;
        pan[j].Col:=col;
        le:=tle;
        col:=tcol;
    end;

    CreatePanel(pb.Width);

    CurrentPan:=i+1;

    pan[CountPan].Col:=col;
    if bRepaint then begin
        RepaintPan(CurrentPan);
        RepaintPalitra(i, i+2);
    end;
end;
//---------------------------------------------------------------------------------------------
procedure TForm2.RepaintPan(Num: Integer);
var x1, x2, y1, y2:integer;
begin
    if Num = 1
        then x1:=0
        else x1:=pan[Num-1].x + PalProp.mW05+1;
    if Num = CountPan
        then x2:=pb.Width
        else x2:=pan[Num+1].x - PalProp.mW05-1;
    y1:=PalProp.mT + 1;
    y2:=PalProp.mT + PalProp.mH + 1;

    bmp.Canvas.Pen.Color:=clBtnFace;
    bmp.Canvas.Brush.Color:=clBtnFace;
    bmp.Canvas.Rectangle(x1, y1, x2, y2);

//    if Num = CurrentPan
//        then bmp.Canvas.Pen.Color:=clRed
//        else bmp.Canvas.Pen.Color:=clBlack;
    bmp.Canvas.Pen.Color:=clBlack;
    bmp.Canvas.Brush.Color:=pan[Num].Col;
    bmp.Canvas.Rectangle(pan[Num].x - PalProp.mW05, y1, pan[Num].x + PalProp.mW05, y2);

    x2:=x2 - x1;
    y2:=PalProp.mH+2;
    BitBlt(pb.Canvas.Handle, x1, y1, x2, y2, bmp.Canvas.Handle, x1, y1, SRCCOPY);
end;
//---------------------------------------------------------------------------------------------
procedure TForm2.RepaintAllPan;
var i:integer;
begin
    bmp.Canvas.Pen.Color:=clBtnFace;
    bmp.Canvas.Brush.Color:=clBtnFace;
    bmp.Canvas.Rectangle(0,
                         PalProp.mT + 1,
                         pb.Width,
                         PalProp.mT + PalProp.mW + 1);
    for i:=1 to CountPan do begin
//        if i = CurrentPan
//            then bmp.Canvas.Pen.Color:=clRed
//            else bmp.Canvas.Pen.Color:=clBlack;
        bmp.Canvas.Pen.Color:=clBlack;
        bmp.Canvas.Brush.Color:=pan[i].Col;
        bmp.Canvas.Rectangle(pan[i].x - PalProp.mW05,
                             PalProp.mT + 1,
                             pan[i].x + PalProp.mW05,
                             PalProp.mT + PalProp.mW + 1);
    end;
    BitBlt(pb.Canvas.Handle,
           0,PalProp.mT + 1,
           pb.Width, PalProp.mH,
           bmp.Canvas.Handle,
           0, PalProp.mT + 1, SRCCOPY);
end;
//----------------------------------------------------------------------------------------------------------------------------------------------------------
procedure TForm2.RepaintPalitra(Num1, Num2:integer); // перерисовка
var i, j, a, b:integer;
begin
    if Num1 < 1 then Num1:=1;  // проверка выхода за пределы (они есть в Panels_onDblClick)
    if Num2 > CountPan then Num2:=CountPan;

    b:=pan[Num1].x;
    for i:=Num1 to Num2-1 do begin
        a:=pan[i+1].x - pan[i].x;
        for j:=0 to a do begin
            bmp.Canvas.Pen.Color:=ColorChange(pan[i].Col, pan[i+1].Col, a, j);
            bmp.Canvas.MoveTo(b+j, 0);
            bmp.Canvas.LineTo(b+j, PalProp.mT);
        end;
        b:=b+a;
    end;
    BitBlt(pb.Canvas.Handle,
           pan[Num1].x, 0,
           pan[Num2].x - pan[Num1].x, PalProp.mT,
           bmp.Canvas.Handle,
           pan[Num1].x, 0, SRCCOPY);
    if not Form1.bFirstLoad then SaveChangeToMainForm;
end;
//---------------------------------------------------------------------------------------------
procedure TForm2.SaveChangeToMainForm;
var i:integer;
begin
    for i:=0 to 511 do
        Form1.pal[i]:=bmp.Canvas.Pixels[i, 1]; // заганяем новую палитру
    Form1.DrawFromArray(Form1.FractArr, Rect(0, 0, Form1.pb.Width - 1, Form1.pb.Height - 1), Form1.Bmp);
    Form1.pbPaint(self);
end;
//---------------------------------------------------------------------------------------------
procedure TForm2.RandomPalitra;
var i, j, k:integer;
    r:real;
begin
    if cb1.Checked
        then begin
            Randomize;
            CountPan:=2;
            pan[1].x:=0;
            pan[1].col:=RGB(Random(256), Random(256), Random(256));
            pan[2].x:=pb.Width;
            pan[2].col:=pan[1].col;

            k:=Random(10) + 3;
            r:=pb.Width / (k + 1);
            CreateAndSortPanel(9, false);
            for i:=1 to k do begin
                j:=Round(i*r);
                if Random(2) = 1 then CreateAndSortPanel(j - PalProp.mW - 1, false);
                CreateAndSortPanel(j, false, ColorArray[Random(NumbColor - 1)]);
                if Random(2) = 1 then CreateAndSortPanel(j + PalProp.mW + 1, false);
            end;
            CreateAndSortPanel(pb.Width - PalProp.mW - 1, false);
            RepaintAllPan;
            RepaintPalitra(1, CountPan);
    end
    else begin
        if Form1.smnPerelyv.Checked then Form1.smnPerelyvClick(Self);
        RedCount:=Random(9)+1;
        GreenCount:=Random(9)+1;
        BlueCount:=Random(9)+1;
        RedPos:=Random(511)+1;
        GreenPos:=Random(511)+1;
        BluePos:=Random(511)+1;
        bMoveR:=Random(2)=1;
        bMoveG:=Random(2)=1;
        bMoveB:=Random(2)=1;
        ChangeParam;
    end;
end;
//---------------------------------------------------------------------------------------------
function ColorChange(col1, col2: TColor; R, i: real): TColor;
var cr, cg, cb:real;
    cr1, cg1, cb1:byte;
    cr2, cg2, cb2:byte;
    dcr, dcg, dcb:byte;
begin
    cr1:=GetRvalue(col1);   cr2:=GetRvalue(col2);
    cg1:=GetGvalue(col1);   cg2:=GetGvalue(col2);
    cb1:=GetBvalue(col1);   cb2:=GetBvalue(col2);
    dcr:=abs(cr1 - cr2);
    dcg:=abs(cg1 - cg2);
    dcb:=abs(cb1 - cb2);
    if cr1 <> cr2
        then begin
            if cr1 < cr2 then cr:=cr1 + dcr*i/R;
            if cr1 > cr2 then cr:=cr1 - dcr*i/R;
        end
        else cr:=cr1;
    if cg1 <> cg2
        then begin
            if cg1 < cg2 then cg:=cg1 + dcg*i/R;
            if cg1 > cg2 then cg:=cg1 - dcg*i/R;
        end
        else cg:=cg1;
    if cb1 <> cb2
        then begin
            if cb1 < cb2 then cb:=cb1 + dcb*i/R;
            if cb1 > cb2 then cb:=cb1 - dcb*i/R;
        end
        else cb:=cb1;
    Result:=RGB(Round(cr), Round(cg), Round(cb));
end;
Выложил я только 1/10 часть кода, которая отвечает за формирование рендомной палитры, по которой потом отрисуется фарктал в красивых, пестрых красках. Вчера он мне понадобился. Я хотел сделать то же но на Java. Я хотел приделать эту же палитру к своему недавнему минипроектику "Рисуем фракталы на Java".

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

Первым делом я написал некоторое подобие тестов и сделал механическое превращение (портирование) кода Pascal в Java. Работа не сложная, но требует внимания, ведь чем больше я ошибок тут сделаю, тем больше потом времени потрачу на отладку. 30 минут и код компилировался в Idea. Первая зеленая полоса! Я даже на радостях закоммитился, хотя понимал, что ничего оно не работает.

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

Еще пол часа внимательного рефакторинга, и постепенного разтуманивания прдназначения методов и переменных. Потом полировка и тюнинг под новые нужны. Тоже пол часа. Итого я получил вот это
public class RandomPalette implements Palette {

    class Marker {
        int x;
        int color;

        public Marker(int x, int color) {
            this.x = x;
            this.color = color;
        }
    }

    private static final int MW = 8; // ширина маркера
    private static final int[] colorArray = new int[]{
            0xFFFFFF, 0x00FFFF, 0xFF00FF, 0xFFFF00, 0x0000FF, 0xFF0000, 0x00FF00,
            0xC0FFFF, 0xFFC0FF, 0xFFFFC0, 0xC0C0FF, 0xFFC0C0, 0xC0FFC0, 0xC000FF,
            0x00C0FF, 0x00FFC0, 0xC0FF00, 0xFFC000, 0xFF00C0};

    private List<Marker> markers = new LinkedList<Marker>();
    private int[] palette;

    public RandomPalette(int size) {
        palette = new int[size];

        int color = getRandomColor();
        markers.add(new Marker(0, color));
        markers.add(new Marker(size, color));

        int count = random(size / (MW * 3)) + 3;
        double r = size / (count + 1);

        addBlackMarker(MW + 1);
        for (int i = 1; i <= count; i++) {
            int j = (int) (i * r);
            if (yesOrNo()) {
                addBlackMarker(j - MW - 1);
            }
            addMarker(j, getRandomColor());
            if (yesOrNo()) {
                addBlackMarker(j + MW + 1);
            }
        }
        addBlackMarker(size - MW - 1);

        calculatePalette();
    }

    private int getRandomColor() {
        return colorArray[random(colorArray.length)];
    }

    private boolean yesOrNo() {
        return random(2) == 1;
    }

    private void addBlackMarker(int x) {
        addMarker(x, 0);
    }

    private void calculatePalette() {
        int x = markers.get(0).x;
        for (int i = 0; i < markers.size() - 1; i++) {
            int length = markers.get(i + 1).x - markers.get(i).x;
            for (int dx = 0; dx < length; dx++) {
                palette[x + dx] = colorChange(markers.get(i).color, markers.get(i + 1).color, length, dx);
            }
            x = x + length;
        }
        palette[0] = 0;
    }

    private int colorChange(int from, int to, double len, double x) {
        double red = change(getR(from), getR(to), len, x);
        double green = change(getG(from), getG(to), len, x);
        double blue = change(getB(from), getB(to), len, x);

        return rgb((int) red, (int) green, (int) blue);
    }

    private double change(double from, double to, double len, double x) {
        if (from == to) {
            return from;
        }

        double delta = Math.abs(from - to) * x / len;

        if (from < to) {
            return from + delta;
        } else {
            return from - delta;
        }
    }

    private int getR(int col) {
        return (col & 0x0000FF);
    }

    private int getG(int col) {
        return (col & 0x00FF00) >>> 8;
    }

    private int getB(int col) {
        return (col & 0xFF0000) >>> 16;
    }

    private int rgb(int r, int g, int b) {
        return (r) | (g << 8) | (b << 16);
    }

    private void addMarker(int x, int color) {
        // определяем после которого маркера будет создаваемый
        int index;
        for (index = 0; index < markers.size(); index++) {
            if ((markers.get(index).x < x) && (x < markers.get(index + 1).x)) {
                break;
            }
        }

        // если после последнего то выходим
        if (index == markers.size()) {
            return;
        }

        // если маркер некуда втиснуть между двумя ближайшими то выходим
        if (markers.get(index + 1).x - markers.get(index).x < MW + 2) {
            return;
        }

        // очень близко ставить маркер возле соседнего нельзя
        if ((markers.get(index).x + MW + 1 > x) || (markers.get(index + 1).x - MW - 1 < x)) {
            return;
        }

        markers.add(index + 1, new Marker(x, color));
    }

    private int random(int n) {
        return new Random().nextInt(n);
    }

    @Override
    public int getColor(int r) {
        return palette[r];
    }

    @Override
    public int getSize() {
        return palette.length;
    }
}
И я более чем уверен, что глядя на этот код спустя некоторое время я буду считать его говнокодом. Если этого не случится – значит. Не даром, если заметил, я этот код назвал «это». «Это» только оно сейчас работает… Вообще считаю, стоит относиться к своему коду не как к произведению искусства, а как к продукту жизнедеятельности, тому что могло быть чуточку лучше, раз уж появилось на этот свет.

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

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

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

среда, 16 января 2013 г.

Refactoring: По чуть-чуть

Все говорят - делайте рефакторинг по чуть-чуть. Не накапливайте технический долг. Супер. Но я не видел, кроме как в книге Мартина Фаулера, демонстрации рефакторинга "по чуть-чуть".

Не так давно мы с другом Виталиком учились ТДД. Мы играли в tetris coding dojo, но в отличие от ребят игравших одновременно с нами мы делали акцент в сторону обучения. И вот в какой-то момент на руках у нас был код.

    String answer(String figure, int x, int y, String glass, String next) {
        if (glass.charAt(0) == ' ') {
            return "left=4, drop";
        }
        if (figure.equals("I") && glass.charAt(1) == ' ') {
            return "left=3, drop";
        }
        if (glass.charAt(2) == ' ') {
            return "left=2, drop";
        }
        if (figure.equals("I") && glass.charAt(3) == ' ') {
            return "left=1, drop";
        }
        if (glass.charAt(4) == ' ') {
            return "drop";
        }
        if (figure.equals("I") && glass.charAt(5) == ' ') {
            return "right=1, drop";
        }
        if (glass.charAt(6) == ' ') {
            return "right=2, drop";
        }
        if (figure.equals("I") && glass.charAt(7) == ' ') {
            return "right=3, drop";
        }
        if (glass.charAt(8) == ' ') {
            return "right=4, drop";
        }
        if (figure.equals("I") && glass.charAt(9) == ' ') {
            return "right=5, drop";
        }
        throw new UnsupportedOperationException();
    }

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

    String answer(String figure, int x, int y, String glass, String next) {
        boolean isO = figure.equals("O");

        int dx = 0;
        while (dx <= 10) {
            if (isFree(glass, dx)) {
                return getCommand(dx);
            }
            dx += (isO)?2:1;
        }
        throw new UnsupportedOperationException();
    }

    private String getCommand(int dx) {
        if (dx < 4) {
            return left(dx);
        } else if (dx == 4) {
            return drop();
        } else {
            return right(dx);
        }
    }

    private String left(int dx) {
        return "left=" + (4 - dx) + ", drop";
    }

    private boolean isFree(String glass, int dx) {
        return glass.charAt(dx) == ' ';
    }

    private String drop() {
        return "drop";
    }

    private String right(int dx) {
        return "right=" + (dx - 4) + ", drop";
    }
Да, тут осталось пару магических констант, но в контексте исходного класса, не сложно догадаться что это за 4-ки, 2-ка и 1-ка.

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

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


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

Надеюсь, пригодится. Виталик, спасибо тебе! Классно покодили в тот день!

четверг, 23 февраля 2012 г.

Я люблю рефакторинг

Я люблю рефакторинг и потому говорю о нем еще и еще. Я считаю, что истинное понимание чего либо начинается с простых правил, которые приводят из текущего состояние к предмету исследования. ООП и Рефакторинг именно так связаны. Рефакторинг Фаулера приводит к ООП коду.

Не так давно в блоге выложил mind map с моими мыслями о рефакторинге, а чуть позже нас с Сережей Зелениным пригласили выступить на 4ю встречу QA Kiev Club. Мы там рассказывали про JBehave и про рефаткоринг, основываясь на той самой mind map карте. Если лень делать презентацию - сделай mind map и выступи с ней.

На этом видео можно посмотреть как все получилось.



Полезное чтиво - от рефакторинга к пониманию:
1) Фаулер "Рефакторинг"
2) Фримены "Head First - Паттерны Проектирования"
3) Мессарош "Шаблоны тестирования xUnit. Рефакторинг кода тестов"

Успехов!

понедельник, 12 декабря 2011 г.

Java for fun: Кодревьюшки #1

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

Сегодня рабочее утро у меня началось с этих ревьюшек.



Это первая серия, дальше будет...

понедельник, 28 ноября 2011 г.

Refactoring: Еще немного про рефакторинг

Вот случайно нашел презенташку со своего техтолка "Чистый код". Планировалась серия таких презентаций. Это первая. Рассмотрел пару Фаулеровских запахов:
- дублирование
- код с комментариями
- большой метод
- завистливая функция



Продолжение следует...

воскресенье, 20 ноября 2011 г.

Рефакторинг: Что такое рефакторинг?


Хочу сделать презенташку по Рефакторингу. Вот сел и набросал в mindmap'чик все что было в голове по этому поводу. Надеюсь кому-то пригодится.

суббота, 11 июня 2011 г.

Рефакторинг: Замена рекурсии делегированием. Часть 2

В первой части было сформулировано задание - избавиться от рекурсии. Тут я приведу лишь результат рефакторинга, а в результате этом остался всего один класс. Его я назвал OOPTree и поместил в новый пакет. Описание того, как это было пошагово сделано - выложу позже.
package com.binarytree.oop;

import com.binarytree.Node;

public class OOPTree implements Node {
    
    private int value;
    private Node left;
    private Node right;

    public OOPTree(int value) {
        this.value = value;
        left = null;
        right = null;
    }
        
    @Override
    public int getMaxLeafDepth() {
        int depth = 0;
        
        if (left != null) {
            depth = left.getMaxLeafDepth();
        } 
        
        if (right != null) {
            depth = Math.max(depth, right.getMaxLeafDepth());
        } 
                    
        return 1 + depth;            
    }    
    
    @Override
    public void addValue(int newValue) {
        if (newValue < value) {
            if (left == null) {
                left = new OOPTree(newValue);
            } else {
                left.addValue(newValue);
            } 
        } else if (newValue > value) {
            if (right == null) {
                right = new OOPTree(newValue);
            } else {
                right.addValue(newValue);
            } 
        }
    }
    
    @Override
    public String toString() {
        return String.format("(%s, %s, %s)", value, left, right); 
    }    
}

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

понедельник, 23 мая 2011 г.

Рефакторинг: Замена рекурсии делегированием. Часть 1

Часто сталкиваюсь с проблемой, когда процедурный стиль программирования тянется в OOP код. Вот интерфейс бинарного дерева: добавили ноды, померяли глубину самого глубокого листика, напечатали на экране - все просто.



package com.binarytree;

public interface Node {

    int getMaxLeafDepth();

    void addValue(int value);

    String toString();

}

Вот пример кода класса, в котором используется рекурсия.

package com.binarytree.procedure;

import com.binarytree.Node;

public class ProcedureRecursionTree implements Node {

    private NodeElement root;

    public ProcedureRecursionTree(int rootValue) {
        root = new NodeElement(rootValue);
    }

    @Override
    public void addValue(int newValue) {
        addValue(root, newValue);
    }

    private void addValue(NodeElement node, int newValue) {
        if (newValue < node.value) {
            node.left = addTo(node.left, newValue);
        } else if (node.value < newValue) {
            node.right = addTo(node.right, newValue);
        }
    }

    private NodeElement addTo(NodeElement node, int newValue) {
        if (node != null) {
            addValue(node, newValue);
            return node;
        } else {
            return new NodeElement(newValue);
        }
    }

    @Override
    public int getMaxLeafDepth() {
        return getMaxLeafDepthFrom(0, root);
    }

    private int getMaxLeafDepthFrom(int depth, NodeElement node) {
        if (node == null) {
            return depth;
        }

        return 1 + Math.max(getMaxLeafDepthFrom(depth, node.left), 
                getMaxLeafDepthFrom(depth, node.right));
    }

    @Override
    public String toString() {
        return toString(root);
    }

    private String toStringSubnode(NodeElement node) {
        if (node != null) {
            return toString(node);
        }
        return null;
    }

    private String toString(NodeElement node) {
        return String.format("(%s, %s, %s)", 
            node.value, 
            toStringSubnode(node.left), 
            toStringSubnode(node.right));
    }

}
Это класс, в котором хранятся данные
package com.binarytree.procedure;

public class NodeElement {

    int value;
    int parentNodeValue;
    NodeElement left;
    NodeElement right;

    public NodeElement(int value) {
        this.value = value; 
    }

}
Ну и? Все нормально на первый взгляд. Методы маленькие, дублирования нет, все вполне читабельно. Да, но тут код пахнет другим - класс ProcedureRecursionTree инкапсулируя NodeElement root рекурсивно проходится по всем дочерним узлам/листьям root ноды. Это удобно - все ноды одного типа. Но это не по OOP. Правило простое.

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

Вот этой задачкой я и предлагаю заняться. Рефакторинг, как оказалось недавно, в более широком виде, Фаулеровский и называется "Преобразования процедурного проекта в объекты (Convert Procedural Design to Objects)", я же его раньше называл "замена рекурсии делегированием".

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

Вот кстати тест:
package com.binarytree;

import static org.junit.Assert.assertEquals;
import org.junit.Test;
import com.binarytree.Node;

public  class TreeTest {
    
    Node createNode(int rootValue) {
        return new ProcedureRecursionTree(rootValue);
 }
    
    @Test
    public void testAddLeftToRoot() {
        Node node = createNode(5);
        node.addValue(4);
        
        assertEquals("(5, (4, null, null), null)", node.toString());
    }
    
    @Test
    public void testAddRightToRoot() {
        Node node = createNode(5);
        node.addValue(6);
        node.addValue(4);
        
        assertEquals("(5, (4, null, null), (6, null, null))", node.toString());
    }
    
    @Test
    public void testStartSecondLevel() {
        Node node = createNode(5);
        node.addValue(6);
        node.addValue(12);
        node.addValue(3);
        
        assertEquals("(5, (3, null, null), " +
                          "(6, null, (12, null, null)))", node.toString());
    }
    
    @Test
    public void testGetDepthFrom3LevelOnlyRightTree() {
        Node node = createNode(5);
        node.addValue(6);
        node.addValue(12);
        
        assertEquals(3, node.getMaxLeafDepth());
        assertEquals("(5, null, (6, null, (12, null, null)))", 
                node.toString());        
    }
    
    @Test
    public void testGetDepthFrom3LevelLeftRightTree() {
        Node node = createNode(5);
        node.addValue(1);
        node.addValue(2);
        
        assertEquals(3, node.getMaxLeafDepth());
        assertEquals("(5, (1, null, (2, null, null)), null)", 
                node.toString());        
    }
    
    @Test
    public void testGetDepthFrom4LevelleftRightTree() {
        Node node = createNode(5);
        node.addValue(6);
        node.addValue(12);
        node.addValue(16);
        node.addValue(3);
        node.addValue(1);
        node.addValue(0);    
        
        assertEquals(4, node.getMaxLeafDepth());
        assertEquals("(5, (3, (1, (0, null, null), null), null), " +
                         "(6, null, (12, null, (16, null, null))))", 
                node.toString());        
    }
    
    @Test
    public void testGetDepthFrom5LevelTree() {
        Node node = createNode(5);
        node.addValue(4);
        node.addValue(6);
        node.addValue(3);
        node.addValue(7);
        node.addValue(2);
        node.addValue(8);
        node.addValue(1);    
        node.addValue(9);
        
        assertEquals(5, node.getMaxLeafDepth());
        assertEquals("(5, (4, (3, (2, (1, null, null), null), null), null), " +
                         "(6, null, (7, null, (8, null, (9, null, null)))))", 
                node.toString());        
    }

}
Приятного аппетита. Продолжение следует.

пятница, 24 декабря 2010 г.

Подборка #38

Есть и другие подборки: #1, #2, #3, #4, #5, #6, #7, #8, #9, #10, #11, #12, #13, #14, #15, #16, #17, #18, #19, #20, #21, #22, #23, #24, #25, #26, #27, #28, #29, #30, #31, #32, #33, #34, #35, #36, #37, #38, #39, #40

Как бы ножки не украли...


Читать дальше...

суббота, 7 августа 2010 г.

Подборка #11

Есть и другие подборки: #1, #2, #3, #4, #5, #6, #7, #8, #9, #10, #11, #12, #13, #14, #15, #16, #17, #18, #19, #20, #21, #22, #23, #24, #25, #26, #27, #28, #29, #30, #31, #32, #33, #34, #35, #36, #37, #38, #39, #40

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


Тем не менее многие из нас (по умолчанию) думают, что являются центром вселенной (я не исключение)...Читать дальше...

понедельник, 26 июля 2010 г.

Подборка #2

Есть и другие подборки: #1, #2, #3, #4, #5, #6, #7, #8, #9, #10, #11, #12, #13, #14, #15, #16, #17, #18, #19, #20, #21, #22, #23, #24, #25, #26, #27, #28, #29, #30, #31, #32, #33, #34, #35, #36, #37, #38, #39, #40

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



Читать дальше...

пятница, 11 июня 2010 г.

Рефакторинг: Интересный примерчик

Как бы вы порефакторили этот кусок кода?
boolean extendsReal = false;
boolean abstractManager = false;
if ( someObject.className == null ) {
    out.println("    extends TransportImpl<" + (isPKeyLong ? "Long" : "Integer") + ">");
} else if ( someObject.className.contains("AbstractManager") ) {
    out.println("    extends " + someObject.className + "<" + (isLong ? "Long" : "Integer") + ">");
    abstractManager = true;
    extendsReal = true;
} else {
    out.println("    extends " + someObject.className);
    extendsReal = true;
}
Уже порефакторили? Хотите глянуть как это сделал я? Читаем дальше...

суббота, 26 декабря 2009 г.

Игра под названием Рефакторинг: Метод в ООП. Extract Method. (часть 1)

Этой статьей начинается серия статей про рефакторинг [1] - изменение кода без изменения того, за чем этот код написали. Статья рассчитана на читателя, который наслышан про рефакторинг от более продвинутых напарников но сам никогда не использовал его или использовал но крайне редко; для тех, кто хотел бы копнуть вглубь и попрактиковаться; для тех, кто хотел бы добавить рефакторинг в арсенал инструментов "на каждый день". Автор основывается на двух довольно известных книгах [1, 2] с дополнением знаниями, полученными им в процессе практических экспериментов с рефакторингом. Чтобы заразить идеей потенциального читателя, предлагаю перед углублением в текст статьи посмотреть видеоролик, в котором Автор записал сеанс одного из своих рефаткорингов.

Мое знакомство с методом началось с замечания, что он должен помещаться на экран: «метод, не помещающийся на экран — плохой метод». На вопрос "почему?"я получил ответ "чтобы было удобно читателю". С тех пор прошло несколько лет. И теперь я не согласен с этим утверждением. Попробую в этой статье раскрыть этот вопрос.

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

Т.к. процедуроведение оттачивалось годами, то это хорошо отпечаталось на моем понимании ООП. Класс был просто удобным хранилищем для сходных процедур, которые я с гордостью называл методами. Редко, но некоторые из них все же использовали общие данные — поля класса. Позже пришло понимание инкапсуляции — класс не хранилище подобных процедур, а модель объекта реального мира. Методы начали рассматриваться немного иначе, но процедурный стиль не был искоренен окончательно - об этом свидетельствовала все еще большая величина методов.

Слава Байту, в моей жизни появился Рефакторинг. Он то и поставил все на свои места. Если оставлять мост между процедурным и ООП стилем, то метод — это процедура, делающая одно действие и обрабатывающая при этом данные своего объекта. Следом объявилось новое свойство методов - они выделяются не только с целью устранения дублирования [2]. Это было ценное открытие для меня. Моя эйфория по этому поводу была предметом многочисленных споров с сотрудниками во время парной разработки. «Зачем создавать метод который никогда не будет использован?!!» А затем, чтобы сделать нечто, что будет просто осуществлять одно действие и больше ничего. Если уж метод не будет востребован — сделай его приватным.

Цель этой статьи - продемонстрировать, что можно вытворять с большими методами делая процедурный код более объектно ориентированным с помощью Рефакторинга. На словах что-то объяснить сложно. В работе мне помогает парное программирование. Тут же на примере я попробую продемонстрировать базовый тип рефакторинга — выделение метода (Extract method). Я всегда ленился делать это руками, но когда я узнал, что в IDE (любой) присутствует подобная функция - я влюбился в этот тип рефакторинга. Итак, вначале был экстракт метод (Extract Method). Он же самый, как мне кажется, используемый — дорога в увлекательный мир ООП, шаблонов, рафакторинга и архитектуры.

Суть Extract Method — часть сложного метода сделать отдельным полноценным методом, после использовать делегацию (старый вызывает новый). Подобным образом мы устраняли дублирование в процедурах. Наш исходный метод (для простоты понимания выбирался довольно простой метод):

public void foo(List<String> answers) {
    for (int index == 0; index < this.size; index ++) {
        this.answer = answers.get(index); 
        if (this.answer == Answers.DEFAULT_ANSWER) {
            this.count = this.count + SOME_CONSTANT;
        } else {
            this.count = this. count - SOME_CONSTANT;
        }
        this.saveAnswer();
    }
}


Первый шаг - опишем что же метод делает на родном языке. «Проходясь по всем answers, выбирает и устанавливает каждый его элемент в this.answer, а потом сравнивает это свойство с чем-то. Основываясь на результате сравнения определяет - уменьшать или увеличивать значения this.count. После выполняет некое сохранение».

Второй  шаг — выделим из описания все действия (все формы глаголов).
Проходясь по всем answers, выбирает и устанавливает каждый его элемент в this.answer, а потом сравнивает это свойство с чем-то. Основываясь на результате сравнения определяет - уменьшать или увеличивать значения this.count. После  выполняет некое сохранение.
Итого: проходится, выбирает, устанавливает, сравнивает, основываясь на чем-то определяет, уменьшает, увеличивает и выполняет сохранение. 7 действий на 1 метод. Многовато, если вспомнить, что наша цель - не более одного действия/глагола на метод.

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

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

Возьмем исходный метод и оценим отдельные его составляющие:

public void foo(List<String> answers) {
    for (int index == 0; index < this.size; index ++) {   
        this.answer = answers.get(index);     
        if (this.answer == Answers.DEFAULT_ANSWER) {   
            this.count = this.count + SOME_CONSTANT;   
        } else {   
            this.count = this. count - SOME_CONSTANT;   
          
        this.saveAnswer();   
    }   
}


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

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

Выделим низкоуровневый код:

private boolean isDefaultAnswer() {
    return this.answer == Answers.DEFAULT_ANSWER;   
}

priavte void increaseCounter() {
    this.count = this.count + SOME_CONSTANT;   
}

priavte void decreaseCounter() {
    this.count = this.count – SOME_CONSTANT;   
}


исходный метод немного преобразится:

public void foo(List<String> answers) {
    for (int index == 0; index < this.size; index ++) {   
        this.answer = answers.get(index);     
        if (this.isDefaultAnswer()) {   
            this.increaseCounter();   
        } else {   
            this.decreaseCounter();   
        }   
        this.saveAnswer();   
      
}


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

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

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

private void changeCounter() {   
    if (this.isDefaultAnswer()) {   
        this.increaseCounter ();   
    } else {   
        this.decreaseCounter ();   
    }   
}

public void foo(List
<String>
answers) {
    for (int index == 0; index < this.size; index ++) {   
        this.answer = answers.get(index);     
        this.changeCounter();   
        this.saveAnswer();   
    }   
}


Стоит отметить очень важное свойство метода — его Имя. Что есть имя метода? Это то, что скрывает реализацию. Это то, что отвечает на вопрос "что?". Реализация же — отвечает на вопрос "как?". Название метода — это и есть тот глагол, о котором мы говорили выше. Если удается создать красивое имя для метода содержащее один глагол, то это верный знак - метод выделябельный. В противном случае лучше оставить как есть.

Еще раз (это важно): название метода должно отвечать на вопрос ЧТО делает метод и содержать ОДИН глагол. Методы с названием типа doSomething1AndSomething2 – это не методы, а процедуры.

В нашем случае изменения счетчика и сохранение ответа это действия в разных плоскостях, и если удастся их объединить под красивым одноглагольным именем — супер! Мне пока не приходит ничего в голову, потому оставляем как есть.

В самом конце стоит пересмотреть имя для исходного метода foo - теперь эта задача решается программистом в разы легче.

public void processAnswers(List<String> answers) {
    for (int index == 0; index < this.size; index ++) {   
        this.answer = answers.get(index);     
        this.changeCounter();   
        this.saveAnswer();   
    }
}


Кстати, изменения названия метода (Rename method) так же частый гость, и в ИДЕ, с большей долей вероятности, она так же автоматизирована.

В этом примере нам не приходилось делать никаких действий перед выделением - мы просто выделяли нужный блок в метод. Иногда бывает, что блок не готов к выделению. Чаще всего выделению мешает локальная переменная. О ней узнаем больше в статье «Локальные переменные — зло?». Забегая наперед скажу, что нам помогут такие звери как: встраивание локальной переменной, замена локальной переменной вызовом метода, расщепление локальной переменной, введение поясняющей переменной и некоторые другие [1].

Выделение метода — отправная точка к другим типам рефакторинга. В результате у нас образовалось некоторое количество новых методов, которым, возможно, не место в этом классе (как это определять и что с этим делать расскажу в «Где моя тачка, Чувак?»).  Кроме того у нас остался исходный метод processAnswers. Его как раз и предлагаю еще поковырять. 

Воспользуемся для установки значения поля this.answer его сеттер (если его нет, то создадим):

public void processAnswers(List<String> answers) {
    for (int index == 0; index < this.size; index ++) {
        this.setAnswer(answers.get(index)); 
        this.changeCounter();
        this.saveAnswer();
    }
}


Т.к. цикл проходится по всем элементам списка, то воспользуемся его сокращенной версией (ведено в Java 1.5):

public void processAnswers(List<String> answers) {
    for (String answer : answers) {
        this.setAnswer(answer); 
        this.changeCounter();
        this.saveAnswer();
    }
}


Вот теперь намного лучше.

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

Вот так вот мы расправились с довольно простым методом и поняли глубже как он работает.

Продолжение следует....

Список чтива:
1. Мартин Фаулер "Рефакторинг"
2. Стив Макконнелл "Совершенный код"