Věčné dilema odsazování kódu — tabulátory vs. mezery

Rubrika: Počítače

Tohle téma se vynořuje se stejnou pravidelností jako nutnost vstávat ráno do práce. Vždy se zase objeví někdo, kdo začne hlásat svou svatou pravdu:

Je přísně zakázáno používat mezery místo tabulátorů. A povinně si všichni nastavíte odsazení tabulátorů na 4 znaky a basta.

A já do toho vstoupím s úplně opačným kacířským přístupem. Řeknu vám totiž, že jediným správným odsazením kódu je právě to, které vyhovuje vám. Používáte mezery? V pořádku, používejte. Nastavujete si mezery pouze na dvě? Je to jen a jen vaše volba a nikdo vám do toho nemá co kecat.

Jediné, co je zapotřebí dodržovat, je konzistence. Tedy dodržujte jeden standard všude: celý zdroják, všechny soubory projektu, mají být naformátované stejně. Pokud používáte dvě mezery, pak prosím všude. Guláš z toho nedělejte.

Ale pokud máte nějaký svůj oblíbený styl odsazení, používejte právě ten.

A co ostatní?

Samozřejmě podotknete — ale jak máme spolupracovat v týmu na jednom projektu, když si každý bude dělat odsazení po svém? Přeci nejde, aby Pepa používal 4 mezery, Franta dvě a Jarda odsazoval pomocí tabulátorů. Přece se musíme domluvit na společném standardu a ten budou pak používat všichni.

Ale nepovídejte! Vždyť jde jen o náhradu tabulátorů za mezery a to je snad algoritmicky jednoduchá úloha. A ani ji nemusíte programovat, na to už existují nástroje.

Pokročilejším linuxákům (tj. těm, kteří nemají Linux jen jako status svého ega, ale skutečně vědí co a jak) určitě nemusím vysvětlovat, co dělají příkazy expand a unexpand. Pokud to nevíte, pak tyto příkazy nahrazují tabulátory za mezery a naopak. Přičemž lze nastavit, kolik mezer má jeden tabulátor být a taky je možné zapnout, že se přeformátovávají jen počáteční tabulátory/mezery na každém řádku, všechny další už to nechá být. Neboli to zpracuje právě jen odsazení kódu a nic jiného.

Takže následujícím příkazem:

expand --tabs=4 --initial

převedete soubor používající pro odsazení tabulátory na princip čtyř mezer. Řekněme, že to je ten standard v kterém rádi pracujete. Uděláte své změny ve zdrojáku a až ho budete chtít převést zpátky na tabulátory, tak pro změnu spustíte:

unexpand --tabs=4 --first-only

A co velká mlčící většina, která používá Windows? Patřím do ní také. Vždyť existuje několik řešení, která přinášejí linuxové / unixové programy pro Windows, např. Cygwin.

Hlavně žádnou manuální práci!

No jo, jenže to by bylo moc pracné, když si budu vyměňovat zdrojáky s Pepou, abychom to vždy přeformátovávali tak, jak to každému vyhovuje.

Ale vy si přeci v týmu nevyměňujete zdrojáky ručně, že ne? Určitě používáte nějaký verzovací program. Nejspíš SVN nebo git. A jsme u meritu věci.

Nejprve si řekněme, že git umí pracovat i s repozitáři SVN, takže se budeme tvářit, jako že SVN vůbec neexistuje. Pokud potřebujete přistupovat k repozitáři SVN, používejte jako klienta git a postupujte dále podle tohoto textu.

Git podporuje filtry, které umí spustit nad zpracovávaným souborem vždy, když ho checkuje z repozitory ven, resp. druhý filtr může použít, když commituje soubor do repository. A můžete nastavit, že filtr se aplikuje pouze na soubory určité přípony, takže ho git bude spouštět jen u zdrojáků, ale už ho nebude používat třeba pro obrázky.

