V čem je PHP navrženo lépe než Java

Školení, která pořádám

Článek vyšel na serveru Zdroják.

Existuje spousta článků, které kritizují návrh PHP, nejznámější je asi PHP: a fractal of bad design. Na ten jsem napsal jen poměrně krátkou reakci, protože se zbytkem článku v zásadě souhlasím. PHP je skutečně v mnoha ohledech špatně navržený jazyk. V čem je ale navržený lépe než Java?

Anonymní funkce

Java dlouhou dobu neměla anonymní funkce a tzv. lambda expressions zavedla až letos. Do té doby se používaly jen krkolomné anonymní třídy. V PHP jsou naproti tomu anonymní funkce už pět let (2 roky s podporou $this) a do značné míry změnily, jakým způsobem se v PHP programuje.

Nutnost používání $this

V PHP se musí při přístupu k vlastnostem a metodám objektů vždy uvádět $this. V Javě to není potřeba, tedy pokud název vlastnosti nekoliduje s názvem lokální proměnné. Zní to jako výhoda Javy, ale mně dalo dost práce, než jsem si na to zvykl. Při pohledu do kódu Javy často přemýšlím – je tohle lokální proměnná, vlastnost objektu nebo je snad deklarovaná v rodiči? Připomíná mi to doby register_globals, kde jsem také často dumal, kde se nějaká proměnná vlastně vzala.

Hodnota null

V Javě je hodnota null implicitně povolená u všech neprimitivních datových typů. Ošetření této hodnoty prolézá kódem jako rakovina. Existuje pokus o řešení v podobě anotace NotNull, ale její použití a ostatně i definice jsou nejednotné. Jiný pokus o řešení téhle hrůzy je nově uvedená třída Optional, místo které ale taky můžete dostat null… V PHP naproti tomu jde do parametru s type hintem poslat null, jen pokud je volitelný. Je velká úleva nemuset v těle funkce přemýšlet o tom, jestli mi náhodou někde nemůže přijít null.

Volitelné parametry

Java nezná volitelné parametry. Místo toho si můžete nadefinovat více metod stejného jména, které přijímají různé parametry. V některých situacích se dají použít i varargs, které mimochodem do jazyka byly dohackované neuvěřitelným způsobem (lze je předat jako seznam parametrů nebo jako pole, takže pokud chcete předat jediný parametr obsahující pole, tak se pěkně zapotíte). Přetěžování metod obecně nepovažuji za dobrý nápad, často v kódu koukám na volání a lámu si hlavu tím, co se vlastně zavolá, a odpověď často poskytne až IDE. Přístup PHP, kdy jeden název může mít jen jedna metoda, mi vyhovuje mnohem líp. Jen je škoda, že pokud parametr může být různých typů bez společného předka, tak musíme oželet type hint. V tomhle se mi líbí Closure, kde se sjednocení různých typů používá zcela běžně.

API pro regulární výrazy

Java má hezký koncept explicitní kompilace regulárních výrazů. V PHP se zkompilované regulární výrazy automaticky kešují a programátor nad tím nemá kontrolu. To nevadí u krátkých skriptů, ale u dlouhoběžících aplikací (např. démonů) to může být problém, obzvlášť pokud někdo vytváří regulární výrazy dynamicky. Potíž s Javou je v tom, že funkce pro pohodlnou práci s regulárními výrazy String.matches a String.replaceAll přijímají regulární výraz ve tvaru řetězce a nikoliv jeho zkompilovanou podobu. To by mimochodem bylo jedno z mála smysluplných použití přetěžování. V Javě si tedy můžete vybrat, jestli budete používat pohodlné API s anti-patternem (za který se opakované používání nezkompilovaných regulárních výrazů považuje) nebo API nepohodlné. Mimochodem rozlišit metody přijímající řetězec nebo regulární výraz pomocí názvů replace a replaceAll nepostrádá notnou dávku zbabranosti.

Inicializace polí a map

Java dovoluje inicializovat pole konstrukcí {}, bohužel to jde ale jenom u deklarace. Předávání pohotově vytvořeného pole by se hodilo třeba v testech. Pole si musíte přiřadit do proměnné a tu teprve poslat funkci. Že byste proměnnou přepsali nějakým jiným pohotově vytvořeným polem, na to rovnou zapomeňte a na inicializaci map raději ani nemyslete. Když chcete vytvořit třeba konstantu s mapou, musíte ji inicializovat ve statickém konstruktoru. Guava nabízí alespoň jednoduchou možnost vytvoření ImmutableMap, na vytvoření Map ale nic standardního není. V PHP jde pole inicializovat triviálně, včetně přiřazení klíčů.

Pole a seznamy

Java vznikla s podporou polí (array), později doplnila také seznamy (List). Obojí se používá v zásadě pro to stejné, některá API používají pole, jiné seznamy. Trochu mi to připomíná nejednotnost PHP v chování k nativním polím a ArrayObject, zmatek v Javě je ale přeci jen o poznání větší.

Iterace

Java 1.5 přinesla jednoduchou možnost iterace pomocí for each. Pokud ale chcete procházet klíče i hodnoty mapy, tak se i s touto konstrukcí pěkně zapotíte. Asi nejčistší je použití metody entrySet, která vrací Map.Entry, na které můžete zavolat getKey a getValue. To by se místo:

for (Map.Entry<String, String> entry : map.entrySet()) {
    String key = entry.getKey();
    String value = entry.getValue();
}

… nemohlo psát třeba for (String key, String value : map) {}? V PHP je iterace klíčů i hodnot polí maximálně pohodlná.

