Jak psát hezký kód II

V minulém díle tohoto miniseriálu jsme se seznámili se základní teorií a prakticky jsme si ukázali, jak vytvářet různé názvy (proměnných, funkcí, tříd atd.) či jaké psát funkce. Dnes, jak jsme si minule v závěru slíbili, si povíme o psaní objektů (tříd), komentářů a o tom, jak vlastně kód formátovat.

Seriál: Hezký kód (3 díly)

  1. Jak psát hezký kód I 13.4.2010
  2. Jak psát hezký kód II 20.4.2010
  3. Jak psát hezký kód III 27.4.2010

Objekty

Objekty není jistě potřeba moc rozepisovat, protože pro ně platí více méně stejné zásady jako pro funkce/metody. Objekty pište malé! Možná se teď divíte, proč psát objekty malé. „Co když bude prostě objekt muset obsahovat víc věcí? Například objekt obsluhující e-shop?“ Nemusí! Proč byste psali jeden objekt, která obsluhuje e-shop, když můžete elegantně napsat více objektů – jeden se stará o přihlašování, jiný zase o katalog, další o nákupní košík, .. Okamžitě potom víme, který kus kódu kde hledat. V jednom objektu bychom těžko hledali, kde je kód obsluhující nákupní košík.

Tím se také dostáváme k tomu, že každá třída, objekt, by měl(a) mít na starost jen jednu věc. Pokud nastane jakákoliv změna v aplikaci, tak důvod, proč jít změnit určitý objekt, by měl být jen jeden. Vezměme například opět e-shop – chceme v této aplikaci změnit způsob listování v katalogu, kam půjdete? Půjdete najít aplikační (chcete-li logický) kód, změníte obsah proměnných a poté půjdete udělat potřebnou úpravu do šablon. Toto je logický postup, který během chvilky provedete, ale záhy zjistíte, že najednou objekt obsluhující nákupní košík hází chyby – jak to? To bude asi tím, že nějaký Franta Moula, který nečetl nic o tom, jak psát kód co nejlépe a hezky, provázal nelogické části. Vyhněte se tomu! Logicky spojujte to, co k sobě patří, a nezavádějte zbytečné propojení. Při rozdělování velkých objektů na menší se nesnažte propojení udržovat – zrušte ho.

Další pravidlo: Zapouzdřujte! Nenechávejte ukazovat světu, jak vaše bezva-cool třída funguje. Ukažte jen to, co je nutné. V minulém díle jsme si ke konci říkali, že při velkém počtu argumentů je dobré tyto argumenty obalit do objektu. Ukazovali jsme si to na příkladu souřadnic bodu. Dnes do ukázky doplníme samotný objekt a ukážeme si špatný a dobrý způsob.

class Point:
    x = 0
    y = 0

    def __init__( self, x=None, y=None ):
        if not x == None:
            self.x = x
        if not y == None:
            self.y = y

class Point:
    __x = 0
    __y = 0

    def __init__( self, x=None, y=None ):
        self.setXY( x, y )

    def setXY( self, x=None, y=None ):
        self.setX( x )
        self.setY( y )

    def setX( self, x=None ):
        if not x == None:
            self.__x = int( x )

    def setX( self, y=None ):
        if not y == None:
            self.__y = int( y )

    def getXY( self ):
        return [ self.__x, self.__y ]

    def getX( self ):
        return self.__x

    def getY( self ):
        return self.__y

V tomto příkladu je jasně vidět, že první způsob je sice rychlejší, ale zato méně bezpečný. V proměnné x nebo y se může vyskytnout klidně i řetězec. Můžeme si s instančními proměnnými dělat doslova co chceme. Naopak v druhém případě svoje proměnné chráníme. (Může ale vzniknout chyba při převádění do integeru – v ukázce není uvedeno zpracování výjimky.) Je možné, že na první pohled nevidíte přínosy tohoto řešení – vždyť se upíšete, a přitom jde jen o hlídání proměnných. Ale co kdybychom chtěli přidat ještě možnost pracovat s polárními souřadnicemi? V prvním případě, kde jsme nezapouzdřovali, bychom museli pracovat s převodem složitě. Všude, kde bychom pracovali s polárními souřadnicemi, bychom museli napsat kód s převodem, a u toho nezapomínat, že můžeme dostat nesprávné hodnoty. Složité, komplikované a porušovalo by se pravidlo DRY (Don’t Repeat Yourself). Při zapouzdřování bychom jednoduše přidali další metody a při využívání objektu bychom se už o nic nestarali.

