Dev48
ЯЗЫК
  • О нас
  • Услуги
  • Индустрии
  • Технологии
  • Статьи
  • Контакты
Забронировать звонок
    Главная/Статьи/Object calisthenics 9 uprazhneniy kotorye podnimut kachestvo vashego koda na nov
Dev48
info@dev48.org+375 29 320-40-96WhatsApp@isky_by

© 2026 · All rights reserved.

Object Calisthenics: 9 упражнений, которые поднимут качество вашего кода на новый уровень

Источник: Telerik Blogs•5 ноября 2026 г. в 23:41

Object Calisthenics: 9 упражнений, которые поднимут качество вашего кода на новый уровень

Object Calisthenics — это набор из девяти правил, который ставит под сомнение то, как мы думаем и пишем объектно-ориентированный код. Узнайте, как они могут помочь в развитии нашего кода.

Object Calisthenics — это набор из девяти правил, который ставит под сомнение то, как мы думаем и пишем объектно-ориентированный код. Узнайте, как они могут помочь в развитии нашего кода.

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

Именно в этом контексте Object Каlisthenics сохраняет свою актуальность. Созданные как набор упражнений для формирования лучших привычек объектно-ориентированного программирования, эти правила не предназначены для слепого следования. Вместо этого они призваны бросить вызов тому, как мы моделируем объекты, и побудить нас писать связный, выразительный код с четко определенными зонами ответственности.

В этой статье мы рассмотрим девять правил, предложенных в Object Calisthenics, разберем логику каждого из них и проанализируем, почему они по-прежнему актуальны для современных приложений на C# и ASP.NET Core.

Что такое Object Calisthenics?

Object Calisthenics — это набор из девяти правил, предложенных Джеффом Бэем (Jeff Bay) и впервые опубликованных в 2008 году в книге The ThoughtWorks Anthology.

Термин «каллистеника» (calisthenics) относится к физическим упражнениям, выполняемым с использованием собственного веса тела для развития силы, координации и дисциплины. Идея Object Calisthenics следует той же философии: вместо обучения новым возможностям языка она предлагает ряд ограничений, которые побуждают разработчиков практиковать более качественное объектно-ориентированное моделирование.

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

Хотя правила были предложены много лет назад, проблемы, которые они призваны решить, по-прежнему существуют в современных приложениях. Мы все еще сталкиваемся с анемичными доменными моделями, классами с избыточной ответственностью и сильной связанностью между объектами. Object Calisthenics предлагает практический способ выявления этих проблем и поощрения решений, основанных на поведении, а не на данных.

Знакомство с 9 правилами

В следующих разделах мы подробно разберем каждое правило, поймем его суть и обсудим, как его можно применять или адаптировать в современных приложениях, разработанных на C# и ASP.NET Core.

Эти девять правил таковы:

  • Один уровень вложенности на метод
  • Не используйте ключевое слово ELSE
  • Оберните все примитивные типы и строки
  • Коллекции первого класса
  • Одна точка на строку
  • Не сокращайте названия
  • Сделайте все сущности маленькими
  • Никаких классов с более чем двумя переменными экземпляра
  • Никаких геттеров, сеттеров и свойств

Все примеры кода, рассмотренные в публикации, доступны в этом репозитории: Practicing Object Calisthenics.

1. Один уровень вложенности на метод

Первое правило рекомендует использовать только один уровень вложенности в методе. Всякий раз, когда мы сталкиваемся с несколькими вложенными блоками (такими как if, foreach, while, try и т.д.), это обычно указывает на то, что метод берет на себя слишком много обязанностей. Чрезмерная вложенность усложняет понимание потока выполнения и увеличивает сложность, что затрудняет поддержку кода.

Рассмотрим следующий пример сервиса, отвечающего за одобрение заявок на кредит.

❌ До

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

Более объектно-ориентированный подход заключается в делегировании некоторых решений меньшим методам и использовании защитных выражений (guard clauses) для прерывания выполнения, когда предварительное условие не выполнено.

✅ После

Обратите внимание, что поведение метода ApproveAsync осталось прежним, но теперь каждый вспомогательный метод имеет единственную ответственность. Метод ApproveAsync четко демонстрирует намерение операции, в то время как CanBeApproved инкапсулирует правила допустимости, а ApproveLoan выполняет только само одобрение.

Основная цель этого правила — избежать создания методов, которые пытаются делать слишком много дел одновременно. При оценке каждого метода по отдельности обычно проще понять общий поток. Следовательно, предпочтительнее использовать лаконичные методы.

2. Не используйте ключевое слово ELSE

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

Чтобы следовать этому правилу, мы можем использовать такие стратегии, как ранний возврат (early returns) или даже полиморфизм, когда существует несколько условных поведений.

Рассмотрим метод отмены кредитной заявки.

❌ До

Метод Cancel имеет два совершенно разных потока: один для успешной операции, а другой для обработки недействительного состояния.

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

✅ После

Теперь метод представляет только основной поток выполнения операции. Исключительная ситуация обрабатывается в самом начале, и выполнение немедленно прекращается, что делает блок else ненужным. Такой подход улучшает читаемость и четко выделяет «счастливый путь» (happy path) операции.

