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).
Extract pure logic to module-level functions; keep the service thin
Move stateless transforms to pure functions above the @Injectable class so the class only does DI + orchestration and the pure logic is trivially unit-testable.
Bad example
| 1 | @Injectable() |
| 2 | export class PriceHistoryProvider { |
| 3 | async fetch(query) { |
| 4 | // date math, mapping, fx conversion and grouping all inlined as |
| 5 | // private methods on the class, each needing the full DI graph to test |
| 6 | const range = this.resolveDateRange(query); |
| 7 | const rows = await this.primary.fetch(range); |
| 8 | const grouped = this.group(rows); |
| 9 | return this.applyFx(grouped, this.currencies); |
| 10 | } |
| 11 | private resolveDateRange(q) { /* pure, but trapped in the class */ } |
| 12 | private group(rows) { /* pure, but trapped in the class */ } |
| 13 | private applyFx(g, c) { /* pure, but trapped in the class */ } |
| 14 | } |
Explanation (EN)
When pure logic lives as private methods on an injectable, every unit test must instantiate the class through the DI container even though the logic touches no dependency, bloating tests and blurring what is orchestration vs. computation.
Objašnjenje (HR)
Kada cista logika zivi kao privatne metode na injectable klasi, svaki unit test mora instancirati klasu kroz DI kontejner iako logika ne dira nijednu ovisnost, sto napuhuje testove i zamuti sto je orkestracija a sto izracun.
Good example
| 1 | // ─── Pure helpers ─────────────────────────────────────────── |
| 2 | function resolveDateRange(period, from, to) { /* no deps */ } |
| 3 | function groupRows(rows) { /* no deps */ } |
| 4 | function applyFx(points, currency, rates) { /* no deps */ } |
| 5 |
|
| 6 | // ─── Provider ─────────────────────────────────────────────── |
| 7 | @Injectable() |
| 8 | export class PriceHistoryProvider { |
| 9 | constructor(private readonly primary: Primary) {} |
| 10 | async fetch(query) { |
| 11 | const range = resolveDateRange(query.period, query.from, query.to); |
| 12 | const rows = await this.primary.fetch(range); |
| 13 | return applyFx(groupRows(rows), query.currency, rates); |
| 14 | } |
| 15 | } |
Explanation (EN)
Pure functions can be imported and tested with plain inputs/outputs, the section comment signals the boundary, and the class reads as a short orchestration of named steps.
Objašnjenje (HR)
Ciste funkcije mogu se importati i testirati s obicnim ulazima/izlazima, komentar sekcije oznacava granicu, a klasa se cita kao kratka orkestracija imenovanih koraka.
Exceptions / Tradeoffs (EN)
Helpers that genuinely need an injected dependency (logger, config, cache) stay as class methods. Only extract logic that is referentially transparent.
Iznimke / Tradeoffi (HR)
Pomocne funkcije kojima stvarno treba injektirana ovisnost (logger, config, cache) ostaju kao metode klase. Izdvoji samo logiku koja je referencijalno transparentna.