Na navigaci | Klávesové zkratky

Translate to English… Ins Deutsche übersetzen…

Best practices pro jmenné prostory v PHP

Pár dobře míněných tipů, jak navrhnout strukturu jmenných prostorů (namespaces) a názvy tříd v PHP 5.3.

Ačkoliv jmenné prostory jsou asi nejznámější novinkou PHP verze 5.3, za tolik klíčové je nepovažuji. Respektive na seznamu přání, co bych rád v PHP viděl, by se mezi první desítku dostaly jen s odřenými oddělovači. Praxe totiž ukazuje, že jmenným konfliktům spolehlivě předejde použití 1–2 písmenného prefixu, stejně tak jsem se nikdy nedopustil názvů tříd jako Zend_Service_DeveloperGarden_Response_ConferenceCall_AddConferenceTemplateParticipantResponseType (97 znaků, zajímalo by mě, jak s tím dodržují pravidlo o max. délce řádků 80 znaků :-) ). Avšak PHP kráčí ve šlépějích Javy a tak tu máme namespaces. Jak s nimi naložit?

Přínos jmenných prostorů

Asi nejsložitější otázka, na kterou si musíte odpovědět, zní: čemu prospěje přejmenovat třídu:

  • sfFormSymfony\Component\Form\Form

Otázka je osvědčeným závdavkem k nekonečným flame wars. Z hlediska pohodlí programátora, intuitivnosti a zapamatovatelnosti je vhodnější původní stručné a výstižné sfForm. Odpovídá totiž tomu, jak třídu programátoři označí hovorově, tj. „formulář v symfony“. Nový a delší název je zase správný z jiných hledisek, u nichž si ovšem nejsem jist, jestli je běžný uživatel ocení.

Může se tedy stát, že jmenné prostory budou jednou částí programátorské obce vnímány jako skvělá věc, pro kterou je třeba něco obětovat ve „vyšším zájmu“ (srovnej s XHTML), a druhou částí jako něco, co z dobře uchopitelných frameworků udělalo těžkopádné obludy (a tak jsme se na ně těšili).

Jak jmenné prostory rozvrhnout?

Syntaktickou stránku použití popisuje dokumentace, objevit správné vzory však chce praxi, na kterou zatím nebylo dost času. Prostory v PHP mají svá specifika daná celou řadu faktorů, proto není ideální 100% kopírovat konvence používané třeba v Javě nebo .NET. Dá se z nich však dobře vycházet.

Více si řekneme u jednotlivých pravidel pro pojmenování.

1) třída má mít výstižný název i bez uvedení NS

Název každé třídy i bez jmenného prostoru musí vystihovat její podstatu. Nevhodné by bylo přejmenovat třídu ArrayIteratorSpl\Iterators\Array, protože pod názvem Array by člověk neočekával iterátor (pomiňme, že třídu nelze nazvat klíčovým slovem). A pozor, ani z názvu Spl\Iterators\Array nemusí být zřejmé, že jde o iterátor, protože nelze sázet na to, že namespace Spl\Iterators obsahuje pouze iterátory. Mohou tam být třeba i nějaké pomocné třídy.

Pár příkladů:

  • nevhodné: Nette\Application\Responses\Download – nelze dovodit, že Download je odpověď
  • nevhodné: Zend\Validator\Date – předpokládali byste, že Date je validátor, nikoliv datum?
  • nevhodné: Zend\Controller\Request\Http – předpokládali byste, že Http je požadavek?

Proto krom specializace tříd je vhodné v názvu ponechat i obecnost:

  • lepší: Nette\Application\Responses\DownloadResponse
  • lepší: Zend\Validator\DateValidator
  • lepší: Zend\Controller\Request\HttpRequest

Ideální je, pokud existuje jednoslovný a přitom výstižný název. Ten lze vymyslet hlavně u tříd, které reprezentují něco z reálného světa:

  • nejlepší: Nette\Forms\Controls\Button – dvouslovné ButtonControl netřeba (avšak HiddenControl na Hidden zkrátit nelze)

2) jmenný prostor má mít výstižný název

Pochopitelně i samotný název jmenného prostoru musí být výstižný a výhodou je název kratší, bez zbytečností. Takovou zbytečností se mi zdá např. Component v Symfony\Component\Routing, protože bez něj by název nijak neutrpěl.

