Pull to refresh

Comments 119

Я бы не был так категоричен и описал бы также исключения. Да, исключения подчеркивают правило, но тем не менее они дают четкое понимание, когда не стоит париться! Я бы предложил, а это чисто мое мнение, что при создании прототипа системы, когда мы хотим всего-лишь посмотреть «работает или нет», то вполне допустимо писать по методу «дампа потока сознания», хотя нужно осознавать что прототип нужно будет выкинуть и написать с нуля принятое решение!
Исключения портят привычку ;). Если на одном проекте можно, а на другом нельзя, возникает резонный вопрос: «а так ли уж нельзя?» Теоретически, прототип так писать можно, но только если есть уверенность, что руководство не «продавит» на «сохранить работающую функциональность». Выкидывать очень сложно, обычно.
>>но только если есть уверенность,
Такая уверенность вполне возможно, когда выкладываешь в цифрах и фактах, что и чем грозит. Програмеры часто забывают, что с руководством нужно говорить не только технически, но и немного о финансовой стороне.
Люди которые считают деньги и завязаны на деньгах, как правило факты любят. Не редко слышу от програмеров «Вы знаете это грозит нам плохим кодом, что выльется в плохую сопровождаемость кода». Чтобы подобные слова достигли цели, нужно говорить совершенно в другом ключе, к примеру «Мы можем создать прототип и быстро оценить что хочет заказчик. Однако этот код нужно будет выкинуть, мы конечно же потеряем 2 чел-дня, которые были затрачены на разработку примера для заказчика. Однако мы с экономим 3-4 чел-месяца поняв что система может быть и не совсем такой какой она нужна заказчику.» Когда говоришь руководителям в чел-днях, они считают деньги посредством этих цифр и до них куда быстрее доходит подобные слова нежели «Вы знаете это грозит плохой сопровождаемостью».
Подписываюсь под каждым вашим словом. Всё, что нельзя умножить на рейт, звучит для заказчика как полный бред.
Вариант — писать прототип на экзотическом для руководства/заказчика языке/фреймворке, который точно не пойдет в продакшен/релиз. Я так пишу прототипы на рельсах, чтобы к функциональности у заказчика претензий не было, а потом пишу релиз на пхп. Правда один фейл был — заказчик не захотел, чтоб я переписывал, сказал, что и так пойдёт.
Нет ничего более постоянного, чем временное.

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

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

Я на 110% согласен с автором. Правильная архитектура и декомпозиция любой задачи — единственный путь к ее успешному и качественному решению.
>>Вы же не станете есть грязными руками и ходить по дому/квартире в уличной грязной обуви только потому что никто не смотрит?
А если я только только что приобрел квартиру и там нет ни ремонта, ни мебели, что предлагаете? Снять обувь и не дай бог стяжку замараю?

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

Наверное, у каждого программиста в прошлом есть много кода, который родился «по прототипу», да так и остался жить (да ещё, наверняка, и в продакшене). А уж сколько больших систем, написано такими сменными программистами…

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

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

Если абстрагироваться от слов «программист» и «ПМ», то мысли из статьи должны жить в голове любого человека, делающего из буратин инженеров. Не важно, программисты это или авиаконструкторы.
Кстати, это излюбленная отмазка программистов: сделал плохо, потому что нужно было быстро :)
нет, излюбленная «она компилируется»
Не актуально для интерпретируемых языков (:
hiphop фейсбука с вами не согласен :)
Или «но у меня же всё работает» :)
Полезно как можно быстрее познакомить новичка с юнит-тестированием. Конечно, не стоит принуждать сразу же вести разработку по TDD. Сначала пускай дампит, а после этого пытается тестировать, то что написал. Это приучит строить код с низкой связностью.
Знаю пару примеров, когда и TDD и best practicies, а написанный человеком код ни сопровождать, ни использовать не представляется возможным.
И даже SOLID соблюдается?
Что-то стало страшно мое понимание в паблик выкладывать, а тем более в виде поста на хабре оформлять :-/
По поводу использовать возможно, а сопровождать — врядли. Такой код обладает по крайней мере одной положительной харрактиристикой — его можно безопастно отрефакторить.
Отрефакторить-то можно. Но иногда получается что силы, затраченные на рефакторинг такого поделия, сравнимы с силами, которые должны быть затрачены на написание с нуля.
Я, видимо, отношусь к какой-то средней нише. Не сажусь думать с карандашом (за редким исключением), а сразу бросаюсь писать код. Единственный нюанс, отличающий от «дампа потока сознания» в продукте — не код продукта, а код его тестов, как юнит (по несколько тестов на метод для разных юзкейсов, пришедших в голову), так и интеграционных/функциональных/приемочных (так толком и не понял различий между ними). Да, не редкость, что первая пришедшая в голову мысль оказывается неудачной в плане, например, связности и связанности объектов и приходится переписывать как код продукта, так и тестов («но тесты вперёд!» почти © Ильф и Петров), но большинства проблем описанных в топике получается избежать на стадии написания тестов.

