Рефакторить или не рефакторить?

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

    Если вы хоть немного разделяете мои ощущения, то нам есть о чём поговорить. Дело в том, что со временем что-то внутри меня начало подсказывать, что рефакторить всё подряд, везде и всё время — не самая лучшая идея. Поймите меня правильно, код должен быть хорошим (а лучше бы ему быть идеальным), но в условиях суровой реальности не всегда разумно постоянно заниматься улучшением кода. Я вывел для себя несколько правил о своевременности рефакторинга. Если у меня начинают чесаться руки что-нибудь улучшить, то я оглядываюсь на эти правила и начинаю думать: «А действительно ли сейчас тот момент, когда нужно нарефакторить?». Давайте порассуждаем о том, в каких же случаях рефакторинг уместен, а в каких — не очень.


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

    Слишком ранний рефакторинг


    Можете ли вы припомнить, когда у вас последний раз было постоянное ТЗ, которое месяцами не менялось? У меня вот такое вспомнить не очень получается. Мы живём в реальном мире, требования всё время меняются. Причём это не обязательно внешние требования — это могут быть ваши собственные требования к проекту. Поясню мысль на примере: допустим, вы взяли задачку среднего размера на один-два дня. Первые несколько классов уже написаны, но запустить пока нечего — идёт процесс написания суровой архитектурной части. И тут вы замечаете, что одна из частей проекта написана не особо универсально: «А вот если через полгода понадобится сделать X, то все будут страдать». Вполне разумно, что вам не хочется отправлять в репозиторий хреновый код, чтобы другие разработчики потом вспоминали вас плохим словом. И вы начинаете рефакторить ещё не готовую фичу. Иногда это оправдано, но на подобном пути следовало бы повесить табличку «ОПАСНОСТЬ». Вот поправите вы одну штуку, потом другую, потом третью. Неделя прошла, фича всё ещё не запускается, а вы говорите: «Как-то всё неправильно сделано. Но теперь я точно понял, как надо делать. Сейчас быстренько всё перепишу с нуля». Основная проблема заключается в том, что фидбека по фиче ещё не получено, а вы уже начали работать над улучшением кодовой базы. Подобный подход редко приводит к успеху. Не знаю, как у вас, а у меня часто бывает, что после реализации фичи я начинаю понимать, что работать всё должно несколько иначе. И это не из-за того, что я такой глупый, заранее не смог нормально продумать. Просто некоторую функциональность нужно «пощупать», чтобы понять как всё должно быть в релизе. Иногда нужен небольшой прототипчик (пусть даже с говнокодом и багами), чтобы обсудить фичу с коллегами. Иногда нужно что-то показать заказчику, чтобы он мог сказать: «Не, ну я не так хотел, всё должно быть наоборот». Порой пользователями не нравятся нововведения, они хотят всё как было. Проблема новых фич в том, что сложно предсказать их судьбу. Нередко случается так, что все наработки отправляются в помойку, т. к. после обсуждения первой версии коллектив принял решение делать всё иначе. Общий вывод: не стоит рефакторить код слишком рано, особенно если вы не уверены, что этот код 100 % останется в проекте.

    Нецелевой рефакторинг


    Скорее всего, у вас есть план разработки на ближайшее время. Вполне вероятно, что у вас есть сроки (даже если вы их поставили сами). Релизы нужно делать вовремя, затягивать разработку не стоит. Нужно контролировать себя, нужно заниматься теми вещами, которые входят в ваши непосредственные цели. Допустим, у вас есть кусок кода, который выглядит как полное… Ну, в общем, плохо выглядит. Но, продолжим наше допущение, вы с ним сейчас не работаете. Этот плохой кусок кода стабильно работает, успешно справляется со своими задачами и никак не связан с вашей текущей задачей. Ну так и не трогайте его! Да, вас может крайне печалить то обстоятельство, что на другом конце проекта всё очень плохо. Но заметьте, что прямо сейчас вам это никак не мешает. У вас есть текущие задачи, занимайтесь ими. Конечно, бывают задачи по улучшению кодовой базы, но нечасто — зачастую важнее добавлять новый функционал или фиксить баги. Концентрируйтесь на текущих задачах и не бросайте их из-за того, что где-то там что-то как-то не так.

    Рефакторинг ради рефакторинга


    Ок, вы пришли к выводу, что нужно обязательно отрефакторить часть проекта. Хорошо, давайте отрефакторим. Вроде бы запланированные улучшения выполнены, но тут возникает мысль: «А что я могу ещё улучшить? Ага, вон ту штуку». А после вон той штуки появится вот эта штука, а потом ещё одна, а потом ещё и т. д. Нужно понимать, что есть плохой код, есть хороший код, есть идеальный код. Последнего в большом проекте у вас никогда не будет. Это не значит, что не нужно к нему стремиться, но нужно понимать его недостижимость. Обычно задача стоит в написании хорошего кода, а не идеального. Допустим, после рефакторинга у вас получился вполне читаемый код, который работает более или менее очевидным образом, в котором нет костылей и которым не так сложно пользоваться. Задайте себе вопрос: «А может, пора остановиться?». Да, код можно улучшать. Причём в достаточно большом проекте его можно улучшать до бесконечности. Но вот прямо сейчас он справляется со своими функциями, им удобно пользоваться, он практически не вызывает у вас дискомфорта. Очень важно определить для себя приемлемое качество кода, после которого вы перестанете его улучшать (до тех пор, пока свойство приемлемости не будет утрачено). Вспомните, что есть ещё так много разных клёвых штук, которые можно дописать. Не нужно рефакторить ради самого рефакторинга, ради идеального кода. Нужно рефакторить, когда у вас есть веские причины на это: код сложно прочитать, код сложно поддерживать, код сложно развивать, код сложно использовать и т. п. Если ни одного «сложно» не возникает, то веских причин тратить время на рефакторинг у вас нет.

    Рефакторинг за день до релиза


    Бывает так, что релиз послезавтра/завтра/сегодня/должен был быть вчера (нужное подчеркнуть). Это важный момент в жизни проекта. Нужно уделить особое внимание тестированию, фиксам критических багов, финальным доделкам. Поверьте, это действительно плохая идея — перерабатывать кодовую базу (а ещё хуже — качественно перерабатывать) в тот момент, когда нужно отдавать проект в продакшн. Опытная практика подсказывает, что нужно зарелизиться, а потом с чистой совестью спокойно улучшать код. Некоторые спросят: «А почему?». Если такой вопрос возник, то, наверное, вам ещё не приходилось делать сложный рефакторинг. Подскажу: при переписывании код не всегда обязательно улучшается — иногда он может сломаться. Да что там сложный рефакторинг — бывает, поправишь один метод на пять строк, не уследишь за какой-нибудь зависимостью, а на другом конце проекта выползет бага, с которой сразу же встретятся ваши любимые пользователи. Вот вроде бы ничего плохого и не делаешь, а тут внезапно на тебя нападает зверь «Это было неочевидно» и топит тебя в пруду ложной первоначальной оценки. Хотя, может, это просто я такой плохой программист — люблю что-нибудь сломать. Вполне возможно, что вы всегда рефакторите всё абсолютно правильно и с полным контролем всего проекта. В таком случае я могу вас поздравить, но от совета с запретом предрелизного рефакторинга всё-таки не откажусь. Поверьте, за несколько дней рефакторинг никуда не убежит, а сон у всей команды будет чуточку, но спокойней.

    Рефакторинг очень старого кода


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

    А когда рефакторить-то?


    Я прошу прощения, если из этой статьи у вас сложилось впечатление, что от рефакторинга одни проблемы. Я всё ещё настаиваю на том, что код должен быть читаемым и красивым, им должно быть удобно пользоваться, его должно быть легко развивать. Позитивный подход лучше негативного, так что относитесь к рефакторингу не как к источнику проблем, а как к своему очень хорошему другу, который выручит вас в трудную минуту. Более того, этот друг может сделать так, чтобы в вашем светлом будущем трудных минут было бы поменьше. Хотелось бы в завершении обозначить несколько моментов, когда рефакторинг действительно уместен.
    • Нечего делать. Бывают спокойные времена простоя, когда все важные задачи закрыты, а новых пока не поставлено. Ну, не то чтобы совсем нечего делать, но определённое количество свободного времени есть. Потратьте его на улучшение кода. Дайте сущностям понятные имена, избавьтесь от дублирования, перепишите неудачный кусок архитектуры. Хоть нового функционала и не добавляется, но вы вкладываете свою лепту в душевное спокойствие разработчиков, которые будут продолжать проект. Поверьте, это важно.
    • Каждодневные страдания. А бывает так, что есть кусок проекта, который заставляет вас тяжело вздыхать каждый день. А из-за соседних столов слышатся тяжёлые вздохи ваших коллег. Конечно, хоть и релиз и не завтра, но важных задач хватает. Тем не менее, за неделей проходит неделя, а смотреть на этот кусок проекта всё грустнее. Скажите себе: «Хватит это терпеть!». Если бизнес-план составляет начальник, то объясните ему, что этот код просто необходимо переписать. Если работаете на заказчика, то убедите его, что потраченная на рефакторинг неделя позволит сэкономить много времени на разработку в будущем. Если работаете на пользователей, то примите решение о том, что лучше бы этим пользователям в этот раз подождать релиза на недельку больше, но зато потом наслаждаться стабильным ПО и регулярными обновлениями. Да, договориться со всеми и с самим собой не всегда просто, но вы уж постарайтесь.
    • Проблема запоздалого рефакторинга. Не стоит абсолютизировать правило про слишком ранний рефакторинг. Некоторые считают так: «Я вот сейчас наулучшаю чего-нибудь, а оно не пригодится — обидно же будет». Нужно понимать, что в ядре программы вполне могут быть важные части, которым лучше бы быть написанными хорошо. Помните, что чем позже вы проводите рефакторинг — тем выше его цена, ведь вы потратите больше времени и сил на переписывание. Критические базисные вещи (которые используются по всему проекту) должны быть в максимально хорошей форме постоянно. Будет просто прекрасно, если в вашей команде будет работать тезис «Рефакторинг не опаздывает. Он приходит строго тогда, когда считает нужным».
    • Встреча с монстром. Вы начинаете пилить новый функционал, который должен использовать старый кусок проекта, который выглядит как настоящий монстр: вам становится страшно только при взгляде на внешний интерфейс. Если время позволяет, то поправьте сперва базовую часть кода, чтобы потом сосредоточиться на новом функционале и не отвлекаться на то, чтобы вбить пяток костылей ради повторного использования кода.
    • Разумный перфекционизм. Заметили плохой кусок кода? Хочется его поправить? Очень хочется его поправить? Ну, если прям очень хочется, то можно. Но обратите внимание на слово «разумный». Соотносите время, потраченное на рефакторинг, с выгодой от улучшения кода. Не стоит срывать сроки и зарываться в бесконечные улучшения. Однако если своевременно и в меру заниматься рефакторингом, то проекту от этого станет только лучше.

    Вместо заключения


    Всё вышеперечисленное является чисто субъективным обобщением опыта работы над рядом проектов. Разумеется, я покрыл далеко не все жизненные ситуации. В каждой команде свои требования к коду, свой бизнес-план и свои правила. Уверен, что у многих найдётся пяток историй из серии «А вот у меня был случай, когда все эти советы не работают». Это абсолютно нормально, так и должно быть. Нет универсальной серебряной пули для определения количества усилий на улучшение кода («Мы будем каждый день 47 минут 23 секунды заниматься рефакторингом — и всё у нас будет хорошо»). Вам нужно исходя из собственного опыта в вашем конкретном проекте, в вашей конкретной команде попытаться найти золотую середину между написанием нового кода и улучшением старого. Я агитирую только за то, чтобы ко всему было рациональное отношение без фанатизма («Зачем улучшать код, нового функционала от этого не появится» / «Нужно срочно весь код сделать идеальным, чтобы потом с ним можно было нормально работать»). Подходите разумно к распределению времени на работу над существующим кодом — и всё у вас будет хорошо.

    Приветствуются любые дополнительные мысли о том, когда стоит или не стоит рефакторить. Одна из наиболее ценных вещей в этой области — опыт реальных разработчиков над реальными проектами.
    Enterra
    43.72
    Компания
    Share post

    Comments 66

      +9
      Добавлю, что тесты (хотя бы интеграционные) очень помогают при рефакторинге. При любом более-менее серьезном рефакторинге лучше сделать их для всех роутов, иначе что-нибудь обязательно сломается :)
        +13
        Рефакторят без тестов те, кто любит гулять, но не любит тратить деньги на презервативы.
          +16
          По данным всемирной организации здравоохранения, ежегодно в мире выполняется примерно 46 миллионов искусственных абортов, что составляет 22 % от числа наблюдавшихся беременностей.

          Так то, видать, многие занимаются рефакторингом без тестов.
            +4
            Степень залёта в сравнимых вами ситуациях, имхо, обычно не сопоставима…
              +5
              Но ситуации похожи. 15 минут сбегать в киоск и 15 минут написать тест на функцию перед рефакторингом.
                +9
                Это если архитектура системы изначально поддерживает такую «тестябельность».
                Кроме того, как показывает опыт, тесты пишутся только для понятой части рефакторящегося кода. Но т.к.
                но вы можете просто не осознать всё величие шалаша из костылей
                ,
                то очень велик шанс, что тесты пройдут, а что-то неожиданное все равно сломается.
          –43
          > Не, даже не так. Я чертовски люблю рефакторинг. Я не переношу плохой код и плохую архитектуру.
          Вам 15? Не переживайте, это пройдёт.

          > Меня коробит, когда я пишу новую фичу, а в соседнем классе творится полный бардак.
          А почему в соседнем классе бардак? Учитель не справляется? Пробовали обращаться к директору школы?
            +12
            Слишком толсто, не для этой аудитории.
              +22
              То есть Вы к своим 30 полюбили плохой код и наслаждаетесь плохой архитектурой? Наверное, это уже само не пройдет и, скорее всего, неизлечимо.
                +10
                Вам 15? Не переживайте, это пройдёт.

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

                А почему в соседнем классе бардак? Учитель не справляется? Пробовали обращаться к директору школы?

                Не оценил вашего тонкого юмора, извините.
                  –1
                  На вкус и цвет товарищей нет…
                  Если одному код кажется вершиной совершенства, то другой может тут же опустить этот код ниже плинтуса.
                  И тут я хочу задать Вам вопрос — каковы критерии совершенного кода?
                  Может код, о котором кто-то думает не лестно в силу своей ограниченности, на самом деле совершенен и проблема не в коде?
                  Я пока знаю всего лишь 2 объективных критерия:
                  1) Правильность работы, подтвержденная тестами
                  2) Скорость работы, подтвержденная бенчмарками

                  А как объективно формализовать понятие «плохая архитектура»?
                  На практике бывают такие проекты, осознать которые в целом можно только по прошествии нескольких лет плотной работы над проектом. А рефакторинг может растянуться на несколько человеко лет…
                  Так что видение «плохой архитектуры» совершенно не аргументирует рефакторинг…
                    +2
                    Количество «WTF!» в минуту при чтении кода подойдет за объективный критерий?

                    А вот, что, например, Microsoft говорит по этому поводу:
                    social.msdn.microsoft.com/Forums/ru-ru/af2ebf9f-c8f1-4062-a409-0d7819c1de8f/-?forum=fordesktopru

                    Качество — это далеко не только правильность и скорость исполнения.
                      +6
                      стоимость внесения изменения, например.
                        +3
                        И тут я хочу задать Вам вопрос — каковы критерии совершенного кода?

                        Стоимость поддержки.
                          0
                          Критерии совершенного кода — тема отдельная и очень сложная. По этому поводу можно несколько отдельных постов накатать, т.к. с формализмом тут всё очень сложно. Но можно начать отталкиваться от классической литературы, например:
                          Совершенный код
                          Чистый код
                          Читаемый код
                          А неформальные критерии стандартные: легкость чтения, гибкость, простота внесения изменений и поддержки и т.п. Но тут много субъективизма, так что по каждому пункту можно много рассуждать и холиварить, ведь у каждого свой подход. Например, Кент Бек с Мартином Фаулером любят говорить про плохие запахи кода как критерий.
                            0
                            Как бы говнокод еще и сильно зависит от вашего уровня программирования.
                            Кто-то не сумеет 5 десятков if объединить в бинарные операции, избавившись от условий в принципе.
                            Кто-то сумеет.
                            К примеру, те самые пять десятков if в итоге превратились в такое:

                                    //основная проверка
                                    bool prova = false;
                                    //массив проверок
                                    bool[] pr = new bool[13];
                                    for (int q = 0; q < 13; q++)
                                    {
                                        pr[q] = true;
                                    }
                                    //плоскость xy
                                    for (int j = 0; j < 4; j++)
                                    {
                                        pr[3] = pr[4] = pr[5] = pr[6] = pr[7] = pr[8] = true;
                                        for (int i = 0; i < 4; i++)
                                        {
                                            pr[0] = pr[1] = pr[2] = true;
                                            
                            
                                            for (int k = 0; k < 4; k++)
                                            {
                                                //плоскости
                                                pr[0] &= board[i, k, j] == type;
                                                pr[1] &= board[i, j, k] == type;
                                                pr[2] &= board[k, j, i] == type;
                                            }
                            
                                            prova |= pr[0]|pr[1]|pr[2];
                                            //боковые диагонали
                                            pr[3] &= board[i, i, j] == type;
                                            pr[4] &= board[i, j, i] == type;
                                            pr[5] &= board[j, i, i] == type;
                                            pr[6] &= board[i, 3 - i, j] == type;
                                            pr[7] &= board[j, i, 3 - i] == type;
                                            pr[8] &= board[i, j, 3 - i] == type;
                                        }
                                        prova |= pr[3] | pr[4] | pr[5] | pr[6] | pr[7] | pr[8] ;
                                        //главные диагонали
                                        pr[9] &= board[j, j, j] == type;
                                        pr[10] &= board[3 - j, j, j] == type;
                                        pr[11] &= board[3 - j, j, 3 - j] == type;
                                        pr[12] &= board[j, j, 3 - j] == type;
                            
                                    }
                                    prova |= pr[9] | pr[10] | pr[11] | pr[12];
                            
                                    if (prova)
                                        SomeWin(playertype);
                                }
                            


                            п.с. если сумеете еще улучшить — буду рад :-)
                              0
                              Можете попробовать отправить вышеприведённое на codereview.stackexchange.com. Там иногда дают очень дельные советы по улучшению кода.
                                0
                                Спасибо!
                                +4
                                Это проверка выигрыша в трехмерных 4x4x4 крестиках-ноликах?
                                  +1
                                  Да.
                                  Извините, плюсануть не могу.
                                    +2
                                    Что-то я думал-думал, но идеологически что-то более хитрое, чем проверять поочередно все варианты, ничего не придумывается.

                                    Могу предложить только косметические улучшения, а то сейчас работа c массивом из 13 bool-ов выглядит зловеще :)

                                    Как-нибудь так, например:

                                    var win = false;
                                    var range = Enumerable.Range(0, 4);
                                    Func<Func<int,int>, bool> checkLine = getCell => range.Select(n => getCell(n)).All(c => c == type);
                                    
                                    foreach(var a in range) {
                                        foreach(var b in range) {
                                            win |= checkLine(n => board[n, a, b]);
                                            win |= checkLine(n => board[a, n, b]);
                                            win |= checkLine(n => board[a, b, n]);
                                        }
                                    }
                                    
                                    foreach(var a in range) {
                                        win |= checkLine(n => board[a, n, n]);
                                        win |= checkLine(n => board[n, a, n]);
                                        win |= checkLine(n => board[n, n, a]);
                                    
                                        win |= checkLine(n => board[a, n, -n]);
                                        win |= checkLine(n => board[n, a, -n]);
                                        win |= checkLine(n => board[n, -n, a]);
                                    }
                                    
                                    win |= checkLine(n => board[n, n, n]);
                                    win |= checkLine(n => board[-n, n, n]);
                                    win |= checkLine(n => board[n, -n, n]);
                                    win |= checkLine(n => board[n, n, -n]);
                                    
                                    return win;
                                    
                                      +2
                                      адский код на Си
                                      #include <stdio.h>
                                      #include <inttypes.h>
                                      
                                      inline void board_set(uint16_t *board, int x, int y, int value)
                                      {
                                      	const int boardSize = 4;
                                      	int bitPos = x*boardSize + y;
                                      
                                      	uint16_t mask = 1 << bitPos;
                                      	*board |= mask;		
                                      
                                      	if (value == 0) {
                                      		*board ^= mask;
                                      	}
                                      }
                                      
                                      int main()
                                      {
                                      	int board[4][4][4] = {
                                      		1, 1, 0, 1,
                                      		0, 0, 0, 0,
                                      		0, 1, 0, 0,
                                      		1, 1, 0, 0,
                                      
                                      		1, 1, 0, 1,
                                      		0, 0, 0, 0,
                                      		0, 1, 0, 0,
                                      		1, 1, 0, 0,
                                      
                                      		1, 1, 0, 1,
                                      		0, 1, 0, 0,
                                      		0, 0, 0, 0,
                                      		1, 1, 0, 0,
                                      
                                      		1, 1, 0, 1,
                                      		0, 1, 0, 0,
                                      		0, 1, 1, 0,
                                      		1, 0, 0, 1
                                      };
                                      	uint16_t vertical_start = 0x1111;
                                      	uint16_t horizontal_start = 0xf;
                                      	uint16_t diag1 = 0x8421;
                                      	uint16_t diag2 = 0x1248;
                                      
                                      #define CHECK_MATCH(board, winmask) ((board & winmask ) == winmask)
                                      
                                      #define CHECK_V_LINE(board, line) CHECK_MATCH(board, vertical_start << line)
                                      #define CHECK_V_LINES(board) (CHECK_V_LINE(board, 0) || CHECK_V_LINE(board, 1) || CHECK_V_LINE(board, 2) || CHECK_V_LINE(board, 3))
                                      
                                      #define CHECK_H_LINE(board, line) CHECK_MATCH(board, horizontal_start << line*4)
                                      #define CHECK_H_LINES(board) (CHECK_H_LINE(board, 0) || CHECK_H_LINE(board, 1) || CHECK_H_LINE(board, 2) || CHECK_H_LINE(board, 3))
                                      
                                      #define CHECK_LINES(board) (CHECK_V_LINES(board) || CHECK_H_LINES(board))
                                      
                                      #define CHECK_DIAGS(board) (CHECK_MATCH(board, diag1) || CHECK_MATCH(board, diag2))
                                      
                                      #define CHECK_WIN(board) (CHECK_LINES(board) || CHECK_DIAGS(board))
                                      	
                                      	// original board contains 0 for empty cell, 1 and 2 for players' marks
                                      	const int playerToCheck = 1;
                                      
                                      	int playerWin = 0;
                                      	for (int i=0; i<4 && !playerWin; i++) {
                                      
                                      		// board bitmap for selected player
                                      		uint16_t miniboard = 0;
                                      
                                      		// fill bitmap
                                      		for (int j=0; j<4; j++) {
                                      			for (int k=0; k<4; k++) {
                                      				board_set(&miniboard, j, k, (board[i][j][k] == playerToCheck));
                                      			}
                                      		}
                                      
                                      		playerWin |= CHECK_WIN(miniboard);
                                      	}
                                      
                                      	printf("win: %d\n", playerWin);
                                      
                                      	return 0;
                                      }
                                      
                                      



                                      Суть такова, что каждую плоскую доску представляем в виде 16-битного числа и затем сравниваем побитово с масками. Это конечно ад, но мне просто показалась прикольной эта идея. Да и маски получились тоже с красивыми числами. Трехмерные диагонали не осилил, так как интерес к этому времени уже приостыл
                                        0
                                        оказалось, что всё это время Германия нещадно заколачивала 5 мячей Бразилии :(
                                    0
                                    Как-то так? (не проверял)
                                    bool test(int[,,] board, int type){
                                        for(int i=4;i<=8;i++){
                                    	int vx=i/4-1,x0=i%4;
                                    	for(int j=3;j<=8;j++){
                                    	    int vy=j/4-1,y0=j%4;
                                    	    for(int k=3;k<=8;k++){
                                    	        int vz=k/4-1,z0=k%4;
                                    		if(vx*4+vy*2+vz<=0) continue;
                                    	    	bool pr=true;
                                    		for(int s=0;s<4;s++) pr&=(board[x0+vx*s,y0+vy*s,z0+vz*s]==type);
                                    		if(pr) return true;
                                    	    }
                                            }
                                        }
                                        return false;
                                    }

                                      0
                                      Или так…
                                              static bool testwin(int[, ,] board,int type) {
                                                  ulong mask=0;
                                                  for(int i=0;i<64;i++) mask=(mask<<1)+(board[i/16,(i/4)%4,i%4]==type ? 1u : 0);
                                                  ulong res=(mask & mask>>1 & mask>>2 & mask>>3 & 0x1111111111111111UL);
                                                  res|=(mask & mask>>4 & mask>>8 & mask>>12 & 0x000f000f000f000fUL);
                                                  res|=(mask & mask>>16 & mask>>32 & mask>>48 & 0x000000000000ffffUL);
                                                  res|=(mask & mask>>5 & mask>>10 & mask>>15 & 0x0001000100010001UL);
                                                  res|=(mask & mask>>3 & mask>>6 & mask>>9 & 0x0008000800080008UL);
                                                  res|=(mask & mask>>17 & mask>>34 & mask>>51 & 0x0000000000001111UL);
                                                  res|=(mask & mask>>15 & mask>>30 & mask>>45 & 0x0000000000008888UL);
                                                  res|=(mask & mask>>20 & mask>>40 & mask>>60 & 0x000000000000000fUL);
                                                  res|=(mask & mask>>12 & mask>>24 & mask>>36 & 0x000000000000f000UL);
                                                  res|=(mask & mask>>21 & mask>>42 & mask>>63 & 0x0000000000000001UL);
                                                  res|=(mask & mask>>19 & mask>>38 & mask>>57 & 0x0000000000000008UL);
                                                  res|=(mask & mask>>13 & mask>>26 & mask>>39 & 0x0000000000001000UL);
                                                  res|=(mask & mask>>11 & mask>>22 & mask>>33 & 0x0000000000008000UL);
                                                  return res!=0;
                                              }
                                      
                                    +3
                                    Не согласен с грубым троллингом выше, но я примерно понимаю его мотив.

                                    Я к своим 32 тоже начал скептически относиться к слову «рефакторинг». Не к процессу улучшения качества кода, а именно к слову. В основном потому что у меня оно ассоциируется с другими баззвордами из ООП.

                                    Когда я слышу «рефакторинг», у меня сразу подсознательно возникает такая картина: товарищ, недавно прочитавший книжку по ООП-дизайну, не особенно разбираясь, переделывает работающий метод на страницу кода в две фабрики, четыре стратегии и три каких-нибудь Manager-а. От этого алгоритм не становится сильно более расширяемым, но при этом становится намного менее прозрачным и понятным. И все это — ради соотстветсвия каким-то навязанным канонам, типа про «методы должны быть не более пяти строк».

                                    А со сказанным в статье — в согласен, да.
                                      +1
                                      Согласен с вашей мыслью, со словом «рефакторинг» действительно часто происходит подмена понятий. В статье я имел ввиду улучшение кодовой базы без видимого добавления нового функционала. Это вовсе не означает, что мы должны немедленно воткнуть в нашу архитектуру 600 паттернов проектирования, один лучше другого. Понятно, что к любой переработке кода нужно подходить разумно и без фанатизма, имея на то особый резон. И должны это делать люди, которые понимают, что они делают.
                                    +1
                                    Основная проблема в том что на написание «рабочего кода» нужно X времени, а на написание «хорошего рабочего кода» 2X времени. И пусть даже современный заказчик готов оплатить эти 2Х времени, зачастую возникает ситуация, когда зарелизиться нужно через X времени (а как правило вчера). Поэтому иногда написание нормального (а как позже покажется — плохого), но главное рабочего кода — это необходимость а уже после есть время этот код зарефакторить.
                                      0
                                      Увы, всё так. Бизнес-модель накладывает свои ограничения на тёплый и уютный процесс разработки. Эту тему я уже обсуждал отдельно.
                                    +1
                                      +2
                                      Статья хорошая.

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

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


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

                                      В итоге рассматриваемый код надо классифицировать по менее эмоциональной шкале плохой\хороший. А все эмоциональное стремиться к категоричности — переписать полностью :) Гораздо правильней использовать более конструктивную шкалу: подходит, слабо подходит или не подходит под текущие требования (в том числе и к написанию\оформлению кода). И тогда полный рефакторинг это не «переписать все с нуля», а «взять за основу», «создать аналог с использование другого подхода\архитектуры\библиотек». Это позволит подойти к процессу еще более прагматично и эффективно.
                                        0
                                        И тогда ДАЖЕ полный рефакторинг это не «переписать все с нуля», а «взять за основу», «создать аналог с использование другого подхода\архитектуры\библиотек» и т.д. Это позволит подойти к процессу еще более прагматично и эффективно.

                                        PS: акцент не на полном рефакторенге, а на его примере — как может измениться отношение.
                                          +1
                                          Полностью согласен с вами, хорошие дополнения.
                                          • UFO just landed and posted this here
                                              0
                                              Это не объяснение. Это отмазка.
                                              Куда смотрели сеньоры и лиды, когда такой код уходил в продакшн?
                                                +1
                                                На РП, который сказал «нет времени, сегодня выкатываем как есть».
                                            +1
                                            Не стоит рефакторить код, который кажется ужасным, потому что ты его не понимаешь

                                            Рефакторить нужно всегда перед введением новой фичи — закладывайте +40% времени на совмещение старого кода с новой фичей.
                                              0
                                              закладывайте +40% времени на совмещение старого кода с новой фичей

                                              Очень хорошее правило. Если бы все в своих бизнес-планах закладывали время на улучшение кодовой базы, то мир бы стал чуточку лучше.
                                                +1
                                                А если из понимающих этот код остался в команду один единственный человек и тот уже за давностью лет плохо всё это помнит?
                                                В таких случаях, как мне кажется, отдельное упорядочивание данного кода более чем необходимо. + Иногда и хотелось бы добавить в этот участок какую-то новую фичу… но особзнание, сколько придется потратить на это сил и времени(понять как работает, добавить, убедиться что ничего не сломалось, описать как работает теперь), что все просто боятся с этим связываться.

                                                P.S. к относительно свежим проектам, использующим в основном логику и целочисленную арифметику сказанное относится в меньше степени — их проще покрывать тестами. А вот если, например, проект — решатель специфических систем дифференциальных уравнений, который изначально еще на фортране писался местами, перенес переписывание на C++ и, в силу специфики вычислений с плавающей точкой, не всегда полностью удается покрыть тестами(тупо от перестановки действий, в специфичном случае, может ухудшаться сходимость решения из-за ошибок округления).
                                                  +2
                                                  Ну в таком случае, надо оценить — зачем рефакторить? 90% рефакторинга делается не для «красоты», а для возможности повторного использования. «Старый код» часто сложно повторно использовать, но если надо, лучше сделать просто объектную оболочку, а потом работать с ней, без прямого обращения к «старому коду».
                                                    +1
                                                    Есть два «но» конкретно в данном случае.
                                                    1) Код крайне критичен к производительности. Это самое горячее место программы, обертки явно делу не помогут.
                                                    2) Менять в нем рано или поздно что-то придется. Например делать вариант кода для 3D(существующий 2D).
                                                      0
                                                      Когда Вы пишите, что-то близкое к аппаратному коду на С++ (как это вы описали) — забудьте о рефакторинге и «чистоте кода» — для математики ООП, а следовательно и рефакторинг не нужен, нужно просто умело декомпозировать код на функции в структурном стиле.
                                                        0
                                                        Я согласен, что ООП тут вообще никчему. Простите, а с какой стати рефакторинг как-то связан с понятием ООП? Вроде бы рефакторинг это изменение внутренней структуры программ для улучшения читаемости, упрощения внесений изменений и тп без изменения функциональности.
                                                        И аналогично, как связаны «чистота» и ООП?
                                                        Понятный читаемый код нужен в любой области. А следовательно и рефакторинг при необходимости.

                                                        В данном случае речь о рефакторинге на функции с понятными и хорошими именами, узбавление от ада goto, вынос групп логических условий в отдельные переменные с понятными и отражающими суть именами, выкидывание мертвого кода и тд. Что вы так к ООП привязались то? Рефакторинг никак не связан с парадигмами.
                                                          –3
                                                          Думаю Вы не правы чисто исторически. Рефакторинг появился для ООП и как бы внутри ООП. Думаю все мы читали классику от М. Фаулера, и все что он пишет там относится исключительно к ООП. Никто не спорит, что код улучшали и до ООП, но никто это не называл рефакторингом. И я бы так это и оставил. Так как речь идет в рефакторинге о улучшении структуры кода, а в структурном программировании — просто нет структуры, если только не считать те зачатки в виде функций за структуру. Можно конечно позаниматься и мелочами о которых вы говорили, но я бы не называл это рефакторингом. Это как бы самом собой разумеющиеся и относится скорее к стилю программирования, и хороший программист сразу пишет учитывая все эти пожелания (с понятными и хорошими именами, узбавление от ада goto, вынос групп логических условий в отдельные переменные с понятными и отражающими суть именами, выкидывание мертвого кода).

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

                                                            +2
                                                            Допускаю, что исторически я могу быть и не прав. Но вас действительно не смущет это:
                                                            Так как речь идет в рефакторинге о улучшении структуры кода, а в структурном программировании — просто нет структуры, если только не считать те зачатки в виде функций за структуру.

                                                            Кстати, управляющие конструкции это тоже элементы структуры.

                                                            А про хорошего программиста… он и в рамках ООП по идее должен сам всё сразу писать так, что рефакторинг, в том чисе в вашей трактовке, не нужен.
                                                            Далее…
                                                            Опять же в структурном программировании нет понятия повторного использования (вызов функции дважды к этому не относится). Повторное использование подразумевает исключительно повторное использование классов (предметных сущностей).

                                                            Это вообще как??? Даже затрудняюсь прокомментировать.

                                                            В обще, кажется разговор переходит куда-то не туда… Предлагаю сойтись на том, что «ООП рефакторинг» в данном случае не имеет смысла в силу слабого использования самого ООП, остальные виды, упомнятые тем же Фаулером в своей книге, вы рефакторингом называть не хотите… Ну, ваше право.
                                                              +1
                                                              Ок, сошлись на этом. Единственно, должен дополнить про

                                                              А про хорошего программиста… он и в рамках ООП по идее должен сам всё сразу писать так, что рефакторинг, в том чисе в вашей трактовке, не нужен.


                                                              Нет, так не получается у любого хорошего программиста. Меняются требования к задаче, предусмотреть это не возможно на все 100% — отсюда нужен рефакторинг. Почему именно в ООП это возникает? Ну, согласитесь, что смена требований редко меняет смысл уже использованных переменных их названий, goto и прочие мелочи внутри функции на это не влияют. Смена требований для структурного программирования происходит просто — расширение новых параметров в функциях — все. Для ООП все на порядок сложнее могут меняться роли классов, есл при одних требованиях выносить некий код было не нужно из класса, то при дополнении требований становится ясно, что образуется новая сущность… и т.д.

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

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

                                                                Более того, из-за всего этого иногда имеет смысл перехода от структурного подхода к ООП или наоборот. Это, кстати, тоже один из вариантов глубокого рефакторинга по Фаулеру.
                                                                  0
                                                                  Теперь я с Вами согласен :)

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

                                                                  Но раз Вы задумались о рефакторинге некого старого кода — то это скорее указание на то, что превышен порог «среднего количества объектов, сложных и многочисленных связей». Боюсь может оказаться, что объектная оболочка, о которой я говорил выше вас спасет, и совсем будет не критична к производительности. Тесты производительности после рефакторинга вам в руки :) Если сделать по уму, надо выделить места для рефакторинга, ориентируясь не на домыслы, а на тесты производительности, и окажется, что 80% кода можно превратить в ООП и прорефакторить, выполнив новые требования.
                                                                  +1
                                                                  Смена требований для структурного программирования происходит просто — расширение новых параметров в функциях — все.

                                                                  Какая прекрасная иллюзия. К сожалению, нет, все не так легко. Функции тоже меняют набор ответственности, их необходимо перепроектировать и так далее.
                                                                    0
                                                                    А вот этого в структурном программировании и пытаются всеми силами избежать. Отсюда «легче переписывать заново, чем изменять». И все потому, что когда «Функции тоже меняют набор ответственности» — это указывает на «превышен порог «среднего количества объектов, сложных и многочисленных связей»». И поэтому это повод переписать вначале на ООП, а не разваливать ПО перепроектированием без наличия инструмента для этого.
                                                                      +1
                                                                      А вот этого в структурном программировании и пытаются всеми силами избежать

                                                                      Кто старается?

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

                                                                      Во-первых, иногда варианта «переписать на ООП» нет. А во-вторых, ошибочно думать, что за пределами ООП нет адекватных инструментов проектирования.
                                                                        0
                                                                        назовите хоть одно
                                                                          +1
                                                                          Мозги.
                                                                            –3
                                                                            ч.т.д.

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

                                                                              Уверяю вас, проекты, где это ваше ООП — как корове седло, существуют…
                                                                              –1
                                                                              Вот чтобы работать с конструкциями языка, и нужны мозги. Тогда становится понятно, что ООП — не единственное прибежище сложных систем.
                                                              0
                                                              пруф из книги Фаулера, где он прямо связывает одно с другим

                                                              В течение последующей пары лет у меня были многочисленные возможности поговорить о рефакторинге
                                                              на внутренних совещаниях в AT&T BellLabs, на конференциях и семинарах в других местах. По ходу своих
                                                              бесед с практическими разработчиками я начал понимать, почему мои прежние обращения к слушателям не
                                                              были ими поняты. Отсутствие контакта было частично вызвано новизной объектно-ориентированной
                                                              технологии. Немногие из тех, кто работал с ней, продвинулись дальше первоначального релиза и потому еще не
                                                              столкнулись с трудными проблемами развития продукта, которые помогает решить рефакторинг.
                                                    +1
                                                    И еще анализируйте ошибки приходящие из отдела тестирования — попробуйте понять, чем они вызваны, очень часто бывает так, что ошибки взаимосвязаны (логически), вы их исправляете вразных местах, но отдел тестирования возмущается, что они уже говорили об этой ошибке — но они для вас разные… как сделать рефакторинг в этом случае понять сложно — но думайте и однажды вы устраните целый поток однотипных ошибок.
                                                      +4
                                                      Частенько случается, что вместо
                                                      Скорее всего, в процессе большого рефакторинга или переписывания отдельных частей вы замените старый работающий говнокод новым, идеально написанным кодом, но с багами.

                                                      получается просто
                                                      Скорее всего, в процессе большого рефакторинга или переписывания отдельных частей вы замените старый работающий говнокод новым.
                                                        +4
                                                        Refactoring Guru (отличный сайт про рефакторинг на русском)
                                                      • UFO just landed and posted this here

                                                        Only users with full accounts can post comments. Log in, please.