I potreboval som raz istú knižnicu, prehrabal internety a našiel, ale beda: kód pochádzal z roku 2001 a bol kvality nevalnej. Nie je však cieľom búšiť do autora, ktorý už možno dávno opustil Javu a odišiel kamsi inam. Dôležité je, že i zo zlého kódu sa dá poučiti.
Schválne, čo by sa dalo vylepšiť na nasledovnom kóde?
/**
* Use this constructor when you want all files to have the same probability.
* @param files An arraylist of file names to be searched.
* @param flag This is not used. This is present to distinguish between the two constructors.
*/
public FilePref(ArrayList files, boolean flag) {
this.myPrefs = this.makeEqual(files);
}
Jemná nápoveď: pod týmto konštruktorom sa nachádza ešte jeden:
/**
* Constructor for FilePref. Use this one when you do not want all the files to have the same probabilities.
* @param files An arraylist of file names to be searched.
*/
public FilePref(ArrayList files) {
try {
this.myPrefs = this.calcPref(files);
}catch (IOException e) {
System.err.println("Either the given file does not exist or it does not have a .num file associated to it. If the latter is the case, please make sure you run jstrfile on the file before running jfortune on it.");
System.exit(1);
}
}
Trieda zoberie zoznam čohosi (súborov, v skutočnosti Stringov, hoci v tomto kóde to nie je vidieť) a funguje v dvoch režimoch podľa toho, ktorý konštruktor sa zavolá.
Už máte nápad?
V prvom konštruktore udrú do očí minimálne dve veci:
- druhý parameter, ktorý sa tvári, že tam nie je
- a prvý parameter, ktorý má konkrétny typ miesto interfejsu
V druhom konštruktore je, ó, hrôzo!
System.exit(1)
(tri výkričníky)
Parameter-nevyskytujúci-sa-v-tejto-hlavičke
Keď v pythonovie Svätom grále (nie, nejde o jazyk, ale ten film) vymenovávali družiníkov kráľa Artuša, popri Bedeverovi a ďalších spomenuli Rytiera-ktorý-nevystupoval-v-onom-filme.
A presne taký parameter je flag
z ukážky: je tam čisto na to, aby umožňoval volanie dvoch preťažených konštruktorov.
public FilePref(ArrayList files)
public FilePref(ArrayList files, boolean flag)
Samozrejme, dva konštruktory sa musia líšiť v dátových typoch parametrov. A ak nevieme vymyslieť žiadnu odlišnosť (“zober zoznam súborov a správaj sa tak, alebo zober zoznam súborov a správaj sa onak”), toto je rýchle-a-špinavé riešenie.
Ale dá sa to určite krajšie. Ale na to treba príbehovú odbočku…
…existuje skvelá kniha. Čo kniha, biblia Javáka. Effective Java alias Java efektivně, od samotného Blocha, jedného z autorov Javy. Buď nájdite v antikvariáte prvé vydanie v češtine, alebo investujte 23 eúr do druhej edície pre Kindle. Sedemdesiatosem tipov: a čo kúsok, to užitočná vec.
Hneď prvý item sa volá:
Consider static factory methods instead of constructors
Namiesto konštruktorov uprednostňujte statické továrenské metódy
Nebudem prepisovať päť strán Blochovho evanjelia, ktoré vysvetľuje okolnosti. Tuto stačí vedieť, že: továrenských metód môže byť koľko len chcete a môžu sa líšiť menom.
public class FilePref {
private FilePref(ArrayList files) {
// nechceme vytvárať inštancie inak než továrenskými metódami
}
public static FilePref newWithEqualProbabilities(ArrayList files) {
FilePref filePref = new FilePref();
...
return filePref;
}
public static FilePref newWithCalculatedProbabilities(ArrayList files) {
FilePref filePref = new FilePref();
...
return filePref;
}
}
Vytváranie inštancie je potom malina:
FilePref filePref = FilePref.newWithCalculatedProbabilities(...)
Omnoho čitateľnejšie a bez obskúrnych parametrov. Dokonca samodokumentujúce sa! Názov metódy je totiž dostatočne popisný i bez JavaDocu.
Prvý parameter, ktorý má konkrétny typ miesto interfejsu
I mal som zoznam súborov:
List files = Arrays.asList(
new File("en.dat"),
new File("es.dat"),
new File("sk.dat"));
Lenže beda:
FilePref filePref = FilePref.newWithCalculatedProbabilities(files);
Toto je kompilačná chyba: files
je typu java.util.List
, ale metóda čaká konkrétnu implementáciu ArrayList
. A či ja viem, čo je files
za typ? Možno poľový ArrayList
, možno LinkedList
, ktovie. Možno dokonca Vector
, jeden nikdy nevie. (Ve zzkutečnosti ArrayList
, teda aspoň v Oracle Jave, ale to nás nemá čo zaujímať.)
Neostáva mi nič iné, než vytvoriť nový ArrayList
a strčiť doňho prvky môjho zoznamu. Ešteže to ide ľahko:
FilePref filePref = FilePref.newWithCalculatedProbabilities(new ArrayList(files));
Dizajnér API spravil chybu:
Programujte voči interfejsom, nie voči implementáciám.
Načo je potrebné vedieť, že metóda berie ArrayList
, keď jej stačí všeobecný zoznam súborov List
?
Plus generiká
Áno, už osem rokov máme Javu 5 s generikami. Nie je absolútny dôvod nepoužívať ich.
Opravený kód
Opravený kód je zároveň prehľadnejší: pozriem na List<File>
a vidím, že potrebujem zoznam súborov; môžem do toho napchať spoják, poľák (poliak?), vektor… všetko sú to triedy implementujúce List
. Navyše hneď vidím dátový typ.
Výsledok:
public static FilePref newWithEqualProbabilities(List<File> files) {
FilePref filePref = new FilePref();
...
return filePref;
}
public static FilePref newWithCalculatedProbabilities(List<File> files) {
FilePref filePref = new FilePref();
...
return filePref;
}
Strieľať za System.exit(1)
Elton John sa v roku 2004 preslávil:
Všetci, čo na koncertoch so 75 librovým vstupným spievajú na plejbek, by mali byť zastrelení.
Madona (lebo to bolo na ňu) sa nepotešila.
To isté platí pre System.exit(1)
:
public FilePref(ArrayList files) {
try {
this.myPrefs = this.calcPref(files);
}catch (IOException e) {
System.err.println("Either the given file does not exist or it does not have a .num file associated to it. If the latter is the case, please make sure you run jstrfile on the file before running jfortune on it.");
System.exit(1);
}
}
Ste používateľka megasoftvéru vo verzii 2013, ktorý stavia na tejto triede. Naklikáte zoznam súborov, dáte OK, a vaša aplikácia zhučí. Spolu s rozrobenou prácou. Lebo možno jeden súbor sa nedal prečítať. Alebo mal zlú štruktúru. Ktovie.
Zhučí. S chybovým kódom 1, ktorý sa nedozviete, lebo konzolu nevidíte, a ani sa ho nechcete dozvedieť, lebo vás nemá čo zaujímať. (Samozrejme, tam niekde vonku v šírej záhrade softvéru existuje bashový skript, ktorý volá Java program a po jeho dobehnutí overuje exit code, ale.)
System.exit(1)
sa môže vyskytnúť leda tak v main()
e, aj to s výhradami. Výnimky neboli vymyslené len tak (a Bloch im nevenoval len tak 10 položiek vo svojej knihe).
Nech metóda vyhodí IOException
. Alebo inú výnimku. Alebo nech ju aspoň zaloguje a zhltne. Ale nie System.exit(1)
.
Nie. Fontom veľkosti 144.
Nad tímhle kouskem kódu jsem se pozastavoval už před pár dny na hovnokod.cz, ale jak vidím, tak ta nejlepší část (System.exit) v onom příspěvku vlastně ještě chyběla. :)
Ještě mě tak napadá, že kdyby člověk byl úplný pedant, tak by ony factory metody šly ještě trochu vylepšit pomocí wildcards: public static FilePref newWithEqualProbabilities(List<? extends File> files)
Díky tomuhle by klient mohl nejenom předat jakoukoliv implementaci Listu, ale zároveň i list jakékoliv subclassy File (pokud by teoreticky s nějakou takovou pracoval). Ale to už je trochu vyšší dívčí a je otázka, jestli by to bylo někdy vůbec potřeba.
Línia úvahy išla: hľadal som parser pre unixovské fortune cookies, našiel onú knižnicu, vypadli mi oči, napísal som na hovnokód a potom tento článok.
Fortune cookies som nakoniec vyriešil inak, bez knižnice.
Inak rozmýšľam, či som v nejakom projekte videl subclassnutý java.io.File, resp. čo by taká trieda robila (nejaké dodatočné helper metódy?): Používali ste to niekde?