Zbytečné přetypování

V Javě je potřeba na mnoha místech zbytečně přetypovávat. Např. v tomto kódu:

if (x instanceof String) {
    f((String) x);
}

To by si kompilátor nemohl domyslet, že x nemůže být jiného typu než String? Closure Compiler to zvládne. V PHP žádné přetypování na třídy neexistuje.

Kompilace

PHP skript spustíte a okamžitě běží. Java se nejprve musí zkompilovat, což je např. ve srovnání s Go neuvěřitelně zdlouhavý proces. Kompilace má svou výhodu v tom, že se při ní zaručeně dozvím o chybách, na které by se při běhu ani nemuselo narazit. V PHP tuto roli suplují lintery. Kompilaci a spuštění bych si představoval oddělené. Nerad bych se vzdával všech chyb, o kterých se díky kompilaci dozvím, zároveň bych ale chtěl mít možnost program rychle spustit. Java stejně zkompilovaný bajtkód optimalizuje při běhu, to by to nemohla dělat rovnou ze zdrojového kódu?

Porovnávání řetězců

Řetězce jsou v Javě objekty, takže se nedají porovnávat operátorem ==. Místo toho musíte použít metodu equals, takže místo $a != "a" se obvykle píše podle mě zcela nepřehledné !"a".equals(a). Nejhorší ze všeho je, že Java používá tzv. string interning, takže program porovnávající řetězce pomocí == bude normálně fungovat až do té doby, kdy z ničeho nic fungovat přestane.

import *

Když PHP zavedlo jmenné prostory, tu a tam někdo naříkal, že nepodporuje import všech objektů z daného jmenného prostoru. Java tuto možnost má a v jednorázovém skriptu se docela hodí napsat si import java.util.* nebo něco podobného. V seriózních projektech jde ale o anti-pattern, protože co se asi stane, když nějaký jmenný prostor přidá třídu, jejíž název už používáte z jiného jmenného prostoru (ideálně také přes hvězdičku)?

Vlákna a synchronizace

Může být užitečné vytvořit si více vláken, ale způsob, jakým se s nimi potom v Javě pracuje, je tak nízkoúrovňový, že bych se bez toho snad radši obešel. Mluvím především o potřebě synchronizace. Přístup PHP, kde všechno běží v jednom vlákně nebo procesu a o spuštění více věcí najednou se stará webový server, mi vyhovuje mnohem víc. Když už je někdy potřeba dělat víc věcí najednou v rámci jednoho skriptu, tak je to obvykle čekání na výsledek, a to jde v PHP poměrně snadno pomocí futures. Ostatně i v JavaScriptu plném callbacků najednou běží vždy jen jeden kód.

Závěr

Článek se jednostranně zaměřuje na nevýhody Javy ve srovnání s PHP z mého pohledu. Rozhodně nejde o nezávislé porovnání obou jazyků, ale spíše o ukázku toho, kde měli tvůrci PHP šťastnější ruku. Napadají vás další oblasti, kde je Java špatně navržená? Podělte se o ně v diskuzi. Naopak se prosím zdržte kritiky PHP, ať se zaujatý pohled článku zbytečně nerozmělní.

Viz též Effective Java.

Jakub Vrána, Seznámení s oblastí, 4.7.2014, diskuse: 3 (nové: 0)

Diskuse

Franta:

Asi jediné, v čem se s tebou shodnu, je to přetypování po instanceof. Taky už mne to kdysi napadlo... ale když začneš přemýšlet o tom, jak by se nějaký elegantnější způsob implementoval, zjistíš, že je to celkem netriviální a asi to za to nestojí. Java je přeci jen dost konzervativní a klade velký důraz na kompatibilitu – nemůžeš si dovolit něco rozbít nebo zaneřádit jazyk kvůli takové prkotině.

Další věc k zamyšlení je, jak často by se instanceof mělo v kódu objevovat a jestli jeho (nad)užívání není spíš chyba návrhu – používání příliš obecných typů, nebo hloupé naházení různých objektů do jednoho pytle a následně jejich pracné třídění zase na různé hromádky.

v6ak:

Jde to vylepšit:
a) Magie - autodetekovat podmínku s instanceof. (Už jsem to někde viděl.)
b) Pattern matching - nebudu popisovat detaily a odkážu na Scalu. Pattern matching má nicméně širší záběr, alternativu k instanceof popisuje článek http://kerflyn.wordpress.com/2011/02/14/playing-…-pattern-matching/ v části "Typed pattern".

BTW, Scala údajně má metody isInstanceOf (obdoba instanceof) a asInstanceOf (přetypování) záměrně pojmenované dlouze, aby lidé dali přednost pattern matchingu.

Franta:

Magie/autodetekce je hodně velká prasárna. Tohle nemůže stavět na IFech a na instanceof (jehož výstupem je boolean). Říkal jsem si, že by to mohla být nějaká zvláštní forma switche, ale tady je otázka, jak do jednotlivých větví protlačit proměnnou se správným konkrétnějším typem.

Jinak ta Scala vypadá dobře. Třeba se to do Javy jednou dostane :-)

Vložit komentář

Používejte diakritiku. Vstup se chápe jako čistý text, ale URL budou převedeny na odkazy a PHP kód uzavřený do <?php ?> bude zvýrazněn. Pokud máte dotaz, který nesouvisí s článkem, zkuste raději diskusi o PHP, zde se odpovědi pravděpodobně nedočkáte.

Jméno: URL:

avatar © 2005-2018 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.