Organizace kódu, opakování stejných věcí, Optimalizace

mimacala
Člen | 113
+
0
-

Ahojte,
prosím ti z vás co mají zkušenosti z vývoje nějakých větších věcí, jak řešíte časté opakování napříklád podmínek if else.
Využíváte nějaký zkrácený zápis if else nebo vytvoříte si funkci kde vstupem jsou 2 hlášky a stav true false ?

Moc děkuji

 public function Apiprovozova(Form $form, $data): void // dokončení formu na nastavení api provozoven
    {
        $nastav = $this->uzivatel->DataUzivatel($this->ID())->update(["apiprovozovna" => "$data->provozovny"]);
        if($nastav)
        {
            $this->flashMessage("Provozovna pro API byla nastavena","success");
            $this->redirect("this");
        } else {
            $this->flashMessage("Někde se stala chyba","error");
            $this->redirect("this");
        }
    }

Editoval mimacala (12. 12. 2022 16:37)

Marek Bartoš
Nette Blogger | 1146
+
+4
-

DRY je nejméně důležitý princip ze všech. Nejdřív kód piš pro konkrétní use-case. Až uvidíš obecný vzor, který se ti opakuje a dá se generalizovat do metody, ve které nebudeš přliš podmínkovat konkrétní případy, vyčleň ho. Pro dva statements je to ale imho úplně zbytečné.

Podmínky s jedním statementem se dají zkrátit využitím ternary nebo null-safe operátorů (podle use-case). O tohle se ti ale může postarat automatizovaný coding standard, třeba orisai/coding-standard

Tvůj example by se dal zapsat takto. Redirect můžeš vyjmout z podmínky, je v obou podmínkách stejný. Z ifu o jednom statementu pak můžeš udělat ternary expression. Pokud se $nastav vyhodnotí jako truthy value (hodnota co se při typecastingu na bool vyhodnotí jako true, viz https://www.php.net/…parisons.php) tak se zavolá první větev, jinak druhá

 public function Apiprovozova(Form $form, $data): void // dokončení formu na nastavení api provozoven
 {
    $nastav = $this->uzivatel->DataUzivatel($this->ID())->update(["apiprovozovna" => "$data->provozovny"]);
	$nastav
		? $this->flashMessage("Provozovna pro API byla nastavena","success")
        : $this->flashMessage("Někde se stala chyba","error");
	$this->redirect("this");
}

Editoval Marek Bartoš (12. 12. 2022 18:02)

David Matějka
Moderator | 6445
+
+1
-

poznamka k tomuhle konkretnimu prikladu: proc a za jakych podminek ta funkce vraci false?

mystik
Člen | 284
+
+2
-

Senzkracovanim a odstravovanim podobneho kodu se to nesmi prehanet. Imho je takovahle podminka dobre citelna a udelani nejake fce kterou budes volat podobne dlouhym zapisem je silne kontraproduktivni. Citelnost je dulezitejsi nez kratky zapis nebo DRY.

Je dulezite si taky davat pozor na nahodnou podobnost. To ze v nekolika tridach mas takovouhle podobnou podminku neznamena ze ta logika je koncepcne stejna a tim padem vzdycky bude. V tech vetvich muzes casem chtit delat v kazde tride neco jineho navic. U nahodne podobnosti bys nikdy nemel vytvaret spolecnou abstrakci protoze tim se udrzitelnost kodu zhorsi. Spolecnou abstrakci delat tam kde jde o stejny koncept a ses si dostatecne jisty ze se to vzdycky na vsech mistech bude resit stejne.

Pokud se ale bavime o konkretnim pripade tak bych sel podobnou cestou jako Marek Bartos. Jen ja bych dal jen jedno volani flashMessage a ternarni operator dal jen na text hlasky v jeho parametru.

Kamil Valenta
Člen | 752
+
0
-

mystik napsal(a):

[…] Jen ja bych dal jen jedno volani flashMessage a ternarni operator dal jen na text hlasky v jeho parametru.

A success vs. danger? :)
Často může zůstat i redirect ve větvích podmínky, protože success povede někam zpět a danger na this.

Bulldog
Člen | 110
+
0
-

Mě třeba přijde dry princip jako jeden z nejdůležitějších, ale kdo ví, je to asi o preferenci.
Mě se nelíbí, že pokud bych ten dry princip nepoužil, tak při sebemenší změně budu kód přepisovat na hromadě míst.
Na pár projektech, co jsem pracoval se dry princip nedodržoval tak striktně jak by měl a výsledek byl, že když se měla trochu pozměnit funkčnost, tak musel člověk skrz celý kód hledat, kde je daná věc použitá a tam ji změnit, což znamenalo, že se často na nějaký výskyt zapomnělo apod., což mělo za následek, že někde daná věc fungovala a jinde byla zastaralá. A to nemluvím o tom, že pak při sebemenší změně se stávalo, že změna na jednom místě rozbila kód na úplně jiném zdánlivě nesouvisejícím místě, protože to jiné místo očekává nový způsob a někde se prostě předává starý, takže kód padá.
Tím že nebyl dodržen DRY princip se pak muselo dělat kdo ví kolik iterací oprav, aby kód konečně všude normálně fungoval. Jakože je hezké, že díky tomu, že se nedodržoval DRY princip byl celý kód o 24 tisících řádcích napsán za 140 hodin jedním člověkem, ale to, že pak sebemenší změna trvá 3 hodiny, než se všemi 24k řádky prokouše člověk, aby zjistil, jestli není někde duplikát, akorát jinak nazvaný třeba je peklo.

