PHP triky

Weblog o elegantním programování v PHP pro mírně pokročilé

Piškvorky naslepo

Když Jirka Knesl zveřejní nějaký svůj kód, tak mám často nutkavou potřebu napsat vlastní řešení. Teď se mi to stalo s Piškvorkami naslepo. Tentokrát jsem nebyl sám, stejnou potřebu měl i Aleš Roubíček.

Přiznám se, že mi kód v Clojure přijde dost nečitelný v původní i refaktorované verzi. V PHP jsem napsal přímočaré řešení, které prostě jen řeší daný problém nejjednodušším způsobem:

<?php
echo "Ahoj v piškvorkách naslepo.
Povolené příkazy jsou:
new - nová hra
quit - konec
[a-i][0-9] - tah na pole, kde řada je pozice a, b, c, d, e, f, g, h, i. Sloupec je 1 až 9.
Formát zápisu je např. e5.
";

function isWinningInDirection(array $field, $x, $y, $a, $b) {
    // průzkum jedním směrem
    for ($i = 1; $i < 5; $i++) {
        $yb = $y - $i * $b;
        $xa = $x - $i * $a;
        if (!isset($field[$yb][$xa]) || $field[$yb][$xa] != $field[$y][$x]) {
            break;
        }
    }
    // průzkum druhým směrem
    for ($j = 1; $j < 5; $j++) {
        $yb = $y + $j * $b;
        $xa = $x + $j * $a;
        if (!isset($field[$yb][$xa]) || $field[$yb][$xa] != $field[$y][$x]) {
            return ($i + $j - 1 >= 5);
        }
    }
    return true;
}

function isWinning(array $field, $x, $y) {
    return isWinningInDirection($field, $x, $y, 1, 0)
        || isWinningInDirection($field, $x, $y, 0, 1)
        || isWinningInDirection($field, $x, $y, 1, 1)
        || isWinningInDirection($field, $x, $y, 1, -1)
    ;
}

function isFull(array $field) {
    foreach ($field as $row) {
        foreach ($row as $val) {
            if ($val == '_') {
                return false;
            }
        }
    }
    return true;
}

$playing = 'o';
while (true) {
    $field = array_fill(1, 9, array_fill(1, 9, '_'));
    while (true) {
        $playing = ($playing == 'x' ? 'o' : 'x');

        // získání vstupu od uživatele
        while (true) {
            echo "Hráč $playing: ";
            $input = rtrim(fgets(STDIN));
            if ($input == "new") {
                break 2;
            } elseif ($input == "quit") {
                echo "Naviděnou.\n";
                exit;
            }
            $y = ord(substr($input, 0, 1)) - ord('a') + 1;
            $x = substr($input, 1, 1);
            if (strlen($input) != 2 || !isset($field[$y][$x])) {
                echo "Tah ve špatném formátu.\n";
            } elseif ($field[$y][$x] != '_') {
                echo "Pole je zabráno, hraj znovu.\n";
            } else {
                break;
            }
        }
        $field[$y][$x] = $playing;

        if (isWinning($field, $x, $y)) {
            echo "VÝHRA! Gratulace hráči $playing.\n";
            break;
        } elseif (isFull($field)) {
            echo "Remíza, hrací pole zaplněno.\n";
            break;
        }
    }

    // zobrazení hracího pole
    foreach ($field as $row) {
        foreach ($row as $val) {
            echo "$val ";
        }
        echo "\n";
    }
    echo "Nová hra.\n";
}
?>

Na mé verzi kódu mi nejvíc vadí tři do sebe zanořené smyčky while (true). Víc by se mi líbilo, když by to byly do-while smyčky, ze kterých by bylo jasně patrné, čím končí. To by ale zase kód zkomplikovalo jiným způsobem. Získání vstupu od uživatele vyloženě volá o přesunutí do funkce – k tomu bych přistoupil, pokud by kód byl ve třídě, v současné verzi by předávání stavu bylo dost komplikované.

