Классы
Классы
За всю недолгую историю вычислительной техники никто не написал идеального программного обеспечения. Маловероятно, что первым станете вы. — Энди Хант, автор книги «Программист‐прагматик»
Согласно книге Роберта К. Мартина «Чистый код», первое правило классов гласит: классы должны быть небольшими. Второе правило: они должны быть еще меньше. Ограничение размера класса делает его более целенаправленным и связным. Существующий класс легко обрастает функциями, пока не становится перегруженным. Осознанно делайте классы небольшими: крупные раздутые классы трудно читать и отлаживать.
Метафора газетной статьи Представьте исходный код класса как газетную статью. Чтение начинается сверху: сначала внимание привлекают заголовок и имя автора. Вводный абзац дает общее представление, а по мере продвижения вниз раскрываются подробности.
Журналисты называют такую структуру перевернутой пирамидой. Самое важное излагается в начале, а тонкости раскрываются ближе к концу статьи. Класс должен следовать тому же принципу. Располагайте код сверху вниз и выстраивайте методы в иерархию. Методы верхнего уровня задают общую картину — размещайте их первыми. Ниже располагайте низкоуровневые методы с деталями реализации. Например, метод ThrowBall может вызывать SetInitialVelocity и CalculateTrajectory. Сначала разместите ThrowBall, поскольку он описывает основное действие, а затем добавьте вспомогательные методы. Каждая газетная статья коротка, но множество статей вместе образуют единое целое — газету или новостной сайт. Так же устроен проект Unity: многочисленные классы должны совместно образовывать крупное, но связное приложение.
Организация класса Каждому классу нужна единая структура. Для порядка группируйте члены класса по разделам: — Поля — Свойства — События и делегаты — Методы MonoBehaviour (Awake, Start, OnEnable, OnDisable, OnDestroy и т. д.) — Открытые методы — Закрытые методы Помните правило именования классов в Unity: имя исходного файла должно совпадать с именем содержащегося в нем MonoBehaviour. В файле могут находиться и другие внутренние классы, однако MonoBehaviour в каждом файле должен быть только один, если только несколько MonoBehaviour не разделены пространствами имен.
Принцип единственной ответственности Помните: каждый класс должен оставаться небольшим. В проектировании ПО принцип единственной ответственности помогает сохранять простоту. Суть принципа в том, что каждый модуль, класс или функция отвечает только за одну задачу. Допустим, вы создаете игру Pong. Начать можно с классов ракетки, мяча и стены.
Сыграем в Pong?
Например, классу Paddle может потребоваться: — хранить основные данные о скорости движения — считывать ввод с клавиатуры — перемещать ракетку в ответ на ввод — воспроизводить звук при столкновении с мячом
Поскольку игра устроена просто, все перечисленные задачи можно объединить в базовом классе Paddle. Технически один MonoBehaviour способен выполнить все необходимое.
Хранить данные о скорости Считывать ввод с клавиатуры Перемещать ракетку Воспроизводить звук
Один MonoBehaviour выполняет все задачи
Однако объединение всего в одном классе, даже небольшом, усложняет архитектуру из-за смешения обязанностей. Данные переплетаются с вводом, а класс должен обрабатывать и то и другое. Вопреки принципу KISS несколько простых задач оказываются тесно связанными. Вместо этого разбейте Paddle на небольшие классы с одной ответственностью у каждого. Вынесите данные в PaddleData или ScriptableObject, а остальную логику распределите между PaddleInput, PaddleMovement и PaddleAudio. Класс PaddleLogic может обрабатывать ввод из PaddleInput. Используя данные о скорости из PaddleData, он перемещает ракетку через PaddleMovement. Наконец, при столкновении мяча с ракеткой PaddleLogic сообщает PaddleAudio, что нужно воспроизвести звук.
Скорость
Клавиатура
Движение
Звуки
Разделение обязанностей при рефакторинге класса Paddle
В новой архитектуре каждый класс выполняет одну задачу и остается небольшим и понятным. Чтобы проследить логику, больше не нужно прокручивать несколько экранов кода.
Скрипт Paddle по-прежнему нужен, но теперь он только связывает остальные классы. Основная функциональность распределена между ними. Чистый код не всегда самый компактный. Даже при использовании небольших классов после рефакторинга общее число строк может вырасти, зато каждый класс читается проще. При отладке и добавлении функций такая структура помогает сохранять порядок.
Пример рефакторинга Подробный разбор рефакторинга небольшого проекта приведен в статье «Как проектировать код по мере роста проекта». В ней показано, как разделить крупные MonoBehaviour на небольшие компоненты по принципу единственной ответственности. Также можно посмотреть оригинальный доклад Микаэля Калмса «От Pong до проекта с командой из 15 человек», представленный на Unite Berlin.