A jak zmínil mystik, že důležitější je přehlednost, tak souhlasím, ovšem ono to dost často bývá totéž, jelikož DRY sníží počet řádků, což zvyšuje přehlednost.

A kód bych osobně upravil asi takto:

public function Apiprovozovna(Form $form, $data): void // dokončení formu na nastavení api provozoven
{
	$nastav = $this->uzivatel->DataUzivatel($this->ID())->update(["apiprovozovna" => "$data->provozovny"]);

	$message = "Někde se stala chyba";
	$type = "error";

	if ($nastav) {
		$message = "Provozovna pro API byla nastavena";
		$type = "success";
	}

	$this->flashMessage(message: $message, type: $type);
	$this->redirect("this");
}

Samozřejmě místo ifu se dá použít ternár, ale byly by tam 2, takže je lepší IF.

Já osobně ale i v případě, že dělám jednojazyčný web, tak si implemetnuju translator, protože pokud by se někdy v budoucnu stalo, že má mít web více jazyků, tak není třeba nic předělávat, jen se přidají překlady, což je zase princip rozšířitelnosti, kdy kód by se měl psát tak, aby se v případě potřeby neměnil, ale jen přidával nový. To mi pak pomůže kód ještě trochu zpřehlednit a udělat jej univerzálnější a nezávislý na hláškách, které se budou měnit externě:

public function Apiprovozovna(Form $form, $data): void // dokončení formu na nastavení api provozoven
{
	$nastav = $this->uzivatel->DataUzivatel($this->ID())->update(["apiprovozovna" => "$data->provozovny"]);

	$type = $nastav ? "success" : "error";

	$this->flashMessage(message: "api.provozovna.flash." . $type, type: $type);
	$this->redirect("this");
}

Kde v klíči api.provozovna.flash.success je uloženo Provozovna pro API byla nastavena a v api.provozovna.flash.error je Někde se stala chyba

mystik
Člen | 284
+
+3
-

@KamilValenta Priznavam ze rozdilu v success/danger sem si nevsiml. V tom pripade bych necahl if a jen vytahl redirect az za nej. A nebo Markova verze i kdyz osobne ternary pouzivam jen ve vyrazech co vraci hodnotu, protoze if mi prijde citelnejsi.

mystik
Člen | 284
+
+10
-

@Bulldog DRY se tyka dvou veci. Opakovane vyjadreni aplikacni logiky a opakovany podobny boilerplate kod. U aplikacni logiky je DRY hodne dulezity presne z duvodu co jsi psal. U boilerplate (coz je presne priklad co tu mame) tak dulezity neni.

Pro prehlednost je casto lepsi mit vic radku (s jednoduchymi if) ze kterych je neco dobre videt nez par radku s nejakym „chytrym“ kodem (s tremi urovneni zanorenych ternaru). Takze pozor na to ze strucnost a citelnost nekdy mohou jit proti sobe.

Šaman
Člen | 2632
+
+3
-

@mimacala Používám to přesně tak, jak jsi to napsal v pilotním příspěvku. Jestli vyčlenit redirect až za podmínku asi není tak důležité, ale občas třeba při chybě jen překreslím formulář ajaxem a při úspěchu přesměruji, takže by teoreticky ten redirect nemusel být v obou větvích, případně nebude stejný (při chybě přesměruje na this, při úspěšném přidání položky přesměruje na nějaký list).

V modelu je situace jiná, tam je potřeba věnovat analýze a architektuře mnoho pozornosti, ale v presenterech je lepší nedělat žádné složitosti a napsat to na první pohled čitelně a jednoduše.

Bulldog
Člen | 110
+
0
-

@mystik A není to tak, že často právě dochází k tomu, že boilerplate je spojen s aplikační logikou? Tzn., že když dodržuješ správné postupy, tak aby jsi propojil správné části pospolu (aplikační logika), tak musíš psát spousty toho stejného kódu, jen s různými niancemi (bioler plate)?

A hlavně pokud vím, tak se vymýšlejí všelijaké způsoby, jak tomu boilerplate kódu předejít a nebo jej aspoň právě zestručnit, aby toho opakujícího se kódu bylo co nejméně.

