Trochu mě mrzí, že blog i diskuse bere problém jako java vs. zbytek (neesoterického) programovacího světa, popřípadě java vs. "ty prasata co skriptují v pythonu". Výstižně něco pojmenovat je těžké, a na jazyce téměř nezáleží.
Záleží na tom, jak dlouho žije věc pod tím kterým jménem schovaná. Třídy/typy žijí tak dlouho jako program, a pojmenování CsvImportOrderService
je zcela na místě (i když bych se přikláněl k variantě CSVImportOrderService
, ale to je pro tuhle diskusi nepodstatné). Ale nevidím důvod, proč pojmenovat proměnnou tak úzce podle typu. Buď se k ní můžu chovat jako k nějaké import_order_service
, a pak prefix csv_
přispívá k šumu spíš než k signálu; anebo nebo ne, a potom by se tak neměla jmenovat v první řadě ta třída, ne?
Co se argumentu přehlednosti týče, dlouhé názvy jsou pro mě (osobně) spíš známkou špatného členění programu. Idea funkce by měla jít popsat nejlépe jednou větou, a v takovém rozsahu si dovolím používat jména o "pár" písmenech. Když totiž vidím (a dokážu najednou mentálně uchopit) celý vnitřek funkce, tak mezi buf
a file_like_string_buffer
není žádný rozdíl. To druhé jméno se navíc rozbije ve chvíli, kdy přestanu psát znaky a začnu psát bajty. A když to neopravím, zmatu každého čtenáře.
Lokální a privátní jména můžou být krátká, protože je "na místě zjevné", k čemu se vážou. Vidím, kde vznikají, vidím, proč existují a vidím, kde zanikají. Veřejná a globální jména (už jsem se zmiňoval o typech a třídách) žijí kdoví kde, používá je kdosi kdesi a náhrada CSVImportOrderService
za JSONImportOrderService
typicky vyžaduje úpravu konstruktoru (přinejmenším v tom smyslu, že už tam nemůžu posílat původní "orders.csv"
).
Když totiž vidím (a dokážu najednou mentálně uchopit) celý vnitřek funkce, tak mezi buf a file_like_string_buffer není žádný rozdíl.
Rozdíl ale bude, když vnitřek té funkce neuvidíte. A existuje spousta programů, kde není možné zkoumat vnitřek každé funkce, kterou chci použít (a někdy ani nemusím zdroják té funkce mít).
Vztahoval jsem to na název funkce.
Mimochodem, ta poznámka s bajty a znaky byl přesně příklad, kdy by to jméno mělo být popisné. Pokud se změní význam proměnné, a zejména takovým způsobem, kdy může snadno dojít k záměně, jako jsou bajty a znaky, a proměnná se při takové změně nemusí přejmenovat, je to znak, že byla pojmenovaná špatně.
Mimochodem, to, že by kód funkce měl být krátký a pochopitelný je pravda, ale z kódu poznáte co to dělá, ale proč si musíte odvodit. A když to není zřejmé, správné pojmenování tomu právě může významně pomoci. Pak je ještě možnost dopsat tam komentář, ale podle mne to, co jde vyřešit úpravou kódu, aby byl čitelnější, by mělo být řešení v kódu. Komentář považuju až za poslední možnost, když to nejde jinak.
Třeba porovnejte tyhle dva příklady:
if (vek < 18) { return <Chyba message="Alkohol vám nemůžeme prodat."/> }
const jePlnolety = (vek >= 18) if (!jePlnolety) { return <Chyba message="Alkohol vám nemůžeme prodat."/> }
V prvním případě musíte vědět, že se v ČR nesmí prodávat alkohol neplnoletým a hranice plnoletosti v ČR je 18 let. Bez toho nebudete vědět, kde se tam ta osmnáctka vzala. V druhém případě je zavedená proměnná, která je z hlediska kódu úplně zbytečná, ale slouží k pojmenování významu toho výrazu vek >= 18
. V názvu proměnné se vyskytuje ten termín „plnoletost“, který v prvním příkladu vůbec není, takže to dost pomůže pochopení významu toho kódu.
> Vztahoval jsem to na název funkce.
Zcestně. Ale chyba je na mojí straně - co jsem taky čekal.
> Třeba porovnejte tyhle dva příklady:
No, příklady jsem porovnal, a v obou je ta zásadní chyba, že místo vhodně pojmenované konstanty používají magickou osmnáctku. Zkusme si do obou příkladů mentálně dosadit vek < MIN_AGE_TO_BUY_ALCOHOL
a najednou to prokoukne, že?
Jo, tohle miluju. Řve na mne z kódu konstanta psaná kapitálkami, a ještě místo magické osmnáctky, o které každý ví, co znamená, musím luštit význam daleko magičtější konstanty. Ne, neprokoukne to – je to daleko míň čitelné, protože musím pokaždé zjišťovat, co v té magické konstantě vlastně je za číslo.
A když budete ten soft chtít upravit pro stát, který má jiný věkový limit pro alkohol, tak uděláte co? Budete fulltextově hledat "18"? A pak luštit, jestli tahle 18 je limit pro chlast, nebo třeba limit pro svatbu nebo řidičák?
Co je v té konstantě za číslo máte přece v jejím názvu. Najednou vám dlouhé samopopisné názvy vadí?
Když ten limit bude na jediném místě v kódu, nemusím nic hledat fulltextem. Pokud má na spoustě míst kódu zduplikovaný kód zjišťující, zda můžu dotyčnému prodat alkohol, je špatně to, že je ten kód napsaný několikrát, místo toho, aby to byla jedna funkce, která se volá z více míst.
Vadí mi samoúčelné ukládání hodnoty do proměnné, když se ta proměnná použije jenom jednou a její název má menší informační hodnotu, než uvedení hodnoty samotné. Je jasné, že tohle se těžko rozhoduje a závisí to na konkrétním týmu – pokud to bude český tým, bude pravděpodobně každému jasné, co znamená 18, a bude to pro ně zřetelnější, než když to bude v proměnné. Pokud by to byl mezinárodní tým, bude potřeba tam dát tu proměnnou, protože pro lidi z toho týmu by to bylo magic number.
U toho pravidla o ukládání magic number do konstant je důležité to, že se to netýká všech čísel, ale jenom magických čísel – tedy takových čísel, u kterých není zřejmé, kde se vzala.
> Když ten limit bude na jediném místě v kódu, nemusím nic hledat fulltextem.
To ale netuším, dokud ten fulltext search neudělám. Ani jednomužný hobby projektík neudržím celý v hlavě, natož něco většího.
Když je to pojmenovaný symbol, tak si v IDE ty jednotlivá použití můžu jednoduše projít. Ale s hledáním nejakého čísla mi IDE moc nepomůže.
Když je to pojmenovaný symbol, tak si v IDE ty jednotlivá použití můžu jednoduše projít. Ale s hledáním nejakého čísla mi IDE moc nepomůže.
Začal jste od prostředka. Když to budou pojmenované symboly, budete muset nejprve fulltextem vyhledat všechny výskyty čísla 18, zjistit, do jakých pojmenovaných symbolů se ukládají – a pak teprve můžete hledat v IDE použití těch symbolů.
Pokud byste náhodou chtěl argumentovat tím, že ten pojmenovaný symbol má být v projektu jenom jeden – máte pravdu, ale přemýšlejte o tom, jak se to stane, aby byl jenom jeden. Někdo bude chtít tu hodnotu použít, tak prohledá fulltextem projekt, zda už náhodou někde není ten symbol definován. Nebo jestli se to náhodou někde nepoužívá přímo v kódu. Když najde pojmenovanou konstantu, buď ji použije, nebo kód refaktoruje, aby ji mohl použít. Když najde přímo číslo, zrefaktoruje kód tak, aby třeba vytkl metodu, kde se ta to číslo používá (protože ho bude chtít použít stejným způsobem, takže nebude kód kopírovat).
Nebo-li postup vývojáře bude pořád stejný, ať to bude pojmenovaný symbol nebo přímo číslo v kódu.
Jak už jsem psal, ten věk je zrovna hraniční případ a záleží na týmu, někde bych považoval za správné ten pojmenovaný symbol vytvořit, někde by mi přišlo lepší mít to přímo v kódu, pokud to bude jen na jednom místě. Ale jsou i jasné případy, kdy nemá smysl vytvářet pro to konstantu. Pokud budu mít v aplikaci výpočet plochy čtverce, nebudu tu dvojku označující mocninu dvou nikam dávat jako konstantu. Protože to není žádné magické číslo ale je to prostě číslo, jehož význam každý pochopí daleko snáz, než kdybych se pokoušel uložit to do pojmenovaného symbolu.
> Řve na mne z kódu konstanta psaná kapitálkami
Verzálkami.
> o které každý ví, co znamená
Stačí jeden cizinec v týmu a jste v pytli.
> magičtější konstanty
raise WTFError. To jméno je doslova popisek toho, co se pod tím symbolem skrývá.
> protože musím pokaždé zjišťovat, co v té magické konstantě vlastně je za číslo
Proč?
Verzálkami.
Ano, když na mne někdo řve, jsem z toho tak zmatený, že si pletu verzálky s kapitálkami. Možná kdyby konstanty zapsané verzálkami IDE zobrazovalo kapitálkami, byl bych k nim smířlivější.
Stačí jeden cizinec v týmu a jste v pytli.
To jsem už řešil v předchozím komentáři.
To jméno je doslova popisek toho, co se pod tím symbolem skrývá.
Pokud myslíte to MIN_AGE_TO_BUY_ALCOHOL
, pak není. Vztahuje se to na ČR? Co když je to konstanta dotažená z nějaké knihovny, která to má třeba podle USA?
Proč?
Proto, abych si ověřil, že je tam to, co potřebuju.
Vždycky mne fascinuje, když je na začátku třídy private static final
konstanta psaná VERZÁLKAMI, aby se pak použila v jediném místě v kódu. Takže když na použití narazím, musím odskočit na její deklaraci, tam zjistím její hodnotu, a zase se vrátím zpět. Proč má ta konstanta zamořovat scope celé třídy, když je použitá v jedné metodě? Protože je to uřvaná konstanta psaná verzálkami, tak může? Kdyby si takhle někdo vytáhl z metody do celé třídy proměnnou, tak si bude každý klepat na čelo. No jo, jenže kdybych tu konstantu přesunul do metody před místo, kde se používá, už by to nebyla TA konstanta a leckdo by se mohl ptát, jestli to na to jedno použití opravdu musím pojmenovávat, když se z toho jména dozvím méně, než ze samotné hodnoty.
Znovu opakuju, že ten věk jako hranice pro prodej alkoholu je hraniční případ. V jednom týmu mohou všichni bezpečně vědět, co je 18 v souvislosti s alkoholem, a MIN_AGE_TO_BUY_ALCOHOL_IN_CZECHIA
nemá žádnou přidanou hodnotu. A jsou týmy, kde to třeba jeden cizinec vědět nebude a je dobré tam doplnit komentář, nebo týmy, kde to nebude vědět skoro nikdo a má smysl to číslo pojmenovat. Nakonec je ale stejně nejlepší pojmenovat tu podmínku (udělat z ní funkci).
Ne, tohle je velmi špatné řešení. Když to nechcete vidět v IDE rovnou, ale až v nějakém informačním okně, stačí ta definice hodnoty. Rozumné IDE vám tu hodnotu v rychlé nápovědě zobrazí a nepotřebuje k tomu JavaDoc. Pokud vám to IDE nezobrazuje, doporučuju změnit IDE.
Ten JavaDoc je špatně, protože vám neříká nic víc, než co vidíte v kódu. A navíc může být špatně – někdo změní v kódu konstantu na 21, ale v JavaDocu zůstane 18.
Buď se k ní můžu chovat jako k nějaké import_order_service, a pak prefix csv_ přispívá k šumu spíš než k signálu; anebo nebo ne, a potom by se tak neměla jmenovat v první řadě ta třída, ne?
Tohle je špatný příklad. Kdyby tam to csv nebylo, tak bych předpokládal, že typ toho order bude jen nějaký interface jako ImportOrderService
a pak dává smysl proměnnou pojmenovat jako import_order_service
. Tím odebráním csv
jsi ztratil důležitou informaci a v případě, že budeš mít ještě typy XmlimportOrderService
, tak jsi v koncích a musíš u takové proměnné stále kontrolovat co je to za typ.
Obecně si myslím, že k tomu nezkracovat názvy se musí člověk protrpět. Ono taky ne každý dělá na projektu s více lidmi, který běží několik let. Je jasné, že lidem co dělají na projektu sami tohle přijde divné, ale když musíte luštit co ten člověk před 5 lety chtěl dělat a názvy proměnných jsou x, y nebo buf, tak je to vážně k zlosti
Osobně vidím stejné dilema, jenže obráceně: proč je pro mě podstatné, že je to zrovna CSVImportOrderService
? Všechny věci, které jsou pro csv
specifické za mě řeší ta třída (od toho přeci je), a tam, kde se držím rozhraní, zase nezáleží, jestli je implementací csv
nebo sqlite
(ať se neopakujeme :))
Nehledě na to, že pokud mi ta služba přiteče parametrem, tak ten bude stejně skoro vždycky typu ImportOrderServiceInterface
. Ze situací, kdy tomu tak nebude, mě napadají serializace a debugovací pohledy dovnitř objektu, a obě tyhle situace mohou být stejně pokryté interfacem a metodou - tyhle věci budu chtít dělat ať už budu mít pod rukama instanci CSVImportOrderService
nebo XLSXImportOrderService
.
Jediné místo, které je opravdu specifické pro csv
bude konstruktor (nebo pár řádků těsně za ním), kde budu nastavovat oddělovače, konce řádků, skutečný zdroj těch dat atp., a tam je zase spousta věcí zřejmá z kontextu. Ani tam bych se nedržel typu tak úzce, protože zítra nebo příští týden z toho bude switch
a příští měsíc ImportOrderServiceFactory
.
proč je pro mě podstatné, že je to zrovna CSVImportOrderService? Všechny věci, které jsou pro csv specifické za mě řeší ta třída (od toho přeci je)
Ta třída určitě není od toho, aby řešila všechny věci specifické pro CSV. Jednoduchý příklad – budete mít další služby se stejným rozhraním, ovšem pro XML, JSON atd. Nejprve budete muset detekovat typ souboru, a pak podle toho zavolat službu pro CSV, XML, JSON apod.
Nehledě na to, že pokud mi ta služba přiteče parametrem, tak ten bude stejně skoro vždycky typu ImportOrderServiceInterface.
Nebo také bude typu CSVImportOrderService
. Protože z ničeho jiného, než z CSV, v tuto chvíli objednávky importovat neumíte. Tak proč vytvářet nějaký zbytečný interface?
tam je zase spousta věcí zřejmá z kontextu.
Co tím zkrácením názvu získáte? Vy musíte přemýšlet, jak název zkrátit. Každý, kdo kód čte, pak musí přemýšlet, jestli importOrderService
je CsvImportOrderService
, nebo jestli to náhodou nemůže být něco jiného. Ano, někdy to zkrácení může mít význam. Ale ten postup by měl být přesně opačný, než říkáte vy – by default název nechám, ale pokud je k tomu důvod, mohu ho změnit (zkrátit nebo i prodloužit). Nebudu ho zkracovat jen tak, protože to jde – zkrácením bez dalšího důvodu nic nezískám.
> Nebo také bude typu CSVImportOrderService. Protože z ničeho jiného, než z CSV, v tuto chvíli objednávky importovat neumíte. Tak proč vytvářet nějaký zbytečný interface?
Pokud umím jenom csv, tak ten CSVImportOrderService dává smysl ještě míň. Za prvé nepotřebuju odlišit ten csv service od nějakých jiných. A za druhé, což je ještě důležitější, si to omezení na csv tak tvrdě neprodrátuju skrz celý kód.
Z nějaké ImportOrderService můžu udělat interface nebo předka, když budu potřebovat podporu i pro něco jiného než csv. Budu mít nové CSVImportOdrderService a NecoImportOrderService, ale stávajícího kódu se dost často nebudu muset vbec dotknout, pokud jsem rozhraní té třídy napsal nějak příčetně.
Ok, můžu automaticky přejměnovat ten typ, a podstatně méně automaticky hromadu proměnných "csvImportOrderService". Merge takového commitu může být sranda.
> Ta třída určitě není od toho, aby řešila všechny věci specifické pro CSV.
Ne. Ta třída je tam doslova pro to, aby za mě řešila parsování csvčka, transformaci z řádků na záznamy a vůbec obsluhu toho zdroje.
> Nejprve budete muset detekovat typ souboru, a pak podle toho zavolat službu pro CSV, XML, JSON apod.
Tím spíš tu proměnnou nepojmenuji podle CSVčka, protože to může být třeba yaml.
> Co tím zkrácením názvu získáte?
Lepší odstup signálu od šumu.
> Vy musíte přemýšlet, jak název zkrátit.
Nepřeju si, aby mi kdokoliv vkládal slova do úst! Já tak činit nemusím, a ani tak nečiním. Tohle si fakt od nikoho, kdo do kódu zapeče 18, protože každý ví, že je to limit věku pro nákup alkoholu, líbit nenechám.
Pro všechny ostatní - asi bych tu myšlenku mohl verbalizovat. Neřeším jenom informační obsah (i když ten je důležitý), ale taky odstup signálu od šumu. Jsou věci, které přináši informaci, ale nepřináší podstatnou informaci. Nechci čtenáře kódu utopit informacemi, které jsou sice pravdivé, ale v kontextu použití zbytečné (a v nejhorším případě zastaralé).
Pro všechny ostatní - asi bych tu myšlenku mohl verbalizovat. Neřeším jenom informační obsah (i když ten je důležitý), ale taky odstup signálu od šumu. Jsou věci, které přináši informaci, ale nepřináší podstatnou informaci. Nechci čtenáře kódu utopit informacemi, které jsou sice pravdivé, ale v kontextu použití zbytečné (a v nejhorším případě zastaralé).
Pokud to správně chápu, každá informace, která se dá odvodit z kontextu, je podle vás zbytečná.
Jenže tak to právě není. Zdrojový kód je způsob komunikace programátora s počítačem a zároveň s jinými programátory. Zda komunikace funguje správně s počítačem je relativně snadné ověřit testy – relativně vůči tomu, jak nesnadné je to ověřit u lidí. Komunikace programátor–programátor je ale velmi náchylná k chybám, ke kterým může docházet na obou stranách. Stejně jako u běžné mezilidské komunikace. To, že se používá formalizovaný jazyk, je výhoda i nevýhoda – výhoda je v tom, že se odstraní většina problémů plynoucí z nepochopení syntaxe sdělení, nevýhoda v tom, že ten jazyk je záměrně vzdálen reálnému světu, který má popisovat.
Aby si lidé vůbec rozuměli, přidává se do jejich komunikace spousta redundance. A to platí pro komunikaci v přirozeném jazyce i v komunikaci ve formálním programovacím jazyce – protože v obou případech se bavíme o popisu reálného světa. Ta redundance je tam za třech důvodů. Za prvé mluvčí může znát některé kontextové informace, které adresát nezná. Proto je lepší, když je uvede ve sdělení. Za druhé, běžně se stává, že nějakou věc pochopí mluvčí a posluchač jinak. Pokud pak ta samá informace zazní ještě jednou trochu jinak, je šance, že se tohle nedorozumění napraví. Třetí bod plyne z předchozího – protože může docházet k nedorozuměním a redundance to může opravit, slouží k ujištění, že se chápeme správně.
Pokud to chcete přeložit do jazyka kódování v počítačovém světě, ty redundantní informace mají dvě role – jedna přenáší kontextové informace, které adresát nemusí mít; jednak fungují jako samoopravné kódy resp. jejich kontrolní součty – ujišťují vás, že přenos proběhl korektně, případně umí některé chyby přenosu napravit.
Takové věci si právě představuju, že budou řešené hned za konstruktorem. Tam tu informaci o typu nese sám konstruktor. Ideálně tohle všechno pojme hned sám konstruktor, ale dokážu si představit i důvody, proč tyhle věci vystavit zvlášť. Ale jinak by ta třída měla poskytovat rozhraní nezávislé na tom, odkud ty objednávky importuju. Něco jako "validuj", "počet záznamů", "proveď import", "dej iterátor přes záznamy".
Pracuji 8 let jako softwarový inženýr, specializuji se na backend a Javu. Na Root.cz jsem aktivní již 20 let. Jsem fanda do Unixu, který denně v práci použivám.