Práce s vlastnostmi pomocí metod
Školení, která pořádám
Další věc, se kterou nesouhlasím v knize Dokonalý kód, je doporučení pro čtení a zapisování vlastností objektu používat zásadně metody:
<?php
// vlastnosti
$db->login = $login;
echo $db->login;
// metody
$db->setLogin($login);
echo $db->getLogin();
?>
Mě se tento přístup nelíbí z několika důvodů:
- Je s ním o něco víc psaní.
- Kód je podle mě o krapítek hůř čitelný.
- Nutí nás vytvářet samoúčelné jednořádkové metody:
<?php
// vlastnosti
class Db {
public $login;
}
// metody
class Db {
private $login;
public function getLogin() {
return $this->login;
}
public function setLogin($login) {
$this->login = $login;
}
}
?>
Argument pro používání metod je prakticky jen jeden – pokud bychom se v budoucnu rozhodli, že chceme vlastnost před uložením kontrolovat nebo před vrácením získat odjinud, tak to jde s metodami bez změny API. To ale platí jen v primitivnějších jazycích, které nepodporují přetěžování přístupu k vlastnostem. V PHP totiž můžeme tohoto chování dosáhnout pomocí metod __get a __set i u vlastností. K dokonalosti tuto techniku dotahuje Nette\Object.
Fluent Interface
Settery se můžou jevit jako výhodné i z důvodu možnosti vytvářet tzv. fluent interface. Tedy že setter vrací objekt, který modifikuje:
<?php
class Db {
// ...
public function setLogin($login) {
$this->login = $login;
return $this;
}
public function setPassword($password) {
$this->password = $password;
return $this;
}
}
// vlastnosti
$db->login = $login;
$db->password = $password;
// metody
$db
->setLogin($login)
->setPassword($password)
;
?>
Někomu se druhý zápis může zdát pohodlnější a za určitých okolností s ním skutečně může být míň psaní. Něco to ale stojí – samozřejmě v žádné metodě na return $this
nesmíme zapomenout a pokud používáme cizí kód, tak bychom se správně měli nejprve podívat do dokumentace každé metody a ověřit, že fluent interface podporuje. Uznávám ale, že to je výtka spíše akademická.
Závěr
Používání vlastností (ať už skutečných nebo virtuálních) je podle mě mnohem přirozenější a pohodlnější než manipulace s nimi pomocí metod. Platí to jak pro vývojáře třídy, tak pro jejího uživatele. Na druhou stranu to není potřeba brát nijak dogmaticky – např. NotORM je na setterech a fluent interface založeno a bez toho by se používalo mnohem hůř.
Přijďte si o tomto tématu popovídat na školení Programování v PHP 5.
Diskuse
Václav Vaník:
Imho je používá magických metod __get a __set větší zlo než používání getterů a setterů.
a) mag. metody jsou pomalejší
b) nefunguje v IDE doplňování kódu
c) pro jiného developera je používání takového kódu jeden velkých blackbox
Václav Vaník: doplňování kódu se dá vyřešit pomocí phpDoc anotací @property, @property-read a @property-write.
Magické metody jsou možná pomalejší, ale v rozumné míře užití je časová ztráta zanedbatelná. Já si život bez Nette\Object už ani neumím představit :)
Vladimír Bělohradský:
Blackbox to není, použití magických vlastností lze popsat v komentáři objektu. Chytrý editor jako Netbeans potom dovede takto nadefinované vlastnosti našeptávat a documentor je samozřejmě vypíše do dokumentace. To je za A, a za B dobře navržený objekt by měl být vesměs autonomní jednotkou. Vy po objektu chcete aby něco vykonal a jak to vykoná Vás vesměs nemusí zajímat. Na druhou stranu praxe je taková, že vágní znalost OOP vede ke špatnému návrhu objektů což ústí ve smutný fakt, kdy se programátor stává spíše detektivem a snaží se zmateným myšlenkám předchozího programátora porozumět. Některý kód připomíná diskuse na xchatu.
Holmistr:
Naprosto souhlasím s používáním magických metod, mi to přijde jako velké ulehčení a kód je potom mnohem lépe čitelný.
Martin Hruška:
Bude i polemika s autorem té knihy? Mohli bychom sepsat společný manifest "co se nám v knize nelíbí", ten zde zveřejníme, pan McConnel se k němu vyjádří a my pak jeho nesmysly zde rozcupujeme.
Ale vážně: problém je totiž v tom, že PHP jako jazyk programátorovi neulehčuje jeho úděl a programátor si pak šetří práci tím, že na "dobrý kód" rezignuje a zavede "prasácký kód co se mi líbí"
Opravdový odborník :-):
+1
Já zase s autorem knihy Dokonalý kód naprosto souhlasím.
Ad 1) Že je o něco víc psaní mě nezajímá.
Ad 2) Že je kód hůře čitelný si nemyslím. Ba právě naopak vím při volání metody, že se může dít o něco více, než jen přiřazení proměnné a je to zdůrazněno.
Ad 3) Velmi často ze samoúčelných jednořádkových metod časem vznikne něco více.
Navíc štábní kultura a udržovatelnost přináší obrovské benefity, které vyváží to, že člověk napíše o několik písmenek navíc.
Cílem programování nikdy nebylo napsat o písmenko méně, ale co nejlépe zvládnout proces vytváření programu a udržování programu. Dále také udělat programy co nejlepší, nejspolehlivější a splňující to co se od programu čeká.
Za svých 25 let praxe jsem měl v rukou programy pár lidí, co šetřili písmenky – a celkem lze říci, že každou firmu každé ušetřené písmenko takového lenocha stálo několik tisíc Kč za písmenko a leckdy i mnohem mnohem více.
2. Tvrdíš tedy, že by metoda setLogin() měla dělat ještě něco více, než nastavit login? Jakým způsobem to zdůrazňuje?
V metodě setLogin() může být kontrola typu string, validace délky jména, strtolower apod.
Skvěle vystiženo -- s kódem po šetřílcích, kteří se snažili minimalizovat počet řádků/znaků za každou cenu, mám taky špatnou zkušenost.
Také souhlasím s knihou. Kromě toho, že se podle popisu vůbec nezabývá PHP, takže nějaké magické metody z dynamického jazyka, jako je PHP, autora vůbec nemusí zajímat, tak jde především o zapouzdření a typehinting.
1) Více psaní - pro klid na duši a jistotu, že se nezmění implementance, těch pár znaků navíc klidně napíšu. Kostru mi může dokonce vygenerovat IDE. Často v metodách také probíhá více než jen jednoduché nastavení parametru. Pro sjednocení přístupu tedy upřednostňuji psát metody pro vše.
2) Kód je čitelný bez problému. Naopak, pokud bych místo metod měl použít Nette\Object properties, tak vůbec nevím, co kód dělá - jestli je proměnná public nebo jestli se volá nějaký getter. Navíc v PHP nelze udělat pravé properties (jako jsou v C#) a někdy se přistupuje přímo k parametru bez použití getteru - např. u sourozenců s protected metodami. Zavádí to zmatek do kódu.
3) Viz bod 1 - často jen jednořádkové nejsou a sjednotit kód je vhodné.
ještě bych doplnil:
4) Gettery a settery - zvláště ty "jednořádkové" si člověk může generovat pomocí IDE. Pokud pak potřebuje nějakou logiku dopsat, tak má připravenou půdu.
sakra, tu větu u Ondry jsem přehlíd, ale aspoň je vidět, že na to máme stejný názor :)
Zcela s tebou souhlasím, Ondro. Každá magická funkce a obecně každá magie (automaticky se mi vygeneruje nějaký javascript, automaticky se mi zvaliduje nějaký vstup/výstup) je programátorský dluh. A je tím větší, čím je magie komplexnější.
Pokud napoprvé napíšu CRUD generátor z databáze, tak velmi pravděpodobně zapomenu preLoad, postLoad, preSave a postSave hooky (a to možná nejsou všechny - třeba chci preInsert a preUpdate a možná i preDelete, atd). A přijde na to velmi brzo. Tohle je jasný příklad, protože ho skoro každý viděl. Ale v každé takové magii je "n náleží N" use-cases, které člověk nedohlédne když ji navrhuje. A každý tenhle use-case vede buď na přepsání té magie (lepší případ) nebo na opakování kódu (to je ten horší případ - např. ve chvíli, kdy je ta magie součástí nějakého frameworku a chci časem upgradovat na vyšší verzi, případně pokud je framework closed-source).
Generování kódu nesnáším. Znamená to, že kvůli každé změně musím spouštět generování. To prostě není elegantní. Nedávno jsem o tom psal: http://php.vrana.cz/cena-zmen.php
Já taky, je to zlo. Ale vygenerování pár getterů a setterů (v praxi jde o pár řádků), pokud se ti je při založení třídy nechce psát, ulehčí práci. Vygenerovaný kód v tomto případě vypadá stejně jako ručně napsaný.
Jakube, v OOP jde o posílání zpráv. Existují objekty a existují zprávy. Cokoliv mimo to (tedy například přístup k atributům zvenčí) je už narušení OOP. Můžeme diskutovat o tom, zda má praktický přístup navrch nad čistotou, ale to už bude spíš založením každého konkrétního programátora.
Já sám si myslím, že by nic jako public atributy ani nemělo existovat. Ale zas uznávám, že PHPčku chybí attr_accessory ala Ruby nebo @synthetise v Obj-C, kde je to celé velmi prakticky použitelné.
A to ani nemluvím o tom, že settery (podobně jako dedičnost, private/protected visibility) jsou velmi špatně pochopení a používané. V podstatě mimo Dependency Injection a objekty Container bývá chyba nějaké settery, gettery používat.
Ad generování kódu: mám na plno věcí snippety if[tab] ho rozepíše, while, foreach, for, class, metody, testy. Settery a gettery patří do stejné kategorie - základní stavební kameny aplikace, třířádkové opakující se kusy kódu.
Tvé pojetí OOP není v souladu s pojetím PHP (a většiny ostatních programovacích jazyků, které znám). Objekt je soubor vlastností a metod. A vzhledem k tomu, že vlastnosti mohou mít viditelnost public, tak je zcela absurdní tvrdit, že přístup k atributům zvenčí je porušení OOP (alespoň v pojetí PHP).
Předpokládáš, že existuje více OOP? Podle mě je OOP právě jedno: způsob dekompozice problému do objektů, které si vzájemně zasílají zprávy bez ohledu na jazyk zápisu. OOP je spíše záležitostí návrhu a designu než implementace.
Že jsou dnes jazyky hybridní a umožňují naráz objektový-funkcionální-deklarativní-imperativní přístup vyplývá z toho, že je to často praktické, byť tím samozřejmě vzniká prostor pro nesprávné použití jazyka.
Nicméně jak jsem psal, pro settery/getterů existuje jen velmi málo důvodů a mimo Dependency Injection a kontejnerové objekty bývá obvykle existence setter/getterů (nebo hůř public atributů) důkazem špatné analýzy. Objekt má řešit svou zodpovědnost a jak si uvnitř udržuje data je jeho problém a okolním objektům do toho nic není.
Jakub Vrána :
Pominu, že některé věci jsou psané na míru Javě: např. že veřejné vlastnosti a accessory jsou špatné, protože nemůžeme snadno změnit jejich typ – přitom „typedef float latitude“ bylo už v C, v PHP je to zcela nerelevantní.
I tak bych to asi potřeboval vysvětlit na příkladu. Mně náramně vyhovuje tenhle přístup:
<?php
$mail = new MailMessage;
$mail->from = "from@example.com";
$mail->to[] = "to@example.com";
$mail->cc[] = "cc@example.com";
$mail->subject = "Test";
$mail->message = "Hello World!";
$mail->send(new Mailer);
?>
Dokážu žít i s tímhle:
<?php
$mail = new MailMessage;
$mail->setFrom("from@example.com");
$mail->addTo("to@example.com");
$mail->addCc("cc@example.com");
$mail->setSubject("Test");
$mail->setMessage("Hello World!");
$mail->send(new Mailer);
?>
A rád bych tedy věděl, co je ten svatý grál, který nepoužívá veřejné vlastnosti ani accessory. Podotýkám, že u obou popsaných přístupů očekávám, že mi každý z příkazů vyhodí výjimku, pokud dostane špatná data (např. neplatnou e-mailovou adresu) a nepovažuji to za žádnou magii.
Gettery bych zase potřeboval v případě, když bych chtěl něco z vytvořené zprávy uložit třeba do databáze.
Tharos:
Jako nezávislý zastánce myšlenek zde prezentovaných Ondrou Mirtesem, Jirkou Kneslem a dalšími můžu vyjmenovat namátkou následující:
1. To, že se v prvím případě využívají tzv. magické metody zajišťující validaci, víš Ty, ale jiní programátoři už to vědět nemusí a určitě to z kódu nevidí na první pohled. Osobně pokud bych přišel k podobnému kódu bez jeho předchozí znalosti, dokážu si představit, že bych měl tendenci dopsat validaci platnosti e-mailových adresy, čímž bych ji nechtěně zduplikoval. Troufám si odhadnout, že nezanedbatelné množství nově příchozích vývojářů by nenapadlo zkontrolovat, zda už se pro to náhodou nepoužívají magické metody.
Ve druhém případě je na první pohled jasné, že se hodnota předává přes nějakou k tomu určenou metodu a každý nově příchozí programátor by se myslím na 99% rozhodl nejprve ověřit, co se v onom setteru přesně děje, a validaci by tam v tom případě objevil. Nedošlo by tak k její nechtěné duplikaci.
2. Pokud bys měl ve třídě řekněme deset vlastností a každá by používala jiná validační pravidla, tělo __set metody by asi už nebylo příliš pohledné. Muselo by se v ní rozlišovat, do které z deseti vlastností se přiřazuje a jak se má to či ono přiřazení validovat. S již dvaceti vlastnostmi (třeba u nějaké třídy reprezentující entitu, ty mívají hodně vlastností) by tělo metody __set mohlo být už vskutku odstrašující, nemyslíš? Rozčlenění do dvaceti setterů je mnohem přehlednější. Příchozí programátor nebude muset lovit v __set metodě místo, které se týká jím zpracovávané vlastnosti, ale půjde rovnou k věci (tj. konkrétnímu setteru).
Třeba jenom tohle jsou pro mě osobně důležité argumenty.
Jakub Vrána :
Nic přece nebrání tomu z metody __set($name, $value) volat set$name($value), např. Nette\Object to dokonce dělá automaticky.
Ale špatně sis můj komentář přečetl – já oba způsoby považuji za přijatelné. První mi je trochu sympatičtější jak z pohledu autora třídy MailMessage, tak z pohledu jejího uživatele, ale dokážu žít i s druhým a uznávám, že může mít nějaké výhody, i když ne pro mě.
Od Jirky jsem chtěl vědět, jak by se třída napsala bez veřejných vlastností a accessorů, které tak kritizuje (reaguje dále).
Rene Stein:
Psal jsme původně komentář, který se mi trochu rozrostl. Snad to bude zajímavé i pro vás, i když se to netýká vlastností.
http://bit.ly/lCRnf8
Tomáš Konrády:
Také se mi tento přístup libí více... Ovšem snad v každé literatuře to nedoporučují.
Jsi první, koho vidím, který vyslovil tento názor nahlas.
Každopádně by se v jednom programu měla dodržovat jednotná konvence.
Jiří Knesl:
Silná typovost je jen jeden z mnoha důvodů.
Důvody jsou:
- tedy typová kontrola
- odhaluje se interní struktura objektu
- mel bych byt schopen zcela vymenit tridu a nemelo by to nic postihnout
- misto toho, abych potreboval gettery, mel bych objektu, ktery informace ma, rict, at operaci primo provede
- objekt buďto data reprezentuje (pak má settery/gettery), nebo vykonává činnost (pak dostane data parametry).
Co se tvého příkladů týká, implementoval bych ho podobně, jen:
<?php
$mail = new MailMessage;
$mail->setFrom("from@example.com");
$mail->addTo("to@example.com");
$mail->addCc("cc@example.com");
$mail->setSubject("Test");
$mail->setMessage("Hello World!");
$mailer = new Mailer;
$mailer->send($mail);
?>
Obrátil bych to, MailMessage bude jen Container, který může mít settery (což jsem psal už dřív), ale nemá žádnou zodpovědnost, jen odpovídá MailMessageInterface. A mailer už žádné settery mít nebude, protože to je výkonný objekt se zodpovědností.
Jakub Vrána :
Nad metodou send() jsem taky dost přemýšlel a podle mě by zpráva rozhodně měla mít schopnost sama sebe odeslat. Ale vezměme další příklad:
<?php
foreach ($table->where("id", 1)->order("title") as $row) {
}
?>
Jak by vypadalo API bez setterů?
Ondřej Mirtes:
Zareaguji na Mail x Mailer: Třída by měla mít jednu zodpovědnost. Zodpovědností třídy Mail je reprezentace e-mailové zprávy. Co když jí vytvářím za účelem ne odeslání, ale uložení do databáze, případně si touto třídou reprezentuji zprávy, které stáhnu z IMAPu? Řešit operaci odeslání vně pomocí další třídy mi přijde lepší. Beztak ten Mailer musí mít metodu <?php send(Mail $mail) ?>, tak proč bych ho měl vkládat do objektu Mail, když tuto metodu mohu transparentně zavolat sám...
David Grudl:
Naprostý souhlas. Pošleš patch? ;-)
Jakub Vrána :
MailMessage::send() má své místo podle mě především proto, že to tam lidi prostě hledají. A také tím, že to je ta primární operace, kterou se zprávou dělám.
Ale vezměme další příklad – co třeba takové Image::send() a Image::save()? Obrázek přece taky můžu vytvářet z jiného důvodu, než abych ho odeslal do prohlížeče nebo uložil do binárního formátu. Neměly by se tyto metody v rámci puristické čistoty taky vyhodit? Nepřesouvá se to už trochu do absurdní roviny?
Ondřej Mirtes:
Ano, může znít absurdně, pokud si pokládám otázky jako "měl by se e-mail sám odesílat?", "měl by se obrázek sám umět uložit?", ale pokud vezmu v potaz dvě skutečnosti:
1) Obrázek mohu chtít ukládat nejen na disk, ale i do relační nebo objektové databáze či na cizí server přes HTTP/FTP.
2) V aplikaci nebudu chtít ukládat pouze obrázky, ale i jiné objekty/typy souborů.
Tak toto oddělování začne dávat smysl, protože si tím zajistím neopakování kódu a znovupoužitelnost.
Jakub Vrána :
Tyhle teoretické řeči zní samozřejmě strašně pěkně. Mohl bych tedy vidět konkrétní návrh API?
Ondřej Mirtes:
Konkrétně si můžeš udělat interface IRepository s metodami exists($name), put($name, $content), get($name) a delete($name) a pro ukládání na disk implementaci FileRepository, která bude v konstruktoru přijímat cestu k adresáři, do kterého budeš chtít ukládat. Obrázek pak uložíš třeba takto (dejme tomu, že toString() vrátí jeho binární reprezentaci jako v Nette):
<?php
$repository->put('image.jpg', $image->toString());
?>
Tento FileRepository pak využiješ napříč celou aplikací v mnoha situacích a máš v něm mít na jednom místě vyřešené ukládání a mazání souborů s chybovými stavy, jaké mohou nastat a vyhazováním patřičných výjimek.
Takový kód je pak i testovatelný, protože pokud chceš v nějaké třídě něco ukládat, řekneš si v jejím konstruktoru o objekt IRepository a namísto skutečného ukládání do fyzického úložiště tam můžeš předat mock objekt, na kterém zkontroluješ, jestli do něj ta třída ukládá, co od ní očekáváš, aniž by sis vytvářel odpad na disku, u kterého se musíš starat o jeho mazání.
Jediný problém s tímhle přístupem je, že to někomu může připadat jako "hodně psaní" a nejsou mu jasné výhody, ale u větších aplikací či práci v týmu je to nevyhnutelné.
Jakub Vrána :
Promiň, ale tohle řešení mi přijde tragické. Místo jednoduše použitelné třídy, kterou si můžu stáhnout třeba i samostatně, mám najednou obludný systém, kde si samotná třída ani „neuprdne“. Overengineering jak vyšitý.
Ta původní třída je samozřejmě i perfektně testovatelná, jen se třeba tak dobře nemockuje. Nicméně celý princip mockování považuji za poněkud zvrácený – jednak musím psát třídu, která emuluje standardní chování (zbytečná práce) a jednak tahle třída může skrýt nějaké chyby, ke kterým dojde ve skutečné implementaci za nějakých okrajových okolností. Proč si soubory při testování místo toho neukládat standardním způsobem třeba do tmpfs?
Další bod je ten, že se přenáší data, která by se rovnou mohla uložit nebo poslat do prohlížeče – další zbytečná práce. V tomhle případě to třeba nemusí nijak zvlášť vadit, ale co když budu třeba generovat XML export pro Zboží.cz? Použiju stejný přístup a třeba půl giga dat si budu nejdřív štosovat do proměnné jen proto, abych ji pak poslal třídě, která ji slavnostně uloží?
Vůbec už nemluvím o tom, že jsem chtěl ještě nahradit metodu Image::send(). Navíc Image::save() by ještě měl přijímat formát souboru nebo si ho třeba odvodit z koncovky (jako to dělá Nette).
pajousek:
Ono záleží, k čemu má daná třída sloužit. Pokud si někdo chce udělat obálku nad mail(), aby se mu ta funkce lépe používala, tak ať to klidně send() implementuje. Ale jedná se o pouhou obálku ve třídě (a to, že je něco v nějaké třídě IMHO neimplikuje, že se jedná o OOP), v jednom .php souboru, který si může jedinec stáhnout a používat kde chce.
Ale pokud má třída sloužit v nějaké aplikaci jako "obálka na data", reprezentace mailu jako nějaké datové struktury/typu, tak by to send() obsahovat nemělo. Protože v tento moment už Mail nepoužíváme pouze na odeslání mailu, ale třeba i na vlastního jednoduchého klienta, který nám maily bude vykreslovat do prohlížeče.
Takže v samostatné třídě CoolMail bych hledal asi tu první variantu, ve frameworku tu druhou.
Jakub Vrána :
Ještě by mě zajímalo, jak by se kód změnil, když by metoda save() neměla přepisovat soubor, pokud už existuje. Předpokládám, že $image->toString() by se v tom případě zavolalo jen proto, aby se jeho výsledek vzápětí zahodil.
v6ak:
V takovém případě se místo hodnoty dá předávat funkce. Ale uznávám, je to už změna návrhu.
Jan Tichý:
Metoda Mail::send() tam nemá co dělat, protože single responsibility. To není o teoretickém návrhu, to je o naprosto jasných praktických dopadech do znovupoužitelnosti a testovatelnosti. Argument, že by tam měla být, protože to tam hledá spousta lidí, kteří nemají vyjasněné všechny principy OOP a zatím si na porušení single responsibility nikdy nenarazili čumák, je lichý. A jen tak jim to tam dát, i když je to špatně, protože to tam hledají, mi přijde úplně stejně špatné a kontraproduktivní, jako třeba nechat v Nette globální Environment, protože se přece taky může začátečníkům hodit.
Jakub Vrána :
Zásadní výhodu metody MailMessage::send() vidím v tom, že si Mailer může sehnat sama.
Se single responsibility je to dobré nepřehánět, protože pak dojdeme k tomu, že třída reprezentující soubor na disku přece rozhodně nemůže mít metodu, která ho uloží.
Jan Tichý:
A odkud si ho takhle sama sežene? Z nějaké globální proměnné? Nebo si ho vycucá z prstu?
Jakub Vrána :
Ano, klidně si ho může vzít z repozitáře, klidně ho může mít ve své statické proměnné, klidně může defaultně používat odesílátko využívající PHP funkci mail().
Jako uživatel třídy se o to nechci pokaždé starat, nechci vždycky shánět mailer, který mi zprávu milostivě odešle.
Samozřejmě to musím mít možnost vyměnit, ať už na úrovni toho repozitáře, statické proměnné nebo přímo pomocí parametru té metody. Ale když neřeknu jinak, tak chci, aby to prostě fungovalo.
Jan Tichý:
Souhlasím s tím, že je fajn mít připravenou nějakou defaultní cestu bez jakéhokoliv zbytečného konfigurování. Pro Tebe to je metoda Mail::send(), pro mě je to nějaká metoda SimpleMailer::send(). Obě mohou volat třeba metodu mail(). Ve Tvém případě ušetříš v tomto defaultu několik málo řádků kódu, v mém případě si nezavřeš cestu k znovupoužitelnosti, testovatelnosti a přiohnutelnosti. Plus subjektivní kritérium přehlednosti a čitelnosti, kdy Tobě přijde zjevně čitelnější Tvůj přístup, mně ten můj. Více ale napíšu do diskuze pod Tvým dalším článkem.
Jakub Vrána :
Chceš snad tvrdit, že metoda send() třídy MailMessage volající self::$defaultMailer->send($this) zavírá cestu ke znovupoužitelnosti, testovatelnosti a přiohnutelnosti? Opravdu vůbec nechápu, jak.
Jan Tichý:
Ne, cestu ke znovupoužitelnosti zavírá narvání celé implementace metody send() do třídy Mail. Pokud oddělíš mailer ven a jenom ho injectuješ do třídy Mail (sic takhle defaultně), neboli třetí příklad z Tvého druhého článku, tak u toho mám zase problém s jasností a čitelností kódu. Prostě osobně to vnímám jako velice problematické pro jasnost a čitelnost kódu - což je ale zjevně subjektivní otázka. Kromě toho, že to znamená víc zbytečného kódu uvnitř třídy Mail.
Ondřej Mirtes:
Ten mailer té třídě musíš tak jako tak poskytnout. A co je přehlednější?
1) Zpřístupnit ho přes nějaké Environment::getService('Mailer'), čímž musím vědět o tom, že ta třída nějaké Environment používá, musí ho tedy mít k dispozici a musím ho naplnit mailerem. Případně že používá funkci mail() a musím tak nastavit SMTP server v php.ini, anebo:
2) Transparentně předat Mailu závislosti konstruktorem, protože bez nich nový objekt prostě nezaložíš.
Přičemž mi přijde zbytečné předávat závislosti dovnitř objektu, aby se na něm volaly metody s parametrem $this a proto jsem navrhoval $mailer->send($mail).
Pokud třída vyžaduje 7 závislostí a přitom může v nějaké situaci využít jen 2, mám dvě možnosti:
1) Lazy inicializace - předávat jen továrničku, ve které se vytvoří kýžený objekt až zavoláním nějaké metody. Tím ušetřím prostředky pro vytvoření objektu, který by se třeba nevyužil.
2) Refaktoring - rozdělení tříd tak, aby všechny vždy využily všechny své závislosti.
Franta:
Ono hodně záleží, jestli děláš nějakou knihovnu (byť interní) nebo jen "jednorázové udělátko", které ti má ušetřit práci (takové "rychlé a špinavé").
Jestli to má být znovupoužitelná knihovna, tak asi nebudeme chtít zprávy jen posílat, ale i ukládat (např. do IMAPu) nebo odněkud načítat (z POPu, IMAPu, SMTP...). A najednou bychom vedle metody odešli() měli mít i metodu ulož() a těmto metodám předávat cíl, kam se mají serializovat -- jednou to bude IMAP, jindy SMTP odesílač atd.
Nakonec dojdeme k tomu, že společným kódem je serializace z typů daného jazyka (řetězce popisující odesílatele, příjemce, předmět atd.) do MIME formátu. A co se bude dít s takto serializovanou zprávou už přesahuje hranice třídy Zpráva a měl by se o to starat někdo jiný.
Jan Tichý:
A jinak třída reprezentující soubor na disku by samozřejmě neměla mít metodu, která ho uloží. To by měla dělat třída reprezentující uložiště (disk), kterému předám k uložení data (soubor).
To mi vůbec nepřijde jako extrémní úlet, ale jako naprosto logický, testovatelný a znovupoužitelně přiohnutelný návrh, kdy ten soubor budu chtít třeba někdy místo ukládání na disk vypisovat na výstup nebo odesílat mailem nebo ukládat redundantně na dvě místa najednou.
Jakub Vrána :
A mně to zase přijde jako samoúčelná komplikace kódu na místě, kde to vůbec není potřeba. Ale to jsme asi oba už dříve pochopili.
Jakub Vrána :
Bude absurdita, kterou na tom spatřuji, patrná z těchto příkladů?
Třída Image rozhodně nemůže mít metodu resize(). Musíme mít interface ImageResizer a třídy ho implementující, které se o změnu rozměrů budou starat.
Nesmíme mít ani metodu watermark(), ale opět to musí být jedině ImageWatermarker.
Třída Image vlastně nesmí umět vůbec nic, na všechno musím mít interface a implementující třídy, které budou s obrázkem pracovat zvenku. Rozšiřovat tohle monstrum, ale i s ním jen pracovat, je potom za trest.
David Grudl:
Ačkoliv je určitý rozdíl mezi vztahem Třída <-> Třída a Třída <-> JináTřída, tak v obecném smyslu to tak skutečně je. Resize lze provádět několika algoritmy (strategy pattern), do obrázku lze vykreslit objekt třídy Circle (implementující IShape) s vlastnostmi pen a brush (třídy Pen a Brush), obé mající vlastnost color (třída Color).
Každý bod obrázku by měl představovat objekt TransparentPixel (extends Pixel implements IPixel).
Na případné otázky "V praxi jsem ale narazil na problém: několika set tisíc objektům nastavit několik atributů je nyní výrazně pomalejší." se odpoví ve smyslu "tak děláš asi něco špatně :)" a dodá se "premature optimization :)".
Peace! :-)
Jan Tichý:
Ne, to není absurdní, naopak je to jediná přehledná a udržovatelná cesta, jak si nezavřít cestu k budoucím potřebným úpravám a znovupoužitelnosti. Více pod Tvým druhým článkem.
Michal Till:
Co když ta třída prostě nereprezenzuje emailovou zprávu, ale něco úplně jinýho? Třeba, řekněme, jakousi akci emailové "komunikace"? Když si řeknu, že Ti napíšu mail, tak je to pro mě celkem jasná a ohraničená aktivita a nevím, proč by nemohla existovat třída, která to reprezentuje.
bene:
Příklad s obrázkem, souborem, emailem a pod. často neukáže absurditu, neboť se pohybujeme ve virtuálním světě, kde je v podstatě možné "vše".
Proto je lepší si to uvést na příkladě z "reáného" světa.
Řekněme, že máme materiál, který chceme naložit na náklaďák.
Materiál sám sebe nikam nenaloží/nepřesune, takže existence metody
<?php $material->presunDoNakladaku($nakladak); ?> je nesmyslná.
Stejně tak náklaďák si sám materiál nenaloží, tudíž metoda
<?php $nakladak->nalozMaterial($material); ?> je nesmyslná.
Takže potřebujeme objekt (nakladač), který umí vzít materiál a vložit ho na náklaďák.
Protože náklaďák může pojmout různý materiál (písek, cement, ...) a nakladač může s takovým materiálem manipulovat, zobecníme si vše rozhraním.
<?php
interface IMaterial {
public function getWeight();
}
interface INakladak {
public function vlozMaterial(IMaterial $material);
}
interface INakladac {
public function seberMaterial(IMaterial $material);
public function polozMaterial(INakladak $nakladak);
public function dejCasNakladani();
}
?>
Když známe rozhraní, můžeme nalést kokrétní objekty z reálného světa.
<?php
class Pisek implements IMaterial { /* implementace rozhrani */ }
class Avia implements INakladak { /* implementace rozhrani */ }
class Bobcat implements INakladac { /* implementace rozhrani */ }
?>
V implementaci samozřejmě můžeme napsat validace jako např. na nakladak lze nalozit jen určitou váhu aj.
A nyní provedeme úkon.
<?php
$pisek = new Pisek;
$avia = new Avia;
$bobcat = new Bobcat;
$bobcat->seberMaterial($pisek);
$bobcat->polozMaterial($avia);
echo $bobcat->dejCasNakladani();
?>
A protože jsme zjistily, že nakládání trvá 20 minut koupíme si
<?php Teleporter implements INakladac { /* implementace rozhrani */ } ?> a zefektivníme chod naší firmy ;-)
KLoK:
Kdyz uz to zenem do absurdit, vidim tam logickou chybu v navrhu. Nemel by nakladac nahodou vedet, odkud sbira ten material. Je nelogicke mit metodu seberMaterial(popis materialu) a polozMaterial(kam)...
bene:
Ale no tak, šlo mi o příklad z reálného světa, aby bylo vidět proč by se objekt email neměl sám odeslat.
To bych pak mohl napsat celou aplikaci a pořád by tam něco chybělo...
Příklady by měly být jednoduché, aby se v nich člověk neztratil a vystihnout problém, aby měly nějaký význam.
Pokud jsem tyto dvě podmínky nedodržel, tak se omlouvám.
bene:
Teď mě napadlo, stačilo by aby materiál a náklaďák implementovaly rozhraní GpsLocation ;-)
Otto Šabart:
Moc děkuju:-). Tenhle příklad mi pěkně vyjasnil práci s rozhraními.
Jiří Knesl:
Co se toho skládání dotazu týká, tam jde samozřejmě o hraniční věc. Tedy jak navrhnout objektově čisté rozhraní pro komunikaci s neobjektovou databází.
Tohle je wrapper nad SQL (programátor má představu, co půjde do databáze):
<?php
$table = new XyzTable;
foreach ($table->select()->where("id = ?", $id)->order("title") as $row) {
}
?>
Jenže wrapper nad SQL (tzn.fluent interface) principielně NEJDE navrhnout čistě, pokud výsledek má:
1) připomínat výsledný kód
2) výsledný kód není objektový
Tedy pokud zruším jednu z podmínek:
1) můj kód vůbec nemusí připomínat výchozí - tzn.nebude to připomínat SQL
2) budou používat objektovou databázi jako db4o apod.
Můžu se dobrat k čistému řešení.
U toho SQL se ještě napsat SQL objektově dá a člověku pořád nechodází, že je to hloupé, ale paradoxnost tohoto požadavku se ukáže, jakmile by někdo chtěl třeba generovat assembler (a snažil se tvrdit, že dělá čisté OOP):
<?php
$codeStream->
mov("ah", 300)->
inc("al")->
int(0x21)->
nop()->
nop();
?>
Jakub Vrána :
A kdo tvrdí, že to je wrapper nad SQL? Chci prostě záznamy s nějakým ID setříděné podle titulku. A kde je vezmu, mi je úplně jedno. Zajímá mě, jak vypadá čisté řešení tohoto problému.
Jan Tichý:
Jestli to chceš až takhle moc obecně, bez ohledu na to, jestli je to SQL nebo cokoliv jiného, tak to je úplně jiná!
Protože na hodně obecné úrovni víš o těch získávaných datech a jejich konkrétní struktuře a implementaci poměrně málo. A stejně tak si ani nemůžeš být jistý, že například sortování se dělá ve stejném kroku, jako získávání dat, nebo zda vůbec ten zdroj dat nějaké sortování podporuje.
Záleží pak, jak moc daleko chceš jít nejen v čistotě návrhu, ale i obecnosti a znovupoužitelnosti. Takže to pak může naprosto reálně dojít třeba až sem:
Data se získávají obecně z repozitáře:
<?php
$repository = new FooRepository;
?>
Můžeme mít buď speciální repozitář FooRepository napsaný na míru danému typu dat Foo, nebo pokud je to možné, abychom se neupsali, tak můžeme mít v library už dávno napsaný nějaký obecný Repository, kde se konkrétní Foo zadá například jako parametr při jeho instanciování:
<?php
$repository = new Repository('Foo');
?>
Obecně by v jedné aplikaci měly být možné oba případy najednou pro různé druhy dat, takže je vhodné mít definované nějaké obecné rozhraní IRepository a všude, kde chci pracovat s repozitářem bez ohledu na jeho konkrétní implementaci, pak typehintovat přes toto rozhraní. Rozhraní pak definuje zejména funkci/funkce na získávání dat z rozhraní, například:
<?php
interface IRepository
{
// třeba takhle
public function find($id);
// nebo takhle
public function findBy($index, $value);
// nebo takhle
public function findBy(array $conditions);
// nebo možná zrovna pro IDčko dokonce i natvrdo takhle?
public function findById($value);
}
?>
Konkrétní implementace repozitáře pak můžou být tak různě definované a instancované, že se může dokonce někdy pro získání repozitáře hodit továrna, do které se skryjí rozhodovací detaily o tom, jak konkrétně vlastně zrovna tohle repository vytvářím:
<?php
$repository = $factory->createRepository('Foo');
?>
Jediné, co vím, je to, že mi metoda createRepository() vrátí nějakou implementaci IRepository.
Jak je konkrétně z výše uvedených možností find definovaný, je asi teď jedno, ale zvolený přístup by měl být jednotný napříč všemi repozitáři v dané aplikaci/knihovně/frameworku - a právě proto by všechny repozitáře měly implementovat IRepository. Abych pak mohl kdykoliv udělat například:
<?php
$collection = $repository->findBy('id', 123);
?>
Rozhraní by zároveň mělo definovat návratový typ metod, v netypovém PHP alespoň na úrovni kontraktu daném anotací @return. Pro find metody by se tak mohla vracet nějaká obecně definivaná ICollection nebo v nejobecnějším případě alespoň implementace rozhraní Traversable.
Ve složitějších případech je dokonce vhodné dekomponovat celou podmínku do samostatného query objektu, odděleného od samotného repozitáře. Například:
<?php
$query = new FooRepositoryQuery;
$query->limit('id', 123);
$collection = $repository->findByQuery($query);
?>
Query objekt je samozřejmě opět jen nějaká implementace nějakého obecného IQuery, aby je bylo možné v případě potřeby volně nahrazovat. Stejně tak při použití query objektů by měl IRepository předepisovat metodu findByQuery(IQuery $query).
Když už teď zobecňujeme na maximum, tak jak jsem už předeslal, nevíme vůbec nic o tom, jestli používáme SQL nebo cokoliv jiného. A nemůžeme se spolehnout na to, že náš zdroj dat vůbec umí jakkoliv sortovat.
Na nejobecnější úrovni tedy musíme sortování vyhodit úplně pryč jako samostatnou obecnou funkčnost. Takže v krajním případě můžeme udělat zcela samostatnou znovupoužitelnou třídu jen na sortování:
<?php
$sorter = new Sorter;
$collection = $sorter->sort($collection, 'title');
?>
Každopádně sortovací podmínky můžou být složitější a může za nimi být nějaká zásadnější logika, takže bychom je pak v takovém případě měli zobecnit a dekomponovat do nějaké samostatné třídy. Například:
<?php
$rule = new SortRule(...);
$sorter = new Sorter;
$collection = $sorter->sort($collection, $rule);
?>
Případně:
<?php
$rules = new SortRules(...);
$sorter = new Sorter($rules);
$collection = $sorter->sort($collection);
?>
Pokud bych chtěl dát možnost zase ve specifickém případě SQL dotazu nechat sesortovat výsledky samotnou databázi, tak pak samozřejmě musím SortRules zapojit už do Repository...
A tak dále, ono by se dalo zobecňovat a dekomponovat do nekonečna, záleží vždy na tom, jak moc obecné a znovupoužitelné to potřebuji a kolik už toh mám znovupoužitelného v knihovně napsaného.
Takže ono ač to vypadá jako strašnýho psaní, tak pokud mám k dispozici nějakou knihovnu/framework, kde tohle už je všechni vyřešené, tak v samotné aplikaci, kde mi třeba navíc všechny instance plní přes property injection sám DI kontejner, pak musím napsat jenom:
<?php
class FooPresenter
{
/** @inject */
private $repository;
/** @inject */
private $sorter;
public function FooAction($id)
{
$collection = $this->repository->find($id);
$collection = $this->sorter->sort($collection);
}
}
?>
Jakub Vrána :
Fuj, to /** @inject */ je ale odporná magie, kam se hrabe __get() a __set(), to je aspoň přímo součást jazyka.
Tento způsob uvažování na mě působí dojmem, že hlavní cílem je napsat co nejvíc kódu, který se bude co nejhůř používat a bude pokud možno co nejmíň efektivní. O čistotě návrhu bych si kvůli té magii také dovolil pochybovat a to raději ani nechci vědět, jaký význam má velké písmeno ve FooAction a co se stane s proměnnou $collection po skončení této metody. Také bych rád věděl, jak se sorter uvnitř metody dozví, podle čeho má vlastně třídit. (Pokud ses jen upsal, tak OK, to se samozřejmě může stát každému.)
No a nakonec bych rád věděl, jak vypadá metoda sort() uvnitř a jak by se to ještě rozšířilo o omezení počtu záznamů.
Landy:
Já teda nevim ale vzhledem k tomu, že součástí jazyka je možnost pomocí reflexe číst doc komentáře tak to žádná velká magie nebude ;)
Jakub Vrána :
Magie v tomto kontextu znamená, že něco není na první pohled patrné. Tady mám nějaké soukromé vlastnosti s nějakým dokumentačním komentářem a ono to nejspíš prostě nějak funguje. Kde je @inject popsané? Co musím znát, abych to mohl použít? Chová se to všude stejně? A i když si odpovím na všechny tyhle otázky, pořád to na první pohled patrné není.
Filip Procházka:
To je sakra magie, protože to není součást jazyka ;) Všimni si, že každý FW to řeší po svém (Nette, Symfony, FLOW3, ..)
Metadata ano, ale tomuhle vyžadování služeb pomocí annotací jsem ještě na chuť nepřišel..
Landy:
To ale není vyžadování ale pomůcka pro jeden nástroj nic ti nebrání naplnit si tu vlastnost nebo konstruktor sam :) tohle jen usnadni DI kontejneru praci
Jan Tichý:
Jakube, je důležité rozlišovat, co je v této diskuzi podstatné, a co vedlejší. Že jsem ve svém příkladu pro naznačení dependency injection použil zrovna property injection, je jenom detail. Úplně stejně jsem mohl použít constructor injection nebo setter injection. Prostě si pod tím představ obecně jakýkoliv dependency injection přístup.
Stejně tak velké písmeno ve FooAction je samozřejmě překlep a nemá žádný hlubší význam.
Každopádně asi se chápeme, že to, co jsem napsal výše, je abstrakce a dekompozice dohnaná do extrému, která ovšem někdy mít opodstatnění může.
v6ak:
Volání where nemusí být setter. Může to vrátit nějaký objekt (třeba instanci třídy Query). Na něm pak mohou být volány settery, ale jde to i bez nich.
Jinak u DSL se toho obvykle dá překousnout trošku více než jinde. Taky nemám moc rád settery a mám rád funkcionální přístup, ale tady (až na where, které by jako setter mohlo neblaze ovlivňovat globálnější stav nebo (nebylo by-li to v PHP) jiná vlákna) u DSL pragmatismus vede.
Jakub Vrána :
Takže pak se nedá psát např. <?php
foreach ($where as $column => $value) {
$table->where($column, $value);
}
?>, ale výsledek se musí přiřadit zpátky do $table. Nejsem si jist, co se stane v paměti s těmi původními objekty, které už nejsou na nic potřeba. Ale musím říct, že mi to je taky o něco sympatičtější (i když obvykle méně praktické).
v6ak:
No dala by se zavolat metoda, řekněme, createQuery, a na tom pak volat where. Jeden řádek navíc, ale tady by IMHO šlo o šetření na špatném místě. Například, pokud někde udělám něco z where, pak nastane výjimka a pak začnu tabulku znovu používat. Tí, tam dostanu i podmínky, které tam nepatří, a ušetřený řádek na pár místech (teoreticky by při opakování měla být vytvořena speciální metoda, která to řeší, takže těch míst by nemělo být moc) se vrátí v podobě nepříjemného ladění, protože změní sémantiku na úplně jiném místě.
Možná v tomto případě není ten problém až tak významný, ale obecně bych se tomu vyhýbal a radši napsat pár řádků navíc, než přemýšlet, zda tady mohu si dovolit šetřit. A to nemluvím o jiných čtenářích kódu. Navíc, 'tady to není to riziko reálné' jsem si už párkrát říkal, ale dříve nebo později se to často vyvrátilo...
A o nepoužívané proměnné se postará GC, to zvládne i reference counting. Nebo Ti šlo o něco jiného? (Mám z toho pocit, že jsem to nesprávně pochopil.)
Jakub Vrána :
Zastavil bych se ještě u toho „Mailer žádné settery mít nebude.“ Co když to bude mailer používající SMTP server? Jak mu nastavím server, ke kterému se má připojit, port, uživatelské jméno, heslo, jestli má používat SSL, jestli má použít šifrování hesla, zda se má zkusit ověřit pomocí POPu a já nevím, co ještě? S tím, že prakticky všechno má nějaké smysluplné defaulty.
Denis:
V tomto emailovém příkladě je dobré mít třídu, která bude na vstupu přebírat emaily a bude se starat o jejich odeslání. Mimo to si může ukládat statistiky o odelsaných emailech atd. atd. podle potřeby.
Zápis by potom byl:
<?php
$mail = new MailMessage;
$mail->from = "from@example.com";
$mail->to = "to@example.com";
$mail->cc = "cc@example.com";
$mail->subject = "Test";
$mail->message = "Hello World!";
$mailer = new Mailer;
$mailer->send($mail);
?>
Z tohoto kódu je zcela jasné (a mělo by to tak i být naimplementováno), že třída MailMessage je jen "tupá" a pouze uchovává data. Vůbec neví, jaké data má obsahovat atd. Proto není třeba používat ani settery ani gettery. Byly by VŽDY jen jednořádkové.
Na druhou stranu třída Mailer již umí odesílat emaily a pravděpodobně i kontroluje správnost dat ve třídě MailMessage.
Třída Mailer může umět třeba i:
<?php
$mailer->checkValidity($mail);
?>
Třída Mailer může umět i:
<?php
$mailer->LogEmailsToDB(true);
?>
atd. atd.
Ono toto všechno by šlo udělat i tak, jak to má původně Jakub, že třída MailMessage obsahuje i Send metodu. Ale je dobré si představovat, jako kdyby i metody zabíraly nějaké místo. Potom INSTANCE KAŽDÉ TŘÍDY MailMessage by si s sebou nesla jak infomrace o emailu tak by za sebou vlekla i hromadu metod. (By´t tomu tak ve skutečnosti není).
Správně tedy je několik instancí tříd EmailMessage (bez metod) a jen jedna instance třídy Mailer s metodou Send a dalšími.
Tento emailový příklad je pěknou ukázkou toho, jak nad problémy v OOP uvažovat. Základem zde je pouze třída Mailer.
Je to jakýsi boss který říká: "hele, když mi někdo dá email, tak já ho pošlu, to není problém."
A pak jsou tady nějaké okolní věci, které třídě Mailer tyto emaily dodávají. Jestli to bude v nějaké třídě MailMessage, nebo v nějakém Array, to už není podstatné. Bosse to nezajímá :)
Ondřej Mirtes:
Třída MailMessage se musí starat o konzistenci takových dat. Jinak by její užitečná hodnota byla nulová a do maileru bych mohl předávat pole o libovolné struktuře.
MailMessage by např. měla kontrolovat, že v polí from, to, cc a bcc jsou stringy a zároveň validní e-mailové adresy (případně objekty EmailAddress, které validitu e-mailu řeší uvnitř sebe).
Denis:
Do maileru bys mohl předávat jen instance třídy MailMessage, takže né "pole o libovolné struktuře".
Třída MailMessage je zde jen v roli "držitele informací". Nic nemusí sama kontrolovat a dokonce to je i logické, jelikož třída MailMessage nic neodesílá, tím pádem ani v podstatě neví, co by měla kontrolovat.
Třída MailMessage ani neví, k čemu ty data má a co se s nima bude dále dít. Co když je enbudeme chít odesílat ale budeme s nimi chtít dělat něco jiného? To prostě třída MailMessage neví. To víme jen my, ale v OOP je třeba se dívat na svět vždy očima dané třídy.
Jelikož emaily odesílá třída Mailer, proto tato třída si musí kontrolovat správnost dat, které do ní lezou, jelikož jen ona ví, v jakém formátu ty data potřebuje.
Jakub Vrána :
Když do from uložím "@@@", tak tím nevznikne platná e-mailová zpráva, ať už s ní následně budu dělat cokoliv. Je tedy výlučnou odpovědností třídy MailMessage se postarat o to, aby reprezentovala platnou e-mailovou zprávu. Třída Mailer (a ostatní třídy využívající MailMessage) se o žádnou kontrolu naopak už starat nemusí, protože na vstupu dostávají platnou e-mailovou zprávu.
bene:
Na první pohled to tak může připadat. Taky mě napadlo, že by MailMessage měla validovat format adresy, aby to nebyla jen tupá obálka.
Jenže pokud by MailMessage implementovala nějaké rozhraní, které by mailer v metodě send přijímal (nebo by to mohla být třída zděděná). Jakou záruku má mailer, že třída MailMessage opravdu validaci provedla?
Docela by mě zajímal názor Jiřího Knesla, Jana Tichého a Ondřeje Mirtese.
Díky
Ondřej Mirtes:
Je zodpovědností MailMessage, aby obsahovala konsistentní data.
Vezmu to na jednodušším příkladu - pokud v metodě setEmail přijímáš string, musíš validovat, zdali se jedná o e-mail. Pokud bys přijímal objekt typu EmailAddress, tak už se o to starat nemusíš - je to zodpovědnost EmailAddress.
To samé třeba se string (kontrola existence složky) x třída Directory.
Stejně tak MailMessage musí obsahovat konzistentní a validní data a je jedno, jestli se jedná o interface nebo implementaci.
Denis:
Záleží jak se na věc člověk dívá :) Tvoje tvrzení, že "Je zodpovědností MailMessage, aby obsahovala konsistentní data" nemusí být pravdivé.
Pokud toto chování od třídy MailMessage očekávám, potom by validaci dělat měla a něměla by do sebe pustit "nevalidní" data.
Jenže pokud to od třídy MailMessage neočekávám, potom žádnou validaci dělat nemusí. A tady v tomto pžípadě tvrdím, že to neočekávám, jelikož třída MailMessage nic neumí a hlavně nic s těmi daty nedělá. A já chci, abych si do adresy To mohl napsat i @@@ protože to tak někdo zadal nebi já nevím proč, ale chci to tam mít.
A dále: To, že @@@ není validní emailovou adresou, mě v podstatě nezajímá. Mě ad extrém zajímá jen to, že odesílač (zde je to Mailer) neumí s takto nevalidní emailovou adresou pracovat a vyhodí mi nějakou chybu. ALE, do databáze si email v podobě @@@ uložit můžu. Tady by zase třída, která ukládá MailMessage do DB měla provádět vlastní si kontrolu.
Třída MailMessage může implementovat metodu třeba CheckValidity, a ta může vracet třeba info o tom, že email je nevalidní atd., ale nikdy nemůže data "nevpustit" do sebe a neuložit je. Proč? Protože jsou to public data.
Jakub Vrána :
„do databáze si email v podobě @@@ uložit můžu“ – správně by to jít nemělo a i databáze by měla být navržená tak, aby respektovala integritu dat, která ukládá – ať už formou vlastního datového typu, klauzule CHECK nebo pomocí triggeru.
Denis:
Když budu mít třídu MailMessage s public vlastnostmi, potom je patrné, že tato třída slouží jen jako přenašeč informace. Toto je logicky i koncepčně čistý návrh. (Pomiňme teď zvyklosti a to, že většina si tady dogmaticky prosazuje svoje praktiky :))
Když budu mít ve třídě MailMessage Gettery a Settery a vlatnosti budou privátní a tato třída se bude starat o integritu vlastních dat a nepustí do sebe žádný bordel, potom se jedná opět o logicky a koncepčne čisté řešení.
Jenže tento příklad s MailMessage a Mailerem není šťastný pro třídu MailMessage s tím, aby si kontrolovala integritu.
Proč? Mrotože třída MailMessage reprezentuje emailovou zprávu, ale přitom může obsahovat třeba jen předmět zprávy a nebude obsahovat žádnou emailovou adresu. Je třída MailMessage validní? Není, jelikož zpráva nelze odeslat, nemá adresáta. Takže třída Mailer stejně musí kontrolovat data, které dostane od MailMessage. Ano, stačilo by v Mailerovi jen kontrolovat, zda je příslušný email vyplněný, ale toto již je chaos, jelikož jedna třída kontroluje něco a druhá do "dokontrolovává". Proto pro tento konkrétní příklad s emailem se hodí více jen ta tupá třída MailMessage s public vlastnostmi. Bez setterů a getterů.
I toto by šlo samozřejmě vyřešit tak, že umožníme do MailMessage vložit jen všechny data najednou a buď budou validí a nebo ne a pak se nevloží nic. Ale toto je nešťastné řešení, které nutí programátora vkládat všechna data na jednom míste bez ohledu na to, zda je zná či nikoliv.
Marten:
Mozna bych k tomu jeste pridal, ze MailMessage nemusime chtit jenom odeslat, ale muze se jednat pouze o ulozeni draftu na IMAP. Kde validace adres, nebo prazdny predmet nas opravdu nezajimaji. Tedy ve vetsine pripadu by si validaci mela resit az trida, ktera vykonava logiku (v tomhle pripade Mailer), protoze jen on vi, co je pro nej dulezite. Osobne bych se ale priklanel i k tomu, ze trida MailMessage bude kontrolovat emailove adresy. Je to stejne jako s tim co bylo jiz zmineno, tedy mohu mit:
<?php
$mail = new MailMessage;
$mail->from = new EmailAddress("from@example.com");
?>
kde bych ocekaval, ze se o to stara EmailAddress. Ve tride Mailer by me osobne tedy nezajimalo, jestli jsou vsechny emailove adresy validni, ale budu kontrolovat, jestli mam vyplnenu alespon jednu adresu, na kterou to mohu poslat. Pripadne zkontroluju i jestli je predmet a text emailu.
v6ak:
Dát všechna data najednou - to obecně nemusí být špatný nápad:
* Vyhnu se tím tomuto problému: http://en.wikipedia.org/wiki/Circle-ellipse_problem
* Nutí to místy k lepší dekompozici.
* Případně si mohu napsat Builder. V praxi jsem ho ale viděl používat spíše jako náhradu pojmenovaných parametrů a je to psaní navíc.
v6ak:
Sice nejsem Jiři Knesl, ale taky si dovolím napsat sem svůj názor:
Ačkoli jsem zastáncem defenzivního programování, s tímto nesouhlasím. Nezadává-li tyto objekty nepřítel (například v Javě by to mělo smysl - třídě lze omezit oprávnění), pak prostě budu věřit, že se třída chová tak, jak je ve specifikaci. V opačném případě se již dostáváme do absurdností defenzivního programování, které začne spíše škodit než pomáhat. U Javových kolekcí bychom pak nemuseli věřit, že pmetoda size() podává skutečnou velikost. U getterů bychom zase nemuseli věřit, že nevrhnou výjimku, aniž by to měly ve specifikaci (řízené výjimiky v Javě tomu nezabrání). A taky jim nemusíme věřit, že vrací u neměnných tříd pořád to stejné. Atd...
Defenzivní programování slouží ke včasnému odhalení chyb, které by se jinak možná složitě ladily. Ale asi není cílem předpokládat, že někdo vyloženě chtěl, aby se můj kód choval špatně, tak mu podstrčil špatnou implementaci svého rozhraaní. To obecně často nelze ověřit. Dvojí kontrola je sice ještě relativně použitelná, ale už to zmíněnými absurditami zavání a spíše bych se jí vyhnul.
bene:
S tím souhlasím. To že by měla MaqilMessage mě napadlo jako první. A je pravda, že bych asi měl očekávat, že mi MailMessage nabízí správná data.
Ale k tomu se váže další věc. Chceme aby třída skutečně data validovala?
Co když až v maileru chci rozhodnout, jestli kdyz že email špatný, tak to zaloguji.
<?php
try {
$mail = new MailMessage;
$mail->setEmail('xxx');
$mailer = new Mailer;
$mailer->send($mail);
}
catch (InvalidEmailFormatException $e) {
$logger->log($e);
}
?>
Tudíž v šude v kódu budu muset opakovat toto. Místo aby se mi o logování staral můj speciální mailer rozšíření o logování.
Otázka, co je správnější přístup. Předpokládám že varianta ze samostatným logerem.
Ondřej Mirtes:
Tu výjimku prostě musí vyhazovat MailMessage. Že tuto funkcionalitu (logování) potřebuješ na víc místech v aplikaci neznamená, že tento kód musíš rozkopírovávat. Tu výjimku ti může např. zpracovat jiná (vyšší vrstva) nebo tento kód můžeš mít vystrčený do nějaké servisní metody, kterou pak využíváš na víc místech v aplikaci.
v6ak:
Celkem souhlasím s Ondrou. Třídy jako Mail, Mailer a MailMessage by nějaké logování zajímat nemělo. Můžeš ale mít v případě potřeby nějakou továrnu na Mail, kterou dodáš zvenku, ale přijde mi to trošku jako overkill.
Co tak přemýšlím, stejně i přes logování je potřeba tu chybu asi většinou reportovat uživateli, takže výjimka stejně musí být vržena a zpracována. Takže se stejně moc ušetřit nedá.
Franta:
Vzhledem k tomu, že ten mail může mít klidně hodnotu "To: undisclosed-recipients:;" tak tu validaci založenou na přítomnosti zavináčů můžeš rovnou vzdát. Dobré je to možná při odesílání, ale pokud tu třídu budeme chtít použít i pro popis např. zpráv uložených ve schránce, musíme tolerovat všemožný vstup -- přece program nespadne nebo neodfiltruje nějakou zprávu v uživatelově schránce jen proto, že není v úplně správném tvaru, ne?
Jakub Vrána :
Špatně sis to přečetl. Dal jsem ukázku adresy, která není platná (a to právě proto, že obsahuje hned tři zavináče).
E-mailové servery takovou zprávu skutečně odmítnou odeslat a tak je otázka, jak by se do schránky vůbec dostala. Taky je otázka, jak by na ni má vrstva měla reagovat – nejrozumnějším přístupem může být kvůli takové zprávě celou schránku nenačíst nebo ji přeskočit a uživateli zobrazit varování. Pokud ji mermomocí chci taky reprezentovat, použil bych InvalidMailMessage.
Franta:
Ad "jak by se do schránky vůbec dostala"
Mohla se tam dostat všelijak, třeba ji tam uložil nějaký e-mailový klient nebo došlo k nějaké chybě, konec konců e-maily ve schránce jsou obvykle jen obyčejné soubory na disku. Každopádně je to mimo kontrolu tvého programu, natož té jedné konkrétní třídy.
Když tu knížku Dokonalý kód máš, tak koukni na stranu 211 (Robustnost versus bezchybnost v kapitole 8.3).
Ad "Nejrozumnějším přístupem může být kvůli takové zprávě celou schránku nenačíst"
Úplně špatně. Kvůli jedné chybné zprávě (resp. jedné položce v té zprávě) znemožníš uživateli kompletní práci s jeho poštou. O to uživatel nestojí, e-mail není rentgen, kde by byla potřeba 100% bezchybnost.
Ad "nebo ji přeskočit a uživateli zobrazit varování"
To už je trochu lepší. Ale pořád je to špatně -- uživatel si zprávu chce a potřebuje přečíst, chce vidět její předmět, její text... že je jedna z e-mailových hlaviček v nepořádku je podružné, není dobré mu kvůli tomu znemožnit práci se zprávou jako takovou. Navíc pokud je ten e-mail ve složce koncepty, uživatel adresu před odesláním ještě opravit a nic se neděje.
Smysl dává kontrola těsně před odesláním, všude jinde je spíš na škodu -- a je lepší varování než úplně selhat a vypovědět službu. Ale i u toho odesílání jde na kontrolu na straně klienta za určitých okolností rezignovat -- zprávy kontroluje i server a pokud je v adrese chyba, server ji odmítne v rámci SMTP spojení a chyba se projeví okamžitě (většinou tedy nebude potřeba kontrolu duplikovat na klientovi).
BTW: zkus jen tak cvičně nadhodit algoritmus (patrně regulární výraz) kterým bys ty adresy (resp. hodnotu hlavičky To: ) kontroloval.
Ad "Pokud ji mermomocí chci taky reprezentovat, použil bych InvalidMailMessage."
To by byl potomek MailMessage?
Franta:
P.S. jen pro pořádek -- gettery a settery by se měly z principu používat a kontrola hodnot do nich z principu patří -- akorát v tomto případě bych konkrétně e-mailovou adresu striktně nekontroloval.
v6ak:
Taky mě trošku zarazilo, že by zrovna Jakub Vrána chtěl nezobrazit celou schránku kvůli jedné chybné zprávě. Já teda někdy radši nechám aplikaci failnout "i s rachotem", zvlášť pokud bych u failu měl být přítomen, ale to spíše jde-li o chybu, která by neměla nastat.
A InvalidMailMessage jako potomek MailMessage nevypadá jako dobrý nápad, aspoň pokud jsem si to dobře představil:
Takto jsem si představil ten návrh:
MailMessage // dobrá zpráva
InvalidMailMessage extends MailMessage // každá špatná zpráva je zároveň dobrou - WTF?
Takto bych to udělal já:
MailMessage // nějaká zpráva - nevím, jestli je OK nebo není (možná by stálo za zvážení přejmenování)
ValidMailMessage extends MailMessage
InvalidMailMessage extends MailMessage
Ono to dává smysl i při implementaci, nejde jen o hru se sémantikou. Takto v potomkovi validaci neubírám, ale případně přidávám.
Ale pozor, když budou zprávy mutable (tzn. zhruba budou mít settery), můžeme se tu dostat na problém http://en.wikipedia.org/wiki/Circle-ellipse_problem .
Uznávám ale, že někdy se v praxi může hodit mezi dobře a špatně utvořenou zprávou nerozlišovat.
Jakub Vrána :
Celou schránku bych odmítl načíst třeba ve funkci, která by měla zkontrolovat její validitu.
v6ak:
Tím se dostáváme k již vyřčenému problému, že bez konkrétního použití si každý může do zadání doplnit, co chce, a tím z mnohých pro někoho (s jiným zadáním) nepřijatelných řešení udělá ta nejvhodnější.
jos:
takže datovej typ int může pobrat string, protože jen uživatel toho intu ví jak si má hodnotu zvalidovat?
lol
jos:
<?php
$mail = new MailMessage(
"from@example.com"
, array("to@example.com")
, array("cc@example.com")
, "Test"
, "Hello World!"
);
$mail->send(new Mailer);
$mail->ulozNecoDoDbBezGetteru($db);
?>
instanciace je inicializace
Michal Wiglasz:
Tohle je docela nepřehledné, bez dokumentace nevím, co který parametr znamená. A kdybych potřeboval nastavit jenom nějaké, tak se ztratím v NULLech.
jos:
ty parametry by se asi nějak menovaly, takže IDE ti řekne co co znamená, pokud nepoužíváš IDE, jak poznáš jaký máš k dispozici atributy, případně settery k nim?
co by v kontextu emailu mohlo bejt nullovatelný?
Michal Wiglasz:
Ale přes settery na první pohled vidím co je co a ani nepotřebuju IDE. U psaní budu napovídání potřebovat tak či tak, ale u čtení jednoznačně vedou settery.
NULL může být třeba CC (a nejspíš i bude), může to být odesílatel (může být nastavený v konfiguraci PHP) nebo to může být příjemce a adresy budou v BCC.
jos:
když si můžu z kódu přečíst jaký mam k dispozici settery, tak si stejně tak dobře přečíst jaký argumenty bere konstruktor (vlastně líp, mam to na jednom místě)
prázdný CC -> array()
u odesílatele je možností víc, buď to instancuju tak, že si přečtu hodnotu z php.ini, nebo se o to stará až kód co to odesílá
Denis:
Tak toto určitě ne :D Instanciace je sice inicializace, ale né takto. V konstruktoru by měly být jen nejdůležitější parametry, které jsou nezbytné pro vytvoření třídy. Plus mohou tam být ještě dodatečné paramtery, u kterých jsme si jisti, že je uživatel v době vytváření instance zná a že je bude vždy objektu přiřazovat.
Např. nějaká třída Image pro uchovávání obrázkových dat by mohla mít v konstruktoru parametry Width a Height. Ale pokud tato třída bude mít metodu třeba LoadImageFromFile, potom v konstruktoru paranetry Width a Height být nemusí (dokonce by tam být ani neměly).
a toto je logicky špatně:
<?php
$mail->send(new Mailer);
?>
správně je toto
<?php
$mailer = new Mailer();
$mailer->send(mail);
?>
Třída MailMessage by v tomto případě vůbec neměla vědět, že existuje nejaká třída Mailer.
Tvůj zápis by byl akceptovatelný pouze v případě, že by třída mailer implementovala nějaké rozhraní a třída MailMessage by vyžadovala jen toto rozhraní. Ale to je v tomto emailovém příkladě jak jít z puškou na komára :)
jos:
eh, od tebe bych si nenechal poradit ani kde je nejbližší trafika
ad nejdůležitější parametry - v tomhle případě sou důležitý všechny a i kdyby ne, tak na sebe můžu nabalovat instance tříd se stejným rozhraním - každá dodá nějakou informaci
ad třída Image - pro načítání obrázků vyrobim třídu fsImage, do konstruktoru bere cestu, pro výrobu novýno mam emptyImage, do konstruktoru bere rozměry
samozřejmě kdyby PHP mělo opravdovej overloading tak bych to řešil jinak
že by MailMessage neměla vědět že existuje třída Mailer neříkej mě, ale Vránovi (přečti si na co reaguju)
ad puška na komára - diskutujou se tady složitý věci nad jednoduchejma příkladama, nebo bys byl radši aby tady každej vpastil 1000 řádků kódu? že by ve hře byl nějakej interface je tady každýmu kromě tebe jasný
Denis:
"diskutujou se tady složitý věci nad jednoduchejma příkladama, nebo bys byl radši aby tady každej vpastil 1000 řádků kódu?"
:D A víš že asi jo, jelikož pak se tady diskutuje o něčem, co si může každý interpretovat jinak. Problém není dobře definován, tak si každý přimýšlí svoje věci aby mu jeho řešené "lépe sedělo".
Jinak s tou inicializací stejně nesouhlasím, ale to je věc názoru a praxe asi, jak se tak dívám.
Petr Peller:
Jakube, co uděláš, když budeš u většiny properties potřebovat nějakou validaci nebo budeš chtít provést něco víc, než přiřazení?
Dáš do __set metody switch($name)? Ta metoda postupem času bude bobtnat a přestane být přehledná. To se ti nestane, pokud použiješ settery/gettery.
Petr Peller:
Aha, rok 2011. Trapas :)
Franta:
Více méně souhlas. Ale ty gettery/settery mají smysl i u jiných tříd než jsou klasické přepravky. Ono se sice dá říct, že objekt by měl mít nějaké předem definované stavy a metody by měly sloužit buď k přechodu mezi nimi nebo k nějaké činnosti, tudíž getter/setter se zdá být nadbytečný... Ale: toho jde sotva dosáhnout i u čistě obchodních objektů (kde si typicky i děláme stavový diagram a třída je hodně úzce zaměřená), natož u objektů obecnějších (v knihovnách, frameworcích, znovupoužitelný kód). Tam totiž objekt nemá jen pár stavů -- místo toho má mnoho vlastností a může nastat netriviální počet jejich kombinací. Takový objekt si před použitím (nebo v jeho průběhu) naparametrizujeme (uvedeme do jednoho z mnoha jeho možných stavů) a právě k tomu slouží ty gettery/settery -- takže rozhodně nelze říct, že by byly hřích u nepřepravkových tříd.
Jan Tichý:
To, že vlastnosti mohou mít viditelnost public, rozhodně nemusí být argument pro to, že to je správný postup. Taky to může znamenat, že to je chyba v návrhu takového objektového jazyka. Což si třeba já osobně dlouhodobě myslím. IMHO by vlastnosti měly být povinně pouze private. Možnost vlastností být public nebo protected vede k nečistým postupům a systémovým rozporům.
Jakub Vrána :
... a zároveň k jednoduššímu kódu, který je míň náchylný k chybám, jednodušeji se používá a upravuje :-).
Jan Tichý:
...jen do té doby, než začneš dědit, překrývat v potomcích některé metody s těmi vlastnostmi pracující a pak se divit, co se Ti to s těmi daty děje.
...jen do té doby, než začneš používat Nette, kdy $this->foo sice znamená v jedné třídě přístup k vlastnosti, v jejím potomkovi už ale tak úplně nevíš, protože možná to je přístup k property, možná ale taky ke getteru/setteru.
David Grudl:
Public proměnná je jen "zkratka" pro
<?php
function setAbc($value)
{
$this->abc = $value;
}
function & getAbc()
{
return $this->anc;
}
?>
Tj. vedou-li public proměnné k nečistým postupům a systémovým rozporům, vedou i settery & gettery k nečistým postupům a systémovým rozporům.
Keff:
... a což teprve meziobjektová zpráva 'dej mi obsah vlastnosti s daným názvem' :))
BTW, Paul Graham má výbornou esej
http://www.paulgraham.com/popular.html - přikládám prvních pár odstavců kapitoly Hackability:
"There is one thing more important than brevity to a hacker: being able to do what you want. In the history of programming languages a surprising amount of effort has gone into preventing programmers from doing things considered to be improper. This is a dangerously presumptuous plan. How can the language designer know what the programmer is going to need to do? I think language designers would do better to consider their target user to be a genius who will need to do things they never anticipated, rather than a bumbler who needs to be protected from himself. The bumbler will shoot himself in the foot anyway. You may save him from referring to variables in another package, but you can't save him from writing a badly designed program to solve the wrong problem, and taking forever to do it.
Good programmers often want to do dangerous and unsavory things. By unsavory I mean things that go behind whatever semantic facade the language is trying to present: getting hold of the internal representation of some high-level abstraction, for example. Hackers like to hack, and hacking means getting inside things and second guessing the original designer.
Let yourself be second guessed. When you make any tool, people use it in ways you didn't intend, and this is especially true of a highly articulated tool like a programming language. Many a hacker will want to tweak your semantic model in a way that you never imagined. I say, let them; give the programmer access to as much internal stuff as you can without endangering runtime systems like the garbage collector."
A za sebe dodám, povýšení kazatele jediné správné metody, vyližte si ... :)
Jan Tichý:
Ale tak snad je jasné, že tahle diskuze není o tom, že tu dvě strany káží tu svoji jednu jedinou správnou pravdu. Ale že tu třeba já nebo Ondra Mirtes stavíme proti Jakubovi prostě jenom jiný možný přístup. A že veškeré "ultímátní" příspěvky se musí chápat v kontextu této diskuze jako stavění různých možných řešení proti sobě, nikoliv jako hlásání jedné jediné pravdy...
Rene Stein:
Takto podáno to není pravda:
Zprávy vyměňované mezi objekty se někdy dělí na interogativní, informativní a imperativní.
Interogativní můžeme ztotožnit s get akcesory.
Informativní se se set akcesory.
Imperatovní - to jsou zprávy - metody - jak je chápete vy.
Jiří Knesl:
Já netvrdím, že neexistují gettery a settery. Já tvrdím, že existuje jen málo důvodů je používat (a tam, kde je to odůvodněné, bych sám settery/gettery napsal) a mnohdy je jejich použití špatné. Dokonce jsem nalinkoval článek, ve kterém je to velmi podrobně vysvětlené.
Radek:
Také se přikláním k getterům a setterům. V praxi jsem ale narazil na problém: co takhle několika set tisíc objektům nastavit několik desítek atributů? Oproti nastavení public vlastností jsou settery výrazně pomalejší.
Ondřej Mirtes:
Pokud v rámci jednoho requestu chceš "několika set tisíc objektům nastavit několik desítek atributů" a nejspíš to ještě někam uložit, tak děláš asi něco špatně :)
Každopádně - nejužší bottleneck představuje jakýkoli vstup/výstup, tj. načítání a ukládání do databáze či na disk, takže pokud zoptimalizuješ aplikaci z tohoto hlediska a zbývá ti už jen řešit rychlost samotného PHP, tak seš za vodou. V tom případě můžeš použít nějakou opcode cache nebo HipHop. Dávat public atributy namísto metod kvůli rychlosti je premature optimization :)
Tomáš Fejfar:
Radku, to ale mluvíš o DTO, kde se to řeší buď interním polem hodnot + jeden setter, případně ani ty public metody mi v tom případě nepřijdou jako problém.
Radek:
Dobrý odhad, jsou to DTO. Ve smyslu zápisku jsme používali pro plnění DTO atributy metodu (__set) a v tuto chvíli jsme ustoupili k jednomu hromadnému setteru.
Tipuju, žes chtěl napsat public atributy a ne metody. Uvažuju o nich, jestli budou mít dostatečné výhody (např. při čtení).
Martin Hruška:
Já stále zastávám názor, že Jakub Vrána píše bastlený kód a že OOP v podstatě vůbec nerozumí. Příspěvky pana Knesla v této diskusi mi připadají mnohem víc odborné.
Srovnejme navíc třeba toto ublognutí s článkem na stejné téma:
http://www.javaworld.com/javaworld/jw-09-…-toolbox.html Který článek je lepší?
Jenže teď je pan Vrána v USA ve Facebooku (tedy téměř). Kde je pan Knesl nevím.
A včil mudruj!
Jiří Knesl:
Pan Knesl buduje vlastní firmu a nechce se nechat zaměstnat - kompromisů si už v životě užil dost. :)
Co se Jakuba týká, myslím, že to je velmi zručný programátor (žádný bastlíř), který umí dobře použít PHP. Sám píšu věci jinak, s Jakubem jsme se na tohle téma několikrát bavili a myslím, že u něj vidím v posledních měsících posun k lepšímu chápání OOP. Já obecně soudím, že u Jakuba hraje ještě roli snaha o úspornost kódu.
Kirara:
Možná to bude tím, že ve Facebooku právě takových lidi potřebují. Lidi co nejsou indoktrinovaní nějakými správnými formálními akademickými postupy programování, ale naopak se nebojí takové postupy zpochybnit. Neboť Facebook se pohybuje na hranici známého vesmíru a snadno se může ukázat, že zákony v takto kosmických rozměrech přestávají platit.
Má představa je, že ve Facebooku se "bastlí" každý den. A bastlí se tak dlouho, dokud z toho nevyleze něco v daných podmínkách funkčního a výjimečně užitečného a pak se to nějak pojmenuje a představí vývojářské obci se slovy "pokrok nezastavíš". Je to vůbec nejblíže na poli (webového) programování k něčemu skutečně objevnému. Ale je třeba se přestat ohlížet.
Tomáš Fejfar:
Spíš tam jde o to, že je lepší mít v té zátěži, která tam je, kód třeba neudržovatelný, ale o 10ms rychlejší. Čiliže je Kuba ideální kandidát. Myslím, že své schopnosti optimalizace velikosti/rychlosti dostatečně ukázal na Admineru :)
Hds:
Raději jednoho "praktického" Jakuba Vránu než deset teoretiků, kteří jsou ochotni deset dní diskutovat a analyzovat problém, než do toho šlápnout a něco opravdu udělat (tímto vůbec nemířím na pány Knesla či jiné).
Marten:
Skoda ze tady neni like. Jedna vec je naucit se spravne OOP ve skole, nebo jakkoliv jinak. Obcas je pak ale dalsi vec dokazat to v praxi. S cistym OOP by aplikace mela neuveritelne mnozstvi trid, kde polovina z nich bude jen nejaka obalka. Vsechno bude konfigurovatelne, menitelne, krasne univerzalni. Bohuzel PHP na tohle zrovna neni moc stavene a kdyz do takove aplikace vleze pak vic lidi, tak clovek bude litovat, ze to "nanabastlil" trochu vic. OOP je dobra vec, ale nic se nesmi prehanet.
v6ak:
Ono není potřeba to šrotovat do řetězce. V Javě (a na mnoha dalších platfomáchje to obdobně) jsou OutputStreamy, které problém mohou řešit. Můžeš ukládat 'někam' a nevědět, jestli se to šoupe do souboru, socketu, paměti, zda je to po cestě gzipováno apod. V PHP to lze udělat taky, ale nevypadá to až tak přímočaře v obecném případě.
Přidat metodu pru uložení do souboru... možná to není 100% čisté, ale je to praktické.
BTW: Ve Scale by asi nebyl problém 'přidat' metody save(File), save(String) a getBytes() (spíš by se podle konvencí jmenovala jen 'bytes') všem objektům s metodou save(OutputStream). Všechno jen ve vyhrazeném scope, bez monkeypatchingu. Taková menší odbočka, jak se to taky dá udělat.
Druhá otázka je, zda by třída reprezentující obrázek měla znát/určovat formáty souborů. Opět, puritánství vs. pragmatismus.
Mr.S1lent.cz:
Mozna je dobre, ze kazdy haji svoje pragma. Nekdo holt jede striktne 20cm od okraje vozovky za kazdou cenu i v pripade mensich, ci vetsich vymolu, nekdo si najede doprostred a za pomoci signalizacnich prvku vozidla nenarusi plynuly chod a bezpecnost komunikace, ovsem vyhne se vsem nesvarum ceskych silnic - pouziva selsky rozum - prakticke dovednosti nad striktnim dodrzovanim teoreticke metodiky. Jeho jizda je citelna, predvidatelna, ma svuj rad,je efektivni a zbytecne nenici sve vozidlo :-)
Z toho prispevku je doufam jasne, ze je to mirne metaforicky-ironicky vysmech vsem sekretarkam, ktere plodi tisice zbytecnych radku kodu a vymysli nova horska kola na ukor efektivity...
*Ti z Vas, co jeste nevideli nebo necetli Svejka - mozna se v tom nekteri najdete :-)
Diskuse je zrušena z důvodu spamu.