Важно отметить, что это правило не предписывает полностью отказываться от else. Есть ситуации, когда его использование делает код более понятным, например, в простых выражениях (вроде тернарного оператора ?:) или когда два потока имеют одинаковую важность. Цель состоит в том, чтобы стимулировать создание методов с легко читаемым потоком, а не в том, чтобы устранять else любой ценой.

3. Оберните все примитивные типы и строки

Третье правило рекомендует инкапсулировать примитивные типы (int, decimal, string, DateTime и т.д.) в объекты, которые представляют концепции предметной области.

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

Рассмотрим пример ниже.

❌ До

Этот пример представляет собой классический случай использования примитивных типов. На первый взгляд в нем нет ничего плохого. Однако такая реализация позволяет создавать объекты с недопустимыми состояниями:

Компилятор принимает все эти значения, поскольку они допустимы для типов decimal и int, но для кредитной заявки они не имеют никакого смысла, так как являются отрицательными или равными нулю.

В качестве альтернативы мы можем инкапсулировать эти концепции в объекты-значения (Value Objects).

✅ После

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

Таким образом, код начинает отражать концепции реального мира, такие как «Деньги», «Процентная ставка» и «Количество платежей».

Заметьте, что код становится гораздо более выразительным. Теперь мы создаем экземпляры бизнес-концепций, а не просто манипулируем числами и другими данными. Помимо читаемости, этот подход централизует логику валидации и предотвращает создание объектов в невалидных состояниях.

4. Коллекции как объекты первого класса

Четвертое правило рекомендует не относиться к коллекции просто как к свойству класса. Напротив, предпочтительно, чтобы коллекция обладала собственным поведением.

Исходя из этого правила, нам следует избегать создания свойств, которые возвращают List<T> и позволяют любой части приложения напрямую добавлять, удалять или изменять его элементы. Проблема в том, что в таком случае правила, определенные для этой коллекции, оказываются разбросанными по всему коду, который с ней работает.

Рассмотрим кредитную систему, где заявка состоит из нескольких платежей.

❌ До

Обратите внимание, что любой код, имеющий доступ к списку, может изменять его напрямую:

Это означает, что LoanApplication не имеет реального контроля над собственной коллекцией.

Возможным решением является превращение самой коллекции в объект, отвечающий за свое собственное поведение.

✅ После

Теперь коллекция (Installments) «знает» правила, определяющие ее элементы. Следовательно, мы больше не можем добавлять в нее элементы без использования ее собственных методов:

Чтобы добавить элемент в коллекцию теперь, нам нужно использовать ее специализированный метод:

Основная идея правила коллекций первого класса заключается в том, что коллекция может быть гораздо большим, чем просто List<T>. Этот подход стоит выбирать, когда у коллекции есть собственные правила, проверки или поведение, которые должны быть представлены в модели.

С другой стороны, в сценариях, где коллекции используются для передачи данных (например, список для формирования ответа API), применять это правило, скорее всего, не требуется.

5. Одна точка на строку

Пятое правило рекомендует запрещать объекту знать внутреннюю структуру других объектов. Один из наиболее частых признаков нарушения этого правила — наличие цепочек вызовов, когда один объект обращается к другому, тот к третьему и так далее.

Представьте, что при одобрении кредита нам нужно зафиксировать город клиента:

❌ До

Проблема здесь заключается не столько в количестве точек, сколько в том, что LogLoanApproval слишком много знает о внутренней структуре LoanApplication. В результате мы получаем следующую цепочку: LoanApplication -> Customer -> Address -> City -> Name.

Если структура изменится с application.Customer.Address.City.Name на application.Customer.Residence.CityName, этот код также придется обновить.

✅ После

Мы можем делегировать ответственность объекту, который хранит эту информацию:

Теперь любому коду, использующему Customer, не нужно знать его внутреннюю структуру. Если реализация Address или City изменится, влияние этого изменения будет ограничено самим классом Customer.

Цель этого правила состоит не в том, чтобы полностью устранить цепочки навигации, а в том, чтобы предотвратить зависимость внешнего кода от внутренней структуры других объектов. Именно поэтому цепочка Address.City.Name все еще существует внутри Customer. Перенеся это знание в Customer, при изменении внутренней структуры Address или City обновлять придется только Customer.

6. Не сокращайте

Шестое правило — пожалуй, одно из самых простых, но в то же время ключевых: не сокращайте имена переменных, методов, классов или свойств.

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

❌ До

Код может быть быстро понят тем, кто его написал, но читателю приходится расшифровывать различные аббревиатуры, такие как «CalcTot» («Calculate Total» — рассчитать итого) и «ords» («Orders» — заказы). Никакой реальной выгоды в сокрытии этой информации нет.

Если записать слова так, как они есть на самом деле, мы получим следующее:

✅ После

Теперь этот код понятен без каких-либо дополнительных объяснений.

7. Держите все сущности маленькими

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

