Moda v pojmenování nebo nešvar?
- Petr Parolek
- Člen | 455
Ahoj,
vždy se mi líbilo označení, co je třída, interface. Nette používalo předponu I- v názvech interfaců. Najednou z neznámých důvodů začal @DavidGrudl mazat předponu I- z názvů rozhraní.
Programátorovi pomáhá systém a ne chaos…
Líbí se mi, jak to má např. Symfony – přípona -Interface.
Nechápu modní hity. Nette není přece kus hadříku na dámy, ale pořádný kus kodu.
- Pavel Janda
- Člen | 977
Souhlasím.
Zavádět novou módu je docela k ničemu. Ve světě se používá buď „I-něco“ nebo „NěcoInterface“. To je jasné, čitelné.
Editoval Pavel Janda (6. 1. 2021 14:24)
- Marek Bartoš
- Nette Blogger | 1274
IDE je dost chytré na to, aby poznalo, zda má napovídat třídu, interface
či výjimku. Pro extends
, implements
a
throw
tedy žádná vodítka nepotřebuješ.
Zkus prefixy a suffixy v těhle případech smazat ve svém kódu – třeba
přijdeš na to, že ti též nechybí.
- dakur
- Člen | 493
Skutečně, často jsou názvy tříd velmi dlouhé, mnohem praktičtější tedy je, pokud mám informaci o typu souboru signalizovanou jinak a mohu mít tedy v názvu o jedno obecné slovo míň – viz https://imgur.com/I8TR9OE – z toho je hned jasné, co je co.
- Petr Parolek
- Člen | 455
Mabar napsal(a):
IDE je dost chytré na to, aby poznalo, zda má napovídat třídu, interface či výjimku. Pro
extends
,implements
athrow
tedy žádná vodítka nepotřebuješ.
Zkus prefixy a suffixy v těhle případech smazat ve svém kódu – třeba přijdeš na to, že ti též nechybí.
Nejde o IDE, ale o čitelnost kodu, abych hned věděl, co je co bez barviček a ikonek.
Editoval ppar (6. 1. 2021 15:39)
- David Grudl
- Nette Core | 8227
Mám zrovna o tom rozepsaný článek :-)
Používání prefixů a suffixů Interface
,
Abstract
nebo I
je velký přešlap. Přeslap o to
horší, že když je to ve frameworku, tak to lidi berou jako vzor. Protože
jsem našel cestu, jak to bez narušení zpětné kompatibility opravit, tak to
postupně a dlouhodobě opravuju.
Proč je to přešlap, o tom bude ten článek.
- David Grudl
- Nette Core | 8227
Ale když už to téma tady je – jaký máte argument pro používání prefixů/suffix?
Buďte prosím konkrétní. „Abych hned věděl, co je co“ tu vyvrátil
@mabar. Prostě když v kódu uvidím new Foo
nebo
throw Foo
, vím že to je třída a není abstraktní.
- Felix
- Nette Core | 1245
Mne osobne je jedno, jak to bude definovat nette. Zda-li s prefixem, postfixem nebo jinak. Prizpusobim se. Oblibil jsem si pouziti I na zacatku, protoze se mi libi jak je to kratke. Vim, ze Symfony pouziva delsi variantu s Interface.
Tohle takhle aplikuju v PHP.
Kdyz programuji v jinem jazyce, treba v Jave, tak mam interface bez niceho a trida ma treba postfix Impl.
V JS to delam zase jinak.
Celym tim chci rict, ze jak to definuje nastroj/knihovna, neovlivnuje jak to mam ja v aplikaci. To je pro me dulezite a to se snazim predat i dal. Dulezita je pro me konzistentnost.
- dsar
- Backer | 53
I prefer the new way.
The single name denotes the interface or the abstract class, while compound names denote the implementations.
Translator is the interface or abstact class, while NeonTranslator and IniTranslator are the implementations.
Authenticator is the interface or abstrat class, while DatabaseAuthenticator and WebserviceAuthenticator are the implementations.
Editoval dsar (7. 1. 2021 0:26)
- neznamy_uzivatel
- Člen | 115
Je dobrým zvykem to dělat snad odjakživa.. Pamatuju prefixy cls, mod,
frm… už před 22 lety ve VB6 :D C++ a C# to stejné.
Vidím pak jasně (už z názvu souboru, kam to taky patří), že je to např
interface aniž bych vůbec musel zkoumat kód a hledat kde a jak se „ten
soubor“ používá.
Odstraňovat to jedno písmenko, které se stalo zavedeným standardem
přehlednosti ničemu nepomůže – minimálně v případech IInterface a
TTrait. Nicméně těším se na článek i když se mi to nelíbí :D
- Kamil Valenta
- Člen | 820
Také se mi zdá, že to pomáhá v orientaci na úrovni FileSystému, nebo pak v tabech editoru, které zobrazují název souboru. Oboje jsou případy, kdy kontext kódu není vidět.
Také by mě zajímaly důvody, proč je IInterface a TTrait velký přešlap. Možná ten článek mohl předcházet.
- dakur
- Člen | 493
@ppar Teď koukám, že jsi tu otázku pokládal i v článku k Latte 2.7 – tam ti na ni taky hezky odpověděli, myslím. :-) https://blog.nette.org/…ivas-a-batch#…
Ale jinak se mi líbí, jak to píše @Felix. Ono je to nakonec jedno, protože nikdy nepřinutíš celý svět, aby to dělal to stejné. 🙂
- Pavel Janda
- Člen | 977
@Mabar To, že ti nějakou věc usnadní IDE, není známka toho, že
„věc“ je správně. Příklad: pokud bychom posílali do Latte šablon
proměnné, které by nebyly dostupné pod svým jménem, ale třeba (a úplně
zbytečně) pod jménem $_template_variable_originalName
, je sice
pěkné, že ti IDE napoví, pod čím najít kýženou proměnnou, ale
chování Latte se nám zdá dost divné, je to tak?
Nemluvě o tom, že mnoho a mnoho lidí IDE nepoužívá. Znám desítky
programátorů, kteří používají PhpStorm a desítky programátorů, kteří
používají Sublime, Atom či Vim. Takže se, prosím, nespoléhejme na Nette
pluginy do PhpStormu, ale řešme konstrukce aplikací a úrovni jazyka, pokud
se bavíme o frameworku.
Často se mi v produkčním prostředí vyplatilo nekoukat na to, co je
„akademicky správně“, ale co přinese nejvíc užitku obecně. Pokud je
nějaký kus kódu brutálně optimalizovaný a nejrychlejší na světě, je to
sice pěkné, ale každý nově příchozí programátor takový kód nebude
umět přečíst a co hůř – upravit/opravit, pokud bude zásah potřeba.
Taky se u toho programátor bude, myslím, cítit demotivovaný, což je
důležité brát na vědomí. A tak se použije řešení, které běží
o řád pomaleji (nikoliv 1 nanosekundu, ale 10 nanosekund), ale ušetří
100 řádků kódu, nervy programátorů a desítky placených hodin. Všichni
tak budou happy. ->
Neprogramujeme přeci jadernou elektrárnu.
A k tomu jsem se chtěl dostat – php framework není jaderná fyzika. Má nám usnadnit a zpřehlednit práci. Třeba to bude na úkor toho, že nebude něco akademicky „správně“, ale desítky tisíc programátorů bude bavit a budou ho považovat za jeden z nejefektivnějších nástrojů pro svou práci.
Nette v sobě má pár ne úplně akademicky správných postupů. Ale skvěle se používá a každý se ho rychle naučí. To nejdůležitější – je čitelný pro nováčky. 🤷♀️
(Lidé jsou zvyklí na I-něco či Něco-Interface. Je to na první pohled čitelné.)
Tak to, prosím, neměňme. :)
Editoval Pavel Janda (7. 1. 2021 10:38)
- Marek Bartoš
- Nette Blogger | 1274
Stejně tak se v průběhu času přestala používat maďarská notace. Přitom z ní je na první pohled jasné, jaký má property kupříkladu datový typ nebo modifikátor přístupu. Proč se tedy přestala používat? Imho jsou odpovědí nástroje, které dovedou tyhle informace vyčíst z dokumentace nebo samotného jazyka. V čem se tenhle případ liší? Pro ty, co IDE nepoužívají je to úplně stejné. V obou případech není informace ihned patrná z textu, ale s vhodným nástrojem na tom nezáleží.
- Marek Bartoš
- Nette Blogger | 1274
Rád bych se zeptal vás, kteří preferujete prefixy či suffixy – vadilo
vám někdy, že SmartObject
se nejmenuje TSmartObject
nebo SmartObjectTrait
?
- baraja
- Nette Blogger | 29
Osobně jsem taky za zavedení co nejkratších názvů, protože:
- Podporuje to sémantiku, například
Translator
je hezký příklad rozhraní, protože z názvu je jasně vidět, že jde o obecný překladač, ale konkrétní implementace pak bude třebaMyTranslator
- Zkracuje to nejednoznačnost, například
IIdentity
vypadá na první pohled jako překlep, protože tam jsou zdvojenéI
a lepší je pojmenovat obecné rozhraníIdentity
a pak implementovat konkrétníSimpleIdentity
@Mabar Od SmartObjectu se snažím postupně opouštět a raději jdu cestou striktně definovaného kódu a nastaveného PhpStanu na nejpřísnější úroveň. SmartObject zahrál zásadní roli v minulosti, ale dnes zhoršuje testovatelnost a předvítatelnost kódu. Datové typy od PHP 7.4 jsou obrovský gamechanger, který dost věcí vyřešil. Navíc ze SmartObjectu často vznikají problémy s kompatibilitou (viz opravy v Doctrine, v Kdyby Translatoru a podobně).
@DavidGrudl Skvělá práce! Už mnohokrát se v Nette ukázalo, že lidé novinky spíše odmítali, ale pak se to zpětně ukázalo jako skvělá věc. :)
- David Grudl
- Nette Core | 8227
Ty problémy s kompatibilitou nebyly na straně SmartObjectu, ale jde o chybu ve TranslateMacros, který dědí od cizí třídy a zároveň nesmyslně používá traitu Kdyby\StrictObjects\Scream, která se bije s předkem.
Nette\SmartObject nikdo z nich nepoužívá! 🤷♂️ To je jen dojem vzniklý tím, že Filip zkopíroval název SmartObject pro svůj projekt. Prostě normální chyba, co ale nechápu, že ji za ty roky nikdo neopravil.
- Kamil Valenta
- Člen | 820
dakur napsal(a):
@ppar Teď koukám, že jsi tu otázku pokládal i v článku k Latte 2.7 – tam ti na ni taky hezky odpověděli, myslím. :-) https://blog.nette.org/…ivas-a-batch#…
Opravdu? Reply #2 je pro, #3 je proti, #4 a #5 se toho netýkají.
V #3 je:
ad a] subjektivní a neodůvodněné
ad b] uznávám, ale jak často se ze třídy později stává interface a kolik
lidí refaktoruje ručně?
ad c] při prefixu I nebo T ztrácí bod smysl
Takže to moc za „hezkou odpověď“ nepovažuju…
Editoval Kamil Valenta (7. 1. 2021 17:24)
- Kamil Valenta
- Člen | 820
baraja napsal(a):
Osobně jsem taky za zavedení co nejkratších názvů, protože:
lepší je pojmenovat obecné rozhraní
Identity
a pak implementovat konkrétníSimpleIdentity
Identity (class) + IIdentity (interface) = 17 znaků
SimpleIdentity (class) + Identity (interface) = 22 znaků
BTW, u prefixů „I“ a „T“ mi fakt připadá argument o co
nejkratších názvech zcestný. Jakoby ten jeden znak byl alarmující.
Chápal bych takový argument v ZendFrameworku, kde ty názvy byly fakt na
celý řádek (nevím jak dnes)…
- Šaman
- Člen | 2662
Jen si teda říkám, jestli je to tak zjevné?
Storage je interface? UserStorage je implementace?
Ani ne: interface
IUserStorage
Editoval Šaman (8. 1. 2021 16:28)
- David Grudl
- Nette Core | 8227
User in UserStorage is possessive, not adjective. It is User's Storage, ie Storage for User.
- Šaman
- Člen | 2662
Ok, but without deep knowledge i cant say, if UserStorage
is
interface or class. IUserStorage
is conclusively interface.
And there can be both interface IUserStorage
and class
UserStorage
. I'm affraid, there will be many eq. interface
Translator
and class MyTranslator
(because there is
only one translator in app and I must find unique name in pattern
fooTranslator
).
I'm waiting for that blog article with deep explanation.
- David Grudl
- Nette Core | 8227
Proč jsme přepli do angličtiny? :-)
Samozřejmě že z žádného podstatného jména nemůžeš poznat, jestli je to interface nebo třída. Jen jsem chtěl říct, že vztah mezi Translator–MyTranslator je jiný než mezi Storage–UserStorage v případě odkazovaného interface.
Rozlišovat mezi rozhraním a třídou je totiž nutné pouze v jednom případě – že používáš nějaký prefix/suffix. Protože má pak jiný název. Když žádný prefix/suffix nepoužíváš, pak ani nemáš potřebu to rozlišovat.
- Milo
- Nette Core | 1283
Za mě – už hodně let bez prefixů/suffixů.
Argument „aby to bylo jasné i bez IDE“ – OK, takže si představme, že nemáme IDE. A teď jdeme dělat co?
- Prohlížet zdrojáky – jakmile uvidíme
new
,implements
,use
,extends
, nebothrow
, je nám jasné o co jde. Jediné místo jsou typehinty a tam nám to může být úplně jedno, pokud pouze čteme. Jiná situace je při refaktoringu, nebo code review. Tam už můžeme váhat, jestli tam má být třída, nebo interface. A když tam prefixI
je, tak co? Tak prostě věříme autorovi, že je to deklarováno jako interface? Code review není o důvěře, ale prověře. - Programovat – budu chtít implementovat nějaký interface a padlo tady,
že ve filesystému se bude lépe hledat. To opravdu někdo bude ručně
procházet adresář po adresáři a hledat soubory začínající na
I
, neboT
? To spíš CLI:
find . -name 'I*'
# ale to už můžu rovou udělat
grep -r --include='*.php' interface
Pokud bude knihovna fakt velká, stejně si nejřív najdu psanou
dokumentaci, API dokumentaci, nebo examples. A tam už to bude. A pamatujme na
to, že nemáme IDE. Tedy musíme ručně upravit use
importy a
napsat implementace metod. IMHO to člověk udělá 2× a už si název
pamatuje, ať tam ten prefix je, nebo ne :)
Je to jenom mentální blok. Mně to trvalo asi rok, než jsem si ho odboural. Takový ten „pocit bezpečí“, když tam ten prefix vidím. Ale teď když prefix/suffix někde vidím, spíš mě to rozčiluje, protože to rozptyluje úvahy.
A nevím, jestli to už ve vlákně někdo zmínil, jen jsem ho prolétl: „výhodou ne-prefixů je, že mohu změnit deklaraci interfacu na abstraktní třídu a vice versa a nemusím přejmenovat“ :) (berte to jako vtip – touhle změnou si vykouzlím tolik problémů, že přejmenování je ten nejmenší)
- Milo
- Nette Core | 1283
Ale vlastně k té „změně deklarace z interface na třídu a vice versa“ – napadl mě jen případ, kdy to vlastně dělám :-D Nette DI a továrničky.
Většinou začínám:
interface EditorFactory
{
public function create(): Editor
}
ale když se časem ukáže, že továrna musí udělat i nějaký setup, změním to na
final class EditorFactory
{
public function create(): Editor
{
... setup
}
}
no a kdybych neměl IDE, tak nemusím nikde nic upravovat :-D
- joe
- Člen | 313
Osobně používám prefixy I
a T
, ale jen ze
zvyku.
Ve svých projektech mám taky implementaci překladače s jednoduchým názvem
Translator
(nechci nad názvama přemýšlet, takže volím co
nejjednodušší), nemůžu tedy říct, že Translator
je
rozhraní, ale…
Spíš přemýšlím nad tím, k čemu je vlastně dobré vědět, že jde o rozhraní, traitu nebo třídu.
Ale když v Nette nebude prefix I
, spíš mi to pomůže
v tom, že mi IDE přednostně po napsání I napoví rozhraní
z mého projektu a ušetří čas. A pokud tu @Mabar zmiňoval, že IDE
je chytré, nevím proč mi po implements
nabízí první čtyři
položky, které rozhraní nejsou.
- neznamy_uzivatel
- Člen | 115
Milo napsal(a):
To opravdu někdo bude ručně procházet adresář po adresáři a hledat soubory začínající naI
, neboT
? To spíš CLI:
O to přece nejde. Jde o to, že to právě na první pohled vidím a nemusím tak čachrovat s grep interface (což je stejne velmi nespolehlivé).
--
Nebo mám ve stejném namespacu (adresáři) Article a IArticle, atd… Za mě
výrazné zpřehlednění jak v kódu tak ve filesystému. Nepřijde mi
logické nepojmenovat si interface jako interface.
- David Grudl
- Nette Core | 8227
Koncovna .phpi
pro rozhraní a .aphp
pro
abstraktní třídy. Jako se používalo .inc.php
pro soubory,
které se includují, než se přišlo na to, že kromě index.php
se includují všechny.
- janpecha
- Backer | 75
Milo napsal(a):
Padají tu argumenty, že je to na první pohled vidět z názvu souboru. A to v jakých situacích koukáte na názvy souboru?
Když řeším s tužkou a papírem návrh nějakého kusu kódu a hledám interface (případně naopak konkrétní implementaci) v cizích zdrojácích na GitHubu (navíc třeba ještě přes mobil) ;)
- Petr Parolek
- Člen | 455
A proto je řešení být striktní programátor a názvy souborů mají odpovídat názvům tříd, trait a rozhraní a používat předpony I- a T- nebo přípony -Interface a -Trait! Komu uškodí napsání pár písmen navíc? Kod je přehledný a jasný. Nerad míchám jablka s hruškama.
- Polki
- Člen | 553
Obsah adresáře ‚App/Forms/ContactForm‘:
ContactFormFactory.php
ContactFormProcessor.php
‚ContactFormProcessor‘ je interface, které říká, že ten kdo ho implementuje musí umět odeslat kontaktní formulář.
Implementace ‚ContactFormFactory.php‘
<?php declare(strict_types=1);
namespace App\Forms\ContactForm;
use Nette\Application\UI\Form;
use App\Forms\FormFactory;
class ContactFormFactory
{
public function __construct(
private FormFactory $formFactory,
private ContactFormProcessor $formProcessor,
) {}
public function create(): Form
{
$form = $this->formFactory->create();
$form->addEmail('email', 'E-mail');
$form->addText('content', 'Obsah dotazu');
$form->onSuccess[] = [$this, 'onSuccess'];
$form->addSubmit('submit', 'Odeslat');
return $form;
}
public function onSuccess(Form $form): void
{
$this->formProcessor->process($form->getValues());
}
}
1)
Nyní můžeme vzít náš kontaktní formulář a dát si ho do jiného
projektu, kde potřebujeme ten stejný kontaktní formulář. Pokud jsme tvůrce
projektu, je to jednoduché. Víme, že ‚ContactFormProcessor‘ je interface
a tak nám stačí nějaké naší třídě prostě ten interface implementovat
a díky DI máme vystaráno. Prostě přidáme jednu metodu a ‚implements‘.
(To samé platí i pro FormFactory, ale v tomto konkrétním případě, kdy
je FormFactory jinde a je tedy společná pro více formulářů je jedno,
jestli zkopírujeme interface a poté donutíme jinou třídu jej implementovat,
nebo jestli si rovnou napíšeme konkrétní třídu. Tedy FormFactory nás
nezajímá. Pokud jej nezkopírujeme z projektu taky, tak jej stejně budeme
muset dopsat.)
Opačná situace ale nastane, pokud jsme po někom zdědili projekt a šéf chce nový web, kde má být kontaktní formulář, přičemž chce, aby se dodržel firemní codding standard, takže musí na novém webu být kontaktní formulář udělán stejně. Nováček přijde, zkopíruje složku s formulářem, a najednou se diví, že mu Tracy hlásí, že DI nenalezl třídu ‚ContactFormProcessor‘. Byl jsem svědkem, kdy se nad takovou věcí zasekli nováčci na několik hodin, protože nevěděli, že pod ‚ContactFormProcessor‘ je ukrytý interface a ne již hotová třída. (To, že by neměl ContactFormProcessor odkud brát data jako email odesílatele pomineme, jelikož se jedná o příklad).
2)
Pokud je firma správně hierarchicky rozdělená, tak má tým developerů a
tým testerů. Tedy tester je někdo, kdo neví, jak ten člověk naprogramoval
aplikaci, jen ví, co má daná třída za metody a co má dělat. Tedy že má
metodu sečti a ta opravdu vrátí jako výsledek součet dvou či více hodnot.
Když tedy budeme jako testeři testovat třídu ‚ContactFormFactory‘, tak
když si dáme v testu ‚new ContactFormFactory‘ tak nám IDE vyhodí
nabídku například: ‚ContactFormFactory(FormFactory $formFactory,
ContactFormProcessor $formProcessor) App\Forms\ContactForm‘. Jak potom my jako
testeři poznáme, jak se zrovna programátor vyspal a jak to udělal? Když
budeme chtít mocknout ‚FormFactory‘ a ‚ContactFormProcessor‘, tak jak
poznáme, jestli máme tvořit třídu, která implementuje interface, nebo
máme tvořit potomka třídy? Zase musíme dané soubory najít na disku,
rozkliknout a prozkoumat, co se tam děje. (Případně si nechat napovědět
IDE když zkusíme napsat new a název třídy…)
Závěr
Všechno toto značně znepřehledňuje kód a tím, že to musí člověk
neustále kontrolovat co to je to zhoršuje výkonnost práce. Kdyby se prostě
na začátek názvu pomocí nějakého standardu přidal prefix I-,T- atd.., tak
hned každý ví o co jde a nemusí nic rozklikávat a hledat a ztrácet
hromady drahoceného času nad tím, že neví co na daném místě v danou
chvíli je.
Trochu mi to připomíná skryté závislosti, kdy se lidi bouří, že třída musí vše načítat přes konstruktor, nebo ze setterů, protože například přímé načítání z konzole například je skrytá závislost, která má za následek nejasnost kódu a aby uživatel pochopil co dělá metoda sečti(), tak se musí kouknout do ní. Stejně tak to vidím u prefixů I-, T- atd.. Prostě pokud to přímo nenačítám někde a IDE mi to nenapoví tedy, tak musím stejně jako v případě metody sečti vlézt dovnitř třídy a prozkoumat to. Pokud je tedy v pořádku odstranit prefixy, tak by mělo být i v pořádku povolit skryté závislosti a tedy zavést do programování chaos.
Už vidím, jak se to za pár let dovede do extrému a lidi budou říkat, že jsou všechny prefixy a suffixy zbytečné, takže všechny buildery a factory si odeberou suffix, takže místo ‚ContactFormFactory‘ a ‚FormFactory‘ bude ‚ContactForm‘ a ‚Form‘ kde pak nebude nikdo vědět, jestli je to faktorka a má se nad tím teda volat $form->create() nebo jestli je to konkrétní třída a máš nad tím volat new Form(), nebo se to dovede ještě dál a z interface ‚ContactFormProcessor‘ zbyde jen ‚ContactForm‘ takže nebude člověk vědět který z ‚ContactForm‘ je továrna na formulář, nebo jestli je to samotná třída formuláře, případně jestli je to interface či třída, která zpracovává ten form, nebo to dotáhneme úplně k dokonalosti a vše co se bude týkat formulářů se bude jmenovat prostě ‚Form‘ nehledě na to co to dělá… To se pak v tom vyznat bude žůžo labůžo.
Otázka nakonec:
Proč vlastně přechází PHP k silnému typování, aby bylo na
první pohled jasně zřejmé, co je to za proměnnou a co má v sobě
uloženo, když si to programátoři nelogicky nahradí tím, že začnou
zobecňovat názvy tříd tak, že nikdo neví, co je uloženo
v dané třídě?
- dsar
- Backer | 53
Milo napsal(a):
V aplikaci mám svoji implementaci translátoru pojmenovanou zase
Translator
.namespace App; use Nette\Localization; final class Translator implements Localization\Translator {}
joe napsal(a):
Osobně používám prefixy
I
aT
, ale jen ze zvyku.
Ve svých projektech mám taky implementaci překladače s jednoduchým názvemTranslator
(nechci nad názvama přemýšlet, takže volím co nejjednodušší), nemůžu tedy říct, žeTranslator
je rozhraní, ale…
(Sorry for my english posts, but this forum is the most active place to talk about Nette)
In my opinion a final class Translator doesn't mean anything. Translator using what source? The same for the Authenticator, it authenticates against what? An implementation that got a so simple name doesn't give a so much important information, so it looks like something abstract ;-)
In two different projects I have NeonTranslator and DatabaseTranslator, just
by looking at the name I know where the class takes data.
Otherwise the only way is by looking at source code. Yes, because in PHP we can
do that. What if I have translator.so/translator.dll? With an hex editor or
disassembler? :-)
Furthermore many OOP books use this style, Tree is the generic class while EmployeeTree is the specialisation class, etc..
- David Grudl
- Nette Core | 8227
Proč vlastně přechází PHP k silnému typování, aby bylo na první pohled jasně zřejmé, co je to za proměnnou a co má v sobě uloženo, když si to programátoři nelogicky nahradí tím, že začnou zobecňovat názvy tříd tak, že nikdo neví, co je uloženo v dané třídě?
To je výborný point. Proč když PHP směřuje k silnému typování,
není u proměnných na první pohled zřejmé, jaký mají
typ? Musí se to dohledat v deklaraci. Proč se nepoužívá prefix
$this->intFoo
nebo $this->strFoo
, kde bych hned
věděl? Tedy tzv. maďarská notace.
Totéž se týká viditelnosti. Nelze rozlišit, jestli
$this->foo
je privátní nebo public. Tady jsou osvědčeným
řešením podtržítka.
Jsou jen dvě možnosti
- buď tyhle notace odmítnout
- nebo tyhle notace přijmout
Ale v některých případech je odmítnout jako zbytečné či dokonce rušivé a ve druhým případech je přijmout, protože „je to pak na první pohled zřejmé“, ztrácí vnitřní integritu.
- David Grudl
- Nette Core | 8227
@dsar: I agree that there must be specificity in the name. But
sometimes it can also be expressed in a namespace. Ie.
Gettext\Bridge\Translator
is good if it is the only translator in
package.
The same is the case of App\Translator
. It is THE translator
used in my single simple app. There is no need to express implementation details
in name.
- Polki
- Člen | 553
@DavidGrudl $message, $form, $telephoneNumber, $zip, $name, $surname, $city…
Asi těžko bude proměnná $name obsahovat desetinné číslo.
Stejně tak proměnná $form, ta má v sobě objekt. Jak poznáme, že v sobě má objekt? Díky silnému typování.
Uvnitř funkce, které si ji nechám předat ‚public onSuccess(Form $form);‘ vím, že je $form datového typu Form a neřeším.
Pokud ji něco vrací: ‚$contactFormFactory->create();‘ tak asi mi metoda ‚create()‘ nad třídou, co se jmenuje ‚$contactFormFactory‘ nebude vracet řetězec. Jako bonus mi napoví IDE jaký návratový typ metoda má. Stejně tak dá rozum, že metoda ‚sečti($a, $b)‘ bude vracet číslo. Jestli int, nebo float mi může být jedno, protože INT se dá bez problémů zaměnit za float. Takže budu se vším pracovat jako s floatem a jsem vystaraný. tak samo metoda ‚concat($a, $b)‘ mi vrátí řetězec a ne int nebo nějaký objekt apod…
Takže ano, pokud je proměnná správně nazvaná, tak je vždy na první pohled zřejmé, co obsahuje.
Příkladem můžou být proměnné $user a $userId, přičemž očividně proměnná $user obsahuje instanci třídy User a proměnná $userId obsahuje integer znamenající uživatelovo ID.
Ale prosím vysvětli, jak poznám z názvu třídy ‚ContactFormProcessor‘, jestli se jedná o Interface, nebo Class? A to jsem schválně dal jako příklad zasazení do kontextu stejně jako jsou zasazeny do kontextu proměnné. U těch tříd/rozhraní to prostě nejde. Proto se ty prefixy/suffixy používají.
Kdysi jsem viděl, že se nazývaly rozhraní s příponou -able, protože přeci jako v reálném životě, tedy člověk umí chodit, zvířata a někteří roboti třeba, takže rozhraní Walkable. Známe to někdy i dnes, jako třeba Serializable. Většinou jsem viděl Drawable, Printable atd. Ale taky jsem viděl jak ti, co se to naučili od toho začali rychle upouštět v momentech, kdy viděli, že se to někde používá dost těžko. Například rozhraní FormFactory. Jak ho nazvat? FormFactoriable? To nedává moc smysl. Tak FormCreatable? A co to znamená? Že je ten formulář vyrobitelný, nebo že ho někdo umí vyrobit? Tady dává větší smysl ho prostě nazvat jako FormFactory a aby bylo známo, že se jedná o Interface, tak buď dát sufix -Interface nebo prefix I-, přičemž preferuji prefix, jelikož je kratší.
Editoval Polki (10. 1. 2021 14:44)
- David Grudl
- Nette Core | 8227
Pokud přijdeš z názvu na to, že proměnná $user
obsahuje
objekt a dokonce jakého typu, nemůže být pro tebe problém zjistit informace
o ContactFormProcessor
:-)
- David Grudl
- Nette Core | 8227
Každý identifikátor v jazyku s sebou nese nějaké metainformace. Co je to vůbec za prvek, jaký má typ v případě proměnných, viditelnost u členů tříd, další flagy jako abstract/final/&, výčet toho co implementuje/extenduje/useuje atd.
Po třiceti letech, co se věnuju programování, se dospělo k tomu, že vůbec žádná z těchto metainformací se nezrcadlí v názvu.
Ale než se k tomu dospělo, tak se o každém krůčku strhla vášnivá debata, protože spousta programátorů to považovala za krok špatným směrem, jelikož se tak z kódu vytratí důležitá informace.
Zajímavé je, že zpátky se nikdo nikdy nevracel. Pochopitelně je pravda, že člověk o střípek informace přijde. Ale vždy se ukázalo, že pozitiva převážily.
Tento krok a takhle diskuse nejsou výjimkou.
- David Grudl
- Nette Core | 8227
btw, pro mě bylo velkým krokem opustit T
na začátku názvu
každé třídy (pamatujete ještě někdo tuto zvyklost?) Anebo C
. Pro miliony další
programátorů bylo velký krokem opustit m_
na začátku každé
proměnné třídy. Atd… Ve své době velké téma. Zkuste si představit,
že byste to dnes začali používat.
- Polki
- Člen | 553
Pokud je vše dobře nazvané tak stačí kouknout a je to hned jasné. Pokud ovšem začneme věci nazývat špatně tak, že nejsou jednoznačné, tak pak se to zjistit nedá kolikrát ani hloubkovou kontrolou.
Například je asi jasné, že když v Presenteru napíšu
$this->user
tak dostanu instanci třídy
Nette\Security\User;
Když to udělám například v nějaké
modelové třídě tak je jasné, že to je stejného datového typu jako
třídní proměnná a pokud dodržuju zapouzdření, tak je to snad jasné na
první pohled, že tam necpu nic jiného.
Navíc pokud mám vícero částí aplikace a přihlašuje se více lidí a
například používám ORM, tak mám entity
Customer, Merchant, Admin ...
a tedy nazývám proměnné
výstižně tedy
$customer/$customers, $merchant/$merchants, $admin/$admins
atd…
Stejně tak z názvů vidíš, jestli se jedná o jednu instanci třídy, nebo
jestli se jedná o pole instancí a i kdyby to pole bylo nějaký objekt, tak
při správném použití můžu k tomu objektu přistupovat jako k poli,
takže s tím pracuji pořád stejně a je jedno, jestli je tam pole nebo
nějaký třeba iterable object. Z názvu musím vědět, jestli je jeden,
jestli je jich více a jaký to je datový typ.
Proto instanci třídy ‚ContactFormFactory‘ uložím do proměnné
‚$contactFormFactory‘, čímž budu vědět, že v té proměnné je
uložena instance této třídy. (Tedy si proměnnou správně nazvu)
Pokud mám v aplikaci vícero tříd ‚User‘, pak mám povinnost
i správně nazvat proměnnou tak, aby z ní bylo jasně zjevné, o jakou
instanci třídy se jedná. Pokud jsem v Presenteru tak tam je to jasné tam je
to Security\User a vždy se to jmenuje $user. Pokud jsem jinde, musím odlišit
jestli je to Nette\Security\User
nebo jiný a to rozliším buď
tak, že v kontextu se vyskytuje pouze jeden User a je tedy jasné který, nebo
si je odlišit.
Často vídám v různých třídách dvě instance. Jednu s
Nette\Security\User
a druhou s modelovým například
App\Model\User
kde neťácký je nazvaný jednoduše
$securityUser
a ten modelový tedy definovaný programátorem
v modelu se označuje čistě podle názvu třídy tedy $user
.
A k původnímu argumentu. Pokud do proměnné $user
ukládám
toto: $user = $this->userRepository->getById(1);
tak je asi
jasné, že v proměnné ‚$user‘ není instance Talíře…
Díky tomu už nikdy nemusíš koukat na definice co co obsahuje, protože je to hned zřejmé z názvu proměnné a není třeba ani psát komentáře a kód je ‚self-explanatory‘, což se tak už mnoho let dělá snad ve všech větších firmách. A součástí toho jsou i názvy tříd. Ne jen proměnné by měly být pojmenovány tak, aby bylo jasně zřejmé, co v nich je, ale také třídy/rozhraní/traity a vlastně všechny soubory a složky by měly být pojmenovány tak, aby bylo jasně zřejmé, co v nich je.
Stejně tak jako složku ‚Presenters‘ která očividně asi znamená, že v sobě má Presentery nepojmenujeme Foo, tak bychom ani třídu ‚ContactFormFactory‘ neměli pojmenovat například ‚Factory‘ jelikož se neví o jakou Factory jde a stejně tak ‚IContactFormProcessor‘ by nikdy neměl být pojmenován ‚ContactFormProcessor‘ protože není jasné, o co jde.
Edit 1:
Psát C- prefix na začátku každé třídy ztratilo význam proto, že pokud
máme:
C- třídy
I- rozhraní
T- traity
Tak každý, kdo kdy začne přemýšlet nad touto problematikou zjistí, že
pokud máme pro každý typ nějaký prefix, tak máme striktně oddělené typy
jednotlivých souborů, což je velké plus pro orientaci a rychlý vývoj.
Taktéž ale zjistíme, že když máme v kódu například 60% tříd, 30%
rozhraní a 10% traity, tak to znamená, že na 100 souborech napíšeme 60*C-,
30*I- a 10*T-
A ten, kdo se nad tím zamyslí hlouběji zjistí, že když vypustíme
prefix C-, tak ušetříme na prefixech 60% prostoru a tedy i paměti a bla
bla. Přehlednost a roztřízení ale zůstane zachováno. Proč? Protože nám
vznikne toto:
''- třídy
I- rozhraní
T- traity
takže třídy POŘÁD mají svůj rozlišující prefix.
Akorát je prázdný. Což jsme ušetřili znaky ale vůbec
nijak jsme nesnížili informační hodnotu. Protože i vůbec nic je
informace.
Jendou jsem četl článek o tom, že internet váží stejně jako
průměrně velká jahoda. Počítali tam s tím, že každý aktivní spoj
(kladně nabitá částice, spojené drátky kterými proudí proud atd.) má
nějakou váhu navíc oproti svojí surové váze. Tedy kdyby byly všechny
přístroje vypnuté a vynulované, tak by podle tohoto článku internet
vážil přesnou 0. To je ale zavádějící informace, jelikož i to, že je
to vypnuté a jsou všude pomyslné nuly je taky informace, stejně jako
prázdný prefix před názvem tříd.
Pokud ale přestaneme přemýšlet a řekneme si no tak když to udělali
s třídami tak proč bychom to nemohli udělat my s interface a traitami, tak
se chováme stejně jako opice v tomto
výzkumu.
Prostě tím, že se odstranilo například C- jako prefix od tříd se
zachovala stále stejná výpovědní hodnota. Co má prázdný prefix (nemá
tam I, T ani C) je třída. Pokud ale odstraníme další díky opičímu
syndromu, tak se najednou stane, že 2 různé typy souborů
mají prefix stejný a tedy je již nelze od sebe odlišit. Je to to stejné
jako bychom se rozhodli dávat třídám (class) prefix I-, čímž by byl
prefix stejný jako u rozhraní. U tohoto si říkáte, že to je nesmysl a
proč to dělat? No odstranit prefix I- od rozhraní je to stejné.
Editoval Polki (10. 1. 2021 15:32)
- David Grudl
- Nette Core | 8227
@Polki jak zjistíš, jakého typu je
$this->visibility
? Podíváš se do deklarace property?
Proč je to takový problém v případě Visibility
?
- David Grudl
- Nette Core | 8227
Jazyk Dart má jednu featuru, kterou strašně chci dostat do PHP. Možnost implementovat tzv. implicitní rozhraní.
O co jde. Když vytvořím třídu, tak tím vytvořím:
- rozhraní (název + veřejné metody bez těl)
- implementaci (těla metod + privátní věci)
Obojí sdílí jeden název.
Problém je v tom, že implicitní rozhraní nejde implementovat. Tj nejde napsat:
class MockPDO implements PDO
{
...
}
Aby to bylo možné, to bych právě moc rád viděl v PHP. Řeší to elegantně pár věcí, jako třeba právě testování. (Btw není to tak snadné, muselo by se vyřešit co s properites a protected metodama.)
Ale zároveň to zcela pohřbívá argumenty kolem psaní prefixů/suffixů pro rozhraní.
- David Matějka
- Moderator | 6445
No když vidím ContactFormProcessor, tak je hned jasné, o co jde. Budu tam očekávat objekt, který poskytne rozhraní pro zpracování kontaktního formuláře. A je mi celkem jedno, jestli ten typ odkazuje na interface nebo na třídu.
- Polki
- Člen | 553
David Grudl napsal(a):
@Polki jak zjistíš, jakého typu je
$this->visibility
? Podíváš se do deklarace property?
To záleží na kontextu.
- Je to řetězec obsahující nějaký význam (například z CSS ‚visible‘, ‚hidden‘, ‚none‘) atd…
- Je to instance třídy, která reprezentuje jednotlivé stavy viditelnosti, které mohou být například reprezentovány v databázi jako číselník. V tomto případě by však měla být visibility vypsatelná a tedy by se s ní mělo dát pracovat jako s řetězcem, přičemž o to, aby se nastavila na správný objekt by se měl starat nějaký Setter a o to aby se získala správná textová hodnota by se měl starat nějaký Getter.
Pokud by tam měl být uložený bool, který definuje jestli je to
viditelné nebo ne, pojmenování by bylo například $visible
,
pokud by tam měla být výhradně třída, tak například $visibilityType, kde
v coding standardu je, že typy jsou vždy číselník, kde každý typ je
reprezentován instancí třídy. Atd..
Tedy pokud je nastaven dobře CodingStandard, tak je to zřejmé vždy hned:
V $this->visibility
je uložena instance třídy
Visibility
jež vypadá nějak takto:
/**
* @property string $name Name of concrete visibility type
*/
class Visibility
{
use \Nette\SmartObject;
private string $name = '';
public function getName(): string
{
return $this->name;
}
public function setName(string $val): void
{
$this->name = $val;
}
}
\--
Tady je to hned jasné. Například:
Visibility
může nabývat několika konkrétních hodnot takže
číselník → třída. Na každý druh visibility je potom
jiná třída. Například UserVisibility
,
ProductVisibility
atd…
Name
je obecný název, který se může skládat ze
série znaků → řetězec.
S takovou jmennou konvencí a nastaveným standardem jasně každý, kdo se
podívá do kódu vidí, že $this->visibility
je instance
třídy Visibility
a třeba $this->userVisibility
je instance třídy UserVisibility
, přičemž naproti tomu
v proměnné $this->name
je uložen řetězec, tedy string.
Pokud by však byla nějaká jména, která je nutno vyjmenovat a tedy
udělat v z nich číselník, jako například názvy všech českých měst,
pak by se proměnná jmenovala $this->cityName
, což by byla
podle standardu jasně instance třídy CityName
.
V realitě samozřejmě použijeme u měst spíše než číselník, tak celý
objekt kde bude u každého města název, počet obyvatel, velikost atd..,
takže by třída CityName
neexistovala a místo ní by byla
například třída City
, která by v sobě měla proměnnou
$name
, která by mohla zase nabývat všech možných hodnot,
protože víme, že počet měst už omezuje sama tabulka. (Takový
rozšířený číselník.)
Toto je jen příklad.
Tedy jak jsem psal hned na začátku. Je třeba znát kontext a hlavně standard, ve kterém pracujeme.
Ale pokud nazvu tyto dvě třídy: CFormFactory
a
‚IFormFactory‘ stejně tedy prostě FormFactory
tak mi žádná
znalost kontextu ani CodingStandardu nepomůže zjistit, co se vlastně reálně
za kterým souborem skrývá.
Editoval Polki (10. 1. 2021 16:27)
- David Grudl
- Nette Core | 8227
Před chvilkou jsi mi tady dával $this->city
a
$this->surname
jako jasné příklady řetězců (nebo to tak
aspoň vyznělo), teď je to „jasně instance třídy CityName“.
Nechme toho.
- Polki
- Člen | 553
David Grudl napsal(a):
Jazyk Dart má jednu featuru, kterou strašně chci dostat do PHP. Možnost implementovat tzv. implicitní rozhraní.
O co jde. Když vytvořím třídu, tak tím vytvořím:
- rozhraní (název + veřejné metody bez těl)
- implementaci (těla metod + privátní věci)
Obojí sdílí jeden název.
Problém je v tom, že implicitní rozhraní nejde implementovat. Tj nejde napsat:
class MockPDO implements PDO { ... }
Aby to bylo možné, to bych právě moc rád viděl v PHP. Řeší to elegantně pár věcí, jako třeba právě testování. (Btw není to tak snadné, muselo by se vyřešit co s properites a protected metodama.)
Ale zároveň to zcela pohřbívá argumenty kolem psaní prefixů/suffixů pro rozhraní.
To je super myšlenka. Pokud by se tohoto dosáhlo, tak by to byla paráda. Ovšem to by se pak Interface a Class staly jedním, což by mělo potom smysl sjednotit i názvosloví. Dokud se tak ale nestalo, tak nevidím důvod sjednocovat názvy pro dvě zatím různé věci.
Edit 1:
BTW příklad na implicitní interface v Dartu: https://www.codevscolor.com/…it-interface
Jak by v tomto případě DI rozlišilo, jestli má do třídy
s konstruktorem:
public __construct(private Vehicle $vehicle,) {}
Předat instanci třídy Vehicle
nebo instanci třídy
Audi
?
Případně jak by poznalo DI, že se jedná o generovanou továrničku, pokud by to nebylo interface? Stačilo by mít jako obsah jen jednu abstraktní metodu ‚create()‘?
Editoval Polki (10. 1. 2021 20:06)
- Polki
- Člen | 553
David Matějka napsal(a):
No když vidím ContactFormProcessor, tak je hned jasné, o co jde. Budu tam očekávat objekt, který poskytne rozhraní pro zpracování kontaktního formuláře. A je mi celkem jedno, jestli ten typ odkazuje na interface nebo na třídu.
Ok tak konkrétně v případě, že se například firma rozhodne použít úplně jinou databázi. Například z MySql, kde je v aplikaci použito Nextras Orm na ElasticSearch a napsat si vlastní driver například, tak pokud nebudu v aplikaci používat rozhraní, tak jsem namidlený, jelikož nemám odstíněnou vrstvu pro práci s databází od aplikace a musím přepisovat kódy v celé aplikaci. Když to navrhnu správně a DB driver odstíním od aplikace, tak potom můžu udělat kouzla jako:
config.neon:
search:
model:
in: %appDir%/MySqlModel
změnit na
search:
model:
in: %appDir%/ElasticSearchModel
A najednou nemusím měnit nikde nic aplikace bude fungovat stejně jako
před změnou DB, akorát se budou data ukládat do jiné databáze.
Problém nastane ve chvíli, kdy nevím, co je v aplikaci rozhraní a co
je třída.
V takové chvíli, když něco opomenu, tak na mě vyskočí hláška
podobná: Service 'application.X' (type of App\*):Service of type MerchantRepository needed by $merchantRepository in __construct() not found. Did you add it to configuration file?
A já si řeknu, že to není možné, že jsem třídu MerchantRepository přece implementoval a načetl pomocí DI tak jakto, že ji nezná? Prohledám svůj kód, zjistím, že tam je, pak vyzkouším jiný zápis v DI, zjistím, že tam je taky. Tak se pak kouknu znova a zjistím, že je trochu jiný namespace takže to asi bude úplně jiná třída. Jaká? Jak z toho mám na první pohled poznat, co ta aplikace po mě chce? Musím rozkliknout danou třídu, podívat se na její předpis a zjistit o co jde (že je to interface) a bla bla.
Pokud bych ovšem použil prefix I- a odlišil tak rozhraní, tak by to bylo
jednoduché. Tracy by napsala něco jako:
Service 'application.X' (type of App\*):Service of type IMerchantRepository needed by $merchantRepository in __construct() not found. Did you add it to configuration file?
a já aniž bych musel otevírat kód tak se plácnu do hlavy a zvolám ahá
otevřu si třídu App\ElasticSearchModel\MerchantRepository
a
přidám tam implements IMerchantRepository
a nemusím nic dalšího řešit. Odebrání I- a tedy nevědomost jestli jde o Interface nebo o Class programátorům přidá práci navíc a to úplně zbytečnou.
Editoval Polki (10. 1. 2021 17:03)