Refaktoring legacy kódu bez strachu – postup krok za krokem
Jak bezpečně upravit starý kód bez testů – charakterizační testy, malé kroky, strangler fig, feature flagy a kdy refaktoring vůbec nedělat.
Legacy kód je kód, který vydělává peníze, ale nikdo se ho nechce dotknout. Funkce na 800 řádků, globální proměnné, žádné testy a komentář „NEMĚNIT – nevím proč, ale funguje to“. Přepsat to celé od nuly je lákavé a skoro vždy špatné. Tady je postup, jak legacy kód zlepšovat postupně a bez rozbití produkce.
Nejdřív: proč refaktorujete?
Refaktoring je změna struktury kódu bez změny chování. Dělejte ho, když:
- potřebujete do kódu přidat funkci a v současném stavu to nejde bezpečně,
- se v dané části opakovaně objevují chyby,
- kód často měníte a každá změna trvá zbytečně dlouho.
Nedělejte ho jen proto, že je kód ošklivý. Ošklivý kód, na který nikdo nesahá, nikoho nebolí.
Krok 1: zajistěte si záchrannou síť
Bez testů nevíte, jestli jste něco nerozbili. U legacy kódu ale testy nejsou a kód není napsaný tak, aby se dal snadno testovat. Řešením jsou charakterizační testy (také „golden master“ nebo „snapshot“ testy): netestují, co by kód měl dělat, ale zachycují, co skutečně dělá teď.
# tests/test_invoice_legacy.py
import json
from pathlib import Path
from legacy.invoice import render_invoice
CASES = json.loads(Path("tests/fixtures/invoices.json").read_text())
def test_render_matches_snapshot():
for case in CASES:
result = render_invoice(**case["input"])
assert result == case["expected"], case["name"]
Jak získat expected? Spusťte současný kód na reprezentativních vstupech a výstup uložte. Pokud v něm najdete chybu, zatím ji neopravujte – zapište si ji. Oprava chyby je změna chování a patří do samostatného kroku.
Pro snapshoty existují i hotové nástroje: syrupy pro pytest, toMatchSnapshot() v Jestu, ApprovalTests pro více jazyků.
Krok 2: malé kroky, každý zvlášť commitnutý
Každá změna by měla být tak malá, aby se dala popsat jednou větou a vrátit jedním git revert:
- Přejmenování proměnné → testy → commit.
- Vytažení bloku do funkce → testy → commit.
- Odstranění mrtvého kódu → testy → commit.
Nikdy nemíchejte refaktoring a novou funkčnost v jednom commitu. Recenzent pak neumí rozlišit, co je přesun a co změna logiky.
Krok 3: bezpečné refaktoringové operace
Začněte těmi, které jsou téměř bez rizika – ideálně provedené nástrojem v IDE, ne ručně:
| Operace | Kdy |
|---|---|
| Rename (přejmenování) | Nejasné názvy jako data2, tmp, flag |
| Extract function | Blok s komentářem „// spočítej DPH“ |
| Extract variable | Složitý výraz v podmínce |
| Inline | Funkce, která jen volá jinou funkci |
| Introduce parameter | Funkce čte globální proměnnou |
| Replace magic number | if status == 3 → if status == Status.PAID |
Příklad – závislost na globálním stavu znemožňuje testování:
# Před
def is_overdue(invoice):
return invoice.due_date < datetime.now()
# Po – čas jde zvenku, funkce je testovatelná
def is_overdue(invoice, now=None):
now = now or datetime.now()
return invoice.due_date < now
Krok 4: švy (seams) pro testovatelnost
Michael Feathers v knize Working Effectively with Legacy Code zavádí pojem šev – místo, kde můžete změnit chování kódu, aniž byste upravovali samotný kód. Typicky parametr, rozhraní nebo injektovaná závislost. Když funkce přímo volá databázi, e-mail a platební bránu, vytáhněte tato volání do parametrů nebo samostatných tříd. V testu je pak nahradíte falešnými implementacemi.
Krok 5: větší přestavby – strangler fig
Když je potřeba vyměnit celý modul, nepřepisujte ho najednou. Vzor strangler fig (škrtící fíkovník):
- Před starý kód postavte rozhraní (fasádu, router, proxy).
- Novou implementaci pište vedle staré, část po části.
- Přesměrovávejte provoz na novou implementaci postupně – funkce po funkci.
- Když stará část nemá žádný provoz, smažte ji.
V kombinaci s feature flagy můžete novou cestu zapnout jen pro část uživatelů nebo ji v případě problému okamžitě vypnout:
if flags.enabled("new_invoice_engine", user):
return new_render(invoice)
return legacy_render(invoice)
Pokročilá varianta: spouštět obě implementace a porovnávat výsledky (vrací se výsledek té staré, rozdíly se logují). Knihovna GitHub Scientist tento přístup zpopularizovala.
Najděte mrtvý kód
Část legacy kódu se vůbec nevolá. Pomohou nástroje:
# Python
pip install vulture && vulture src/
# JavaScript / TypeScript
npx knip
A pak ověření v produkci: přidejte do podezřelé funkce log a pár týdnů sledujte, zda se objeví.
Refaktoring s pomocí AI
AI je při refaktoringu užitečná na vysvětlení nesrozumitelného kódu, generování charakterizačních testů a mechanické úpravy. Zásady:
- Žádejte jeden typ změny najednou („vytáhni výpočet DPH do samostatné funkce, nic jiného neměň“).
- Vždy spusťte testy – AI může nenápadně změnit chování (pořadí, zaokrouhlení, ošetření
None). - U velkých souborů chtějte cílené úpravy, ne přepsání celého souboru.
V Projektech ColdHost AI umí asistent upravovat soubory blokovými SEARCH/REPLACE změnami, takže v diffu vidíte přesně, co se změnilo, a refaktoring zůstává přehledný.