Это не значит, что класс длиной, например, в 49 строк лучше класса в 51 строку. Скорее, это призыв воспринимать размер как предупреждающий знак: когда класс становится слишком большим, в нем обычно появляются обязанности, которые можно было бы передать другим объектам.

Рассмотрим класс, отвечающий за представление заказа:

❌ До

Проблема здесь не в количестве строк, а в том, что класс совмещает обязанности из разных контекстов: представление заказа, расчет итоговой суммы, валидацию заказа, отправку электронной почты, экспорт в CSV и сохранение данных. По мере добавления новых функций класс Order будет становиться все больше и больше.

Чтобы избежать такого непропорционального роста, мы можем сосредоточить Order исключительно на поведении, которое действительно относится к заказу:

✅ После

Валидация может располагаться в собственном компоненте:

Отправка электронной почты может быть ответственностью выделенного сервиса:

А за персистентность отвечает репозиторий:

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

Столкнувшись с очень большим классом, мы можем задать себе вопрос: «Если мне потребуется изменить эту часть поведения, сколько других обязанностей внутри этого класса могут затронуть эти изменения?»

Если ответом будут разные причины для модификации класса, значит, появилась возможность разделить эти обязанности.

8. Никаких классов с более чем двумя переменными экземпляра

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

Рассмотрим простой класс для представления студента:

❌ До

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

✅ После

Теперь у нас есть модель, в которой концепции выражены более явно:

Самое главное здесь — не просто сократить количество переменных в классе Student с семи до двух. Реальная польза заключается в выявлении концепций, которые были скрыты внутри класса, и преобразовании их в явные объекты. Следовательно, это правило следует рассматривать как ограничение, стимулирующее поиск хороших объектов, а не как математическую догму.

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

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

Рассмотрим простой пример:

❌ До

Проблема заключается в том, что любая часть приложения может напрямую изменять баллы:

Таким образом, у класса нет механизма для защиты своего состояния. Более объектно-ориентированный подход заключается в инкапсуляции поведения:

✅ После

Теперь потребителю не нужно манипулировать состоянием напрямую, сам объект решает, как должны выполняться эти операции.

Стоит отметить, что это правило не предписывает полностью отказываться от использования свойств. Скорее, цель состоит в том, чтобы наши классы не превращались в простые «хранилища данных», где вся логика в итоге оказывается разбросанной по другим компонентам.

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

Заключение

В этой статье мы рассмотрели девять правил Object Calisthenics и увидели, как каждое из них решает проблемы, все еще распространенные в современных приложениях: анемичные модели доменной области, классы с избыточной ответственностью, сильную связанность между объектами и бизнес-правила, разбросанные по слоям.

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

Для кода, работающего с инфраструктурой, DTO, контроллерами и другими граничными слоями, обычно более умещен простй подход.

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

Объектно-ориентированное программирование предлагает нам отличные инструменты, и Object Calisthenics — это способ помочь нам извлечь из них максимум пользы.

Продолжайте изучать материалы по ASP.NET Basics.

Продолжайте изучать материалы по ASP.NET Basics.

← Все статьи

О чём эта статья

Ещё в разделе «Разработка ПО»

Все →
Конкурс Kotlin Multiplatform открыт: выиграйте поездку на KotlinConf
JetBrains

Конкурс Kotlin Multiplatform открыт: выиграйте поездку на KotlinConf

Спецификация графа онтологий Neo4j: прагматичный, открытый подход, ориентированный на ИИ
Neo4j

Спецификация графа онтологий Neo4j: прагматичный, открытый подход, ориентированный на ИИ

Как BondiBoost поддерживает 98% своевременного выполнения заказов и точность прогнозов доставки с помощью ShipBob [Кейс]
Shipbob

Как BondiBoost поддерживает 98% своевременного выполнения заказов и точность прогнозов доставки с помощью ShipBob [Кейс]

ИИ ускоряет процесс обнаружения и устранения уязвимостей безопасности в Windows
Microsoft

ИИ ускоряет процесс обнаружения и устранения уязвимостей безопасности в Windows

HCLTech Enterprise AI-DLC: Руководство для практиков по разработке программного обеспечения с использованием ИИ
HCLTech

HCLTech Enterprise AI-DLC: Руководство для практиков по разработке программного обеспечения с использованием ИИ

Платформенная инженерия для гибридного облака
HCLTech

Платформенная инженерия для гибридного облака

Ещё от Telerik

Проектирование динамических приложений для пользователей и агентов
Telerik

Проектирование динамических приложений для пользователей и агентов

Основы Blazor: Сохранение состояния компонентов в Blazor с .NET 10
Telerik

Основы Blazor: Сохранение состояния компонентов в Blazor с .NET 10

Проектирование делегирования с прогрессивной автономностью
Telerik

Проектирование делегирования с прогрессивной автономностью

Введение в gRPC как в инструмент для связи микросервисов
Telerik

Введение в gRPC как в инструмент для связи микросервисов

Как создавать эффективные тепловые карты в Blazor
Telerik

Как создавать эффективные тепловые карты в Blazor

Что такое оценки ИИ-агентов и почему они важны?
Telerik

Что такое оценки ИИ-агентов и почему они важны?