Můžeme si uvést hned 2 příklady, co mě tak rychle napadly:

  1. Továrny. Když v aplikaci použijeme návrhový vzor factory, abychom ji měli čistější, testovatelnější atp., tak nám vyvstane to, že kolik máme tříd, z nichž chceme manuálně tvořit objekty, tolik máme továren a v 99.99% případů (v životě jsem napsal přesně 2 továrny, co uměly něco více, než předání závislostí a tupé vytvoření třídy – pokud nejde o továrny tvořící formuláře) ty továrny vypadají stejně, jen se mění parametry create funkce a také závislosti, co potřebuje továrna v konstruktoru, aby je mohla předat do nové instance. Takže když tento boilerplate můžeme vyhodit pryč, tak proč to neudělat a nezcvrknout tu továrnu do jednoduchého 3 řádkového interface i za cenu přidání magie, která není hned srozumitelná a nováčci s tím mají problém podobně jako s pointrama v C?
  2. Webové aplikace jsou z velké části tvořeny prezentací dat. Tedy výpisem dat z databáze uživateli v nějakém gridu se stránkováním, filtrem atp. Takové gridy vypadají pořád stejně. Je zde Control, která bere na vstup obecnou třídu, která umí s daty pracovat, je zde továrna na tuto Control a další podobné srandy. Taktéž je uvnitř této Control nějaká logika, která se stará o stránkování a případně filtr, který si tahá formulář přes další factory. No a co se stane, když máme například 10 různých výpisů? (např. seznam klientů, seznam aut, seznam kuponů atp.) No máme tuto control 10× rozkopírovanou napříč aplikací, což je zase boilerplate. A aby tento boilerplate nebyl, tak se to celé zabstraktní a hle, máme tu tuny DataGridů. Výhoda je v tom, že když se například přejmenuje metoda, co získává data, nebo se konstanty přesunou nově v PHP8.1 do ENUMŮ místo konstant ze třídy, tak nemusím upravovat 10 tříd, ale jen 1 a to ten DatagGrid. A tohle jsou zrovna změny, které nemají s aplikační logikou co dělat, ale podléhají boilerplate kódu a když v nějakém Control na to zapomeneš, tak se ti aplikace rozsype.

Takže jak vidíš můj příspěvek se netýkal jen aplikační logiky, ale i právě boilerplate kódů.

Samozřejmě způsobů, jak se boilerplate kodu zbavit je požehnaně a určitě budou i lidi, kteří si řeknou OK, když redirect a flashmessages nastavuju vždy jen v Presenteru, tak si v předkovi udělám metodu, která bude příjmat jen ty věci, které se opakují a zbytek bude dělat ona, protože je to vždy stejný:

public function Apiprovozovna(Form $form, $data): void // dokončení formu na nastavení api provozoven
{
	$nastav = $this->uzivatel->DataUzivatel($this->ID())->update(["apiprovozovna" => "$data->provozovny"]);

	[
		$type,
		$destination,
	] = $nastav ? ["success", "this"] : ["error", "error"];

	$this->flashAndRedirect(
		messagePrefix: "api.provozovna.flash.",
		type: $type,
		destination: $destination,
	);
}

Dokonce se dá , pokud aplikaci přizpůsobíme, nastavit aby v abstraktním presenteru bylo nějaké konstantní pole, které by bylo namapované na chybové kódy a pak by se to ještě zjednodušilo:

public function Apiprovozovna(Form $form, $data): void // dokončení formu na nastavení api provozoven
{
	$nastav = $this->uzivatel->DataUzivatel($this->ID())->update(["apiprovozovna" => "$data->provozovny"]);

	$this->flashAndRedirect(
		messagePrefix: "api.provozovna.flash.",
		errorCode: $nastav,
	);
}

a abstraktní presenter:

abstract class Presenter extends Nette\Application\UI\Presenter
{
	private const
		DEFAULT_ERROR_CODE = 2,
		ERROR_TYPES = [
			0 => "success",
			1 => "warning",
			2 => "error",
		],
		REDIRECT_DESTINATIONS = [
			0 => "this",
			1 => "this",
			2 => "error",
		];

	protected function flashAndRedirect(string $messagePrefix, int $errorCode): void
	{
		$type           = self::ERROR_TYPES[$errorCode] ?? self::ERROR_TYPES[self::DEFAULT_ERROR_CODE];
		$destination    = self::REDIRECT_DESTINATIONS[$errorCode] ?? self::REDIRECT_DESTINATIONS[self::DEFAULT_ERROR_CODE];
		$this->flashMessage(message: $messagePrefix . $type, type: $type);
		$this->redirect($destination);
	}
}

Nebo můžeš mít obsahem if/switch/jinou srandu. Ale myslím, že to, jak moc to abstraktizujeme a jak moc velcí fanatici v dodržování daných principů jsme záleží na osobních preferencích a taky na zkušenostech, jak často pracujeme i s cizími kódy a jak často jsme si nabili ciferník, abychom poznali, co se nám oplatí a co se neoplatí dodržovat.

Jasně, pokud si budu psát SW sám pro sebe na koleni jen pro zábavu, nebo tak a vím, že to nikdo jiný nikdy neuvidí a já se v tom vyznám, tak pak nemá smysl nad tím trávit spousty času a dodržovat tuny principů. Pokud ale chci tvořit aplikace, které po mě někdo převezme, má se v tom taky vyznat, budeme to rozšiřovat apod., tak by se měly nějaké pravidla dodržovat. Jaká a proč to už je na týmu jak si to nastaví. Však i v mém posledním přehnaném příkladu je nutné dodržovat, že každý Presenter, bude mít existující akci error, která se zavolá v případě chyby a o tom musí všichni programátoři, co to budou upravovat vědět, tedy i oni musí dodržovat určité principy, které si zvolili, aby se jim aplikace psala snadno. A od toho, aby byly veškeré aplikace přenositelné jsou právě návrhové vzory, programátorské principy a u PHP PSR standardy, které se striktně dodržují všude, kromě ČR, protože to tady David bojkotuje. :D

DefenestrationPraha
Člen | 99
+
+1
-

Bulldog napsal(a):

@mystik A není to tak, že často právě dochází k tomu, že boilerplate je spojen s aplikační logikou? Tzn., že když dodržuješ správné postupy, tak aby jsi propojil správné části pospolu (aplikační logika), tak musíš psát spousty toho stejného kódu, jen s různými niancemi (bioler plate)?