V hlavním kódu, který komunikuje s uživatelem, je i část herní logiky – konkrétně inicializace pole, změna hráče, zápis tahu a volání testů na remízu a výhru. Nevidím to ale jako velký problém – pokud bych hru chtěl udělat třeba pro prohlížeč, tak bych určitě využil funkce isWinning a isFull, ale zbytek bych napsal znovu. Přiznávám, že abstrahovat hru tak, aby se dala napojit jak na konzoli, tak třeba na web, by dalo dost práce.

Jakub Vrána, Řešení problému, 26.11.2014, diskuse: 7 (nové: 7)

Co se mi nelíbí na JavaScriptu

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

Když jsem v 90. letech s JavaScriptem začínal, tak jsem ten jazyk nesnášel. Hlavní důvod byl ale ten, že co jsem napsal pro jeden prohlížeč, nefungovalo v druhém. Když jsem pochopil, že to není až tak problém jazyka, ale rozdílného API v prohlížečích, tak jsem si JavaScript postupně začal oblibovat, až se stal mým druhým nejoblíbenějším jazykem. Navíc je mnohem elegantněji navržený než PHP a temných zákoutí je v něm mnohem míň.

Na JavaScriptu se mi nejvíc líbí myšlenka, že funkce je úplně obyčejná hodnota podobně jako třeba řetězec – dá se kdekoliv definovat, předávat, není na ní zkrátka nic zvláštního. Většina ostatních jazyků, které znám, považuje funkce za něco speciálního a terpve v poslední době umožňuje funkce vytvářet a používat na místech, kde se dá pracovat s jakýmikoliv jinými hodnotami.

Část věcí, které se mi na JavaScriptu nelíbí, je nejspíš dána tím, že byl navržen za 10 dní před téměr 20 lety a od té doby se prakticky nezměnil. Část je ale také dost možná jiná filozofie.

Nutnost psát var

Pro vytvoření lokální proměnné je potřeba použít klíčové slovo var. Na tom by nebylo nic až tak špatného, pokud by jeho neuvedení neznamenalo, že pracujete s globální proměnnou. Chování v PHP, kde se defaultně pracuje s lokálními proměnnými a pokud chcete globální, tak musíte použít global, mi přijde mnohem lepší.

Řekl bych, že to je do značné míry vynuceno vlastností, že funkce jsou normální hodnoty. Když v JavaScriptu voláme funkci f, musí se najít proměnná f a zkusit se zavolat hodnota, která je v ní uložená. Pokud by práce s globálními proměnnými byla explicitní, tak bych vždy musel uvést, že chci volat globální f a ne tu lokální.

Function scope proměnných

Block scope je mnohem přirozenější a příjemnější na používání než function scope, kromě toho vede k méně chybám. Navíc v JavaScriptu všechny lokální proměnné vzniknou hned na začátku funkce, takže následující kód ja platný:

function () {
	alert(a); // Zobrazí undefined
	var a = 'Ahoj';
}

Pokud navíc v jedné funkci stejnou proměnnou deklarujete vícekrát (např. dva cykly ve tvaru for (var i = 0; ; )), tak vám nástroje jako JSHint vyhubují. Abyste se tomu vyhnuli a abyste zdůraznili okamžik vzniku proměnných, bylo by nejlepší všechna var napsat hned na začátek funkce, to je ale pořádný opruz.

ECMAScript 6 zavádí block scope pomocí klíčového slova let, v prohlížečích ho ale stejně nepůjde dalších několik let používat kvůli kompatibilitě.

Proměnná arguments

Proměnná arguments, která automaticky vzniká při volání funkce, sice vypadá jako pole, ale ve skutečnosti to pole není, takže na ni nejdou přímo používat metody pole. Je to vynuceno nejspíš tím, že obsah proměnné je svázán s parametry funkce – pokud uvnitř funkce do parametru přiřadíme nějakou hodnotu, projeví se to i v arguments a naopak. To ostatně považuji také za dost pochybnou vlastnost, která může kód spíš znepřehlednit, než že by mu nějak pomohla.