Как только видишь, что количество моков и стабов становится несуразным для вроде бы юнит-тестов и он превращается в интеграционный, то что-то выделяешь, минимум, в метод, а то и вводишь новый класс, если нарушается SOLID и тестируешь их отдельно. DRY — это мелочи, можно отложить на потом, когда тесты написаны и есть время :) Пускай это и нарушает обычный цикл TDD. А вот нарушение SOLID даёт сложности непосредственно в тестировании.

P.S. Тут столкнулся с проблемой — задумал написать статью (или цикл статей) про миграцию со «спагетти»-кода (PHP) на код покрытый простыми тестами (и сам собой приходящий к парадигме типа MVC в процессе покрытия), а значит легко модернизируемого. Начал писать в стиле «дамп потока сознания» что-то вроде SaaS записной книжки, а не получается — тесты пишу мысленно и архитектуру строю исходя из них. Может есть у кого-то образец процедурного (лучше всего) или «PHP с классами» кода сайта типа визитки или «бложика» строк этак на тысячу-другую (не считая HTML), соблюдающего все мыслимые антипаттерны типа глобальных переменных, перемешивание всех логик, функций (не отделения представлений от содержания), копипаста и т. п., но обязательно с ацтентифкацией и авторизацией посетителей? Сейчас привожу к такому виду проект на 30к строк, но для статьи это, имхо, перебор. А есть меркантильное желание больше этим не заниматься, может статья хоть чуть-чуть этому поспособствует.
Опять дамп потока сознания получился :)
К вопросу о PS. Могу на перле порекомендовать :)
Увы, лет 10 назад выбирал между C/C++, Perl и PHP для веба и выбрал PHP (из-за его близости к C с одной стороны и к HTML с другой). Сходу даже хелловорлд на Perl не напишу.
PHPStorm в последнее время пользуюсь.
Возникло ощущение, что в Вашей компании работают одни студенты-кодеры. Можно было бы хотя бы совсем нубов отсеить, использовав книгу, в честь которой назван раздел. Ну или хотя бы не тратить время полезных сотрудников на обучение студентов, а просто дать им в руки эту книжку, и как сказал один известный персонаж — «выучить от сих до сих, приду проверю, если не выучите ...» (с) Сами знаете кто.
Знаете, у меня в команде несколько очень хороших ребят, которые сейчас на последних курсах. Есть и такие, которые за несколько лет после универа стали отличными спецами.

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

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

А вообще, я Вам советую почитать что-нибудь по управлению командами. Например, начать с книги «Успешные проекты и команды» Демарко и Листера (aka Peopleware). Немного меняет взгляд на отдельных людей и команду в целом.
какие-то пустые разговоры, господа программисты. код в таком стиле, код в секом стиле — просто кунгфуисты прямо. есть верное решение задачи, а есть решение, которое отдалено от верного по такой-то и такой-то причине, и нужно это просто понимать. я думаю если всё время, потраченное на смакованиерассуждения о том, как надо рассуждать, рассуждая при написании кода потратить на очередную ревизию, было бы больше пользы
Верное решение то, которое соответствует ТЗ. Но верное решение не одно, а их бесконечное (практически) множество. Задача программиста выбрать оптимальное по каким-то критериям, ну, или близкое к нему. Причём заказчик (внутренний или внешний — не суть) о каких-то критериях типа поддерживаемости может даже и не знать.
UFO just landed and posted this here
Вы никогда не работали в команде?
На нынешнем месте моей работы (30го увольняюсь) у меня есть коллега. Еще в универе с ним учились. До сих пор придерживается взглядов «главное чтобы работало, какая разница, использую я ООП, или все пишу в одной большой функции». Работает над своими проектами один.
Я работаю с командой и иногда показываю ему код и он удивляется: «а зачем тут отдельный класс для конвертирования величин? Можно же там где выводишь сразу написать?» или «а зачем метод парсинга писать отдельно от метода приема данных по TCP??? Все равно же одну из другой вызываешь!».

Объясняешь, показываешь, приводишь пример, когда разные люди дорабатывают эти процедуры одновременно и о чудо — оказывается, это удобно и круто.
«который, к сожалению, очень часто встречается у молодых и неопытных специалистов»
«Есть задача – её надо решить. Их так учили в университетах.»
и
«это всё вопрос религии»