A hlavně pokud vím, tak se vymýšlejí všelijaké způsoby, jak tomu boilerplate kódu předejít a nebo jej aspoň právě zestručnit, aby toho opakujícího se kódu bylo co nejméně.

Můžeme si uvést hned 2 příklady, co mě tak rychle napadly:

  1. Továrny. Když v aplikaci použijeme návrhový vzor factory, abychom ji měli čistější, testovatelnější atp., tak nám vyvstane to, že kolik máme tříd, z nichž chceme manuálně tvořit objekty, tolik máme továren a v 99.99% případů (v životě jsem napsal přesně 2 továrny, co uměly něco více, než předání závislostí a tupé vytvoření třídy – pokud nejde o továrny tvořící formuláře) ty továrny vypadají stejně, jen se mění parametry create funkce a také závislosti, co potřebuje továrna v konstruktoru, aby je mohla předat do nové instance. Takže když tento boilerplate můžeme vyhodit pryč, tak proč to neudělat a nezcvrknout tu továrnu do jednoduchého 3 řádkového interface i za cenu přidání magie, která není hned srozumitelná a nováčci s tím mají problém podobně jako s pointrama v C?
  2. Webové aplikace jsou z velké části tvořeny prezentací dat. Tedy výpisem dat z databáze uživateli v nějakém gridu se stránkováním, filtrem atp. Takové gridy vypadají pořád stejně. Je zde Control, která bere na vstup obecnou třídu, která umí s daty pracovat, je zde továrna na tuto Control a další podobné srandy. Taktéž je uvnitř této Control nějaká logika, která se stará o stránkování a případně filtr, který si tahá formulář přes další factory. No a co se stane, když máme například 10 různých výpisů? (např. seznam klientů, seznam aut, seznam kuponů atp.) No máme tuto control 10× rozkopírovanou napříč aplikací, což je zase boilerplate. A aby tento boilerplate nebyl, tak se to celé zabstraktní a hle, máme tu tuny DataGridů. Výhoda je v tom, že když se například přejmenuje metoda, co získává data, nebo se konstanty přesunou nově v PHP8.1 do ENUMŮ místo konstant ze třídy, tak nemusím upravovat 10 tříd, ale jen 1 a to ten DatagGrid. A tohle jsou zrovna změny, které nemají s aplikační logikou co dělat, ale podléhají boilerplate kódu a když v nějakém Control na to zapomeneš, tak se ti aplikace rozsype.

Takže jak vidíš můj příspěvek se netýkal jen aplikační logiky, ale i právě boilerplate kódů.

Samozřejmě způsobů, jak se boilerplate kodu zbavit je požehnaně a určitě budou i lidi, kteří si řeknou OK, když redirect a flashmessages nastavuju vždy jen v Presenteru, tak si v předkovi udělám metodu, která bude příjmat jen ty věci, které se opakují a zbytek bude dělat ona, protože je to vždy stejný:

public function Apiprovozovna(Form $form, $data): void // dokončení formu na nastavení api provozoven
{
	$nastav = $this->uzivatel->DataUzivatel($this->ID())->update(["apiprovozovna" => "$data->provozovny"]);

	[
		$type,
		$destination,
	] = $nastav ? ["success", "this"] : ["error", "error"];

	$this->flashAndRedirect(
		messagePrefix: "api.provozovna.flash.",
		type: $type,
		destination: $destination,
	);
}

Dokonce se dá , pokud aplikaci přizpůsobíme, nastavit aby v abstraktním presenteru bylo nějaké konstantní pole, které by bylo namapované na chybové kódy a pak by se to ještě zjednodušilo:

public function Apiprovozovna(Form $form, $data): void // dokončení formu na nastavení api provozoven
{
	$nastav = $this->uzivatel->DataUzivatel($this->ID())->update(["apiprovozovna" => "$data->provozovny"]);

	$this->flashAndRedirect(
		messagePrefix: "api.provozovna.flash.",
		errorCode: $nastav,
	);
}

a abstraktní presenter:

abstract class Presenter extends Nette\Application\UI\Presenter
{
	private const
		DEFAULT_ERROR_CODE = 2,
		ERROR_TYPES = [
			0 => "success",
			1 => "warning",
			2 => "error",
		],
		REDIRECT_DESTINATIONS = [
			0 => "this",
			1 => "this",
			2 => "error",
		];

	protected function flashAndRedirect(string $messagePrefix, int $errorCode): void
	{
		$type           = self::ERROR_TYPES[$errorCode] ?? self::ERROR_TYPES[self::DEFAULT_ERROR_CODE];
		$destination    = self::REDIRECT_DESTINATIONS[$errorCode] ?? self::REDIRECT_DESTINATIONS[self::DEFAULT_ERROR_CODE];
		$this->flashMessage(message: $messagePrefix . $type, type: $type);
		$this->redirect($destination);
	}
}

Nebo můžeš mít obsahem if/switch/jinou srandu. Ale myslím, že to, jak moc to abstraktizujeme a jak moc velcí fanatici v dodržování daných principů jsme záleží na osobních preferencích a taky na zkušenostech, jak často pracujeme i s cizími kódy a jak často jsme si nabili ciferník, abychom poznali, co se nám oplatí a co se neoplatí dodržovat.