Osobně bych se klonil k tomu, aby funkce s nepojmenovanými parametry vůbec nemohla pracovat a aby volání funkce s více parametry, než kolik jich má, skončilo chybou. Vytvořit pole je v JavaScriptu velmi snadné, takže předat pole hodnot je téměř stejně snadné jako předat hodnoty v parametrech. Naopak to vede k přehlednějšímu API a někdy i přehlednějšímu kódu (protože není třeba používat Function.apply, ale stačí funkci rovnou zavolat).

ECMAScript 6 situaci trochu vylepšuje operátorem spread.

Iterace polí

Nejspolehlivější způsob iterace polí je pomocí for cyklu procházejícího od nuly do array.length. Je to zbytečně krkolomné, index prvku mě často vůbec nezajímá, počet prvků taky zjišťovat nechci. Navíc k iterovanému prvku musím přistupovat přes pole a aktuální index. Pokud chci navíc procházet výsledek funkce, musím si ho uložit do dočasné proměnné. Kód pak vypadá nějak takhle:

var tags = getTags();
for (var i = 0; i < tags.length; i++) {
	var tag = tags[i];
	// tady bude kód pracující s tag
}

Oč jednodušší to je v PHP:

<?php
foreach (getTags() as $tag) {
    // tady bude kód pracující s $tag
}
?>

V ECMAScript 5.1 jde používat array.forEach, ale ani to se mi kvůli API používajícímu callback nezdá jako nejlepší řešení.

Iterace objektů

Iteraci objektů taky nepovažuji za ideální především proto, že se prochází i uživatelem definované vlastnosti na prototypu. Takže pokud nějaký chytrák definuje třeba Object.prototype.clone, tak bude clone strašit při iteraci všech objektů. Jako obranu byste ve všech iteracích měli používat hasOwnProperty:

for (var key in map) {
	if (map.hasOwnProperty(key)) {
		var value = map[key];
		// tady bude kód pracující s value
	}
}

Sám jsem naštěstí závislost na cizím kódu hackujícím Object.prototype řešit nemusel, takže hasOwnProperty nepoužívám a problém mě tolik netrápí. I když iterace výhradně přes klíče a nutnost hodnotu získat mě taky obtěžuje. Oč jednodušší je PHP verze:

<?php
foreach ($map as $value) {
    // tady bude kód pracující s $value
}
?>

Objekt je HashMap, nikoliv LinkedHashMap

Pole v PHP dovoluje přistoupit k prvku podle klíče v konstantním čase a zároveň ručí za pořadí při procházení. V Java terminologii jde o LinkedHashMap. JavaScript kupodivu za pořadí při procházení objektu neručí (jde tedy o HashMap), i když ho prohlížeče až na některé okrajové případy dodržují. Donedávna jsem o téhle záludnosti neměl tušení. Pokud potřebujete pořadí dodržet, můžete si klíče ukládat do pole, které budete používat při iteraci.

Čárka za posledním prvkem

Za posledním prvkem pole nelze psát čárku. Tedy – už ECMAScript 3 to umožňuje, ale Internet Explorer to měl špatně, takže [0,].length všude vrací 1, ale IE<9 vrací 2. Za posledním prvkem objektu jde psát čárku až od ECMAScript 5, kvůli kompatibilnímu režimu prohlížečů to tedy půjde univerzálně používat až za několik let.

Tento nedostatek vadí především u diffů větších polí a objektů formátovaných na více řádek. Kdykoliv na tohle narazím, tak si představuji, jak to mohlo vypadat v květnu 1995: „Hele, parser nějak funguje, zítra to odevzdávám, trailing comma tam doplním v další verzi.“

Nepovinný středník

Automatické vkládání středníku na nahodilá místa také nepovažuji za právě povedenou vlastnost. Co myslíte, že vrátí následující funkce?

function () {
	return
	{
		x: 2
	}
}

Uhodli jste, že undefined? Za return si totiž JavaScript domyslí středník a následující objekt pochopí jako blok s návěštím x a kódem 2 (za kterým si taky domyslí středník).

