Философские рассуждения строят примерно так. Давайте все согласимся с тем, что "цель жизни — быть счастливым". Затем напишем один том про счастье, другой про цели, третий про жизнь, и четвёртый про бытие.
Принципы ОО-дизайна строят, в свою очередь, так. Давайте согласимся с тем, что "класс должен отвечать за нечто одно". И напишем один том про классы, другой про ответственность, третий для прояснения вопроса что является чем-то одним, а что следует считать двумя разными вещами.
Как не трудно видеть, принципы ОО-дизайна являются видом философских рассуждений, с поправкой на то, что их авторы пытаются, так сказать, сесть не в свои сани: пропускают наработки нескольких веков на тему того, как строить рассуждения на общие темы, чтобы они не превращались в пустопорожнюю болтовню и ментальный эксгибиционизм, и сразу начинают выводить законы функционирования компьютерных систем и человеческого разума.
Натужные попытки проиллюстрировать общие принципы конкретным кодом превращаются, обычно, в иллюстрацию, отрицающую исходный посыл: автор примера сразу же вынужден делать оговорки типа "пример получился дурацкий, и никто так не пишет, но надо же было как-то показать что имеется в виду".
Однако, без каких-то общих принципов тоже сложно обходиться, всякому рациональному человеку хочется поверх практики построить теорию.
Для дизайна простых классов вот к чему стремится всякий разумный программист:
1. Минимизировать "состояние". На практике, это означает использование минимально возможного количества instance-переменных, задающих "базис" объекта. Если у человека в instance-переменных хранится имя и фамилия, то не должно быть instance-переменной, где хранилось бы имя с фамилией одной строкой.
2. Не смешивать "классы-вещи" и "классы-процессы". Если объект "человек" должен, кроме хранения данных об означенном человеке, ещё после своей инициализации сходить на сторонний сервис и отправить пару email-ов, то данный принцип оказался нарушен. Процесс "Регистрация человека" должен быть выделен отдельным классом.
3. Не нарезать код на части без необходимости. Если у некоего метода 200 строк это не повод нарезать его на 20 методов по 10 строк. Поводом станет (реальная или реалистично прогнозируемая) необходимость повторно использовать логику фрагмента этого метода в другом методе.
4. Использовать язык программирования для того, чтобы сделать код понятным другому программисту. Соглашусь, что принцип широкий. Но всё же поддаётся формализации — лингвисты даже естественные языки формализуют вполне успешно. В соответствии с данным принципом:
- название любой сущности (класса, метода, переменной, константы) должно соответствовать содержимому (
array плохо, apples хорошо и др.)- если нечто делается дважды одинаково, то и на третий раз следует делать точно также (следование этому принципу, кроме всего прочего, облегчит рефакторинг повторяющегося кода)
- если нечто можно выразить управляющей конструкцией или оператором языка, не следует изобретать или прятать это внутрь некоего класса или метода
5. Избегать циклических зависимостей (на уровне методов, классов, архитектурных частей проекта).
6. Избегать зависимостей "через слой" — некий архитектурный элемент должен знать только то, что лежит не более одного уровня "выше" или "ниже" (также на уровне методов, классов, архитектурных частей проекта). Снова, на первый взгляд, философский принцип. Но, на самом деле, понятие "архитектурных уровней" вполне неплохо операционализируется — понятно, что "уровень базы данных" на один "ниже", чем "уровень модели" (и в прочих случаях).
7. Снижать "цикломатическую сложность". Например, не вкладывать условие в условие, если можно добавить ветку в корневое условие (аналогично, не вкладывать условие в rescue-блок, проверяя вид исключения, если можно добавить отдельную rescue-ветку; и т. п.).
Вот такие вот семь заповедей разумного программиста :)
#programming