V některých situacích je třeba se rozhodnout mezi jednotným a množným číslem (Zend\Validator vs Zend\Validators), což je podobně nerozhodný problém, jako při volbě jednotných a množných čísel u databázových tabulek.

3) rozlište jmenné prostory a třídy

Pojmenovat třídu stejně jako jmenný prostor (tj. mít třídy Nette\Application a Nette\Application\Request) je technicky možné, mohlo by to však programátory mást a lépe se bude tomu vyhnout. Myslete i na to, jak dobře se bude výsledný kód číst nebo jak budete API někomu vysvětlovat.

4) omezte zbytečné duplicity (+ parciální namespace)

Ideální je, pokud název třídy a název prostoru neobsahují duplicitně stejnou informaci.

  • místo Nette\Http\HttpRequest raději Nette\Http\Request
  • místo Symfony\Component\Security\Authentication\AuthenticationTrustResolver raději třídu TrustResolver

Třída Nette\Http\Request neodporuje pravidlu č. 1 o výstižném názvu třídy i bez uvedení jmenného prostoru, naopak nám dovoluje elegantně využít parciální namespace:

use Nette\Http; // alias pro namespace

// dostupné jsou všechny třídy via Http:
$request = new Http\Request;
$response = new Http\Response;
// a navíc Http\Response je srozumitelnější než samotné Response

Pokud jmenné prostory chápeme jako balíčky, což je obvyklé, vede to k nešťastnému duplikování posledního slova:

  • Zend\Form\Form
  • Symfony\Component\Finder\Finder
  • Nette\Application\Application

Jmenné prostory také doslova svádí sdružovat třídy (např. různé implementace téhož rozhraní apod.) do vlastních prostorů, což opět vytváří duplicity:

  • Nette\Caching\Storages\FileStorage – tj. všechny úložiště v samostatném prostoru Storages
  • Zend\Form\Exception\BadMethodCallException – všechny výjimky do Exception
  • Symfony\Component\Validator\Exception – opět všechny výjimky do Exception

Sdružující namespaces prodlužují název a vytváří v něm duplicity, protože obecnost často není možné z názvu tříd odstranit (1. pravidlo). Jejich výhodou může být lepší orientace ve vygenerované API dokumentaci (téhož by však šlo dosáhnout i jinak) a snazší dostupnost při použití plnohodnotných IDE s napovídáním. Každopádně je doporučuji používat jen s rozvahou. Například pro vyčlenění výjimek se moc nehodí.

5) nezaměnitelnost tříd z více prostorů

Dle bodu 1) má mít třída výstižný název, což ale neznamená, že má být i jednoznačný v rámci celé aplikace. Obvykle stačí, aby byl jednoznačný v rámci jmenného prostoru. Pokud se však v kódu často používají vedle sebe dvě třídy z různých prostorů, nebo pokud mají mezi sebou jinou podstatnou souvislost, neměly by mít stejný název. Jinými slovy, nemělo by být nutné používat AS v klauzuli USE.

6) jednosměrné závislosti

Zvažte, jaké závislosti mají mezi sebou mít třídy z jednotlivých jmenných prostorů. Snažím se dodržovat:

  • pokud má třída ze jmenného prostoru A\B závislost na třídě ze jmenného prostoru A\C, neměla by žádná třída z A\C mít závislost na A\B
  • třídy ze jmenného prostoru A\B by neměly mít závislost na třídě z prostoru A\B\C (tohle berte s rezervou)

p.s.: neberte prosím tento článek jako dogma, jde jen o zachycení aktuálních myšlenek

