Rules Hub
Coding Rules Library
Rule priority, scope & exceptions
Use this to align rules with the senior-level structure (P0/P1/P2, scope, exceptions/tradeoffs).
Use a discriminated union only when variants actually diverge in shape
Prefer one shared interface with optional fields over a discriminated union when only one variant diverges; promote to a union when a consumer-side undefined access actually becomes a bug.
Bad example
| 1 | type TagType = 'category' | 'instrument' | 'person' | 'author' | 'column'; |
| 2 |
|
| 3 | // Discriminated union: four of five variants are identical, |
| 4 | // only `column` lacks imageUrl. The narrowing now ripples into |
| 5 | // every consumer for a payoff that lands on exactly one variant. |
| 6 | type Tag = |
| 7 | | { type: 'category'; id: number; name: string; imageUrl?: string } |
| 8 | | { type: 'instrument'; id: number; name: string; imageUrl?: string } |
| 9 | | { type: 'person'; id: number; name: string; imageUrl?: string } |
| 10 | | { type: 'author'; id: number; name: string; imageUrl?: string } |
| 11 | | { type: 'column'; id: number; name: string }; |
| 12 |
|
| 13 | function renderIcon(tag: Tag) { |
| 14 | // Consumer must narrow on `type` just to read imageUrl |
| 15 | if (tag.type !== 'column') return tag.imageUrl; |
| 16 | return undefined; |
| 17 | } |
Explanation (EN)
Modeling five near-identical variants as a discriminated union forces every consumer to narrow on `type` before touching a shared-but-optional property, even though only `column` differs. The verbosity and rippling narrowing cost outweigh the type-safety win that applies to a single variant.
Objašnjenje (HR)
Modeliranje pet gotovo identičnih varijanti kao diskriminirana unija prisiljava svakog potrošača da suzi tip preko `type` prije pristupa zajedničkom, ali opcionalnom svojstvu, iako se razlikuje samo `column`. Opširnost i širenje suženja kroz potrošače košta više od dobitka na sigurnosti tipova koji vrijedi za samo jednu varijantu.
Good example
| 1 | type TagType = 'category' | 'instrument' | 'person' | 'author' | 'column'; |
| 2 |
|
| 3 | // One shared interface; the divergent property is optional. |
| 4 | // Simple to construct and to consume; revisit only if an |
| 5 | // undefined access on a specific variant actually bites. |
| 6 | interface Tag { |
| 7 | type: TagType; |
| 8 | id: number; |
| 9 | name: string; |
| 10 | imageUrl?: string; |
| 11 | } |
| 12 |
|
| 13 | function renderIcon(tag: Tag) { |
| 14 | return tag.imageUrl; |
| 15 | } |
Explanation (EN)
A single interface with the divergent field marked optional keeps construction and consumption simple while still expressing that the property may be absent. Promote to a discriminated union later, only if a real consumer-side undefined access turns into a bug for a specific variant.
Objašnjenje (HR)
Jedno sučelje s opcionalnim poljem koje se razlikuje održava i konstrukciju i potrošnju jednostavnom, a i dalje izražava da svojstvo može izostati. Prebaci se na diskriminiranu uniju kasnije, samo ako stvarni pristup `undefined` vrijednosti na strani potrošača postane bug za određenu varijantu.
Notes (EN)
Discriminated unions shine when variants carry genuinely different fields (e.g. a success payload vs an error with a `code`). The signal to introduce one is divergence across many variants or an exhaustiveness need, not a single optional property.
Bilješke (HR)
Diskriminirane unije briljiraju kad varijante nose stvarno različita polja (npr. uspješan payload nasuprot greške s `code`). Signal za uvođenje je razilaženje kroz više varijanti ili potreba za iscrpnošću, a ne jedno opcionalno svojstvo.
Exceptions / Tradeoffs (EN)
Do reach for a discriminated union upfront when you need compile-time exhaustiveness over `type`, or when multiple variants carry mutually exclusive required fields. Balance against model-entity-states-as-discriminated-union: keep one optional-field interface when only a field or two differ between variants. Balance against prefer-strict-required-fields-over-optional-properties: accept a few optional fields over a union only when variants barely differ and stay safe to read. Balance against define-distinct-types-for-distinct-data-shapes: use one interface with optional fields when shapes differ only slightly and don't warrant separate types.
Iznimke / Tradeoffi (HR)
Posegni za diskriminiranom unijom unaprijed kad trebaš iscrpnost nad `type` u vrijeme kompajliranja ili kad više varijanti nosi međusobno isključiva obavezna polja.