Z jakých důvodů a kdy bychom měli vytvářet funkce? Na to se podíváme právě dnes.
Během mého doktorského studia na VUT FIT mi kolega vyprávěl zajímavou historku. Dělal tehdy cvičícího a při opravování projektů narazil na jeden, který jej zaujal jednou velmi specifickou vlastností. Tou vlastností bylo, že všech 2 000 řádků projektu bylo v jediné funkci, main()
. Když se studenta ptal, proč kód nerozdělil do funkcí, dostalo se mu následující odpovědi: "Na základech programování nás učili, že funkci máme vytvořit v případě, kdy se nám kód opakuje. Mně se ale v projektu žádný kód neopakuje, takže mi přišlo zbytečné vytvářet další funkce." Ano, zní to úsměvně, ale realita je taková, že důvody pro vytváření funkcí nejsou vždy dobře známy. Pojďme se tedy na několik důležitých důvodů podívat.
Dobrých důvodů se najde hned několik.
can_edit_project_settings = user.is_admin() or user.is_developer() if can_edit_project_settings: ... # ... can_edit_project_settings = user.is_admin() or user.is_developer() if can_edit_project_settings: ...
Znalost o tom, kdy může uživatel změnit nastavení projektu, je na dvou místech. Pokud budeme oprávnění pro editování nastavení potřebovat upravit (např. přibude další role), tak je potřeba změnu provést na všech místech, na což se snadno zapomene. To může mimo jiné vést na bezpečnostní díru. Pokud je však znalost obsažena na jednom místě, tak stačí změnit toto místo:
def can_edit_project_settings(user): return user.is_admin() or user.is_developer()
void add_new_arg(FunctionCall fc, Expression arg) { Function f = fc.get_function(); unsigned int arg_count = fc.get_arg_count(); if (f.get_param_count() > arg_count || f.is_var_arg()) { // Můžeme přidat další argument. // ... } else { // Nelze přidat další argument. // ... } }
Do hlavní vysokoúrovňové logiky funkce (lze přidat další argument?) se nám pletou nízkoúrovňové detaily (jak zjistit, že do volání funkce lze přidat další argument?). Vhodnější je tuto kontrolu extrahovat do samostatné funkce (metody):
void add_new_arg(FunctionCall fc, Expression arg) { if (fc.new_arg_can_be_added()) { // ... } else { // ... } }
Koho by zajímaly detaily, může se podívat na implementaci FunctionCall::new_arg_can_be_added()
.
Dalším častým nešvarem je používání nízkoúrovňových operací ve vysokoúrovňovém kódu. Mějme následující kód v Pythonu:
order_date = db.get_order_date(order_id) # Datum je v databázi uloženo v UTC. My jej ale potřebujeme v lokální časové zóně. order_date = order_date.replace(tzinfo=datetime.timezone.utc).astimezone(tz=None).replace(tzinfo=None) # ...
Lepším řešením je nízkoúrovňový kód zapouzdřit do funkce:
order_date = db.get_order_date(order_id) order_date = utc_to_local_time(order_date) # ...
Kód bude mnohem čitelnější. Související výhodou je, že vytvořením funkce odpadne i nutnost vysvětlujícího komentáře.
Mailman
, který doručuje balíček za 200,- Kč zákazníkovi, reprezentovaného třídou Customer
. Při doručení je potřeba tento obnos zaplatit. Kód pošťáka by pro tuto akci mohl v Javě vypadat takto:customer.getWallet().withdraw(new Money(200));
Tento kód však neodpovídá reálnému světu. Dali byste pošťákovi svou peněženku, ať si peníze vybere sám? Dále je kód špatně rozšiřitelný o další metody placení a porušuje princip Tell, don't ask. Především však ale obsahuje tzv. strukturální duplikaci (to, že zákazník má peněženku, přístupnou přes getWallet()
, je obsaženo jak ve třídě Customer
, tak ve třídě Mailman
kvůli kódu pošťáka výše). Co s tím? Metodu getWallet()
ve třídě Customer
nahradíme za metodu getPayment()
:
public class Customer { public Money getPayment(Money amount); // ... };
Tím, že jsme přidali tuto novou metodu se kód pošťáka zjednoduší:
customer.getPayment(new Money(200));
Navíc nyní mnohem více odpovídá realitě, je rozšiřitelný (změny v Customer.getPayment()
se ho nedotknou), odpadne zbytečná závislost třídy Mailman
na Wallet
a strukturální duplikace, kód je lépe testovatelný a již neporušuje princip Tell, don't ask.
Napadá vás ještě další důvod, proč vytvářet funkce? Podělte se o něj v komentáři!
Přidat komentář