Moda v pojmenování nebo nešvar?

Petr Parolek
Člen | 455
+
+3
-

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
+
+4
-

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 | 1171
+
-2
-

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
+
+1
-

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
+
+3
-

Mabar napsal(a):

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í.

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 | 8136
+
+13
-

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 | 8136
+
0
-

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 | 1189
+
+2
-

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
+
+6
-

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
+
+2
-

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 | 762
+
+3
-

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
+
+1
-

@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
+
+10
-

@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 | 1171
+
0
-

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 | 1171
+
+3
-

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
+
+1
-

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řeba MyTranslator
  • 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 | 8136
+
0
-

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 | 762
+
+1
-

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 | 762
+
+1
-

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 | 2635
+
+1
-

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 | 8136
+
+1
-

User in UserStorage is possessive, not adjective. It is User's Storage, ie Storage for User.

Šaman
Člen | 2635
+
+4
-

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 | 8136
+
0
-

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
+
+5
-

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, nebo throw, 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 prefix I 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, nebo T? 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
+
0
-

V aplikaci mám svoji implementaci translátoru pojmenovanou zase Translator.

namespace App;

use Nette\Localization;

final class Translator implements Localization\Translator
{}
Milo
Nette Core | 1283
+
+4
-

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
+
+1
-

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
+
+1
-

Milo napsal(a):
To opravdu někdo bude ručně procházet adresář po adresáři a hledat soubory začínající na I, nebo T? 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.

Milo
Nette Core | 1283
+
0
-

Jak to na první pohled vidíš? Jako že si necháš vylistovat všech X set souborů?

Neexistuje argument, který tvrdí, že název souboru je spolehlivější než grep.

Milo
Nette Core | 1283
+
0
-

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?

David Grudl
Nette Core | 8136
+
+1
-

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
+
0
-

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) ;)

David Grudl
Nette Core | 8136
+
0
-

Zkus search milý Lojzo.

Myslím, že už jsme sklouzli hodně mimo.

Petr Parolek
Člen | 455
+
-2
-

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
+
0
-

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
+
+1
-

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 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…

(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 | 8136
+
+3
-

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 | 8136
+
0
-

@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
+
0
-

@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 | 8136
+
+2
-

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 | 8136
+
+2
-

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 | 8136
+
+4
-

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
+
0
-

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 | 8136
+
0
-

@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 | 8136
+
+1
-

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
+
+2
-

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
+
0
-

David Grudl napsal(a):

@Polki jak zjistíš, jakého typu je $this->visibility? Podíváš se do deklarace property?

To záleží na kontextu.

  1. Je to řetězec obsahující nějaký význam (například z CSS ‚visible‘, ‚hidden‘, ‚none‘) atd…
  2. 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 | 8136
+
+4
-

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
+
0
-

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
+
0
-

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)