Перейти к содержанию

Правила размера (Size)

Правила размера выявляют классы и пространства имен, которые стали слишком большими. Большие классы сложнее понять, протестировать и поддерживать. Эти правила устанавливают верхние границы допустимого размера.

Реализация принадлежит capability Analysis\Evidence\Size и по-прежнему использует и публикует факты метрик Measurement; идентификаторы правил и конфигурация не меняются.


Количество методов (Method Count)

Идентификатор правила: size.method-count

Судимая метрика: size.method-count

Что измеряет

Подсчитывает поведенческие методы класса. Метод с именем get/is/has считается геттером только при отсутствии параметров и единственном return $this->property;; set считается сеттером только при присваивании единственного параметра свойству экземпляра, с необязательным return $this;. Вычисляющий или пересылающий вызов метод с таким именем остаётся поведенческим. size.method-count.total считает все объявленные методы. Класс со слишком большим количеством методов, вероятно, делает слишком много и должен быть разделен на более мелкие, сфокусированные классы.

Как читать значение:

Методов Интерпретация
1--10 Сфокусированный класс
11--20 Большой класс
21--30 Очень большой -- проверьте ответственности
30+ God Class -- немедленно разделите

Пороговые значения

Значение Серьезность Значение
1--19 OK Приемлемый размер класса
20--29 Warning Класс становится большим, подумайте о разделении
30+ Error Класс слишком большой, нужен рефакторинг

Конфигурация

Опция По умолчанию Описание
enabled true Включить или выключить правило
warning 20 Количество методов для предупреждения
error 30 Количество методов для ошибки
# qmx.yaml
rules:
  size.method-count:
    warning: 20
    error: 30

Сокращённая запись с threshold:

rules:
  size.method-count:
    threshold: 25   # warning=25, error=25 → все нарушения — ошибки
bin/qmx check src/ --rule-opt="size.method-count:warning=25"
bin/qmx check src/ --rule-opt="size.method-count:error=40"

Пример

class OrderService
{
    public function createOrder() { /* ... */ }
    public function updateOrder() { /* ... */ }
    public function deleteOrder() { /* ... */ }
    public function findOrder() { /* ... */ }
    public function listOrders() { /* ... */ }
    public function validateOrder() { /* ... */ }
    public function calculateTotal() { /* ... */ }
    public function applyDiscount() { /* ... */ }
    public function sendConfirmation() { /* ... */ }
    public function generateInvoice() { /* ... */ }
    public function processPayment() { /* ... */ }
    public function refundOrder() { /* ... */ }
    public function shipOrder() { /* ... */ }
    public function trackShipment() { /* ... */ }
    public function notifyCustomer() { /* ... */ }
    public function exportToCSV() { /* ... */ }
    public function importFromCSV() { /* ... */ }
    public function archiveOrder() { /* ... */ }
    public function restoreOrder() { /* ... */ }
    public function auditOrder() { /* ... */ }
    // ...и еще -- этот класс делает всё!
}

Как исправить

  1. Определите группы методов. Найдите кластеры методов, которые работают вместе (например, все методы оплаты, все методы уведомлений).

  2. Извлеките сервисные классы. Переместите каждую группу в свой класс:

    class OrderService       { /* create, update, delete, find */ }
    class OrderPricing       { /* calculateTotal, applyDiscount */ }
    class PaymentProcessor   { /* processPayment, refundOrder */ }
    class OrderNotifier      { /* sendConfirmation, notifyCustomer */ }
    class OrderExporter      { /* exportToCSV, importFromCSV */ }
    
  3. Используйте композицию. Если исходный класс должен оркестрировать эти операции, внедрите новые сервисы как зависимости.


Количество классов (Class Count)

Идентификатор правила: size.class-count

Судимая метрика: size.class-count

Что измеряет

Подсчитывает классы, объявленные непосредственно в каждом пространстве имён (пакете), включая родительские. Классы потомков остаются в публикуемой сумме поддерева size.class-count.sum, но не влияют на решение правила для родителя. Пространство имён со слишком большим количеством собственных классов трудно обозревать.

Как читать значение:

Классов Интерпретация
1--10 Сфокусированное пространство имён
11--14 Умеренное
15--24 Большое -- рассмотрите разделение на подпространства
25+ Раздутое пространство имён

Пороговые значения

Значение Серьезность Значение
1--14 OK Сфокусированное пространство имен
15--24 Warning Пространство имен переполнено
25+ Error Нужно разделить на подпространства имен