я, к сожалению, почему-то очень часто встречаю именно в кругах опытных программистов из разных контор(все конторы до 10 человек и несколько синьёров в 2 конторах от 30 человек). Всё сводится примерно к следующему: шаблоны это конечно всё хорошо, но надо решить задачу. А что будет завтра — будем делать завтра. Все они слышали про рефакторинг только из статей и сами с трудом умеют пользоваться, зато выполняют свои задачи быстро. Это их стратегия
Но мне порой даже кажется, что у таких, на самом деле, учатся многие джунеоры писать спагетти, поток кода и т.д. И таких становится всё больше…
Я уже прокомментировал ниже. Я бы не назвал этих программистов «опытными». Это программисты — которые просто имеют «большой стаж работы», но так и не переросли состояние джуниора.

С другой стороны, я не исключаю, что это настоящие опытные профессионалы, которые пытаются донести до молодых (и не очень) перфекционистов простую мысль: код не должен быть «идеальным». Он должен быть структурирован (без излишеств), быть понятным и, главное, выполнять поставленную задачу.
Мне, в своё время, хватило прочитать книгу Рефакторинг от Фаулера, чтобы перестать писать длинные методы без особого разбиения. Первые главы книги сразу разъясняют почему не надо писать методы длиной больше 20 строк и как от этого избавиться в коде, который уже есть. Если новичку нравится программирование и он не глуп, то с большей вероятностью, книги будет достаточно, чтобы он перестал писать свой поток сознания.

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

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

Методы длиной в тыщу строк, закомментенные куски кода которые как бы и не нужны, но выкинуть жалко, полное отсутствие комментов — это не признак начинающего программиста. Иногда бывает зайду в cpp файл и начинаю что то искать в чужом коде. Вижу, что начинается метод, кручу колёсико мыши 10 секунд, кручу 20 секунд, а конца метода всё не видно. Иду в шапку файла, чтобы посмотреть кто же написал такой шедевр и поражаюсь — код написал тот, кто уже лет 10 работает программистом. Такое встречается часто.

Ограничить длину метода 20 строчками слишком сурово даже по меркам «военного» IBM — там вроде как 25 строчек было ))
Я думаю, что это признаки программиста, который так и остался начинающим, несмотря на свой стаж…

Кстати, про комментарии: я своих учу, что, надо комментировать только «нетрадиционные» решения и (увы, такое тоже случается) заплатки. Если в нормальном методе есть комментарий типа «дальше мы делаем что-то», то это что-то — первый и практически стопроцентный кандидат на вынос в отдельный метод.
Некоторые программисты поступают хитро — они не подписывают свой код. И это беда.
Беда — программисты, которые не используют системы контроля версий. Зачем подписывать код, если есть annotate?
Простой жизненный пример:
Был код, версионность поддерживалась, всё было хорошо.
Пришёл новый тех.дир и решил изменить систему контроля версий.
Изменил, без порта с пред системы.
Итог — куча кода, не понятно кем написанная.
Хорошая, годная и, что немаловажно, очень актуальная статья. Но закрываются глаза на одну важную проблему. Начну немного издалека. 1. Поскольку требования заказчика имеют свойства изменяться, код также имеет свойство изменяться. Иногда задачу приходится решать в стиле «быстро и грязно», откладывая на потом переписывание начисто. Короче — код меняется. 2. Чтобы можно было смело изменять/рефакторить код, нужно сначала покрыть его автоматическими тестами. Иначе становится очень сложно проверить, приводят ли изменения кода к «взрывам». Ручное тестирование лучше, чем никакого, но оно не решает эту проблему полностью. 3. Как писать тесты для сложных приложений? Например, которые много работают с БД, сетью или используют много IPC? Вот было бы очень интересно почитать про #3.
Для стабирования БД используйте готовый стаб — БД в памяти. Для стабирования каких-то ваших сервисов пишите стабы ручками. Еще лучше пишите сервисы так, что их можно запустить не только как удаленный сервис, но и как локальный. Решения есть. Что бы можно было использовать БД в памяти скорее всего нужно использовать ORM. Самое сложное что может быть — это написать стаб ручками. Помню, как-то пришлось ручками писать стаб для RabbitMQ. Веселуха. Хотя подумаешь, ну потратил 3 дня времени. Зато сколько пользы.
Довелось тут рефакторить PHP-код, работающий с БД (mysql_*) и сетью (curl_*). Код — типичный дамп потока сознания — index.php порядка трех тысяч строк с трехуровневыми switch и далее вложенные if и for и не одной функции в нём не описано. Зато много вызывается функций, использующих глобальные и суперглобальные переменные и меняющие их состояние. Хорошо хоть шаблонизатор используется.

