Překrývající se veřejné funkce si nemají volat navzájem
Píšeš veřejnou funkci foo a všimneš si, že existující veřejná bar dělá skoro totéž. Instinkt DRY říká zavolat ji. Matklad radí opak: společnou logiku přesuň do soukromého helperu a nech obě funkce volat ten. Rozdíl vypadá kosmeticky. Jde ale o to, kdo smí ovlivňovat tvar veřejného rozhraní.
Škoda přichází až po sdílení
Veřejné funkce časem nabírají volitelné parametry. Když foo volá bar, jakákoli potřeba foo se dá vyřešit dalším přepínačem na bar. Přepínač pak vidí všichni volající, i když ho nikdy nemají nastavovat.
// BAD
pub fn foo() void {
return bar(.{ .frobnicate = true });
}
pub fn bar(options: struct {
frobnicate: bool = false,
}) void {
// ...
}
V první verzi tohoto kódu přepínač být nemusel. Ke zhoršení rozhraní dojde později, v jiném pull requestu, a každý krok vypadá rozumně. Proto to review nezachytí v okamžiku, kdy se chyba rodí. V tu chvíli je to prostě znovupoužití.
// GOOD
pub fn foo() void {
return helper(.frobnicate);
}
pub fn bar() void {
return helper(.transmogrify);
}
fn helper(mode: enum { frobnicate, transmogrify }) void {
// ...
}
Teď má každá veřejná funkce podpis šitý na své volající. Helper bude možná potřeba upravovat, ale je soukromý, takže je ta změna levná a lokální. Mizí napětí mezi tím, co je pohodlné pro uživatele, a tím, co je pohodlné pro modul. Tahle dvě kritéria se dřív prala o jedno rozhraní. Teď má každé svoje.
Kdy platí opak
Výjimkou jsou zkratky. Funkce, která je definovaná jako obvyklý řetěz jiných veřejných volání, má volat jen veřejné funkce a žádné soukromé. Tím je zaručené, že je to opravdu zkratka a uživatel si ji může kdykoli rozepsat sám. Kdyby sáhla na soukromý helper, slibovala by něco, co se veřejnými prostředky složit nedá.
Takže pravidlo nerozhoduje podle toho, jestli se kód opakuje, ale podle významu funkce. Buď je to samostatná operace s vlastním rozhraním, nebo je to pojmenovaná kompozice. Míchat se to nemá.
Matklad zároveň odmítá častý argument pro totéž pravidlo, totiž že volání mezi veřejnými metodami komplikuje mockování. Podle něj se mockuje I/O, ne metody, a testuje se funkčnost, ne jednotlivé metody. Výjimku tvoří veřejné knihovny se stabilním API. Dobrý závěr se tu opírá o špatný důvod, a proto je třeba znát důvod vlastní.
Co si z toho odnést
Až budeš chtít z veřejné funkce zavolat sousední veřejnou, zastav se u otázky, jestli jde o zkratku. Pokud ano, volej jen veřejné API. Pokud ne, vytáhni společné jádro do soukromého helperu a dej mu úzký parametr, třeba enum s jednou variantou pro každý režim. Stojí to pár řádků teď a ušetří to přepínače, které by později nešly odstranit, protože na nich někdo závisí. Hodí se to hlavně u knihoven a hranic modulů. Tam je rozhraní nejdražší věc, kterou máš.