V praxi se s tímto problémem člověk naštěstí často nesetká, dnes a denně na něj ale narážím prostřednictvím Google JavaScript Style Guide, který kvůli vkládání středníků vyžaduje zalamování dlouhých řádků za operátorem, nikoliv před ním (jak jsem na to zvyklý jinde).

Prototypová dědičnost

Nápad dědit z vytvořených objektů místo ze tříd jsem nikdy nevzal zasvé. Když už pominu paměť zbytečně alokovanou při vytváření objektu přiřazovaného do prototypu potomka, jak asi nastavím parametry konstruktoru předka, když ty budu znát až v konstruktoru potomka, aha? ECMAScript 5.1 dovoluje vytvořit prázdný objekt předka pomocí Object.create, sám používám goog.inherits nebo obdobu, což do prototypu přiřadí prázdný objekt, jehož prototyp nastaví na prototyp předka.

Přepisování metod objektu

Hrůzou mi vstávají vlasy na hlavě při představě, že zavolám nějakou cizí funkci, a ona mi pod rukama změní metody mého objektu – třeba je vymění za něco jiného. V testech se to může hodit, ale v běžném životě to nepřináší nic dobrého. Navíc nejsem nijak chráněn proti překlepům – když něco přiřadím do this.itme místo this.time, tak se o tom nijak nedozvím.

ECMAScript 5.1 nabízí řešení v podobě Object.freeze a Object.seal, to je ale poměrně krkolomné na používání:

function Square(x) {
	this.x = x;
}
Square.prototype = Object.freeze({
	getArea: function () {
		return this.x * this.x;
	}
});
var square = Object.seal(new Square(2));
console.log(square.getArea());
square.x = 3;
console.log(square.getArea());

Object.freeze způsobí, že vlastnosti nejde přidávat, měnit ani mazat – to chceme typicky u prototypu. Object.seal způsobí, že vlastnosti nejde přidávat a mazat, pořád jdou ale měnit – to chceme typicky u vlastností definovaných v konstruktoru.

Předávání this

Funkce jako first-class citizen miluji, dojem ale poněkud kazí chování this. Jde o to, že když někam předáte funkci, tak její this bude nějaký jiný, typicky globální objekt. To je potřeba zcela ojediněle a případně se to dá triviálně vyřešit předáním onoho objektu jako parametru funkce. Lépe by mi vyhovovalo, když by this bylo nastaveno na objekt, přes který jsme k metodě přistoupili, případně na aktuální this uvnitř closure. V praxi je pak kód zaset spoustou zbytečných Function.bind, které jen nastavují this na this.

S tímto nedostatkem souvisí i to, že vaší metodě může někdo nastavit this na libovolný objekt.

Dvě prázdné hodnoty

V JavaScriptu jsou podle mě úplně zbytečně dvě různé hodnoty, které znamenají „nic“ – null a undefined. Obě bohužel potřebujete, protože undefined mají proměnné, dokud do nich nic nepřiřadíte, a null vrací některé funkce, např. prompt. V Closure anotacích pak lze najít nádhery jako /** @type {?number|undefined} */, kde ? znamená null a undefined znamená undefined.

Ostatní jazyky, které používám, si vystačí s jednou prázdnou hodnotou. I když chápu akademický smysl dvou různých prázdných hodnot, tak v praxi je to k ničemu a akorát to otravuje život.

Chybějící operátor list

Občas mi chybí operátor list dovolující přiřadit více proměnných najednou. Ale je to vlastně jen po volání regExp.exec. Většinou absence této konstrukce vede jen k tomu, že funkce vracející více hodnot vrací objekty a ne pole, což je přehlednější. V ECMAScript 6 to řeší destructuring assignment.

Řetězce v apostrofech nebo uvozovkách