Jasně, pokud si budu psát SW sám pro sebe na koleni jen pro zábavu, nebo tak a vím, že to nikdo jiný nikdy neuvidí a já se v tom vyznám, tak pak nemá smysl nad tím trávit spousty času a dodržovat tuny principů. Pokud ale chci tvořit aplikace, které po mě někdo převezme, má se v tom taky vyznat, budeme to rozšiřovat apod., tak by se měly nějaké pravidla dodržovat. Jaká a proč to už je na týmu jak si to nastaví. Však i v mém posledním přehnaném příkladu je nutné dodržovat, že každý Presenter, bude mít existující akci error, která se zavolá v případě chyby a o tom musí všichni programátoři, co to budou upravovat vědět, tedy i oni musí dodržovat určité principy, které si zvolili, aby se jim aplikace psala snadno. A od toho, aby byly veškeré aplikace přenositelné jsou právě návrhové vzory, programátorské principy a u PHP PSR standardy, které se striktně dodržují všude, kromě ČR, protože to tady David bojkotuje. :D

Musím říci, že ten abstraktní prezentér by se mi hrozně blbě četl. „Maintainability“ záleží i na čitelnosti a věci jako dlouhá konstatní pole indexovaná integery tomu moc nepomáhají.

Pavel Kravčík
Člen | 1180
+
+2
-

KISS > DRY until possible, neexistuje samospasné řešení, vždy záleží na velikosti týmu, konkrétním projektu a zkušenostech. Pokud to dokáže snadno přečíst junior a opravit, tak je to lepší, než „DRY dlouhé pole“. Po osmi letech jsme se vrátili k refactoringu velké aplikace (psané jedním člověkem, který v tom měl svůj systém). Nesnáším se všechny za všechny podobné „DRY věci“, které radil @Bulldog. Na druhou stranu dříve měli opodstatnění a ušetřili hodně času.

Bulldog
Člen | 110
+
0
-

DefenestrationPraha napsal(a):

Musím říci, že ten abstraktní prezentér by se mi hrozně blbě četl. „Maintainability“ záleží i na čitelnosti a věci jako dlouhá konstatní pole indexovaná integery tomu moc nepomáhají.

Souhlasím s tím, že to není pěkné, ale je to hlavně udržitelné, jelikož v případě přidání nové ‚podmínky‘ se pouze přidá řádek do pole a nemusíš hledat, kde všude si to pole použil.

Jde o to, že když to začneš používat a sžiješ se s tím, tak to budeš okamžitě chápat hned jak přijdeš do kódu. Ale uznávám, že ten přechod trochu skřípe.

Nicméně je to právě jeden z dalších přístupů a doporučení, jak psát udržitelný a čitelnější kód a ve světě se to tak používá běžně. Vlastně nejde o nic jiného, než slovník. A spousta jazyků na to má přímo předpřipravené třídy, které ten slovník nějakým způsobem simulují, například JAVA.
Příklad v Pythonu: https://www.youtube.com/…/UFdEp9wrtOY

První s čím člověk začne je redukování Else statementů pomocí GURAD CLAUSE a pak když už vidíš, že ti zůstane série gurad clausulí s porovnáváním oproti konstantám (pomocí === ne větší/menší), nebo switch, který překládá konstanty na jiné konstanty (uff), tak udržitelnost takového ifu je dost sebezabijácká, protože musíš manipulovat s těmi ify/case přidávat je, odebírat atp. No za chvíli se v tom nevyznáš.

Co si o tom myslí ChatGPT? (Přikládám mnou položený dotaz v textové podobě, aby sis to mohl taky vyzkoušet – pokaždé dá trochu jinou odpověď tak si to zkus :), a taky, protože na obrázku by to bylo blbě vidět)
Jaký je rozdíl mezi těmito kódy?
Tímto:
---

const
    ALL_OK = 0,
    TOO_MANY_ARGUMENTS = 1,
    MISSING_ARGUMENT = 2,
    ZERO_DIVISIBILITY = 3;

public function getErrorMessage(int $errorCode): string
{
    switch ($errorCode) {
        case self::ALL_OK:
            $retVal = 'Everything is OK';
            break;
        case self::TOO_MANY_ARGUMENTS:
            $retVal = 'You have provided too many arguments';
            break;
        case self::MISSING_ARGUMENT:
            $retVal = 'One or more arguments are missing';
            break;
        case self::ZERO_DIVISIBILITY:
            $retVal = 'You divided by zero.. REALLY?!?!?';
            break;
        default:
            $retVal = 'Unexpected error';
            break;
    }

    return $retVal;
}

---
tímto:
---

const
	ALL_OK = 0,
	TOO_MANY_ARGUMENTS = 1,
	MISSING_ARGUMENT = 2,
	ZERO_DIVISIBILITY = 3;

public function getErrorMessage(int $errorCode): string
{
	if ($errorCode === self::ALL_OK) {
		return 'Everything is OK';
	}
	if ($errorCode === self::TOO_MANY_ARGUMENTS) {
		return 'You have provided too many arguments';
	}
	if ($errorCode === self::MISSING_ARGUMENT) {
		return 'One or more arguments are missing';
	}
	if ($errorCode === self::ZERO_DIVISIBILITY) {
		return 'You divided by zero.. REALLY?!?!?';
	}
	return 'Unexpected error';
}

---
a tímto kódem:
---

