Как стать автором
Обновить

Книга «Пять строк кода. Роберт Мартин рекомендует»

Время на прочтение9 мин
Количество просмотров16K
Всего голосов 13: ↑12 и ↓1+11
Комментарии24

Комментарии 24

Чего только люди не придумают лишь бы удобные языки с паттерн-матчингом и гардами прямо в сигнатуре функций не использовать)

Я могу быть неправ, но мое мнение об этом всем такое:

  1. Да, логику фильтрации/валидации нужно отделять от процессов обработки

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

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

Согласен

А потом мы удивляемся почему простенький код стал поглощать такое огромное количество памяти...

По мне свичи действительно много удобнее, чем нагромождение if else, но менять их на классы тоже кажется перебором...

с else if снаружи понятно, что исполняется только одна ветка, со switch нужно смотреть тело: есть ли там break

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

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

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

Ну микроконтроллеры это отдельная планета, и к ним БОЛЬШИНСТВО рекомендаций по архитектуре неприменимы, точнее, там свои архитектурные принципы, не укладывающиеся в парадигмы ООП.

Часто там нельзя использовать даже динамическое выделение памяти (ибо это недетерминированная вещь), поскольку есть требования жесткого реального времени. А именно на динамическом выделении памяти, основаны все эти модные, молодежные концепции :)

Абсолютно верно!

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

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

Но вообще есть такой рефакторинг -- "замена условия полиморфизмом", смысл его в следующем.
Вот у вас есть enum, и вы обрабатываете его с помощью switch -- при этом при добавлении нового элемента перечисления вам нужно найти все свитчи и внести в них изменения. Это явное нарушение OCP.
Если же у вас вместо енума набор классов и для принятия решений вместо свитча используется полиморфизм, то при добавлении нового элемента вы просто пишите новый класс, клиентский код при этом не затрагивается. OCP радуется :).

Разумом я понимаю что вы правы в данном вопросе.

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

У меня просто своя специфика - большая часть программирования для меня так называемые "встраиваемые системы". Причём их "младшая" часть - микроконтроллеры. Несмотря на то, что уже лет 10 исключительно на ARM делаю устройства, часто приходится оптимизировать изделие по стоимости комплектации, а производители любят ограничивать размер как RAM так и FLASH памяти в наиболее дешёвых чипах в линейке.

По этой причине роскошью иногда выглядит даже переход с C на С++. До последнего времени компиляторы плюсов генерили существенно более "тяжёлый" код...

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

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

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

Обычно выручает метод SPIFE - Stable, Performant, Intuitive, Functional, Extendable - и именно в таком порядке.

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

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

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

А наворачиванием шаблонов качество можно только ухудшить.

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

Как когда-то давно говорил один мой преподаватель, "пишите код для компьютера, а комментарии для людей. И не перепутайте!"

Конечно, производительный код может быть write only, особенно если речь об интринсиках. Там не то что комментарии, а картинку надо прикладывать.

Или обратный пример из жизни - пришёл на новый проект на с++ бывший ява-программист. И решил известный ему ява-фреймворк на с++ повторить, потому что по другому он работать не умел, видимо. А когда он уволился, оказалось, что то, что он наваял, не то что поддерживать, даже отлаживать было почти не возможно - паттерн на паттерне, 100500 классов на каждую мелочь, методы по 2-3 строчки... В общем когда это чудо выпилили, проект стал чудесным образом раза в 3 быстрее работать)

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

На этом же примере (игровом) видно, какое тут качество проектирования. Вся нарисованная гибкость очевидно идет в мусорку при расширении условий до, скажем, 3d перемещений. 2д случай с диагональными перемещениями тоже что так, что этак потребует переписывание "клиентского" кода. Контрольный выстрел - добавить хэндлинг "fire" в игру.

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

ЗЫ в конкретном примере fsm очевидно самая подходящая абстракция.

я думал, будут рецепты от гуру, как сделать код еще короче

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

const keyMap = {
  "a" : Input.LEFT,
  "w" : Input.UP,
  "d" : Input.RIGHT,
  "s" : Input.DOWN,
}

window.addEventListener("keydown", e => {
  const input = keyMap[e.key];
  if(input) inputs.push(input);
});

А если, например, добавим на Shift присесть, будет работать?

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

допишите в объект
"Shift":Input.Shift,

window.addEventListener("keydown") в JavaScript обрабатывает и Shift

а вместо значений Input в поля объекта можно сразу писать функции,
сократив код сразу до целевой логики

только там еще и функции разные:

moveHorizontal(1);

moveVertical(1);

Но это тоже решается! Для илюстрации идеи вот такой ПСЕВДО код:

const keyMap = {
  "a" : new ConstObj(moveHorizontal, 1),
  "w" : new ConstObj(moveHorizontal, -1),
  "d" : new ConstObj(moveVertical, 1),
  "s" : new ConstObj(moveVertical, -1)
}

window.addEventListener("keydown", e => {
  const input = keyMap[e.key];
  if(input) inputs.push();
});

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

push() просто должен вызвать этот сохраненный в классе метод с сохраненным параметром.

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

И никаких Switch-ей не надо!

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

НЛО прилетело и опубликовало эту надпись здесь
Зарегистрируйтесь на Хабре, чтобы оставить комментарий