Refaktorizovat, nebo ne?

Kit

Re:Refaktorizovat, nebo ne?
« Odpověď #15 kdy: 04. 09. 2015, 18:02:13 »
U toho ifu je důležité, zda obsahuje business logiku. A zrovna tenhle if ji obsahuje, protože definuje, jaké username se použije, pokud uživatel není přihlášen. Pokud proměnná user je statická a její hodnota se naplní jen jednou, pak není co řešit a zůstává to jako if. Pokud je to ale operace, která se vyskytuje v kódu vícekrát, pak jednoznačně patří do samostatné funkce. Účel poznáte ve chvíli, kdy budete chtít logiku změnit, kupříkladu místo 'guest' použít 'anonymouse'.

Zjednodušeně řečeno je moje doporučení: pokud ta část kódu dělá nějaké rozhodnutí, které se dělá shodně na více místech, tak ji má smysl vyčlenit jako samostatnou funkci tak, aby ve zdrojovém kódu byla ta část kódu zapsána jen jednou. A to i když se jedná o jediný řádek.

V podstatě souhlas, ovšem s jednou otázkou: Jak v rozsáhlém projektu poznám, že se nějaké rozhodnutí dělá na více místech? V tuto chvíli se dělá na jednom, ale co zítra? Mám při refaktorování pátrat, na kolika dalších místech se ta podmínka vyskytuje?

Vyhledávání, zejména ve velkém projektu, je největší brzdou. Proto je nutné vyčlenit business logiku do třídy a metody, která za ni bude zodpovědná. Tam se nám najednou tyto metody začnou scházet, tam kolega při pokusu o refaktorování jiného bloku narazí na to, že jméno metody je již obsazeno a že ta metoda dělá přesně to co chtěl. Pokud ne, tak ji pojmenuje jinak.

Proto tvrdím, že další výskyty je zbytečné hledat, protože bychom přitom vypadli z kontextu právě refaktorovaného kódu a vyplýtvali bychom zbytečně mnoho času opětovným navazováním. Je nutné se soustředit výhradně na blok, který právě refaktoruji.

Je také nutné se každého kandidáta na vyčlenění ptát: Kam patříš? Ti, kteří si nedokáží odpovědět na takovou jednoduchou otázku, strkají takové bloky do nejrůznějších registrů a tříd obsahujících jen statické metody. Typickým registrem je config, typickou statickou třídou je třeba Math. Netvrdím, že jsou špatné a zbytečné, ale je nutné se ubránit choutkám k nim přidávat cokoli dalšího, co může být delegováno do třídy, do které patří.

Krásnou ukázkou chybné implementace v OpenCart, je například databáze. Hned v index.php je kód:
Kód: [Vybrat]
$db = new DB(DB_DRIVER, DB_HOSTNAME, DB_USERNAME, DB_PASSWORD, DB_DATABASE);Je zde použito 5 konstant, z nichž ani jedna není na předchozích řádcích definována. Je mi jasné, že je to v některém z předchozích  require_once(), ale proč bych je měl prohledávat? Ve svých aplikacích to řeším takto:
Kód: [Vybrat]
$db = new DB($config);Třída DB dostane v parametru možnost nahlédnout do $config a vytáhnout si potřebné údaje. Nikde žádná konstanta, kterou bychom museli vláčet celou aplikací. Pokud chci použít jinou DB, dám jí jiný $config. Už se však nemusím přetahovat o globální konstanty.

Proč se ta DB ihned otevírá, když ještě ani nevíme, zda ji budeme potřebovat? Přitom stačí ve třídě DB udělat odloženou inicializaci, která nám zajistí podobný efekt jako populární Singleton a přitom nás neomezuje v počtu současně otevřených databází.

Příkazy require_once ani include_once zásadně nepoužívám. Od doby, kdy funguje autoload, jsou nepotřebné. Obvykle si v jedné aplikaci vystačím s jediným require. Výjimkou jsou některé cizí knihovny, které jsou tak dobře napsány, že je zbytečné je vynalézat znovu.


Kit

Re:Refaktorizovat, nebo ne?
« Odpověď #16 kdy: 04. 09. 2015, 18:27:23 »
Co se týká dopisování čehokoliv do stávajícího "bordelu", není to tak jednoduché. Systém je dobré čas od času aktualizovat a průšvih je na světě, co se přepsalo, musí se přepsat znova. Existuje na to sice "suprovej" modul vqmod, kterej dělá podle šílenýho XML zápisu rewrite php souborů se změnama, ale tomu se vyhýbám, jak to jen jde (používám ho, protože je to bohužel nejrozumější způsob úpravy core files, ale opravdu jen pokud neexistuje žádná jiná možnost). Pokud se mi to hodí, raději si dopíšu vlastní metody s tím, že volají ty původní, jenom třeba trochu jinak. Proto raději udělám vlastní metodu se třema parametrama, než přepisovat core soubory tak, aby mi stačil pouze jeden parametr.

Ten vqmod se podle popisu chová opravdu šíleně. Do toho bych nešel, na generování kusů kódu mám editor.

Citace
A ještě jedna otázečka... napadá někoho, jak zredukovat tenhle bordel, nebo ho mám prostě jenom vyhodit do vlastní metody getLanguageStrings a nechat to být?
Kód: [Vybrat]
        $data['heading_title'] = $this->language->get('heading_title');
        $data['text_edit']    = $this->language->get('text_edit');
        $data['text_enabled'] = $this->language->get('text_enabled');
        $data['text_disabled'] = $this->language->get('text_disabled');
        $data['text_content_top'] = $this->language->get('text_content_top');
        $data['text_content_bottom'] = $this->language->get('text_content_bottom');     
        $data['text_column_left'] = $this->language->get('text_column_left');
        $data['text_column_right'] = $this->language->get('text_column_right');
        $data['entry_code'] = $this->language->get('entry_code');

je to jen kousek kódu, někde je to 50 i víc takových úžasných řádků. Jenom upozorňuju, že $this->language->get chci v nějaké formě zachovat a nepřepisovat, protože je to opět jedna z core funkcí, která se prostě používá všude a nechci zavádět další na to samý.

Popsal jsem to v předchozím příkladu s databází. Zjednodušeně takto:
Kód: [Vybrat]
$data += $this->getLanguage();
Metoda getLanguage() mi dodá všechny potřebné hodnoty v jednom slovníku, který jen začlením do slovníku $data. Bude to i rychlejší. Pokud bys to chtěl řešit nějak univerzálně, tak se to dá i takto:
Kód: [Vybrat]
$data += $this->getLanguage(array(
    'heading_title',
    'text_edit',
    'text_enabled',
    'text_disabled',
    'text_content_top',
    'text_content_bottom',
    'text_column_left',
    'text_column_right',
    'entry_code',
));
Postrádá to však eleganci předchozího zápisu. Zároveň předpokládám, že tohle bude v aplikaci potřebné neustále ve stejné sestavě a pokus o univerzálnost je tedy zbytečný.