С умной IDE собственно оказалось всё не так плохо, как я себе представлял. Делаем классы-обертки для вызовов mysql и curl, выделяем почти весь код index.php в метод run с параметрами $_GET, $_SESSION, и т. п. класса IndexFrontController c конструктуром принимающим экземпляры оберток mysql, curl и template. Всё это довольно безопасно, где IDE помогает встроенным рефакторингом, где простой поиск и замена. Отправная точка для тестирования создана. Теперь берём одну ветку кода и пишем для неё тест, конструируя IndexFrontController с моками наших оберток. Далее идем глазами по коду ветки смотрим где эти обертки вызываются, с какими параметрами и что могут возвращать и формируем соответствующие ожидания вызовов и возвращаемые значения. То есть для кода метода
public function showUsers() {
  $this->db->connect('localhost', 'root', 'pass');
  $this->db->select_db('database');
  $result = $this->db->query("SELECT * FROM users");
  $users = array();
  while($row = $result) {
    $users[] = $this->db->fetch_array($result);
  }
  $this->db->free_result($result);
  $this->template->assign_vars(array('users' => $users));
  $this->template->show('index_users.tpl');
}
(где в оригинале вместо $this->db->* было mysql_*) формируем моки db и template с условиями что метод connect вызывается точно один раз с параметрами 'localhost', 'root' и 'pass', метод select_db вызывается точно один раз с параметром 'database', метод query вызывается точно один раз с параметром «SELECT * FROM users» и возвращает некий результат (я использую случайное число), метод fetch_array вызывается N раз с параметром вернувшимся от query и возвращает массив значений, метод free_result вызывается ровно один раз с тем же параметром, метод assign_vars вызывается ровно один раз с соответствующим массивом и, наконец, метод show вызывается ровно один раз с параметром 'index_users.tpl'.

Всё! Мы зафиксировали поведение этого участка кода и можем смело его рефакторить. Мы не тестируем класс db, он элементарен (хотя можем и его покрыть тестами, но это будут уже не юнит-тесты, для запуска каждую минуту они не подойдут), а функции mysql_* оттестированы разработчиками расширения. Мы не тестируем класс template — его за нас оттестировали разработчики Smarty.

Теперь мы можем смело заменить код выше на что-то вроде
public function showUsers() {
  $repo = new UsersRepository($this->db);  
  $tpl = new UsersView($this->template);

  $users = $repo->fetchAll();
  $tpl->show($users);
</souce>
инкапсулировав логику хранения и отображения, но начальный тест всё ещё будет проходить, если мы ничего не сломали при инкапсуляции, а значит поведение программы не изменилось, если считать, что первое изменение (преобразование "плоского" кода в метод объекта) мы провели аккуратно и безопасно, что довольно вероятно, особенно при поддержке современных IDE.

Теперь мы можем начальный тест разбить на несколько, тестирующих UsersRepository и UsersView отдельно, а в начальном проверять только вызов fetchAll и show (правда это потребует сначала ещё небольшого рефакторинга, например, введение Service Locator или IoC, чтобы избавиться от жестких зависимостей и иметь возможность подставлять моки). После этого мы без проблем сможем сменить mysql_* на PDO или Doctrine2, а Smarty на Twig или нативные шаблоны (тестирование шаблонов отдельная тема).

Пример, конечно, высосан из пальца, но основные принципы показывает, имхо.
А откуда вообще взялся миф, что «через несколько дней не только «человек с улицы», но и сам автор не сможет понять, что происходит внутри этого куска, и за что отвечает это ветвление...»? Я такого почему-то не наблюдаю. Когда мне приходится возвращаться к своему коду, написанному 5-10 лет назад в лучшем стиле «дампового» программирования — с однобуквенными переменными, без комментариев, с методами по 100-200 строк и десятикратными вложенными for/if, то все равно все сразу видно: здесь инициализируются места для будущих выходных данных, здесь изображение делится на непрерывные участки, в этом цикле идет распознавание/фильтрация/уточнение плоских фрагментов (а вон та переменная «K» отвечает за то, какое конкретно из действий сейчас происходит, и вот эти два if и один switch как раз обеспечивают различное поведение для случаев поиска, проверки и определения порога распознавания), а вот эти куски (с новым параметром) добавлены позже — и в трех местах появились ветвления, отвечающие за новый подрежим… И все понятно — ведь код очевиден сам по себе.
Возможно, другим людям придется затратить некоторые усилия, чтобы что-то понять. Но я же в их коде разбираюсь, а он написан не лучше!
Вы так говорите, как будто вам нравится ковыряться в таком коде )
Я в нем не ковыряюсь, я его читаю. И редактирую. Обычно сложен не код, а проблема, которую он решает.
И потом, людям же нравится читать детективы! )))
Детектив — это если читать код на языке программирования которого не знаешь. Догадаться по первым строчкам кто убийцачто там вообще делается
Ну а кодобред по ассоциациям мне больше напоминает ситуацию когда в в переполненную мусорку упало что то нужное и надо перелопатить всю эту кучу чтобы найти нужную вам вещь. Так вот если это не что то архиважное без которого нельзя обойтись то проще забить и не ковыряться там.
Увы, это не миф а суровая реальность, подкреплённая опытом… Ну и очевидность кода — вещь сильно субьективная.