Dřív mi vyhovovalo, že hodnoty atributů v HTML, řetězce v PHP, řetězce v JavaScriptu a dokonce i řetězce v MySQL se dají uzavírat jak do uvozovek, tak do apostrofů. Časem mi ale začala nejednotnost vadit víc než to, že občas ušetřím pár zpětných lomítek. V PHP aspoň uvozovky a apostrofy znamenají dvě různé věci, v JavaScriptu bych uvozovky využil na něco jiného – ideálně na regulární výrazy, jejichž uzavírání do lomítek ztěžuje takové to domácí parsování JavaScriptového kódu.

V praxi na kód s uvozovkami naštěstí díky coding style nenarážím.

Callback hell

Krása konceptu předávání funkcí zavolaných po dokončení asynchronní operace se bohužel poněkud rozmělňuje nutností do sebe tyto callbacky hluboko zanořovat. Vede to ke kódu mnohem nepřehlednějšímu než když je psaný sekvenčně. Sám na to naštěstí tolik nenarážím, protože na klientské straně na sebe asynchronní operace většinou moc často nenavazují. Ale je to jeden z důvodů, proč mě příliš neláká node.js.

Ve Facebooku se dal asynchronní kód psát sekvenčně díky operátoru yield, který bude i v ECMAScriptu 6. Osobně by se mi ale ještě víc líbilo async/await, což je ale hudba vzdálené budoucnosti, pokud vůbec nějaké.

Závěr

JavaScript považuji za dobrý jazyk. Kromě zmíněných hodnot typu funkce se mi líbí třeba i objektové literály nebo fakt, že kód běží v jednom vlákně, i když je celkem běžně asynchronní – kód a uvažování o jeho chování to dramaticky zjednodušuje a žádné výrazné problémy to nezpůsobuje. Perfektní je i to, že || vrátí první truthy prvek. Vím, že některé nedostatky by mi pomohl překlenout CoffeeScript nebo jiný transpiler, na JavaScriptu mi ale vyhovuje hustota informací ve zdrojovém kódu, kterou považuji u CoffeeScriptu za příliš vysokou. Ze stejného důvodu jsem si nikdy neoblíbil Perl – přišel mi příliš nepřehledný, zato kód v PHP jsem chápal, aniž bych se jazyk musel učit. Java je pro mě zase příliš řídká. S některými věcmi pomáhá Closure Library a Closure Compiler, ale milejší by mi bylo, když bych na nich nemusel záviset. TypeScript mi ve srovnání s možnostmi ekosystému Closure přijde jako chudý příbuzný.

Sérié článků „Co se mi nelíbí“ na Javě, na Go, na JavaScriptu nasvědčuje tomu, že hledám jazyk, ve kterém by se mi programovalo stejně pohodlně jako v PHP (nebo pohodlněji). JavaScript tím jazykem, který bych bez přemýšlení použil na cokoliv, bohužel také není.

Přijďte si o tomto tématu popovídat na školení JavaScript a AJAX.

Jakub Vrána, Osobní, 21.11.2014, diskuse: 0 (nové: 0)

Co se mi nelíbí na Go

Go je relativně nový programovací jazyk navržený v Google. Setkal jsem se s názorem, že do pěti let půjde o nejpoužívanější programovací jazyk, tak jsem se přihlásil na školení základů tohoto jazyka. Jinou zkušenost s Go nemám, takže moje postřehy jsou povrchní a nepodložené praxí (kromě příkladů, které jsme na školení dělali). Některé body se v praxi jako tak problematické projevit nemusí, některé další naopak můžou vyplout na povrch.