Komentáře

  1. v6ak #1

    Co se týče situace v Javě/Scale, tak tam situaci z celké části řeší IDEčka (zvlášť v Javě) a na stručnost zápisu se mnohdy až tak nedbá (opět asi spíš v Javě), protože to pro programátora není s nekonfliktními názvy rozdíl. Jak toto řeší IDEčka? Pár příkladů:

    • Napíšu SEx, stisknu Ctrl+Space a mám SecurityException :)
    • Napíšu InStr, stisknu Ctrl+Space (popř. Ctrl+Alt+Space), vyberu první možnost a mám doplněný InputStream a importnutý java.io.InputStream.
    • Eclipse někde umožňuje u Javy automaticky importovat vyjmenované statické metody. Po příslušném nastavení jsem tedy jen napsal „asList“ (nebo dokonce jen „aL“), Ctrl+Space, Enter a měl jsem doplněný asList a k tomu import statické metody (import static java.util.Arrays.asList).
    • Zrovna dnes jsem našel v nastavení modulu pro Scalu to IntelliJ IDEA možnost automaticky importovat třídy, které nekolidují. Uvidím, jaké to bude v praxi, ale první dojmy jsou pozitivní.

    Prostě a stručně, ať si je jmenný prostor třeba kilometr dlouhý, nějak zásadně to nevadí.

    Neznám aktuální stav všech IDEček pro PHP, ale, co jsem to naposledy zkoušel, byla to celkem bída.

    K jednosměrným závislostem:

    • V prvním bodě jsi zapomněl dodat podmínku, že B != C :-P
    • Ten druhý bod se mi až tak nezdá, ale nevím přesně proč.
    před 6 lety
  2. mishak http://mishak.net #2

    avatar

    třídy ze jmenného prostoru A\B by neměly mít závislost na třídy z prostoru A\B\C.

    Např. při psaní složitějšího parseru mám kód rozdělený na
    MPQ/File a jeho vlastnost header je objekt MPQ/File/Header, příjde mi to přehlednější. Pokud bod 2 předpokládáš pak pravidlo

    třídy ze jmenného prostoru A\B by neměly mít závislost na třídy z prostoru A\B\C.

    mi příjde zbytečné. Mohl by jsi to vysvětlit na příkladu, pokud jde o jiný případ?
    Teď mi došlo, že asi myslíš vnější závislost při použití kódu, např. aby new MPQ/File nepotřeboval v konstruktoru MPQ/File/Reader?

    před 6 lety | reagoval [3] David Grudl
  3. David Grudl http://davidgrudl.com #3

    avatar

    #2 mishaku, tohle je skutečně jen soukromé vodítko. Jelikož se vyhýbám tomu mít stejně pojmenovanou třídu i namespace, měl být třídy MPQ\File a MPQ\FileHeader, takže by závislost do hloubky neexistovala. Nebo bych mohl mít interface MPQ\IHeader a pak by konkrétní implementace mohla být v podprostoru MPQ\Headers\FileHeader, ovšem samotný MPQ\File by pracoval pouze s interface.

    před 6 lety
  4. xspider #4

    avatar

    Upřímně řečeno nesouhlasím s bodem 4.
    ( 4. omezte zbytečné duplicity + parciální namespace)
    Podle mě to v rozporu z 1 je. Tím pádem může nastat stejná třída v jiném namespace a z toho jsou následující problémy:

    • při psaní v IDE se jednoduše překlepneš a zvolíš jinou Třidu než chceš (a pokud je ta jiná třeba potomkem té co jsi zvolit chtěl ani si toho nemusíš všimnout).
    • při generování dokumentace a vypsání všech tříd (např apigenem) si nejsem jist co se se stane, ale mám pocit, že se vypíše jen jedna třída stejného názvu.

    Závěr: mě přijde jediná funkční možnost rozdělovat třídy do namespace + psát výstižné názvy (tedy psát duplicitní názvy) což sice natahuje název, ale minimalizuje se pravděpodobnost chyb (viz víše).

    před 6 lety
  5. xspider #5

    Mimochodem právě jsem se o tom přesvědčil přímo v dokumentaci Nette. Když dáte zobrazit všechny třídy (All Classes) tak to třidu DI/Context nidky nevypíše, protože existuje Http/Context a ta ji přepíše. (Samozřejmě v namespace DI je třída správně, ale stejně mi to nepřijde ideální)

    před 6 lety | reagoval [6] David Grudl
  6. David Grudl http://davidgrudl.com #6

    avatar

    #5 xspidere, to byl čistě bug generátoru, nikoliv návrhu jmenných prostorů.

    před 6 lety
  7. xspider #7

    jj to jsi udělal hezky, ale asi bych ten levý panel trochu zvětšil ;-)

    před 6 lety

Tento článek byl uzavřen. Už není možné k němu přidávat komentáře.