Dohodněme se, že soubory uložené v repository budou vždy používat tabulátory. Tedy pokud někdo rád tabulátory, nemusí dělat vůbec nic. Ale pokud někdo preferuje mezery, může si nastavit podle následujícího postupu převod z tabulátoru na mezery a nazpět a pracovat v tom, co má rád.

Takže nejprve určíme, pro které přípony se bude filtr aplikovat: V podadresáři .git vaší kopie projektu si najděte soubor info/attributes (nejspíš nebude existovat a budete ho muset vytvořit) a do něj pro každou příponu zdrojáku zapněte zpracování určitým filtrem:

*.cpp filter=tabspace
*.php filter=tabspace

Název filtru „tabspace“ jsem si vymyslel a můžete si ho pojmenovat podle sebe, jen musíte stejný název použít i níže v definici filtru.

Důležité: soubor attributes je součástí repository a proto si ho musíte vytvořit v každé repository znova. A samozřejmě může mít v každé repository odlišný seznam přípon, pro které se bude filtr aplikovat.

Nyní, když máme definovaný seznam přípon, řekneme gitu, jak ten filtr bude vypadat. Z terminálu / příkazové řádky spusťte:

git config --global filter.tabspace.clean 'unexpand --tabs=4 --first-only'
git config --global filter.tabspace.smudge 'expand --tabs=4 --initial'

Tím jste gitu zadefinovali filtry a pokud jste použili volbu –global, pak to stačí provést takto jen jednou a filtr automaticky existuje pro všechny repository. Samozřejmě nadále platí, že v každém repository musíte vytvořit soubor attributes a v něm definovat přípony, pro které se filtr bude používat.

Případně můžete použít volbu –local a pak se filtr zapíše jen do té konkrétní repository a jiné neovlivní. Ale myslím, že taková opatrnost není zapotřebí, všechny tyto volby i v případě global zůstávají zapsané jen na tomto jednom počítači a platí jen pro váš účet, nikam jinam se to nedostane. (Ještě existuje volba –system, asi tušíte co dělá, ale tu bych nedoporučoval)

Hotovo, veškerá magie je nastavena a soubory se vám automaticky budou převádět mezi tabulátory a mezerami. Pokud preferujete jiné množství mezer než 4, tak si jen upravte příslušný parametr v definici filtru.

Poznámka: Pokud na Windows používáte Git for Windows, pak vše platí i pro vás bez jakékoliv úpravy. A ani nemusíte stahovat Cygwin, jelikož Git for Windows obsahuje v sobě solidní emulaci řady linuxových příkazů, včetně utilit expand a unexpand. Vlastně ani nezkoušejte volat Cygwin, protože pak dojde ke konfliktu shellů obou dvou balíků a stejně to nebude fungovat.

Git for Windows umí používat pro účel verzování jak populární grafická nástavba TortoiseGit, tak také UltraEdit Studio. Nevím, jak je to s jinými prostředími, nezkoušel jsem Visual Studio (jen vím, že také podporuje git). Možná bude v některých těchto jiných prostředích zapotřebí vyřešit utility expand a unexpand, které nemusejí být jejich součástí.

Domácí úkol

Filtry lze v gitu samozřejmě použít nejen na odsazení, ale na cokoliv dalšího. Filtr čte vstup na standardním vstupu a píše na standardní výstup, takže je lze i řetězit pajpou a tímto způsobem jich použít několik.

A na co jiného by to šlo použít? Tak například by šlo úplně automaticky ve zdrojácích mazat zapomenuté mezery na koncích řádků (trim trailing spaces). A nebo jsou lidé, kteří v céčku nechtějí psát otevírací složenou závorku na samostatný řádek, ale mají ji rádi na konci předchozího statementu (podmínky, začátku cyklu, definice funkce a podobně). I to by mělo jít poměrně snadným Perl scriptem zprocesovat, aby se řádky se samostatnými závorkami při checkoutu přesouvaly na konec předchozího řádku a pří checkinu zase naopak. A taky by šly elimitovat mezery za otevírací závorkou a před uzavírací závorkou (a vracet to zpět při checkinu). Možností je spousta.