Booleovské parametry u metod

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
dmajda
Člen | 22
+
0
-

V různých metodách Nette se občas předávají booleovské paramety, u kterých není při pohledu na volající kód zřejmý jejich význam (případně význam zřejmý je, ale plete se, co znamenají hodnoty true a false). Takovéhle používání booleanů není moc vhodné a mate uživatele, viz třeba Try to avoid having BOOL function parameters.

Zatím jsem narazil na následující případy (ale určitě jich bude víc):

$form->setAction($action, $isPost = NULL)

Zde by IMO bylo hezčí boolean odstranit a zavést separátní metodu setMethod, případně aspoň umožnit místo $isPost psát rovnou název akce nebo předdefinovanou konstantu pro ni.

$httpRequest->getRemoteAddress($dns = FALSE)

Tady se podle hodnoty parametru vrací buď $_SERVER['REMOTE_ADDR'] nebo $_SERVER['REMOTE_HOST'] (modulo nějaká menší magie s případným vytvářením druhé proměnné). To jsou dvě různé věci a lepší by bylo je mít ve zvláštních metodách (tj. getRemoteAddress a getRemoteHost).

$httpResponse->setHeader($name, $value, $replace = TRUE);

Parametr replace by bylo myslím lepší nahradit nějakou konstantou. Být to v Javě, C# apod., dal bych tam místo booleanu enum { REPLACE, ADD }.

David Grudl
Nette Core | 8218
+
0
-

Vítej na fóru!

To je velmi dobrá poznámka. Už dřív jsem se pokusil na řadě míst TRUE/FALSE/NULL nahradit (příklad), ale má to svá úskalí. Tvé příklady jsou poměrně jasné, setMethod a getRemoteHost přidám. Stejně tak setHeader by se dalo rozšířit do dalších metod appendHeader a deleteHeader (tedy až to bude možné).

V Nette se nejčastějí bool proměnná používá pro rozhodnutí, jestli má metoda vyhodit výjimku nebo vrátit FALSE. Taková obdoba vykřičníku v Ruby, nemýlím-li se.

Component::lookup($type, $need = TRUE)
Component::lookupPath($type, $need = TRUE)
ComponentContainer::getComponent($name, $need = TRUE)
Environment::getService($name, $need = TRUE)
ServiceLocator::getService($name, $need = TRUE)
Application::getService($name, $need = TRUE)
PresenterComponent::getPresenter($need = TRUE)
FormControl::getForm($need = TRUE)
Session::configure(array $config, $throw = TRUE)

Některé použití odpovídá konvencím PHP u obdobných metod:

HttpResponse::setHeader($name, $value, $replace = TRUE)
HttpResponse::setCookie($name, $value, $expire, $path = NULL, $domain = NULL, $secure = NULL)
HttpResponse::deleteCookie($name, $path = NULL, $domain = NULL, $secure = NULL)
Session::setCookieParams($path, $domain = NULL, $secure = NULL)
Debug::dump($var, $return = FALSE)

a pak jsou tu zbývající metody, u některých by se určitě hodilo najít lepší řešení:

ComponentContainer::getComponents($deep = FALSE, $type = NULL)
Html::getIterator($deep = FALSE)

Debug::enable($level = E_ALL, $logErrors = NULL, $sendEmails = FALSE)

Environment::setVariable($name, $value, $expand = TRUE)
Environment::loadConfig($file = NULL, $useCache = NULL)

Form::addGroup($label = NULL, $setAsCurrent = TRUE)
Form::validate($breakOnFailure = FALSE)
Rules::toggle($id, $hide = TRUE)

Html::setName($name, $isEmpty = NULL)
Html::insert($index, $child, $replace = FALSE)

Session::destroy($removeCookie = TRUE)
User::signOut($clearIdentity = FALSE)
User::setExpiration($seconds, $whenBrowserIsClosed = TRUE, $clearIdentity = FALSE)
dmajda
Člen | 22
+
0
-

David Grudl napsal(a):

V Nette se nejčastějí bool proměnná používá pro rozhodnutí, jestli má metoda vyhodit výjimku nebo vrátit FALSE. Taková obdoba vykřičníku v Ruby, nemýlím-li se.

Jen na okraj: Takhle vykřičník používají spíš Rails než samotné Ruby, v Ruby typicky (ale ne vždy) značí destruktivnost operace ("ABC".downcase vytvoří nový řetězec, "ABC".downcase! modifikuje původní – rozdíl je vidět, mám-li na něj referenci i odjinud).

Component::lookup($type, $need = TRUE)
Component::lookupPath($type, $need = TRUE)
ComponentContainer::getComponent($name, $need = TRUE)
Environment::getService($name, $need = TRUE)
ServiceLocator::getService($name, $need = TRUE)
Application::getService($name, $need = TRUE)
PresenterComponent::getPresenter($need = TRUE)
FormControl::getForm($need = TRUE)
Session::configure(array $config, $throw = TRUE)

Tady je IMO potřeba mít znalost o typickém use-case. Přijde mi, že u těchhle (vesměs vyhledávacích) metod bude mít jejich volající typicky znalost o existenci vyhledávaného objektu a jeho nenalezení bude pro něj překvapením, což by odpovídalo výjimce. Ale zas tak můžou vznikat prázdné catch {} tam, kde nenalezení bude typické. Těžko říct, zda tady ty parametry vyházet. Asi by bylo dobré koukat, jestli vůbec někdo někde u té které metody používá $need = FALSE.