const
	ALL_OK = 0,
	TOO_MANY_ARGUMENTS = 1,
	MISSING_ARGUMENT = 2,
	ZERO_DIVISIBILITY = 3,

	ERROR_MESSAGES = [
		self::ALL_OK             => 'Everything is OK',
		self::TOO_MANY_ARGUMENTS => 'You have provided too many arguments',
		self::MISSING_ARGUMENT   => 'One or more arguments are missing',
		self::ZERO_DIVISIBILITY  => 'You divided by zero.. REALLY?!?!?',
	];

public function getErrorMessage(int $errorCode): string
{
	return self::ERROR_MESSAGES[$errorCode] ?? 'Unexpected error';
}

---
Odpověď:
První a druhý kód se liší v tom, jak se vybírají a vracejí chybová hlášení pro daný kód chyby. První kód používá strukturu switch, zatímco druhý používá řetězení podmínek if. Třetí kód používá pole konstant s hodnotami chybových hlášení. Tento způsob je nejefektivnější, protože se hodnoty chybových hlášení přiřazují jednou a pak se k nim může přistupovat přímo pomocí kódu chyby.

Kód pro ukázku přes ChatGPT jsem si půjčil ze článku od kámoše.

**Jak ale říkám, jde hlavně o preferenci v rámci projektu/firmy. Takže používat by to každý měl tak, aby jeho práce byla co nejefektivnější :)

Ono totiž čitelnost je dost subjektivní věc.**

dlouhá konstatní pole indexovaná integery tomu moc nepomáhají