Go považuji za celkem zdařilý programovací jazyk, ovlivněný z mého pohledu především JavaScriptem, Pythonem a C. Místy dosahuje expresivity Perlu, ale přitom zůstává čitelný. Co se mi nelíbí?

  1. Ukazatele. V C a C++ jsem ukazatele vždycky nesnášel. V Go jsou naštěstí mnohem umírněnější, např. nejde ukazatel přetypovat, stejně by se bez nich ale podle mě vysokoúrovňový programovací jazyk měl obejít. PHP má místo ukazatelů reference, ty bych klidně taky zrušil (např. Recki-CT je nepodporuje).
  2. Magie. Dereferenci pointeru někde udělá kompilátor za vás, někde ji musíte udělat sami. Nezapamatoval jsem si, kde přesně, a nevidím důvod, proč by to nemohlo jít skoro všude. Typy na některých místech uvádět musíte, jinde ne. To je ale naštěstí celkem logické. V definici konstant se za určitých okolností nemusí uvádět hodnota a odvodí se z předchozí konstanty.
  3. Ošetření chyb. Podle mě jde o nejzásadnější problém jazyka. Neexistují výjimky, místo toho funkce obvykle vrací dvojici návratových hodnot (výsledek, chyba). Volající musí chybovou část návratové hodnoty zkontrolovat. Pokud to neudělá, tak se nic nestane. Pokud k chybě dojde, funkce stejně musí něco předat i ve výsledku, např. (0, chyba), což jen podporuje volajícího v tom, aby chybu nekontroloval. Pokud chcete zjistit, jestli v mapě existuje nějaký prvek, tak se to dělá také druhou návratovou hodnotou, tentokrát ale v přesně opačném významu než u funkcí (v, ok := a[1] oproti v, err = f()).
  4. Nejednotnost. Funkce append změněný slice vrátí, funkce delete mapu změní na místě.
  5. Zkratky. Na většině míst to ničemu nevadí a řádky jsou aspoň kratší. Ale aby se např. zřídka používaná funkce pro zjištění kapacity musela jmenovat cap, to si nemyslím.
  6. Slices. Slice je jen pohled na pole. Když máte dva různé pohledy na to stejné pole a změníte hodnotu v jednom z nich, tak se změní i ten druhý. Klidně můžete přistupovat i k prvkům za délkou slice, pokud to kapacita jeho pole umožňuje. Nevím, jestli tyto vlastnosti vedou k reálným chybám, ale přijdou mi velmi nebezpečné.

Go má i řadu příjemných vlastností, ale tyto problémy jsou pro mě natolik zásadní, že se Go nechystám začít používat.

Jakub Vrána, Seznámení s oblastí, 22.9.2014, diskuse: 17 (nové: 17)

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

Č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é: 3)

Rekurze regulárních výrazů

Když někdo říká, že umí dobře regulární výrazy, tak mu obvykle položím tuto otázku: „Jaký je rozdíl mezi once-only subpattern a possessive quantifier a kdy je použiješ?“ Schválně si na tuhle otázku teď zkuste odpovědět.

Once-only subpattern (?>) se vyznačuje tím, že zkonzumuje, co se dá, a nikdy se nevrací. Pokud tedy za zkonzumovanou částí nenajde text odpovídající zbytku regulárního výrazu, tak skončí chybou. Jindy se v takových situacích pokusí regulární stroj vrátit a hledat shodu dřív.

Possessive quantifier se zapisuje jako plus za kvantifikátorem, např. *+. Tento výraz je velmi podobný – sežere, co jde, a nikdy se nevrací. Jediný rozdíl je tedy v tom, že possessive quantifier je trochu stručnější, pokud chceme vyjádřit opakování. a*+ se dá zapsat jako (?>a*). Naopak to jde taky, i když trochu krkolomněji – (?>ab) se dá zapsat jako (?:ab){1}+. Odpověď na první část otázky tedy je, že rozdíl mezi těmito výrazy není žádný a používají se ve stejných situacích.

Používají se tehdy, když potřebujeme zabránit rekurzi, kterou regulární stroj používá při hledání shody. Na její limit (pcre.recursion_limit) můžeme narazit především při použití opakování uvnitř opakování, např. (\d+,)*.

Alternativou k regulárním strojům používajícím rekurzi nabízí knihovna RE2, která zaručuje běh v lineárním čase. Je trochu omezenější než Perlové regulární výrazy, např. nepodporuje lookahead aserce. Existuje i verze pro PHP, nicméně kvůli její nedostatečné rozšířenosti ji nepoužívám.

Jakub Vrána, Seznámení s oblastí, 25.6.2014, diskuse: 1 (nové: 1)

Starší články naleznete v archivu.

avatar © 2005-2014 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.