Některé použití odpovídá konvencím PHP u obdobných metod:

HttpResponse::setHeader($name, $value, $replace = TRUE)
HttpResponse::setCookie($name, $value, $expire, $path = NULL, $domain = NULL, $secure = NULL)
HttpResponse::deleteCookie($name, $path = NULL, $domain = NULL, $secure = NULL)
Session::setCookieParams($path, $domain = NULL, $secure = NULL)
Debug::dump($var, $return = FALSE)

Tady nadhodím tématický odkaz o tom, že API pod sebou není vždy žádoucí slepě kopírovat: http://www.acmqueue.com/modules.php?…

BTW u Debug::dump by se IMO víc hodilo, kdyby jako návratovou hodnotu vrátil dumpnutý objekt – když bych měl třeba kód f($a, $b, g($x, $y)) a chtěl si dumpnout výsledek fce g, stačilo by mi vložit Debug::dump okolo ní. V současné podobě je nutné ji z výrazu vytrhnout a dumpnout bokem. Situaci podobnou téhle jsem zažil mockrát; situaci, kdy bych chtěl mít dump v ruce, jsem nezažil nikdy (když pominu psaní nástrojů podobných Laděnce kdysi dávno).

a pak jsou tu zbývající metody, u některých by se určitě hodilo najít lepší řešení:

ComponentContainer::getComponents($deep = FALSE, $type = NULL)
Html::getIterator($deep = FALSE)

Debug::enable($level = E_ALL, $logErrors = NULL, $sendEmails = FALSE)

Environment::setVariable($name, $value, $expand = TRUE)
Environment::loadConfig($file = NULL, $useCache = NULL)

Form::addGroup($label = NULL, $setAsCurrent = TRUE)
Form::validate($breakOnFailure = FALSE)
Rules::toggle($id, $hide = TRUE)

Html::setName($name, $isEmpty = NULL)
Html::insert($index, $child, $replace = FALSE)

Session::destroy($removeCookie = TRUE)
User::signOut($clearIdentity = FALSE)
User::setExpiration($seconds, $whenBrowserIsClosed = TRUE, $clearIdentity = FALSE)

Mám pocit, že ve většině případů by pomohlo rozdělení na víc funkcí, ale je mi jasné, že to zas zvyšuje velikost API, takže to také není ideální.

Škoda že PHP neumí keyword parameters jako Python, to by tady moc pomohlo :-(

David Grudl
Nette Core | 8218
+
0
-

dmajda napsal(a):

Jen na okraj: Takhle vykřičník používají spíš Rails než samotné Ruby, v Ruby typicky (ale ne vždy) značí destruktivnost operace ("ABC".downcase vytvoří nový řetězec, "ABC".downcase! modifikuje původní – rozdíl je vidět, mám-li na něj referenci i odjinud).

Díky, konečně v tom mám jasno.

Tady je IMO potřeba mít znalost o typickém use-case. Přijde mi, že u těchhle (vesměs vyhledávacích) metod bude mít jejich volající typicky znalost o existenci vyhledávaného objektu a jeho nenalezení bude pro něj překvapením, což by odpovídalo výjimce. Ale zas tak můžou vznikat prázdné catch {} tam, kde nenalezení bude typické. Těžko říct, zda tady ty parametry vyházet.

V rámci frameworku jde o typický vzor, což existenci parametrů ospravedlňuje a nechal bych je tam.

Tady nadhodím tématický odkaz o tom, že API pod sebou není vždy žádoucí slepě kopírovat: http://www.acmqueue.com/modules.php?…

Koukám, že máš na všechno odkaz :-)

BTW u Debug::dump by se IMO víc hodilo, kdyby jako návratovou hodnotu vrátil dumpnutý objekt – když bych měl třeba kód f($a, $b, g($x, $y)) a chtěl si dumpnout výsledek fce g, stačilo by mi vložit Debug::dump okolo ní. V současné podobě je nutné ji z výrazu vytrhnout a dumpnout bokem.

Výborný nápad, dump upravím.

Mám pocit, že ve většině případů by pomohlo rozdělení na víc funkcí, ale je mi jasné, že to zas zvyšuje velikost API, takže to také není ideální.

To je vždycky otázka, obzvlášť u PHP, kde se volí mezi pohodlím a rychlostí. Každopádně třeba správu cookies a HTTP hlaviček se chystám (už dva roky) předělat.

phx
Člen | 651
+
0
-

Kdyz jsme u te rychlosti a pohodli tak ve vetsi app musi syntakticky sugar delat mozna i vetsi % zateze. Nevim netestoval jsem to, je to jen myslenka. Je to totiz silne navikove a kdyz to pouziva clovek vsude tak nevim nevim.

Napada me, ze by na to slo udelat neco jako u sablon. Prohnat to 1× pres nejaky formatovac, ktery by z $class->metod udelal $class->getMetod(). Otazka zni zda by to vykonove nejak pomohlo a jak hodne. Pokud by to byly jednotky % tok to asi nema cenu.

Editoval phx (23. 10. 2008 19:04)

Ondřej Mirtes
Člen | 1536
+
0
-

Upravil jsem stránku HttpRequest v dokumentaci, getRemoteAddress(true) mi už nezafungovalo, místo něj jsem ve zdrojáku „objevil“ getRemoteHost() :)