А вообще, я Вам и Вашим коллегам сочувствую. Если попробуете писать нормально, то, уверяю вас, сложных проблем станет гораздо меньше ;)
Пробуем. Иногда даже получается. Для простых задач…
Этому учиться надо как раз на сложных задачах. А учиться никогда не поздно :). Тем более, что практически любую сложную задачу можно разбить на кучу простый и «есть слона по частям».
Зависит от того, насколько эти задачи оказываются переплетенными. Если какой-то промежуточный результат создается в одном методе, корректируется в другом, а используется в третьем (причем все эти методы на глубине 3-4 вызова от главного, да еще и в разных ветках), то разбираться в таком коде еще гораздо интереснее :))) прямо сейчас этим занимаюсь :)
У вас наверное очень-очень-(...) хорошая память.
Пожалуй, нет. Просто по внешнему виду фрагментов кода я могу догадаться, что они делают.

Про 20 строк на метод — да, конечно, я стараюсь так писать, кода есть возможность. Но сейчас я смотрю на свой метод длиной 200 строк, и думаю — что будет, когда я разобью его на 15 методов поменьше. В методе — пятикратный вложеный цикл, внутри него — пятикратный if (это только одна ветка, есть и другие). Под одним из if-ов блок как раз в 20 строк. По дороге к этому блоку описано 47 переменных (не считая переменных цикла), 15 из них используются внутри (причем 5 модифицируются). Предположим, что я выделю на этот метод отдельный класс, долгоживущие переменные сделаю его членами, а остальные как-нибудь буду передавать параметрами. Но тогда окажется, что будет намного сложнее отследить моменты создания и использования переменных, которые вынесены в класс (сейчас все просто: скобка закрылась — переменная померла). И что-то я не очень уверен, что в полученном коде будет проще разобраться…
Ну наверное стоит интуитивно думать о том, что 20 строк — не идеал. И подойти может не везде и не для всех. И не стоит идеализировать код ради идеализации. Во всем нужна мера. Чаще всего — интуитивно, но это лично мое мнение. Иногда 5 однострочников лучше чем один на 5 строк.
К сожалению это реальность, единственное что дамп это не совсем причина. Далеко не факт что «дамп» окажется кривым и непонятным, а «правильный код» логичным и очевидным.

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

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

Вы кстати давно разговаривали с бывшими студентами — кандидатами на должность программист? Которые пишут в CV «Уверенное владение С++»? Да половина из них не сможет правильно ответить зачем и когда нужен виртуальный деструктор. Большая часть не объяснит зачем в прототипе копирующего конструктора модификатор const перед ссылкой. Чем чреват сингл тон. Хорошо если понимает чем постфиксный инкремент от префиксного отличается. Кого брать на баг фикс то?

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

Извиняюсь, что такой разрыв по времени.

За студентов +1 — большинство из низ путают перегрузку с полиморфизмом (несколько раз собеседовал). А вы о виртуальных диструкторах, ха. Последний раз запутал респондента с ToString (мой основной язык C#).

По поводу «красивые архитектуры». Не буду кидаться именами. Мне кажется ООП нужен исключительно для того, что бы читатель кода мог максимально быстро понять задумку лежащую в основе системы в целом и фрагмента в частности; максимально быстро начал развивать вверенную ему систему (или ее часть). А архитектура, ради архитектуры — это конечно издержки молодости профессии.
Для вчерашнего студента велика вероятность попасть в команду к «опытному» кодобредогенератору, который 20 лет решает задачи простынями кода (которые по идее даже как то работают). И новенький может подумать что так и надо за полчаса набросать тысячу строк, а потом до полуночи дебажить его. И в таком ритме у новенького не будет времени ни на статьи ни на книги. Действительно зачем, ведь перед глазами у него команда «профессионалов» на коих надо ровняться. И даже если случайно новенький прочтет где то про DRY KISS или не дай бог упомянет при своем «тим лиде» Фаулера, то тут же получит жуткий батхерт а ля «да фуфло это все, настоящие мужики код пишут а не эти сопли архитектурные разводят!!!»
И самое опасное что этот новенький через пару лет может стать сам таким «генералом»
Браво! В статье написано всё то что я очень хотел услышать, прям по нотам сознания. Жаль не могу проголосовать.
По поводу дампа в прототипах я категорически против, писать чистый код это очень хорошая привычка и однажды её приобретая, начинаешь аккуратно подходить и к прототипам и к юнит-тестам.
Другое дело, что сразу написать хороший код сложно, поэтому, нередко, сначала код выходит дампом, но это не беда, так и Фаулер говорит, беда в том, что этот код таким и остается, а должен быть отрефакторен.
UFO just landed and posted this here
Вот тут толстый плюсик. Только по последнему абзацу добавлю: но и думать особо тоже не стоит — все равно правильно не придумаешь пока два раза не перепишешь :)
А ещё бывает так: всё обдумал, разложил по полочкам, красиво написал, а потом раз и требования поменялись. ты ещё раз аккуратно обдумал, переделал, а потом требования снова поменялись. А начальник стоит над головой и каждый раз, когда ты от клавиатуры отрываешься, интересуется, а чего это ты ничего не пишешь? Тут уже невольно начинаешь писать как попало, лишь бы закончить уже этот ненавистный проект.
Как закончите проект поищите себе другого начальника
> Многие из них ещё находятся под влиянием маргинального лозунга «пиши код, б##дь!».
Ну вот, ещё одна непонятая методология.
Спасибо за пост. У меня инсайд случился. Какую литературу посоветуете, дабы развить мысль и закрепить навыками по поводу методов в 20 строк и вообще хорошего кода? Особенно важен совет человека, который написал такой топик.
Присмотритесь вот к этой:
Роберт Мартин — Чистый код (http://www.ozon.ru/context/detail/id/5011068/)
Спасибо! Я рад, что смог помочь :)

