Гимнастика — это греческий термин для обозначения упражнений в смысле гимнастики. Я давно занимаюсь художественной гимнастикой , упражнениями для объектно-ориентированного программирования и считаю их увлекательными, но у меня так и не было времени попробовать.
Отказ от ответственности: правила, используемые во время объектной гимнастики, предназначены для использования в катах и других упражнениях, где есть контролируемая среда, а не в производственном коде.
В конце концов, даже если вы тренируетесь на беговой дорожке, это не значит, что у маратона будут немедленные измерения вашей скорости, кнопок пуска / остановки и наклона клиента. И что вы будете пробегать одинаковое количество километров в одинаковом темпе или придерживаться одинаковой диеты. Или что вы будете прибавлять дополнительные веса (что является лучшим сравнением). Условия тренировки отличаются от условий гонки.
Соревнование
Объектная гимнастика заключается в решении программной задачи и одновременно выполняет эти ограничения.
- Используйте только один уровень отступа для каждого метода: ides избегает циклов вложения и (что еще хуже) ifs
- Не используйте ключевое слово else : предотвращает использование условных ветвей для полиморфизма.
- Оберните все примитивы и струны: тренируйте ваши имена и моделируйте мышцы.
- Используйте только одну точку в строке : читайте как строго соблюдайте закон Деметры.
- Не сокращайтесь: на сегодня напишите сначала ElementSatisfyingCriteria, а не elem.
- Держите все объекты маленькими : это единственный качественный критерий. Вы можете установить приблизительный предел для строк кода, но это зависит от языка программирования.
- Не используйте классы с более чем двумя переменными экземпляра : только с двумя закрытыми полями вы можете достичь максимальной теоретической сплоченности класса.
- Используйте первоклассные коллекции : нет массивов или списков, вокруг которых не заключены объекты, содержащие их в качестве единственного частного поля.
- Не используйте никакие методы получения / установки / свойства: предпочитайте инкапсуляцию и процедуры, которые манипулируют данными рядом с ними.
Мой практический опыт
Я намеревался реализовать основные факторы ката , где целое число раскладывается в список его основных факторов. Это очень простая проблема, которая может быть решена с помощью алгоритма грубой силы; но, поскольку он прост, он обеспечивает рабочую среду для тестирования объектов в течение ограниченного времени.
Давайте посмотрим, как 9 правил повлияли на мое выполнение ката . Мой язык выбора — PHP, так как в каждом новом выполнении ката должен быть изменен только один фактор.
1. Используйте только один уровень отступа для каждого метода
Эта движущая сила побуждает меня постоянно извлекать методы : без этого вы не сможете избежать многоуровневого отступа. Извлечение также вынуждает вас рассмотреть, какая часть текущей области метода используется внутри циклов и ifs, и явно передать ее с параметрами. В этом diff $ divisor является единственным параметром, а $div инкапсулируется новым методом:
for ($divisor = self::TWO(); $divisor->lessOrEqualTo($this); $divisor = $divisor->increment()) { - if ($this->divisibleBy($divisor)) { - $divided = $this->divideBy($divisor); - $factors = $divided->primeFactors(); - $factors->add($divisor); - return $factors; + $factors = $this->decomposeWith($divisor); + if ($factors) { + break;
2. Не используйте ключевое слово else
В этом нет необходимости. Если вы инкапсулируете в метод if (как минимум), вы можете использовать возвращаемые значения функционально эквивалентным способом:
public function decomposeWith(IntegerNumber $divisor) { if ($this->divisibleBy($divisor)) { $factors = new PrimeFactors(); $divided = $this->divideBy($divisor); $factors = $divided->primeFactors(); $factors->add($divisor); return $factors; } return false; }
3. Оберните все примитивы и строки
Я сразу начал с создания IntegerNumber. Таким образом, то, что казалось бы функцией глобальной библиотеки в процедурном подходе — разложение числа в список факторов — сразу стало методом в экземпляре IntegerNumber.
Поскольку в этом алгоритме есть некоторые магические константы, такие как 1 и 2, я извлек их так же, как и объекты IntegerNumber, а не скаляры. хороший побочный эффект.
<?php class IntegerNumber { private $number; public function __construct($number) { $this->number = $number; } public function primeFactors() {
4. Используйте только одну точку в строке
В случае PHP, мой язык выбора, только одна стрелка после $ this-> , так как первая стрелка не может быть опущена. Соблюдение закона Деметры не было проблемой на этом небольшом графике, но оно заставляло меня постоянно избегать сторонних методов, заменяя их методами подходящего класса IntegerNumber или PrimeFactors.
5. Не сокращайте
Я не особо сокращаюсь, и в этом случае я старался быть еще более выразительным. Поэтому я извлек множество методов в классе IntegerNumber, чтобы дать имя различным выражениям, даже если вызов был таким же длинным, как само выражение из-за описательного имени.
public function divisibleBy(IntegerNumber $factor) { return $this->number % $factor->number == 0; }
6. Держите все сущности маленькими
Я узнал, что со всеми другими правилами это не было проблемой. Факторинг циклов, коллекций и методов постоянно оставляет классу не так много работы (и это хорошо: SRP). Особенно методы вынуждены быть меньше, чем обычно, из-за 1.
7. Не используйте классы с более чем двумя переменными экземпляра.
Проблема была слишком мала, чтобы эффективно проверить этот принцип. Я думаю, что лучший способ испытать это в каком-то DDD-подобном упражнении, где вы разлагаете сущности и цените объекты до конца. В этой задаче, где состояние сохраняется в основном за счет стека вызовов и числовых объектов, это ограничение не применяется.
8. Используйте первоклассные коллекции
Мне пришлось немедленно представить класс PrimeFactors, который обернул результат алгоритма. Это преобразовало старый массив, заполненный циклом, в объект, в который можно добавлять методы: я добавил как фабричный метод, так и метод add (), который в противном случае перешел бы в класс клиента.
<?php class PrimeFactors { private $factors = array(); public function fromScalars(array $scalars) { rsort($scalars); $factors = new self(); foreach ($scalars as $scalar) { $factors->add(new IntegerNumber($scalar)); } return $factors; } public function add(IntegerNumber $factor) { array_unshift($this->factors, $factor); } }
9. Не используйте какие-либо методы получения / установки / свойства
В моих классах нет геттера, который бы выставлял свои поля: даже сравнение в тестах проводится путем создания объекта, равного ожидаемому. Это идет рука об руку с 4: отсутствие геттеров заставляет вас добавлять логику в класс, где находится приватное поле, а не в файл, который вы уже «открыли» в вашем редакторе.
Множество небольших операций (которые вы можете легко выполнить внешне с помощью строки, содержащей вызов getter ) получают имя и публикуются в интерфейсе:
public function increment() { return new self($this->number + 1); }
Выводы
Эти автоматические правила — самые простые, чтобы подтолкнуть нас к объектно-ориентированным хорошим привычкам или, по крайней мере, заставить нас думать, когда мы собираемся их сломать. Тем не менее, дизайн не может быть реализован только по нескольким правилам: всегда есть обходные пути, которые нарушают их дух, например, называя все классы именами 7 гномов.
Разработка через тестирование помогла мне получить дизайн, который постепенно соблюдал правила: сначала я сделал код работающим, а затем сделал его чистым. Разумеется, вся процедура очистки проходила в месте проведения единого прохождения теста.
В целом, объектная гимнастика — хорошее упражнение в дизайне, которое заставляет вас думать, а не выкручивать код. Даже 5 строк могут быть действительно сложными для понимания, но в этом случае удобочитаемость гарантирована; Простота понимания зависит от того, кто пишет код: я мог бы сделать лучше, но именно поэтому я тренируюсь в первую очередь. И окончательный код действительно длинный: но дело в том, чтобы достичь идеального производственного кода.