class Point:
   def __init__( self, x=None, y=None, r=None, angle=None ):
      ...
   def setXY( self, x, y ):
      ...
   def setX( self, x ):
      ...
   def setY( self, y ):
      ...
   def setR( self, r ):
      ...
   def setAngle( self, angle ):
      ...
   def getX( self ):
      ...
   def getY( self ):
      ...
   def getR( self ):
      ...
   def getAngle( self ):
      ...

Všimněte si, že tento objekt nic neříká o tom, jak uvnitř funguje. Nechá nás pracovat pouze s veřejnými metodami, a dává nám možnost pracovat s polárními nebo kartézskými souřadnicemi. A tak to je správně.

Komentáře

Hned na začátek tu mám nemilou zprávu pro kódové spamery: Komentáře nepište! Proč nepsat komentáře? To je jednoduché: Když vyvíjíte aplikaci a píšete do kódu komentáře, tak často zapomenete při změně komentář patřičně upravit, a tím se stává komentář neplatným a zbytečným. Tomu stěží zabráníte. Já osobně jsem komentáře nikdy neudržoval v dobrém stavu. (Ukamenujte mě za to, že jsem líný programátor, co nemá právo psát něco o hezkém kódu.) – pozn.aut. Vždy se stalo, že komentář přestal být aktuální, že nebyl potřeba. Je zbytečné při vývoji ještě hledět na komentáře, už tak je dost práce s údržbou kódu. Proto je nepište.

Komentáře nepíšeme, protože samotný kód je jeden velký komentář. Kód píšeme tak, aby se dobře četl a nepotřeboval komentáře. Pokud není z kódu patrné, co dělá, tak je určitě způsob, jak ho napsat lépe, čitelněji. Podívejme se na následující kód, co asi dělá?

foo = [ [ x*y for y in range( 1,11 ) ] for x in range( 1,11 ) ]

U tohoto kódu stěží uvidíme, co se děje. Někteří by řekli, že by se hodil komentář – ano, určitě tady by byl komentář ideální, ale proč psát komentář, když by šlo kód napsat čitelněji, třeba takto:

lowMultiplication = []
for x in range( 1,11 ):
    line = []
    for y in range( 1,11 ):
        line.append( x*y )
    lowMultiplication.append( line )

Nyní je kód čitelnější. I ti, kteří se nevyznají v Pythonu, dokáží kód přečíst. V kódu je také vidět, jak hodně pomohl lepší název proměnné. Dalším příkladem může být jakákoliv složitější podmínka. V našem e-shopu se může vyskytnou podmínka, kdy se testuje, zda je uživatel přihlášen a má nárok na slevu. Taková podmínka by mohla vypadat například takto:

# podminka, zda je uzivatel prihlasen a zda ma narok na slevu (flag je nastaven na 2)
if user.isLogin() and user.flag == 2:
    ...

Strašlivá podmínka, že ano. A to nesmíme zapomenout, že se kód může kdykoliv změnit. Právo na slevu bude najednou označeno s flagem 3, a pak musíme upravit nejen podmínku, ale i komentář. Nemluvě o tom, že tato podmínka se nemusí vyskytnout jen jednou. Hezké vylepšení by bylo použitím konstanty a tím odstranění konce komentáře. Podmínka ale stále zůstává mohutná a těžkopádná. V takovéto podmínce se mohou vyskytovat chyby – může se stát, že zapomenete na jednom místě přidat kontrolu flagu, a už máte na telefonu majitele e-shopu, co jste to provedl. Hezkým řešením by bylo tuto podmínku vložit do funkce, nebo ještě lépe do metody.

if user.hasDiscount():
    ...

Nyní je jasné, co se děje, aniž bychom potřebovali udržovat komentář nebo se bát, že na některém místě zapomeneme celou podmínku napsat.

Čas od času se ale někde hodí komentář použít. Hodí se to například k regulárnímu výrazu – takový komentář je velmi užitečný, protože řekne mnohem rychleji vše, co by řeklo dlouhé čtení regulárního výrazu. Můžete komentářem vysvětlit, co zamýšlíte a proč to děláte zrovna takto. Já osobně jsem před časem řešil jakýsi problém, a vyřešil jsem jej jedním řádkem kódu (a samozřejmě jsem si k němu nenapsal dobrý komentář, proč jsem to vyřešil zrovna takto). Po čase jsem kód procházel, všiml jsem si toho řádku a zarazil se. Porozhlédl jsem se kolem, a dotyčný řádek jsem nakonec smazal, protože se mi zdál zbytečný. Aplikace poté házela chyby a mně ihned došlo, čím to je – smazaným řádkem, který se zdál zbytečný, a přitom byl velmi důležitý. Napsal jsem k němu komentář, proč tam je, a někde v mých kódech nejspíš žije dodnes.