Основные две книги:

Стив Макконел, «Совершенный код» — www.ozon.ru/context/detail/id/3159814/
Мартин Фаулер, «Рефакторинг. Улучшение существующего кода» — www.ozon.ru/context/detail/id/1308678/

На самом деле, Макконел весьма объёмный, но очень интересный и крайне полезный. У Фаулера наиболее важные — первые главы, где идёт описание общих подходов.

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

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

Сейчас надо писать кое-что на повершелл, и после обработки видео, я уже не могу писать простыней. Сейчас читаю как это красиво делать на повершелле.
Согласен, только думаю «дамп сознания» можно назвать проще и более привычно — это типичная bottom-up разработка кода. Да, top-down позволяет сохранить логическую целостность и т.д. и т.п., но такой подход доступен только опытным разработчикам с развитым абстрактным мышлением, ничего удивительного, что новички так пишут.
Имхо, bottom-up можно писать по разному. Даже на примитивном бэйсике считалось хорошим тоном не писать код, логические блоки которого не помещаются в 24 строки, а ещё лучше выносить логические блоки в подпрограммы, чтобы основная программа выглядела как последовательность операторов GOSUB N с комментарием что собственно там происходит. А пошла эта традиция с фортрана.
Да, вобщем-то физический порядок кодирования не так важен, как важна модель, которую программист держит в голове. Если она есть, то можно писать и снизу вверх, и сверху вниз, результат будет примерно одинаковый. А если модели нет, то ничего не отсается, как решать задачу «в лоб».
Не знаю. Я вот чаще всего решаю задачи «в лоб», но как-то методов по 200 строк не получается. Возможно потому что, даже если тесты не пишу вперёд, но в голове крутится мысль: «А как я это буду тестами покрывать, когда рефакторить придётся? А придётся!». Один раз меня заставили покрыть тестами свой «дамп» — с тех пор даже не размышляя о высоких абстракциях типа разделения логики хранения и представления, SOLID и т. п., а по чисто формальным признакам типа «пять строчек кода преобразуют две переменные в один результат» выделяю методы. Потом вижу, что в классе пять методов работают с одним набором свойств, а пять с другим и разделяю класс на два, даже особо не вникая в то, что они делают. Вижу, что один из этих классов создаётся жестко во втором и ввожу второй в конструктор или сеттер. И как-то само собой получается низкая связанность и высокая связность, а потом и архитектурные паттерны начинают проглядывать.
Сложные (для меня) задачи я решаю только в лоб, как же еще? Область новая, модельки в голове нет. Но код на выходе при должном старании тоже выходит нормальный. Потому как никто не отменял рефакторинг. Написал бяку, понял как надо, отрефакторил, в этом ничего страшного нет.
Во, «при должном старании», наверное, ключевая фраза для топика!
Главное, чтобы модель в голове программиста не ушла вместе с ним в соседнюю фирму, что обычно рано или поздно случается. И тут все вдруг начинают понимать, что «сверху вниз или снизу вверх» было ой как важно…
Решил таки добавить еще «5 копеек» в холивор по поводу критериев «20 строчек» или «25 строчек».

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

Причем не только «копипаста» (копипаст я отношу к грубейшим ошибкам и заставляю устранять в 100% случаев), а и логического дублирования тоже.

