Гроші, час і статуси - три типи даних, які найчастіше моделюють «примітивами» (float, string, int) і які дають найбільше помилок.
Гроші:
- не
float:0.1 + 0.2≠0.3. Сума в мінімальних одиницях (копійках) цілим числом абоdecimalу базі; - валюта завжди поруч з сумою: 100 - це гривні чи долари? Додавання сум у різних валютах має бути помилкою, а не мовчазним результатом;
- об'єкт-значення
Money(власний чи бібліотекаmoneyphp/money,brick/money) збирає правила в одному місці: додавання, порівняння, округлення, розподіл без втрат (100 грн на 3 частини - 33,34 + 33,33 + 33,33, а не три по 33,33 з загубленою копійкою).
Час:
- мить у часі -
CarbonImmutableв UTC; у місцевий час перетворювати лише для показу; - дата без часу (день народження, дата події) - окремий тип, а не
DateTimeз опівнічним часом, який зміщується в іншому поясі; - період - об'єкт
DateRangeз початком і кінцем і методамиcontains(),overlaps()замість пари змінних і перевірок, розкиданих по коду; - незмінні дати (
CarbonImmutable,Date::use(CarbonImmutable::class)) - щоб$start->addDay()не змінював оригінал непомітно; - «зараз» як залежність: код, що порівнює з поточним часом, легше тестувати, якщо час можна підмінити (
Carbon::setTestNow,$this->travelTo()).
Статуси:
- енум замість рядків і чисел:
OrderStatus::Paidзамість'paid'чи3; - дозволені переходи - у моделі чи енумі (
canTransitionTo()), а не вifпо всьому коду; - без суперечливих прапорців:
is_paid,is_shipped,is_cancelledдозволяють неможливі комбінації - один статус робить їх невиразними.
У Laravel все це підтримується з коробки: касти енумів ('status' => OrderStatus::class), власні касти для Money, immutable_datetime.
Загальна ідея - «примітивна одержимість» (primitive obsession) як запах коду: якщо значення має правила (формат, діапазон, операції), воно заслуговує на власний тип.