Плохой код #1
Этот цикл статей вдохновлен книгами Роберта Мартина “Чистый код” и Брайна Кернигана и Роба Пайка “Практика программирования”. В нем я постараюсь привести реальные примеры плохого кода и возможные пути его исправления с подробными пояснениями.
Эта статья начинает серию “Плохой код”, в котором вам предлагается исправить различные ошибки и недостатки небольшого куска кода. Ошибки могут быть разные - код может быть неправильным, код может быть неправильно спроектирован, код может “плохо пахнуть” и так далее.
Важно понимать, что не существует единственно правильного решения. Но при каждом исправлении важно понимать, почему вы это исправление делаете.
Для разминки предлагаю исправить такой код. Он специально утрирован, чтобы показать, что даже в нескольких строках кода может быть большое количество проблем.
Для ясности: handleInput
- метод какого-то класса, возможно персонажа, которым мы можем управлять.
Исправления
Явные недочеты
Начнем с очевидных моментов.
Каждый раз, когда вы видете нетривиальную строку и рядом с ней комментарий, который ее поясняет - это плохая строка. if(input.keyCode == 87) { // 'w'
- неужели каждый раз придется писать рядом // 'w'
? А если мы поменяем ее на if(input.keyCode == 86) ...
нам придется менять и сам комментарий. Один раз мы это сделаем, другой раз забудем. И получится врущий комментарий if(input.keyCode == 86) // 'w'
. Это одна из причин, почему таких комментариев стоит избегать.
Кстати, об изменениях. Что, если ниже у нас есть еще пара методов, в которых тоже есть злополучные if(input.keyCode == 87)
, а мы вдруг захотим вместо WASD
использовать стрелки клавиатуры? Придется в каждом месте руками изменять 87 на 35, 68 на 39 и так далее. Не хорошо.
Первое изменение
Попробуем вынести так называемые “магические константы” в отдельный объект.
Как видите, мы избавились от ненужных комментариев, при этом читаться код хуже не стал.
Второе изменение
Однако, если мы захотим изменить клавиши управления нашим персонажем, у нас получится что-то вроде
Опять получается, что наш код врет. Мы говорим, что букве А соответсвует число 37, хотя это не так. Конечно, наш код будет работать, и наш персонаж будет двигаться при помощи стрелок, однако в дальнейшем такой код может легко запутать всех, кто будет над ним работать, включая вас, самого автора.
Для исправления этой проблемы достаточно изменить имена переменных
Теперь из имен констант нам сразу становится смысл их предназначения, к тому же мы можем легко их переопределять, не боясь, что они потеряют смысл.
Третье изменение
Теперь подумаем, что, если мы захотим изменить скорость передвижения нашего персонажа. Нам придется изменять руками число 5 на значение, которое мы хотим, и делать это по всему коду. Нехорошо, учитывая, что число 5 в другом месте может означать совершенно другое значение, и, если у нас много таких магических констант, каждое такое исправление превратиться в ад. И если такое происходит - ваш код точно плох.
Определим методы для движения нашего персонажа, а также вынесем скорость в отдельные переменные, которые легко изменять.
Мы получили еще один плюс - теперь метод handleInput
читается гораздо легче. Попробуйте произнести его вслух - “если нажата клавиша вверх - двигайся вверх, иначе, если нажата клавиша вправо - двигайся враво и так далее”. Намного лучше, чем “если код клавишы 87, увеличь свою координату на 5, иначе…” - сразу становятся понятны намерения разработчка, сразу ясно, что этот код вообще делает.
Архитектура
Четвертое изменение
Теперь самый главный вопрос, который у вас должен возникнуть - почему сам персонаж должен отвечать за обработку нажатия клавиш, и почему сами константы INPUT_KEY
находятся рядом с ним? Нам явно нужен отдельный модуль, который будет отвечать за взаимодействие с пользователем, а наш персонаж лишь будет получать оповещения о тех или иных событиях. Реализаций такого поведения может быть много в зависимости от ваших предпочтений и более тонких нужд, я предлагаю использовать стандартную событийную модель подписок. Наш персонаж будет подписываться на события ввода и выполнять соответсвующее действие.
Ниже приведен лишь общий код, а не полная и конкретная реализация. InputConroller
по сути является стандартным Observable
. Про него вы можете почитать и реализовать самостоятельно в этой статье.
Теперь не только наш персонаж, но вообще все объекты в игре могут легко подписываться на события ввода. Более того, в сам InputContoller
может быть внесена логика смены управления, при этом весь остальной код трогать не придется - мы ведь просто подписываемся на событие ‘UP’, а какая клавиша за него отвечает - дело самого обработчика.
Вывод
Вот так в 10 строчках кода нашлось большое количество проблем, для решения которых потребовалось много шагов. Надеюсь, этой статьей мне удалось зародить в вас интерес к такой важной теме, как качественное написание кода.
Дальше код будет не только хуже, он будет коварнее. Ошибки будут таиться в самых неожиданных местах.
Спасибо!
P.S.
Еще одна важная проблема, которую я хотел затронуть - неправильное движение персонажа. Однако эта тема далека непосредственно он качества кода, здесь речь идет об игровой механике, и материала здесь на отдельную статью :) А если вкратце - попробуйте посчитать скорость нашего персонажа вдоль осей Х и Y и вдоль диагоналей, если нажимать одновременно W+A например.
Комментарии