Если я вижу, что одна и та же по сути работа делается больше чем в одном месте — значит эта часть алгоритма должна быть обобщена и оформлена в виде параметризованного кода (функции, шаблона и т.п.)
Разумеется, оговорки и частные случаи возможны и будут всегда. Причины «не обобщать» могут быть различными (производительность, намеренное снижение связности кода и т.п.). Но это всегда должно быть четко обоснованное решение.

Если у вас в коде есть константа — одна должна быть оформлена как константа и вводиться ровно в одном месте. Пример для веб-системы: макс. длина input на форме. В типовом случае она (длина) используется как минимум в 3-х различных местах — в коде ПХП серверной валидации, в шаблонах (ПХП или что у Вас для генерации HTML) и в JS (клиентская валидация).

Резюме — для меня основной критерий качества декомпозиции это отсутствие дублирования кода и сущностей.
Подчеркну, сказанное никак не относится к качеству архитектуры и кода в целом! Это только критерий качества декомпозиции.
Поэтому вчерашний студент залезший в эту классную и стройную функцию сломает сразу все… ну или имеет шансы сломать все в остальных паре десятков мест где она используется.
А шаблон который написан так что его просто на этапе компиляции нельзя использовать не правильно, он использует как раз так как использовать было нельзя и не компилятор ни ревьюер этого не увидят и заметит только coredump у заказчика.

Хотя да как признаки(но не как критерий) что код скорее всего годный сойдет.
Зачастую полезно выделять методы/функции даже если дублирования кода нет, хотя это и связано с накладными расходами на вызов без всякой технической надобности. Когда абсолютно линейный код публичного метода на 200 строк разбит на 21 метод по 20 строк, то понять как он выполняет свою функцию можно взглянув только на сам публичный метод, из которого вызываются остальные приватные (при условии нормальных названий).
Да да, необходимо выделять методы для разделения уровней абстракции, даже если нет дублирования кода.
Молодец, красиво написал!
Хочу сказать спасибо.

И если не сложно: посоветуйте несколько фундаментальных книг по архитектуре приложений.
Спасибо!

Пара книг, заслуживающих внимания:
Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес «Приемы объектно-ориентированного проектирования. Паттерны проектирования» www.ozon.ru/context/detail/id/2457392/
Мартин Фаулер «Архитектура корпоративных программных приложений» www.ozon.ru/context/detail/id/1616782/
И вам еще раз спасибо.
Недавно была статья habrahabr.ru/blogs/code_review/135234/#habracut

Цитата оттуда:

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


Ой глухое это дело «хороший код» vs «потоки сознания» и прочее. Вот приходит тут такой умник и начинает писать «хороший код» который лично я называю «академическим» придумывает иерархии интерфейсы и т.д. Но имеем что?
1. Затягивание сроков, что уже плохо.
2. Ситуация когда заказчик знает что ему надо хотябы в общих чертах это уже идеально, обычно он хочет «хочу чтобы было все чиста клева» поэтому часто надо нарисовать по быстрому дабы убедиться что все все поняли.
3. Практика показывает, что все эти стройные классы, инкапсуляции, наследования и другие умные слова в динамично развивающемся проекте старше 3х лет гроша выеденного не стоят. Ибо давно засунули в класс публичный член или сломали иерархию или как то заюзали классик по хитрому.
4. Я не знаю ни одного программера который бы сумел предусмотреть все. Причем чем опытнее программер тем больше он предусмотрит и тем сложнее будет впихнуть то про что он забыл «но заказчику это надо позарез»отсюда см. п3.
5. Костыли в «стройной системе костылей и подпорок» выглядят надежнее и гармоничнее, чем костыли в «стройной иерархии и архитектуре». А я еще не встречался с продуктами где обошлись без костылей.

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

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

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