Конфигурация

Опция По умолчанию Описание
enabled true Включить или выключить правило
warning 15 Количество классов для предупреждения
error 25 Количество классов для ошибки
# qmx.yaml
rules:
  size.class-count:
    warning: 15
    error: 25

Сокращённая запись с threshold:

rules:
  size.class-count:
    threshold: 20   # warning=20, error=20
bin/qmx check src/ --rule-opt="size.class-count:warning=20"
bin/qmx check src/ --rule-opt="size.class-count:error=30"

Пример

App\Service\                  # 28 классов -- слишком много!
├── UserService.php
├── OrderService.php
├── PaymentService.php
├── NotificationService.php
├── ReportService.php
├── ... (еще 23 файла)

Как исправить

  1. Сгруппируйте связанные классы в подпространства имен:

    App\Service\User\           # 5 классов
    App\Service\Order\          # 6 классов
    App\Service\Payment\        # 4 класса
    App\Service\Notification\   # 3 класса
    
  2. Следуйте принципу единственной ответственности на уровне пространства имен -- каждое пространство имен должно представлять одну связную концепцию.


Количество свойств (Property Count)

Идентификатор правила: size.property-count

Судимая метрика: size.property-count

Что измеряет

Подсчитывает количество свойств (полей) в классе. Класс с большим количеством свойств часто имеет слишком много ответственностей или хранит слишком много состояния.

Как читать значение:

Свойств Интерпретация
1--10 Нормально
11--14 Много -- проверьте на группировку данных
15--19 Тяжёлый -- рассмотрите VO или разделение
20+ Чрезмерно

Примечание

Это правило использует включающее сравнение (>=). Класс с ровно 15 свойствами достигает порога предупреждения по умолчанию.

Пороговые значения

Значение Серьезность Значение
1--14 OK Приемлемое количество свойств
15--19 Warning Слишком много свойств, подумайте об извлечении объектов
20+ Error Значительно слишком много, нужен рефакторинг

Конфигурация

Опция По умолчанию Описание
enabled true Включить или выключить правило
warning 15 Количество свойств, начиная с которого выдаётся предупреждение
error 20 Количество свойств, начиная с которого выдаётся ошибка
excludeReadonly true Пропускать readonly классы (DTO, value objects)
excludePromotedOnly true Пропускать классы, где все свойства -- promoted-параметры конструктора
# qmx.yaml
rules:
  size.property-count:
    warning: 15
    error: 20
    exclude_readonly: true
    exclude_promoted_only: true

Сокращённая запись с threshold:

rules:
  size.property-count:
    threshold: 18     # warning=18, error=18 → все нарушения становятся ошибками
bin/qmx check src/ --rule-opt="size.property-count:warning=18"
bin/qmx check src/ --rule-opt="size.property-count:error=25"

Пример

class ReportGenerator
{
    private string $title;
    private string $subtitle;
    private string $author;
    private \DateTimeInterface $createdAt;
    private string $format;
    private string $orientation;
    private float $marginTop;
    private float $marginBottom;
    private float $marginLeft;
    private float $marginRight;
    private string $headerText;
    private string $footerText;
    private string $fontFamily;
    private int $fontSize;
    private string $colorScheme;
    private bool $includeCharts;
    private bool $includeTables;
    private string $outputPath;
    // 18 свойств -- этот класс знает слишком много!
}

Как исправить

  1. Извлеките value objects. Сгруппируйте связанные свойства в небольшие, сфокусированные объекты:

    class PageMargins
    {
        public function __construct(
            public readonly float $top,
            public readonly float $bottom,
            public readonly float $left,
            public readonly float $right,
        ) {}
    }
    
    class ReportStyle
    {
        public function __construct(
            public readonly string $fontFamily,
            public readonly int $fontSize,
            public readonly string $colorScheme,
        ) {}
    }
    
    class ReportGenerator
    {
        public function __construct(
            private ReportMetadata $metadata,
            private PageMargins $margins,
            private ReportStyle $style,
            private ReportOptions $options,
        ) {}
    }
    
  2. Ищите кластеры свойств -- свойства, которые всегда используются вместе, являются кандидатами на извлечение.

Совет

Классы, где все свойства -- promoted-параметры конструктора (типично для DTO), по умолчанию исключены через excludePromotedOnly. Это позволяет избежать ложных срабатываний на классах вроде CreateUserRequest(string $name, string $email, ...).