A jak vidíš, tak na indexování toho konstantního pole si můžeš použít co chceš. Klidně si tam narvi konstanty, nebo řetězcové hlášky, pokud ti nevadí menší snížení výkonu. (Závisí na jazyku třeba u C/RUST/Java/C# atp. se ti o optimalizaci postará překladač, takže konstanty nebudou zpomalovat nijak.)

EDIT
@PavelKravčík S tím plně souhlasím. Otázkou je, jestli na těch nováčcích tak záleží, když je stejně budeš muset zaučit? Dovolím si citovat Davida z konverzace ohledně odstranění I prefixů z interfaců na blogu než ji smazal: ‚Nasrat na nováčky. To, že se v tom nevyznají mě nezajímá.‘

Ono totiž kdyby se to aspoň učilo na školách, tak by to bylo super, ale tam se člověk nenaučí nic a online kurzy tě to zase naučí, takže jako nováček se pak vyznáš všude, ale nikomu se za to nechce platit, tak jdou radši do školy, která je nepřipraví na nic a když se místo zaučení nováčků upravuje kód aby tomu rozuměli na úkor efektivnosti, tak se nemáme divit, že vznikají hovnokódy kam se podíváš, hromada bezpečnostních děr, tvroba nového kódu trvá dlouho a jak říkali v IT oddělení jedné nejmenované banky, že zvětšení fontu a barvy jednoho nadpisu je náročné a bude stát minimálně 200 tisíc korun a musí vznikat takovéto články

To pak člověk přemýšlí, jestli se na programování nevyprdne a nepůjde dělat radši prodavače do Lidlu, když místo edukace nováčků se jim přizpůsobuje kód, který pak stojí za prd a investují se miliony do přepisů…

Editoval Bulldog (14. 12. 2022 13:09)

mystik
Člen | 284
+
+6
-

@Bulldog Tak upřímně ty tvoje verze mi přijdou překomplikované, špatně čitelné a jako noční můra na udržování. Protože tady imho vytváříš nevhodnou abstrakci z náhodné podobnosti. A špatná abstrakce je mnohem horší než žádná. To, že tohle chování je právě teď aktuálně stejné v třídách v tomhle případě pravděpodobně neznamená, že takové bude vždy, ale jde jen o náhodu. A v okamžiku, kdy ti přijde první požadavek, že se to v jednom z tech presenterů má řešit trochu jinak tak máš problém.

Jedna z hlavních poučení, které jsem za svou praxi získal bylo, že než zavedeš abstrakci tak si to musíš dvakrát rozmyslet. Protože udržování kódu s nevhodnými abstrakcemi, které vznikly jen aby se ušetřilo pár řádků nebo jen znaků kódu je často noční můra.

Je důležité položit si otázku jak pravděpodobná je změna, které bude znamenat nutnost změnit to vždy ve všech výskytech přesně stejně. A jak pravděpodobná je změna, které to bude chtít nějak po svém jen na jednom nebo několika. Pokud není první možnost mnohem pravděpodobnější než druhá (a to v tomhle případě imho není) tak abstrakce jen komplikuje věci a přináší problémy.

Souhlas s @PavelKravčík, že KISS > DRY vě většině případů.

Pavel Kravčík
Člen | 1180
+
+1
-

@mystik: To je moc pěkné napsané! Nevhodná abstrakce je mnohem horší, než absentující abstrakce.

@Bulldog: Proto říkám, že hrozně záleží na té konkrétní situaci/firmě. Je nutné plnit cíle s prostředky, které jsou k dispozici. Většina zkušeností je mimo firmu často nepřenositelná. Pak už záleží na metrice hodnocení. Ideální stav je levná údržba a robustní kód pro rozšiřování.

mystik
Člen | 284
+
+1
-

@Bulldog Co se týče dodržování principů a vzorů tak souhlasím, že je důležité je dodržovat. Ale nesmí se dodržovat slepě a člověk si musí být vědom toho, že ve výsledku jdou vždycky různé principy proti sobě a nic se nesmí přehánět.

U programátorů bývá obvyklý proběh toho, když se naučí novou technologii/princip/návrhový vzor takový, že nejdřív ho nepoužívají, pak přijde fáze nadšení, kdy ho používají všude a postupně se dopracují do pragmatické fáze, kdy ho používají po dobrém zvážení.

Předčasné nebo přehnané zavádění abstrakcí u věcí, které jsou možná jen náhodou podobné je jedna z takových věcí, kterou programátoři rádi dělají ve fázi nadšení z DRY.

mystik
Člen | 284
+
0
-

@Bulldog Podobně časná bývá fáze nadšení ze SRP do takové míry, že začnou psát objekty o 1–2 jedné metodách, mezi kterými si složitě -předávají tuny parametrů a kontextu. Což místo aby věci zjednodušilo je neuvěřitelně zkomplikuje a dosáhne pravého opaku toho co je cílem SRP.

Bulldog
Člen | 110
+
0
-

Hoši. To co jste napsali v reakci na mě to pěkně podtrhuje a máte naprostou pravdu.
Vše s mírou, zřetelem na ostatní věci a přemýšlet u toho.

Líbí se mi nakonci popsání od @mystik s tím nadšením pro novou věc. Taky jsem to tak měl, když jsem se učil. Ale jak píšeš, když jsem tak zkusil napsat i jednodušší aplikaci, tak jsem zjistil, že to je zabiják.

Co ale akorát s @mystik nesouhlasím ve tvé první reakci je, že když přijde požadavek, aby se to v nějakém presenteru dělalo trošku jinak, tak to zničí a mám problém. Ono hodně záleží na situaci a to co ty popisuješ je taky důvod, proč se nedoporučuje používání dědičnosti. Lidi to prostě používat neumí a tak ať se to radši nepoužívá vůbec.
Proto jsem ale mluvil o edukaci těch nováčků. Totiž, když máš nastavený v kódu určitý systém, tak je pak jednoduché ho dodržovat a když ty pravidla dodržuješ všechny, jako právě i princip rozšířitelnosti, tak dojdeš k tomu, že i při přidání nového požadavku na to, aby se to jinak chovalo můžeš tento kód zachovat, jen ho nějakým helperem obalit apod.

Tady se mi líbí, jak to řeší C#, kdy prostě do předpisu funkce přidáš parametr a tím vytvoříš zbrusu novou funkci, která by ideálně interně měla volat tu původní, ale s logikou navíc. V PHP holt musíš napsat jinak se jmenující funkci, která bude logiku přidávat, nebo dopsat IF.

Samozřejmě to, co tady píšu nejsou čistě moje názory, nebo věci, co jsem si vymyslel, nebo na ně přišel sám, ale z velké části se učím od starších kolegů, kteří buď dělají kurzy, natáčí videa, nebo je znám, že pracují pro korporáty a radí mi, jak se to dělá tam.
Co jsem ale sám stihl zjistit, tak tím, že tyhle principy dodržuju jak píšu, tak jsem nesčetněkrát potřeboval na zíkladě požadavku přidat nějakou funkčnost, která měla měnit kód a to drasticky a potřeboval jsem na to nějaké nové funkce, nebo úpravu existujícího interface apod., ale poté jsem zpětně děkoval mému dřívějšímu já za to, že to tak napsalo, protože tím jsem si ten kód už dopředu nachystal na takové situace a změna proběhla lusknutím prstu.

mystik
Člen | 284
+
+2
-

@Bulldog Jenže právě to, že jsi zvolil nevhodnou abstrakci a začneš to záplatovat tím, že přidáváš další a další parametry nebo píšeš další a další wrappery a to je ta cesta do pekel. Výsledkem po pár iteracích může být strašně složitý kód, kterému nikdo úplně nerozumí a není si jistý co který parametr dělá a jaké parametry použít pokud přidává novou funkci.

Abstrakce je dobrá věc, ale jen v případě, že je vhodně zvolená a nevede k neůúměrnému nárůstu složitosti.

Pokud abstrakci zvolíš dobře ušetří ti tunu práce při pozdějších úpravách. Pokud je ale zvolíš špatně přidělá ti jí ještě mnohem víc. Dělání abstrakce aby ses připravil preventivně na budoucí změny taky porušuje princip YAGNI (https://martinfowler.com/…i/Yagni.html)

To co tu píšu jsou taky nejen moje zkušeností, ale vychází to hodně z článků/knih např. Uncle Boba a Martina Fowlera.

Bulldog
Člen | 110
+
0
-

záplatovat tím, že přidáváš další a další parametry

Asi si nerozumíme úplně. Nejde o záplaty, ale o rozšíření. Repsektive i kdybych abstrakci nevytvořil, tak bych stejně musel přidat do nějaké funkce někam parametr, nebo vytvořit novou funkci, ale místo na 20 místech to mám na 1.
No a pokud jde o to, že jedna konkrétní část (třída, nebo tak) má dělat danou funkčnost trošku jinak, tak to už je odpovědnost toho, kdo tu ‚abstraktní‘ metodu volá. Jak nám říkal pan profesor na škole: Každá funkce/metoda má nějaký interface a tedy nějaké závislosti a pokud máme spoustu opakovaného kódu, který vyčleníme do nějakého hepleru a tento helper bere jako parametr pole, ale my najednou potřebujeme v 1 jediném případě do něj posílat řetězec, ale nic jiného se nezmění, tak kvůli tomu nebudeme měnit ten helper na univerzální, kdyby tam ještě někdo chtěl poslat řetězec, ale prostě ten, kdo to volá musí být zodpovědný za přecastění toho řetězce na pole, kterému už ten náš helper rozumí.
Vysvětlovat tedy jak jsem to přesně myslel by asi zabralo hodně času, takže se toho vyvaruju.

Důležité je (myslel jsem to ve zkratce tak), že požadavek na funkčnost nemá změnit logiku psaní aplikace, ale požadavek na funkčnost se má formulovat tak, aby zapadal do aktuálního stylu psaní aplikace, který by měl být natolik univerzální, že do něj vše půjde jednoduše zahrnout a v případě potřeby jednoduše vyměnit, jako když k PC připojuješ různá zařízení přes USB.

Totiž jde o to, že když si programátor uvědomí, že ta stejná věc jde zapsat mnoha způsoby, jako například, že cyklus můžeme zapsat pomocí for, while, do-while a rekurze, tak požadavek na sumu hodnot v poli by neměl určovat způsob implementace, například že to chce zákazník for cyklem, ale jen by měl určovat logiku, ‚potřebujeme sečíst tyto hodnoty v poli‘ a programátor sám určí (omlouvám se, od toho nejsou programátoři, ale analytici) takový způsob zápisu, který sedne do aktuálního způsobu, jakým je aplikace napsaná a zároveň aby to bylo co nejefektivnější.

A pokud jde o princip YAGNI, tak ten zrovna zase já nepovažuji za příliš šťastný, protože navádí k psaní jednoúčelových aplikací, které jsou sice rychlejší, ale zhoršuje to rozšířitelnost a udržitelnost, protože při novém požadavku se musí často pak velká část kódu prostě vzít a vyhodit, což je IMHO další na toto navazující rada z Extrémního programování, která říká ‚Nebojte se zahodit svůj kód a celý ho napsat znovu na míru novým požadavkům‘, což se pak na vývoji neskutečně prodraží. On se tento princip hodí pro psaní různých firmwarů a jednoúčelových aplikací, na míru danému HW, který nebudeš měnit a potřebuješ aby šlapal rychle, ale nepotřebuješ znovupoužitelnost, přehlednost nic, jako jsou hodinky, vánoční světélka, sw k lednicím, různým řídícím jednotkám atp. Ale pro vyšší programy se nehodí. Každý princip je vytvořen pro určité druhy aplikací a druhy programování. Jde o nástroje, které nám mají pomoci tvrořit dokonalé aplikace, a pokud někdo nepochopí účel, nebo nástroje používá jinak, než bylo zamýšleno, tak pak chápu jeho frustraci, ale o tom svět je. Musíme se učit.

Jak jsem psal v příspěvsích výše, používám translator i v kódech, kde dopředu vím, že bude jen jeden jazyk a to hned z několika důvodů, ale hlavní dva jsou tyto:

  1. Mám všechny texty na jednom místě a při změně tedy nemusím hledat napříč aplikací, kde jsem daný text použil.
  2. Když se náhodou rozhodne zákazník někdy přidat další jazyk, tak je vlastně vše hotovo a jen se přidá překlad místo abych všechny texty potom extrahoval někam do překladače.

Toto jsou ty případy, kdy sám sobě děkuju za to, že jsem byl prozíravý a napsal si něco sem tam navíc.
A díky tomu, že tato funguju, tak i když náhodou to nepoužiju v budoucnu, tak napsat to tím univerzálním způsobem mi nezabere o žádný čas navíc (u těch překladů jo, ale je to zanedbatelné množství času), což si vykoupím rychlejšími úpravami.

Aneb jak mi teď nedávno napsal jeden nový zákazník:
‚Dobrý den, jsem překvapen s jakou rychlostí pracujete. Všichni ostatní, které jsem poptával odhadli aplikaci na více než 5× tolik. …‘
nebo opět jeden z nových zákazníků mi oznámil, že vzhledem k tomu, jaký mám pracovní výkon mění složení týmu a místo mě a 2–3 spolupracovníků budu na aplikaci sám, jelikož stíhám práci i za ně (Omlouvám se vám hoši)

Takže ono to vypadá, že si aplikaci zesložitím tím, že ji zapíšu trošku jinak a myslím dopředu a je tam nějaký overload v zápisu, ale pravda je taková, že mi tento návrh kódu umožňuje pracovat daleko rychleji, než když bych to nedodržoval.

Chudák kolega mi jednou řekl: ‚Já nechápu jak ty to děláš. U tebe je vechno tak čistý a přehledný, ale když to píšu já, tak je to zvěrstvo, u kterýho musím přemýšlet jak to udělat a ty přijdeš, uděláš nějaký kouzlo a můj 100 řádkový kód má najednou 4 řádky, který naprosto dávají smysl a píšeš to tak elegantně jako by to nic nebylo.‘

A to je právě tím, že mám naučený univerzální postup, který se dá aplikovat všude a jsem striktní v jeho dodržování no.

Každopádně to je debata na spousty dlouhých zimních večerů a tolik času nikdo z nás nemá. :D Tak se mějte a díky za diskusi.

Editoval Bulldog (15. 12. 2022 14:33)