Так что я не доверяю ничему сложнее «калькулятора» и для меня главный критерий качества кода — его понятность/очевидность и уже во вторую очередь «архитектура»
Нет, переписать заново — это не выход. Потому что мы уже точно знаем, как делать не надо, а значит, придется разрабатывать все заново — структуры классов, потоки данных, взаимодействие всего на свете… В итоге мы оказываемся в той же дыре, с какой начали, только на новом уровне :(
дак это и есть развитие — знаете от его можно отказаться… «переписать» == «разработать заново», и конечно же не написать тоже самое.
Хорошая статья, спасибо.
Прочитал,

Компанию Microsoft и Netscape упомянутые в статье я знаю много слышал и использовал, как и про их продукты. А вот про Joel Spolsky, его компанию Fog Creek Software и продукты этой компании я услышал впервые. Так что позвольте с изрядной долей скептицизма отнестись к его советам и коментариям. Переписывание с нуля МОЖЕТ позволить (а может конечно и нет) вывести ваш продукт на качественно новый уровень, в то время как рефакторинг такого качественного скачка (конечно если уж изначально было не все так плохо) не даст.

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

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

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

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

А потом кто то решает переписать фабрику, изменить концепцию, по другому подойти к проблеме пользователя и построить завод деревянных дверей или «придумать» окна. Опаньки новые пользователи, новые характеристики, новые возможности, а все потому что когда то решили закрывать проем тканью вместо того чтобы придумать петли…
Эм… Ну, господина Спольски знать бы надо (или, по крайней, мере погуглить прежде, чем заявлять о его «ничтожности»). Фигура очень известная среди тех, кто следит за индустрией. С 2000 года ведёт блог (один из первых блогов о разработке софта, кстати).

Начал работать в Microsoft (увы, он его не основал, а всего лишь руководил командой программистов в MS Excel).

Помимо всего прочего, он — соснователь StackOwerflow, если вы о таком ресурсе слышали.
Да уже погуглил.
Чтото последнее время не до блогов было. Про StackOverflow конечно же слышал. Но факт остается фактом — критикуя ОДНО ИЗ решений Microsoft человек находится уровнем ниже. А про то что это решение помогло возможно избежать проблем в других сферах? Отрицательный результат это тоже результат и иногда не менее полезный.
Что касается дверей и ковров — не соглашусь. Отличие двери от ковра — это отличие ноутбука от настольного компьютера. Парадигмы разные. Дверь — это не «переписанный» ковёр, а принципиально другой продукт.

В данном случае под «переделать» подразумевается — снести фабрику ковров, чтобы на её месте построить новую ультрасовременную… фабрику ковров. А дальше, как Вы и написали: за время, пока вы перестраиваете фабрику ковров, кто-то начал производить двери.

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

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

А потом вы идете и выясняете а зачем емиу молоток — гвозди забивать, а зачем их забивать — картины на стену вешать, стоп а стены какие — гипсокартонные и у вас молоток плохой — из гипсокартона забитые им гвозди вываливаются. И Ваш архитектор придумывает шуруп и отвертку и ему выдвигают премию. Ура все довольны.
По-моему, хорошая архитектура это синоним «понятность и очевидность». То, про что вы говорите, скорее годится для прототипирования, но никак для коммерческого кода, который подразумевают поддерживаемость более чем одним разработчиком и временем жизни не пару месяцев, а хотя бы пару лет.
Опять же, не знаю. У нас проекту уже лет десять, в нем недавно насчитали полмиллиона строк, работает над ним 4-5 человек (причем состав плавно меняется), почти каждый кусок когда-то переписывался с нуля, иногда с полной заменой структуры классов. О техзадании в начале не могло быть и речи, поскольку это поддержка аппаратуры, возможности которой тоже не стоят на месте. И ничего, живет.
Ой вашими бы устами :)

Продукту лет десять, текущей инкарнации (основной части кода) около 4х лет. Количество этой части далеко за миллион строк. Надежность 365/7/24. Поддержка/разработка полсотни человек.

Предыдущий «проект» слишком громкий. Исходников несколько гиг, разработка по всему миру, надежность не такая высокая но огромная комеррческая стоимость. Разработка по CMM5 и CMMI.

Так что я знаю что такое «хороший код», и какой код оказывается в итоге в конечном продукте. Как его поддерживать, и как тестировать.

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

Может звучит слишком идеально, но зато есть к чему стремиться.
Дак стремиться всегда хорошо. Прочитать того же Фаулера — полезно. Плохо только когда начитавшись и думая что все поняли начинают городить куды не попадя.

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

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

Я, правда, немного в другом ключе, этот же эффект, только при работе с текстами и нежелание что-то править после написания нахрапом за пару часов называю «эффект пиараста».
Термин понравился «дамп потока сознания» — беру себе в копилку :)
Вы подняли важную тему! Соглашусь с вами на 100%, что «дамп» — это зло с которым надо бороться. Мне попадался в руки кусок кода, в котором был один обработчик «Button1_Click» и код на 2000 строк от вычитки из БД до генерации Word и Excel. Выглядело смешно, но кому-то же довелось сопровождать это «чудо», а не просто поржал и забыл…

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

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

А вот написать «Дамп сознания» и потом его структурировать — задача потрудней… Порой этот код уже нельзя «вывести из потока в цивилизованное русло» просто не стерев все и не написав сначала уже как надо.

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

Особенно хорошо этот метод работает вместе с, как ни странно, TDD: после написания прототипа рефакторится его контракт вместе с написанием тестов (не обращая внимание на то, что некоторые тесты будут падать — не исправляя код прототипа), после чего по готовому контракту пишется нормальная программа.
Sign up to leave a comment.

Articles