Čas od času je dobré použít komentář – v opačných případech je to špatně. Podívejte se na následující ukázku. Jsou v ní vidět tři typy špatných komentářů. První komentáře pouze zakrývají hlavičku funkce a neříkají nic nového. Další komentáře u podmínek jsou úplně zbytečné. Přeci vidíme, co se děje – kód vše řekl a komentáře nejsou potřeba. Poslední komentář je naopak chybný, matoucí. Kód vrací při neplatném faktoriálu nulu a v komentáři je napsáno None. Zde se původně asi vracelo None, ale poté byla hodnota upravena na nulu a (zbytečný) komentář se zapomnělo upravit.

# vrati faktorial daneho cisla
# @param cele cislo
# @return faktorial
def factorial( x ):
    if x == 0: # pokud je x rovno 0 vrati 1
        return 1
    if x > 0: # pokud je x vetsi nez 0, tak vynasobi x cislem o jednotku mensi
        return x * faktorial( x-1 )
    else: # jinak vrati 0
        return 0 # neplatny faktorial - vraci se None

Nakonec ještě jedna rada: nezakomentovávejte kusy kódu. Je to zbytečná komplikace. Kód rovnou smažte! Nemusíte se přeci bát, že o něj přijdete – vždyť přeci používáte verzovací systém a kdokoli si kód můžete dohledat. (Nepoužíváte verzovací systém? Chyba! Začněte! Doporučuji používat Git. Je o něm pěkná kniha Pro GIT, kterou si můžete stáhnout v PDF formátu, a i na Zdrojáku vyšel seriál Pět důvodů proč použít Git.)

Závěr

V dnešním dílu jsme si řekli, že:

  • Objekty by měly být malé.
  • Objekt by měl mít na starost jen jednu věc.
  • Nezapomínejte zapouzdřovat.
  • Nepište zbytečné komentáře. Místo komentáře použijte popisnější kód.

Příště se podíváme na formátování kódu.

Michal dělá team leadera v Seznam.cz a hraje si na BOObook.cz. Jeho nejoblíbenějším jazykem je Python, ale nevadí mu třeba ani JavaScript a rád zkouší nové jazyky i technologie. Ve volném čase cestuje, fotí, píše, ale taky plave, jezdí na kole či tancuje.

Komentáře: 30

Přehled komentářů

xx Re: Jak psát hezký kód II
stej Re: Jak psát hezký kód II
mishak Re: Jak psát hezký kód II
xx Re: Jak psát hezký kód II
LZ Re: Jak psát hezký kód II
zasekomentáře A zase a zase - nepište nepište NEPIŠTE!
hrp Re: A zase a zase - nepište nepište NEPIŠTE!
Laethnes Re: A zase a zase - nepište nepište NEPIŠTE!
K.K. Re: A zase a zase - nepište nepište NEPIŠTE!
Laethnes Re: A zase a zase - nepište nepište NEPIŠTE!
uf Re: A zase a zase - nepište nepište NEPIŠTE!
Laethnes Re: A zase a zase - nepište nepište NEPIŠTE!
uf Re: A zase a zase - nepište nepište NEPIŠTE!
Laethnes Re: A zase a zase - nepište nepište NEPIŠTE!
ijacek set/get
Michal Augustýn Re: set/get
ijacek Re: set/get
Jan Kodera komentáře
Honza Kral programovaci jazyk
okbob To snad raději ne
xx Re: Jak psát hezký kód II
Lucien Lachance Komentare nekomentare... Musi to sednout..
Michal Augustýn třídy != objekty
Tomáš Herceg Re: třídy != objekty
alef0 Re: Jak psát hezký kód II
Pavel Dvořák Re: Jak psát hezký kód II
Pavel Dvořák Re: Jak psát hezký kód II
uf preferuji prehlednost
Aleš Roubíček Chybné závěry
uf Re: Chybné závěry
Zdroj: https://www.zdrojak.cz/?p=3215