OOP-ben - ami függvény nem fog dolgozni egy aktuális objektummal (pl. this, $this, etc.), ...

Címkék

az legyen mindig static method
15% (36 szavazat)
az lehet akár static method is
18% (42 szavazat)
az akkor se legyen static method, kivéve ha factory vagy singleton
4% (9 szavazat)
az rossz kód, kivéve ha factory vagy singleton, akkor legyen static
3% (8 szavazat)
az rossz kód, kivéve ha factory és azért static, mert a singleton is rossz kód
2% (4 szavazat)
az menjen egy class-októl távol álló namespace-be, mert semmi köze az osztálypéldányok műveleteihez (kivétel factory pl.)
4% (9 szavazat)
az menjen egy global függvénybe, mert semmi köze az osztálypéldányok műveleteihez (kivétel factory pl.)
4% (9 szavazat)
az bármi lehet, amíg el tudom olvasni és karban tudjuk tartani
16% (38 szavazat)
Egyéb, leírom hozzászólásban
9% (21 szavazat)
Nem írtam még objektumokkal és osztályokkal dolgozó kódot
26% (62 szavazat)
Összes szavazat: 238

Hozzászólások

Az utolso lehetoseg eredetileg az lett volna, hogy "nem irtam még OOP kodot", de szerintem azt sokkal kevesebben irtak, mint ahanyan ertik a kerdest :P

Peldaproblema a szavazashoz pedig:
osztalyszintu konstansokbol lokalizalt string-et hoz ki a fuggveny, ez nem dolgozik this-szel, megis valahol odatartozik a class-ba. Kell ezert nekunk instance? Ha nem kell, ott a helye a static methodnak a class-ban?

Ha osztállyal nem dolgozó függvénynél úgy érzed, hogy mégis az osztályhoz tartozik, akkor valószínűleg az osztály felépítésekkel van a gond. Ha a példányhoz az ég világon semmi köze, akkor semmi keresnivalója az osztályba a függvénynek.

A nagy okosok, könyvek és szakmai cikkek utána szabadon: singleton és factory osztályon kívüli statikus függvény használata, rosszul szervezett kódra utal.

Refaktoráld az osztály felépítéseket, mert valahol elcsúszott a logika.

Singleton és Factory esetén sem kell statikus függvény, sőt, a statikus függvény ellenjavalt. Mert ha használsz valahol direktben egy publikus statikus metódust, akkor az osztályod API-ja hazudik arról, hogy kik az együttműködői.
Képzeljük el, hogy van a következő kód:


public class MyClass {
 public Result doIt() {
  return MyDatabaseConnectionFactory.createConnection().doIt();
 }
}

Ezzel az a baj, hogy hivatkozik egy statikus factoryre.
Amikor tesztelnéd a kódot, vagy éppen más kliens kódot írnál hozzá, azt látod, hogy a rendszeredben Result előállításához nem kell semmi a MyClassnak. Dehogynem, egy felkonfigurált MyDatabaseConnectionFactory kell neki, anélkül nem működik a kódod. Viszont a MyClass API-ja ezt sehol nem mondja meg neked - hazudik.

A Factoryt szépen classként, statikus metódus nélkül kell felépíteni, majd ott, ahol használni akarod a Factoryt, oda egy DI container majd beinjektálja.

A Singleton meg nem attól Singleton, hogy létezik privát a konstruktora, meg létezik egy statikus getInstance() metódusa.
A singleton attól singleton, hogy a program futása alatt egy példány létezik belőle, és mindenki, aki példányt akar a singletonból, ezt az egy példányt kapja meg.
Ezt is megoldja a DI container általi injektálás, ugyanis az objektum létrehozását a DI containerre bízod, és megmondod neki, hogy mindenhova ugyanazt a példányt injektálja be.

Amúgy hogy egy kicsit még OOP-bek legyünk: az osztályszintű konstans is egyfajta bűn. Ugyebár, ha az osztályod minden példányának el kell érnie ugyanazt a singleton adatot, akkor azt bizony egyszer példányosítod (mármint mondjuk a te esetedben az konstansokat tartalmazó leírót) és azt mindenhova injektálod, majd felhasználod.

Objektumorientált dolgokban igazából nem értelmes az osztályszintű konstans. Amint van egy DI keretrendszer, minden osztályszintű konstanshalmaz helyettesíthető egy KonstansHalmaz osztály egy példányával, amely egyszer példányosodik az alkalmazás futása során singletonként, majd mindenhová injektálódik, ahová kell. Akár ez lehet egy sima Map is, a lényeg, hogy a DI példányosítja, és nem a classloaderre bízzuk a példányosítás feladatát és a static() inicializáló blokk végrehajtását. Eleve a classloadernek ez nem feladata :) Az csak a DI rendszer számára tesz elérhetővé classokat, hogy a DI tudjon példányosítani.

Függőséged lesz, csak éppen a konstanshalmazra. De az tény, hogy minden DI container képes arra, hogy az osztályodos összes példányának adattagjait ugyanúgy konfigurálja fel.
Például ha azt akarod, hogy legyen egy FOO nevű, és valamilyen értékű szimbolikus konstansod, ami String, akkor megmondhatod, hogy a FOO az egy private final változó, és a konstruktorban adsz neki értéket. A konstruktorba az értéked pedig szépen a DI container fogja betenni.

És így nem kell OtherClass.FOO-ra hivatkozni, ami egy szép fordítási idejű függőség (de futásidejű nem, a Stringek és az intek azok belekerülnek direkt a bytecodeba), a FOO szimbolikus konstans értéke a DI container konfigurációjában van benne.

Gondolom tudod, de a ket megoldas nem ekvivalens. Van egy kozos reszhalmaza a felhasznalasuknak, ennyi. De pl ha egy loggert szeretnel injektalni, akkor vagy minden osztalynak csinalsz egy sajat logger peldanyt (bean per class qulifierrel), es a di azt tudja injektalni, vagy minden peldanynak lesz sajat loggere, ami lehet, hogy tortenetesen ugyanugy lesz konfiguralva.

DI-vel hogyan oldod meg pontosan ugyanezt:


private static final Logger LOGGER = LoggerFactory.getLogger(SomeClass.class);

Persze lehet bohockodni generikusokkal is. Pl Spring-ben nincs jol megcsinalva (szerintem) a componensek azok oroklese es a scopejuk kozott fennalo problema, ha hirtelen csak keszakarva is, de lehet mondani olyan szitut, ahol serulni fog a mukodes. Egy private static final-nel nincs mese, az mindig megjosolhatoan mukodik.

-
Big Data trendek 2016

Ezt ugy oldod meg, hogy a DI-vel letrehozol egy fqcn-of-SomeClass loggert, és minden SomeClass példány megkapja ezt a logger példányt.
Persze, lehet mondani, hogy de hát akkor hozzá kell nyúlnom a Spring konfighoz, istenem, miért kell azt módosítani?

Miért probléma ez? Az osztály forráskódját is módosítod, amikor a LOGGERT-t bevezeted.

A Spring konfig is forráskód, úgy is kell bánni vele. Ha fel kell venni új függőséget és beant, hát fel kell venni.
Magukat a Logger példányokat is Springgel kellene példányosítani, pont azért, mert akkor látszik a Spring config alapján, hogy hány osztály akar és tud logolni és senki nem tud létrehozni Loggert csak úgy a vadonban, hogy teleszemetelje a logot a szarával. Míg a LoggerFactory-s cuccnál bárki létrehozlat logot, aki akar, még az is, akiről nem akarjuk, hogy logoljon.

Amúgy a Java API egyik legnagyobb hátránya pont a statikus metódusok miatt az, hogy bármely csúnya gonosz class hívhat System.exit()-et. Csodás :D És ez tipikusan az a side effect, amit a statikus metódusok tesznek lehetővé. Mert egy osztály forrákódjának ismerete nélkül, csak az API-ból nem láthatod sehol, hogy nem fog System.exit()-et hívni vagy éppen vadul létrehozni Loggereket.

minden SomeClass példány megkapja ezt a logger példányt.


private static final Logger LOGGER = LoggerFactory.getLogger(SomeClass.class);

vs.

@Configuration
public class SomeConfiguration {

@Bean("SomeClassLogger")
public Logger getSomeClassLogger() {
return LoggerFactory.getLogger(SomeClass.class)
}
}

@Inject
@Qualifier("SomeClassLogger")
private Logger logger;

es nem lesz final a logger, barki lecserelheti, meg csak reflection sem kell hozza. Amugy a logger most csak egy pelda ne ragadjunk le nala, az elv a fontos.

"nem tud létrehozni Loggert csak úgy a vadonban" barki ugyanugy tud, nincs ra semmi garanciad ;)

"legnagyobb hátránya pont a statikus metódusok" ha nem statikus a System.exit(), akkor referenciak kell szerezned a System objektumra.
a) new System(), de ezzel ugyanugy problemas, mert Systembol 1 letezhet csak
b) System.getInstance() de ez ugyanugy statikus
c) DI teszi oda, de tudtommal az SEnek nem resze semilyen DI, meg csunya is volna, ha egy System.exit() miatt egy egesz DI konyvtarra szukseg lenne
d) a main() kapja meg argumentumkent, de akkor meg gondoskodhatsz a terjeszteserol
e) minden osztaly importalja statikusan automatikusan :D

Nekem megvan a helye a statikus temanak az oop-ban. Vannak olyan problemak, amik adott osztalyhoz tartoznak, de vagy nem kotodnek annak peldanyahoz, vagy mindegyik peldany osztozik rajta.

Big Data trendek 2016

Constructor injection, es final field. Es maris nem cserelheti ki. Btw. a constructor injection az igazi injection. Az objektum futasidejeben ne valtoztatgassuk csak ugy mar a kulso fuggeseit. Ha meg megvaltozik a rendszer konfiguracioja, jojjon letre uj objektumpeldany, uj kulso fuggesekkel.

Amugy a Systemre a d) megoldas a jo. A main megkapja fuggosegkent, ugyanugy, mint ahogy "fuggosegken" megkapja az argumentumokat, es hajra.

Amin mindegyik peldany osztozik, az egy olyan egyszer letrehozott (azaz singleton) objektum allapota, ami nem tartozik ehhez a classhoz valojaban.

"Constructor injection" annyira nem hasznaljuk, hogy ki is ment hirtelen a fejembol :D
"a d) megoldas a jo" csak ne kelljen 20 reteggel arrebb hivni System.exit()-et, de amugy nekem is az tetszik legjobban. amolyan tiszta es szaraz erzes :)
"nem tartozik ehhez a classhoz valojaban" mar hogyne lehetne kozos jellemzoje minde objektumnak. A private String asd egy jellemzoje a peldanynak. A private static String ASD meg az osszesnek kozos jellemzoje. Persze lehetne ez is peldany szintu jellemzo, de minek peldanyositani annyi darabszor ahany peldany van. Vegyunk peldaul egy regexpet, ahol egyedi mintat hasznal egy objektum. Ha minden peldanynal letrehozod, akkor mondjuk 5m peldanynal mar komoly bajok lesznek. Ha kiszervezed valahova mashova, akkor vagy lesz egy osztalyod per regexp, vagy lesz egy regexp gyujtemenyed. Ha Di-vel beinjektalod...varj megint ott vagyunk, hogy kell egy DI az alkalmazasodba, amit konfiguralni kell, stb (arrol nem beszelve, hogy kikerul az osztaly scopejarol a minta, aztan lehet vadaszni, hogy hova kerult). Vagy belatod, hogy ez a minta az osszes objektum kozos tulajdonsaga.

-
Big Data trendek 2016

Nem kell annyiszor peldanyositani.
Hanem egyszer, es minden peldanyba injektalni ugyanazt az egy peldanyt.
Hogy maskent mondjam: van egy osztalyod, amelyben van egy csomo osztalyszintu konstans, amelyet az osztalyod peldanyai felhasznalnak.
Nos, ezek a konstansok valojaban nem az osztalypeldanyokhoz kotodo allapotot reprezentalnak, hanem egy olyan allapotot, ami fuggetlen a peldanyoktol (es igy az osztalytol is), hanem sajat kulon kis osztalyt kellene, hogy jelentsenek.

"varj megint ott vagyunk, hogy kell egy DI az alkalmazasodba, amit konfiguralni kell"
Ez az alapvetes. A DI kell. Mindig. Mert igy kulonul el szepen az object graph construction a business logictol.

"egy olyan allapotot, ami fuggetlen a peldanyoktol" ha fuggetlen lenne kulon is tennem ;) egy olyan allapoto tarol, ami az osszes peldanyban kozos. De ez szemlelet beli kulnbseg. En szivesebben tartom az egyszer hasznalatos dolgokat a felhasznalasi helyehez legkozelebb. Szerintem az a zavaro, ha kavirnyasznom kell kell jobbra ballra, qualifier stringek alapjan kell megtalalni a konkret peldanyt, stb. A kod van ertem es nem forditva. (meg kevesebbet is szeretk gepelni ;))

"igy kulonul el szepen az object graph construction a business logictol" A peldanak emlitett egy helyen hasznalatos regexpre visszaterve. Az a kod, ami a regexpet hivja (eltekintve attol, hogy az static vagy injected), az business logic vagy nem? Tippre igen. Valami logikat valosit meg, amihez mintat kell illesztenie. De az a minta a logikahoz tartozik, ahhoz senki masnak semmi koze hozza.

Mi vegyesen hasznaljuk mindig azt, amire szukseg van. Es szerintem nem megy homlok egyenest a az OOP elveknek.

Amugy arrol is el lehetne vitatkozni, hogy az egesz annotaciosdi meg injection mennyire utkozik az OOP elvekben, mert sokaknak arrol is megvan a maga kis velemenye.

-
Big Data trendek 2016

Nem, nem az osztály tulajdonsága - csak éppen az osztály összes példánya ugyanazt a példányt használja.
Regex is ilyen.
Nem class-szintű változó, hogy az összes példány ugyanazt kapja meg, hanem szépen van egy darab Regex példányosítva, és minden példány azt kapja meg, amikor példányosodik.
És igen, DI kell. A DI egy elv, legfeljebb kézzel csinálod (Factoryk meg factory methodok meg az összes creational pattern, ami létezik), vagy megbízol egy DI libraryben, hogy ő készítse el neked az objektumokat.
DI van ám annotációk nélkül is. Sokan összekeverik a DI eleveket a DI containerekkel.

"nem az osztály tulajdonsága" de nem is a kornyezete. Namost, a regexpre szukseg van az objektum helyes mukodesere, ebben egyetertunk. Ha DI-vel kapja a regexpet mi garantalja a objektum helyes mukodeset? Leven nincs kapcsolat az objektum es a minta kozott. Rosszabb esetben meg csak nem is ugyanabban a csomagban vannak. Ellenben ha az egy osztaly szintu tulajdonsag, akkor egysegbe van zarva a kod. A DIvel kokemenyen side effectet teszel a kodba, ami potencialisan el tud romlani, plusz a tesztelesnek is adsz egy pofont, hiszen csak azt tudod tesztelni, hogy adott injektalt regexppel hogyan mukodik az objektumod, de nem tudod a vilag letezo osszes mintajaval tesztelni, vagy teszel valami logikat bele, hogy csak a megfelelo mintaval fusson le? Vagy mivel side effect egyszeruen kimockolod?

"DI van ám annotációk nélkül is." Tudnal mondani egy peldat, amit mondjuk 4 embernel tobben hasznalnak, es nem egy komplett keretrendszer resze, tehat onalloan is hasznalhato (pl Grailsbe van elnevezesi minta szerinti)? Mert melyen legbeblul persze a DI nem mas mint egy kis class loading magia meg egy adag runtime reflection.

-
Big Data trendek 2016

Nem, melyen legbelul a DI simán setter/getterek és/vagy konstruktoron át megadott függőségek sokasága, csak a DI konténerek lehetőséget biztosítanak a reflekción keresztűli beinjektálásra.

Legtisztább formájában a DI valami ilyesmi:


public class Lofasz {
   private final DatabaseConnection conn;
   private RestClient resti;

   public Lofasz(DatabaseConnection conn) {
       this.conn = conn;
   }

   public void setRestClient(RestClient client) {
       this.resti = client;
   }
}

--
Blog | @hron84
Üzemeltető macik

Ez nem DI hanem parameter atadas. En a masik oldalra vagyok kivancsi, arra a reszere, ami felismeri, hogy DatabaseConnection-t kell injektalni az objektumba. Ezt a kododt hiaba futtatom le nem lesz DatabaseConnection meg RestClient benne. new Lof*() es nem tortenik semmi.

-
Big Data trendek 2016

Hidd el ertem. De amiket leirtal azok mezei parameter atadasok. Letrehozok egy peldanyt, aminek atadok dolgokat. Mitol lesz ez DI?. Az mas kerdes, hogy a dependecy injectionnek vannak ilyen faitai, de a dependency injection pont az a kod, ami koruloleli ezt. Attol lesz injection, hogy nem neked kell bajlodni vele. Ehhez kell egy kornyezet, aminek azt tudod mondani, hogy hey.gimme().theLof4sz(), vagy csak egyszeruen @Inject private Lof4sz l.

"dependency injection is a software design pattern that implements inversion of control for resolving dependencies" hol implementaljak az altalad irt sorok ezt?

Mar nem tudom honnan kavarodtunk idaig, de talan onnan, hogy szivesen megneznek a gyakorlatban is, hogyan mukodik egy annotacio mentes DI kontener.

-
Big Data trendek 2016

" Attol lesz injection, hogy nem neked kell bajlodni vele. "

Te kevered a DI elvet magaval a DI kontenerekkel.

" hol implementaljak az altalad irt sorok ezt?"
Inversion of control: nem egy komponens keri le a contextbol a fuggosegeit, vagy kutatja fel, hanem atadjak neki. Mindegyiket. Mindig. Ez a DI. Es az altala irt sorok ezt implementaljak. AKA Hollywood principle.

"hogyan mukodik egy annotacio mentes DI kontener."
Mondom, te osszekevered a DI elvet a DI kontenerekkel. Amugy nezz meg egy Springet, ami csak XML-only konfigot hasznal. Mukodik. Mily erdekes.

"Amugy nezz meg egy Springet, ami csak XML-only konfigot hasznal" valoszinuleg a tudatalattim annyira el akarja felejteni azokat az idoket, hogy eszembe se jutott mint alternativa :) De igazad van elmeletben egy dia filejba is lehetne abrazolni az egeszet, aztan csak egy parser kell, es kesz.

-
Big Data trendek 2016

Nem lett jó a linkem a Dependeny Injection-re, de látom megtaláltad, mert abból idéztél.
Javaslom menj ez az Assembling examples részhez és nézd meg mindjárt az első példát (Manually assembling in main by hand is one way of implementing dependency injection.).

DI kontener.

Már többen is írták, DI-ről van szó, és nem DI konténerről vagy frameworkről.

Akkor itt summazom mivel itt lett feny az ejszakaban. Az en ertelmezesemben a Manually assembling nem minosult DI-nek, mert "with inversion of control, it is the framework that calls into the custom, or task-specific, code." Mert peldaban nem latom a frameworkot:

public class Injector {
public static void main(String[] args) {
Service service = new ServiceExample();
Client client = new Client(service);
System.out.println(client.greet());
}
}

Az Injector nem framework, az az alkalmaza maga, a main metodus sem a framework, a tartalma meg mar maga a logika az sem lehet framework. Mivel a "dependency injection is a software design pattern that implements inversion of control for resolving dependencies" az ioc pedig "it is the framework" ezert nekem eszembe se jutott ezt DInek nevezni. De mivel a szakirodalom valamiert megis annak tekinti, fejet hajtok elotte. Es koszi a kitartast ;)

-
Big Data trendek 2016

Es koszi a kitartast ;)

Szívesen! ;)

Egyébként azért is érthetted félre, mert sok helyen a kolega DI konténerről beszélt (pl. Ezt is megoldja a DI container általi injektálás, ugyanis az objektum létrehozását a DI containerre bízod, és megmondod neki, hogy mindenhova ugyanazt a példányt injektálja be.) vagy úgy beszélt róla, mintha az lenne (pl. a lényeg, hogy a DI példányosítja).

Ez nem DI hanem parameter atadas.

Ez a két dolog nagyon jelentős részben fedi egymást. Ha egy osztály paraméterként kapja a kollaborátorait, nem pedig maga szerez rájuk referenciát (példányosítással vagy service locator-on keresztül) akkor az az osztály a dependency injection tervezési mintára épül. DI-zni pöpecül lehet DI konténer vagy framework nélkül. Ha pl. nézeget az ember egy kis spring javaconfig-ot, akkor hamar elkezdi vakarni a fejét, hogy egyáltalán minek abba spring.

"Ha egy osztály paraméterként kapja a kollaborátorait, nem pedig maga szerez rájuk referenciát (példányosítással vagy service locator-on keresztül) akkor az az osztály a dependency injection tervezési mintára épül"

vs.

"dependency injection is a software design pattern that implements inversion of control for resolving dependencies"
"In software engineering, inversion of control (IoC) is a design principle" "A software architecture with this design inverts control as compared to traditional procedural programming: in traditional programming"

"DI-zni pöpecül lehet DI konténer vagy framework nélkül" nevezheted barminek lesz egy kodod, ami a peldanyok letrehozasaert es eletciklusaert lesz felelos, es ami dinamikusan felderiti, hogy miket kell injektalni, es osszerak mindent.

-
Big Data trendek 2016

Igen, de DI pattern lenyege nem az, hogy magikusan szerezzunk kollaboratorokat, hanem az, hogy ezen keresztul az implementaciok futasidoben allithatova valjanak, ne kelljen ahhoz code change, hogy pl a tesztekbol en egy mokolt DatabaseConnection-t szeretnek hasznalni. Egyszeruen bepasszintom es szol. Aztan hogy ezt reflekcion keresztul teszem, konstruktorparameterrel, vagy setterrel, az mar a kutyat nem izgatja, implementation detail. A lenyeg az, hogy akarmikor akarmilyen, az interfeszt implementalo objektumot be tudok szorni anelkul, hogy a Lofasz.class -nak tudnia kellene arrol, hogy valtozott valami alatta.
--
Blog | @hron84
Üzemeltető macik

Ez az alapvetes. A DI kell. Mindig. Mert igy kulonul el szepen az object graph construction a business logictol.

A DI sem jó mindig (When is it not appropriate to use the dependency injection pattern?), ráadásul vannak más módszerek is a laza kapcsolat elérésére, pl. a Service locator pattern vagy az Actor model.

Sok esetben pedig, megfelelő tervezéssel (pl. Functional design) a függőséget szüntethetjük meg. Tehát ahelyett, hogy a szoros kapcsolatot lecserélnénk lazára, a két modul közötti kapcsolatot szüntetjük meg.

Jajj, ezek annyira vicces dolgok. Majd a konstruktorban parameterkent megkapod. Aztan mikor hirtelen tizenot-harminc parametert kapogat a konstruktorod, es mar maga a definicio harom sor, akkor meg odaallsz sirni, hogy hat ez atlathatatlan.

Ember, sok cuccban meg a ketparameteres konstruktornak sincs tisztesseges dokumentacioja, nehogy mar akkor a tetves konstansok is onnan jojjenek. Akokr mar inkabb gyartsunk enumokat, azok pont erre valok, es nincs szukseg DI kontenerre, meg semmifele magikus hokuszpokuszra.
--
Blog | @hron84
Üzemeltető macik

Ha egy osztaly 15-30 kollaboratorral mukodik egyutt, az az osztaly nagyon tul van terhelve, God object. Es biztosan nem tartja be a single responsibility principle-t.
"Ember, sok cuccban meg a ketparameteres konstruktornak sincs tisztesseges dokumentacioja"
Az, hogy masok (vagy a multban te) szar kodot irsz, nem mentseg arra, hogy a jelenben tovabbra is szar kodot irjunk. Mert ha ez mentseg, akkor sosem lesznek minosegi kodbazisok.

Amúgy hogy egy kicsit még OOP-bek legyünk: az osztályszintű konstans is egyfajta bűn. Ugyebár, ha az osztályod minden példányának el kell érnie ugyanazt a singleton adatot, akkor azt bizony egyszer példányosítod (mármint mondjuk a te esetedben az konstansokat tartalmazó leírót) és azt mindenhova injektálod, majd felhasználod.

Néha hajlamosak vagyunk túltolni, ez is egy tipikusan ilyen eset.


class Cloths {
  public static final SIZE_S = "S";
...
  public String selectPerfectSize(int height) {
    if (height < 160) return SIZE_S;
    ...
  }
}

Mi ezzel a baj? Az égvilágon semmi. Akárhol, ahol használni akarod, ott elég ennyi: Cloths.SIZE_S. Ehelyett a javaslat:


class SizeConstants {
  public String getSizeS() {
    return "S";
  }
...
  // Biztosítani kell, hogy singleton legyen.
}

class Cloths {
  private SizeConstants sizeConstants;

  public Cloths(SizeConstants sizeConstants) {
    this.sizeConstants = sizeConstants;
  }
...
  public String selectPerfectSize(int height) {
    if (height < 160) return sizeConstants.getSizeS();
    ...
  }
}

Mit nyerünk vele?
- Csak azt, hogy le tudjuk cserélni a konstansok értékét, pl. néhol a SIZE_S lehet "S", máshol "Q". Mondhatjuk erre, hogy akkor az nem is igazán konstans.
Mit vesztünk vele?
- Mindenhol, ahol használni akarjuk oda be kell injektálni a SizeConstants osztályt. Egy csomó plusz munkát intéztünk magunknak.
- A tesztelés sem egyszerűbb, sőt, ott is gondoskodni kell egy példány injektálásáról, vagy mockolni kell.
- Potenciális hibalehetőséget raktunk bele, ha pl. valahol null értékkel injektálják.
- Fölösleges objektum létrehozást hoztunk be, ami sem a memória felhasználásra, sem a sebességre nem jótékony hatású.

Szerintem nem, ennek nem is static methodban van a helye.
Több okból sem.
Az első az az, hogy a metódusnak a feladatköre (konstansból lokalizált string létrehozása) más, mint az osztályod feladatköre, ezzel sérted a SOLID designból az S-t.
Azzal semmi gond nincs, hogy a nem lokalizált konstansok a classban vannak, oda tartoznak.
A dolgot úgy tudod szépen megoldani, ha létrehozol egy Localizer osztályt, amelynek van egy
String localize(LocalizableConstant) metódusa. Persze LocalizableConstant az lehet String, int, bármi, amit használsz a lokalizálandó kulcsok tárolására.
És az osztályodnak a konstruktorban átadsz egy Localizer példányt, amely majd elvégzi a lokalizálást.
Ennek a designnak az előnye, hogy megvalósítja a Depenedncy Inversion principle-t is. Ugyanis az osztályod innentől kezdve nem függ konkrétan attól, hogy hogyan történik a lokalizálás (például property file-ból, ini file-ból, XML-ből, adatbázisból, statikus Map-ből, de mégis tudni fog jól lokalizálni.
Az egyes Localizer instance-k meg tudni fogják, hogy ők hogyan lokalizálnak.
Pl. lehet IniLocalizer, PropertiesLocalizer, XMLLocalizer, bármelyikkel együtt tud működni a kódod bármikor.
És az, hogy a rendszerben éppen a lokalizátl stringek honnan jönnek, azt a rendszer összedrótozása dönti el, amely összedtrótozás tkp. a rendszer konfiguráció.

Ennek a designnak az előnye még az is, hogy unittesztelhető szépen. Mockolhatod a Localizert, amit átadsz a tesztelendő osztályodnak, és ellenőrizheted, hogy megfelelő interakciókat csinál vele.
Például ha az osztálynak X inputot adsz, akkor az X inputnak megfelelő lokalizált stringet kérdezi le a Localizertől. Ezt az interakciót nem tudod unittesztelni, ha privát statikus metódusod van.

Persze felmerülhet, hogy de hát emiatt kell egy új interface meg egy class, és példányosításkor még egy paraméter, és hát na...Igen, ilyen a rendes OOP. Egyszerű műveletekre is "komplex" struktúrákat kell létrehozni, hogy a rendszer rugalmas, unnittesztelhető maradjon. A példányosításokat meg elintézi egy DI container (aka IoC container), amiben konfigurálod, hogy akkor neked most milyen Localizer instance-od van valójában.

"az rossz kód, kivéve ha factory és azért static, mert a singleton is rossz kód"

A singleton-t nem gondolnám rossz kódnak. Erőforrás használatakor (adatbázis, fájl, nyomtató...stb), szép és kulturált kódot eredményez. A singleton teljesen OOP szemlélet, csak a példány létrehozás előtt vizsgál egy feltételt, miszerint kell új példány, vagy jó a régi. A kontruktorban ezt már nem tudnád megcsinálni, mert mindenképpen új példányt kapnál vissza, hiába vizsgálsz feltételt.

De persze ez vitatható, mert a kontruktornak eleve nem lehet feladata az erőforrás lefoglalása, pl adatbázis kapcsolódás esetén, nem kapcsolódhat, arra legyen egy külön connect metódus. Ez akár igaz is lehet, de DB osztály nem példányosít az ember csak úgy heccből, tehát úgyis kapcsolódik, vagyis totál felesleges külön metódus hívásra tenni, ha a konstruktor is meghívhatja. És ha az egész singleton, akkor a konstruktor kapcsolódik, ha pedig már kapcsolódik, akkor az ezzel járó kézfogások oda-vissza, nem terhelik a végfelhasználók 100-200ms várakozással.

A singleton-t nem gondolnám rossz kódnak.

Vannak, akik annak gondolják: Why Singletons are Evil.
A The Good, the Bad and the Singleton cikkben olvashatunk az egyes megvalósításokról.
the best answer to the question “How will you implement a Singleton in a specific programming language?” is: “I will not implement it at all!”.

en nem vagyok egy nagy java programozo, de javanal vigyazni kell a singletonra, mert legtobbszor nem thread-safe az implementacio.
(elkepzelheto hogy amig teszteled, hogy van-e mar egy peldany az adatbazishoz, az eppen felepiti a kapcsolatot, igy false-el ter vissza, igy van egy jo kis race condition)

Majd biztos kijavitanak, hogy hogyan szokas, de javascriptben (node.js) en promise-zal oldom meg az adatbazis kapcsolodast es igy teljesen 'atomic' a peldanyositas (persze singletonnal:)

A singletonnak mi is az alternativaja? globalis valtozo?:)

---
Saying a programming language is good because it works on all platforms is like saying anal sex is good because it works on all genders....

a singleton es a dependency injection nem zarjak ki egymast.

Ha szigoruan azt tekintjuk singletonnak, amikor .getinstance()-szal hivod meg,
az tenyleg szivas.

De amikor argumentumkent adod at az adatbazis objektumot (dependency injection), attol meg az az objektum lehet szigoruan egypeldanyu.

Valahol megiscsak el kell erni hogy egy objektum kapcsolodjon az adatbazishoz, kulonben elfogy a connection limited (ulimit) es borul a bili...

Nalam a singleton az, hogy egy objemtumbol szigoruan egy peldany van az alkalmazas eleteben. Ettol meg lehet kukazni es ujra letrehozni, csak 2 peldany mar nem lehet.

De en nem javaban nyomulok, szoval itt lehet, hogy mas a best practice...

---
Saying a programming language is good because it works on all platforms is like saying anal sex is good because it works on all genders....

"Valahol megiscsak el kell erni hogy egy objektum kapcsolodjon az adatbazishoz, kulonben elfogy a connection limited (ulimit) es borul a bili..."

Igazabol nem kell. Lehet ugy is tekinteni, hogy az adatbazishoz kapcsolodas az adatbazis objektum belso maganugye, es ezt belsoleg kell megoldja. Peldaul amikor aztat mondom, hogy dbObj.execute("SELECT * FROM lofasz") akkor az execute intezkejden arrol, hogy a konneksun letrejojjon, ha meg nincs letrejove. Igy maganak a peldany megszerzesenek nem resze a kapcsolat tenyleges kiepulese, az csak menet kozben tortenik meg "valamikor" (amikor szukseg van az aktiv kapcsolatra).
--
Blog | @hron84
Üzemeltető macik

Valoszinuleg van jopar, ami az lehetne, es en igyekszem csak private static-et hasznalni. Szerintem a public static hiba a tesztelhetoseg miatt, mivel nem tudod interface-re rakni, tehat a dependency injection kilove. Singletont is jobb elkerulni ha lehet, szoval a "az rossz kód, kivéve ha factory és azért static, mert a singleton is rossz kód" lehetoseget valasztottam.

Nem, nem az Util tesztelhetősége miatt hiba, hanem amiatt, mert azt nem tudod tesztelni, aki az Utilt felhasználja, addig, amíg az Utilnak nincs egy helyes implementációja- ugyanis közvetlenül hívja, nem injektált dependencyn keresztül.
Viszont arra várni egy osztály tesztelésekor, hogy a függőségeinek van helyes implementációja, sérti a unittesztelést. Nem tudsz teszt kódot írni addig, amíg a Unit implementációja el nem készül. WTF?!
Írsz egy MyClasst, és másra kell várnod (az Utilt fejlesztőre), hogy MyClasst tesztelni tudd? Őrültség.

Ha az Util static, akkor nem tudod tesztelni azt az interakciót, amikor az Utilt a hívó kód meghívja, miszerint helyes paraméterekkel hívja meg.
Tegyük fel, hogy a metódusod X-ből Y-t állít elő. A függvény a futása során meghívja az Utilt. Hogyan teszteled, hogy megfelelő paraméterekkel hívta meg az Utilt? Az Util valójában egy collaboratora a metódusodnak, a metódus vagy class API-ja sehol nem árulkodik azonban erről, ez egy implicit függőség. Ami rossz.

Beinjektálni könnyen nem tudsz egy mock Util példányt, classloadert kéne ahhoz módosítgatni.

Az első részt értem, de kicsit erőltettnek érzem. Tesztet addig is tudsz írni, maximum annyit írsz a függvénybe, hogy return 0, vagy return null. Nem fog a teszt lefutni, de és? Egyébként
se tudna lefutni, hiszen nincs mögötte valós kód. Ráadásul sokszor - majdnem mindig - ugyanaz az ember fejleszti a UTil-t és a tesztet.

Ha írsz egy MyClass-t, ami az Util-ra épül, akkor megint csak nem tudod tesztelni a helyes működést, hiszen nincs kód, amit meg tudnál írni.

"Az Util valójában egy collaboratora a metódusodnak, a metódus vagy class API-ja sehol nem árulkodik azonban erről, ez egy implicit függőség. Ami rossz."

Ha jól értem, itt arra utalsz, hogy a metódus szignatúrából illetve az importból nem látszik, hogy van egy ilyen függőség. Ezt végül is el tudom fogadni, bár első körben nem látom,
hogy hol jelent gyakorlati problémát.

Engem zavar az altala adott megoldas. Ahelyett, hogy irnek egy egyszeru fuggvenyt, ami azt csinalja, amit szeretnek, helyette tervezzek osztalyokat, pollutaljak tele egy komplett nevteret olyan osztalyokkal, amiknek pontosan egy darab metodusa van. Valahol azt olvastam, hogy az OOP eredeti celja a valos eletbeli problemak lemodellezese volt. Hat ha valami, akkor ez eleg messze van ettol az elkepzelestol. Jo hogy ne irjak egy tetves osszeadasra mikroszervizt SOAP-pal mar.

En ertekelem ezeknek az embereknek a munkajat, de azert az ilyen vadhajtasokat inkabb nyesni kellene, mintsem batoritani.
--
Blog | @hron84
Üzemeltető macik

Nem beszelve, hogy a a vegso megoldasa tobb sebbol is verzik.

void transform(File in, File out) {
Collection src = new Trimmed(
new FileLines(new UnicodeFile(in))
);
Collection dest = new FileLines(
new UnicodeFile(out)
);
dest.addAll(src);
}

Pl a dest.addAll(src); az el is engedi a fajlt? Vagy arra kell egy kulon metodus? De ha kell, akkor hogyan lesz a Collection-bol hivhato?

-
Big Data trendek 2016

"Valahol azt olvastam, hogy az OOP eredeti celja a valos eletbeli problemak lemodellezese volt."

Az OOP azért jött létre, mert felmerült az igény arra, hogy sok-sok viselkedés belül változhat az idők folyamán (míg a külső interfésze ugyanaz marad), ezért a konkrét megvalósítást absztrakciókba helyezzük, és hagyjuk, hogy mások ezt az absztrakciót használják, ahelyett, hogy egy konkrét megoldást hívnának meg.

És ahelyett, hogy az egyes implementációk függvénypointereit adogatnánk ide-oda, mint procedurális nyelvekben, létrehoztunk nyelveket, amelyek automatikusan managelik a függvénypointereket.

Gondolj bele, a Linux kernel is így működik. Moduljai vannak, amely modulok elvégzik a konkrét feladatot, amikor az absztrakt feladat mondjuk csak annyi, hogy írjunk ki egy TCP csomagot az eszközre. Persze a Linux kernel is meg lehetne írva úgy, hogy van benne egy függvény, ami elvégzi a TCP csomag kiküldést minden ismert eszközre egy szép nagy switch statementtel, de ez legfeljebb (és rugalmatlanul) csak azokat az eszközöket kezeli, amely az adott függvényben le van írva.

A valóságban azonban nincs ilyen függvény, a kernel nem tudja, hogy ennek konkrétan hogyan kell megtörténnie, ő csak a hálózati eszköz absztrakciót ismeri, majd valaki eldönti a kernel helyett, hogy melyik hálózati eszköz drivert fogja erre használni.
A Linux kernel is OOP módon működik sok helyen, csak nem OOP nyelven van megírva, hanem függvénypointerekkel machinál közvetlenül.

Ezért is tud betölthető modulokkal rendelkezni, és nem csak egy statikusan compile-olt bináris az egész.
Az Utility classok pont a statikusan compile-olt binárisok megfelelői.

Bonyolítsuk el azt, ami egyébként egyszerű. Ha kellően bonyolult, akkor tesztek nélkül úgyis lehetetlen lesz karbantartani. Ez indokolja a további bonyolítást a tesztelhetőség érdekében. Így legalább még 1 millió fejlesztőnek lesz munkája. Mekkora zsírság, hogy csurgatja az adatokat végig négy osztályon keresztül. Ha neadjisten nem működik, akkor debuggold ki, vazze, hogy melyik osztály (szinte megfigyelhetetlen) belső állapota csúszott szét! Nem is beszélve arról, hogy kell-e finalban zárni az UnicodeFile osztályt? Ha nem kell, akkor mégiscsak be van töltve a tartalma, oda az O(1) tárigény. Ha meg nincs betöltve a tartalma, akkor a végén be kéne zárni, de ez a példából pont kimaradt. Pozitívnak szánt példában ilyen hibát véteni durva. Vagy ez a cikk paródia?

Vagy ott van benne, hogy implementáld a trimmelést a Collection interfész szerint. 15 metódus van a Collection interfészen, amiből jelen esetben legalább a felét nem is használnánk. Tehát csinálunk egy rakat tesztelendő és karbantartandó kódot a "szépség" kedvéért. Vagy félig implementáljuk az interfészt, mondván, hogy a másik felét "tudjuk", hogy nem használjuk. Igen, ez a szép megoldás valóban.

És akkor a végén a futásidő. A max metódust a JIT inline-osíthatja, 8 bájtot olvas, és 4-et ír (várhatóan regiszterből regiszterbe, 3-4 ASM utasítással) és zéró szemetet hagy maga után. Mire a Max ojjektum kiszámolja, hogy öt, addig legalább három pointeren keresztüli metódushívás történik és a végén ki kell még ganézni a heapet (egy kb 24-32 bájt körüli ojjektuot tesz a heapre) is. Gratulálok.

"de ez a példából pont kimaradt" raadasul nagy valoszinuseggel a lezaro metodus mar nem is fer bele a Collection interfacebe, aztan lehet implementacio alapjan peldanyositani, vagy runtime castolgatni.

"legalább a felét nem is használnánk" valahol melyen biztos lesz egy Collection, es csak delegalni kell azokat a metodusokat (hogy en azt hogy utalom)

"És akkor a végén a futásidő" Minden Java teljesitmennyel foglalkozo cikk pl azzal kezdi, hogy bizony a franya memoria fogyasztast kell elso korben lecsokkenteni. Probaljuk meg a betoltott osztalyok meg a peldanyok szamat minimalizalni, es koltseges osztalyokat hasznaljuk ujra. Arrol nem tesz meg emlitest a cikk, hogy a Util osztalyok valami elv alapjan osszetartozo metodusokat tartalmaznak, tehat ugyanabban az osztalyban lesz min, max, avg, stb tehat minimalis a footprint. Mig ebben az esetben minden egyes funkciohoz tartozni fog egy sajat osztaly.

-
Big Data trendek 2016

Ott rontotta el az egészet, hogy a problémát rosszul fogta fel.
Például az Utility classoknak van értelme, csak éppen a megvalósításuk rossz.
Az, hogy van egy StringUtils classod, tele statikus függvényekkel, az rossz. Nem tesztelhető a StringUtilst-t közvetlenül hívó kód helyessége a StringUtils helyessége nélkül, nem helyettesíthető más implementációval, stb.
Egy statikus függőséget hoz létre, és kívülről, az API-ból nem látszik.
Például van egy X Class. Megnézed a konstruktorád, megnézed az összes metódusát. Láthatod, hogy ennyi az információ, ami "befolyik a rendszerbe".
Bárki, aki statikus metódust hív, hazudik arról, hogy mik a függőségei.

Könnyen helyettesíthető ez azzal, ha minden utility class utility metódusa nem static, hanem sima példánymetódus.
A DI container majd szépen létrehozza az utility classokat singletonként, amelyik osztályodnak meg szüksége van az adott utility classra, majd kér egyet a konstruktorban, vagy az adott metódus paraméterében. A DI container meg beinjektálja és mindegyik szépen megkapja ugyanazt az utility classt.
Ezek az utility classok azért léteznek, mert a primitív típusaink nem objektumok, vagy éppen az API-juk hiányos (mint pl. a java File, String API-ja csomó hasznos metódust nem tartalmaz). A statikus metódus az API hiány egyik jele. És ha mondjuk van egy


public static List<String> getLines(File txtFile);

metódus, az azt jelenti, hogy a File absztrakció rossz, vagy hiányzik egy absztrakciónk. Mert ez a metódus a File osztályhoz, vagy annak egy gyerekosztályához kéne tartoznia.
A megoldás itt az, hogy a File-nak vagy adunk egy


public List<String> getLines()

metódust, vagy létrehozunk egy TextFile osztályt, ami mögött egy File példány van, és ennek a TextFile osztálynak majd lesz szépen egy getLines() metódusa már.
Ennyi.

Az utility classokat így kellene felbontani/megszüntetni.
Ami ő ír (FileLines meg ilyenek) az totális hülyeség szerintem.

Az ő problémájára inkább a következő a megoldás (csak az API):


class TextFile {
 TextFile(File backed); 

 public List<String> getLines();

 public void addLine(String); // inserts a line at the current position
 public void addLineTrimmed(String) // inserts a line at the current position, trimming the input
 public void addAllLines(List<String>); 
 public void addAllLinesTrimmed(List<String>);
 public void addAllLines(TextFile textFile);
 public void addAllLinesTrimmed(TextFile textFile);
}

Stb.

Ha jól értem, akkor a probléma talán leginkább az, hogy a java nyelvben (és úgy alapból) nincs DI, tehát ha a problémát DI nélkül kellene megoldani, akkor vagy úgy lehetne, hogy
mindig csinálunk egy objektumot, pl.

StringUtils stringUtils = new StringUtils();

if (stringUtils.isAlphaNumeric(myVariable)){
..
}

Csak ez baromi zavaró a könyvtárat HASZNÁLÓ szempontjából.

Mindent ésszel kell csinálni, és nem csak végletekben gondolkozni.
A stringUtils azt pl tipikusan nem fogod mockolni, azt ő úgy használja ahogy akarja, és akár a saját privát fv-nek / osztályának is tekintheted. Nem hazudik róla mert nem kell tudnod, ahogy pl a Collection v a Math fv-eit
se fogod tipikusan mockolni. Ezek a belső megvalósításhoz tartozhatnak. Nyilván lehet olyan util osztály ami vmi külső állapotot változtat vagy attól függ (pl az idő vagy random szám), annak leválaszthatónak kell lennie.
De értelmes szintig kell elmenni, és ez meg cél, projekt, erőforrás és idő függő.

Ezzel meg az a baj, hogy a sorokra bontás implementációját a fájlhoz kötöd (kivéve, ha az egész csak beleprokszizza a funkconalitást a StringUtils-ba :-). Tesztelni még csak-csak lehet, mert a JVM valahogy lehetővé teszi a File típusú objektum mockolását is, de élesben csak fájlon használhatod. Nekem semmi bajom a FileUtils, StringUtils, stb megoldással. A problémák szerintem mondvacsináltak, a megoldások pedig borzalmasan mesterkéltek.

Gyakorlati problémát ott jelent, hogy amikor tesztet akarsz rá írni, nem tudod, hogy kell neked egy működő, felkonfigurált Util class a test setuphoz.
" Tesztet addig is tudsz írni, maximum annyit írsz a függvénybe, hogy return 0, vagy return null."
Melyik függvénybe? Lehet, hogy nincs is hozzáférésem az Util class forrásához.
Másrészt ha az Util forrásához hozzáférek, és return null-lal implementálom, akkor lehet, hogy az osztályom unit tesztje elhal, mert le sem fut az osztályom amúgy helyes kódja.
Valamint: amint az Util implementációja módosul (elkészül), módosítanod kell az Utilt használó osztályok unit tesztjeit. Ez megint ellentmond a unit teszt fogalmának.

"Ráadásul sokszor - majdnem mindig - ugyanaz az ember fejleszti a UTil-t és a tesztet."
Kivéve amikor nem. Az ilyen rossz feltételezések miatt keletkeznek rossz szokások.

Hogy legyen egy példa:

int myMethod(int x, int y){

int z = Util.add(x, y);

// varazslat

return z;
}

Ha az Util kód nincs kész, akkor hogyan akarod tesztelni a saját kódodat, ami az add method eredményére épül?

@Inject
Util util;

int myMethod(int x, int y){
int z = util.add(x, y);

// varazslat
return z;
}

Errol van szo. Es be tudok injektalni a tesztben egy olyan Util-t, ami mock implementacio, hogy tudjam verifikalni, hogy mi tortenik vele, milyen metodusat hanyszor es milyen parameterrel hivta meg a hivo kod.
Epp ezert az Util.add() jellegu hivasok tonkreteszik a unittesztelhetoseget.

Ha az Util meg nincs kesz, attol meg csinalhatok olyan mock implementaciot, amirol tudom, hogy X bemenetre Y kimenetet kell adjon.
Peldaul csinalok egy MockUtil-t, aminel azt mondom, hogy ha (0,0) a bemenet, legyen 3 a kimenet.
Majd megnezem, hogy amikor a myMethod meghivja az addot (0,0) parameterekkel - ezt tudom kontrollalni, hiszen a myMethod szamara en adok meg minden infot, es tudom verifikalni, hiszen en adom meg az Util implementaciot - es visszakapja az eredmenyt (amit megintcsak en kontrollalok, mert en adom az Util implementaciot), akkor azzal helyesen banik.


@InjectMocks
private Flow2Initializer underTest;

@Mock
private EventBus reactor;

@Before
public void setUp() {
underTest = new Flow2Initializer();
MockitoAnnotations.initMocks(this);
}

@Test
public void testInitialize() {
underTest.init();
verify(reactor, times(1)).on(any(Selector.class), any(Consumer.class));
}

A teszt egesz egyszeruen a legutolso sorban megvizsgalja az underTEst.init() hivta-e a reactor.on() metodust, de ehhez eleg, ha a EventBus interface megvan, az implementacio lenyegtelen.
-
Big Data trendek 2016

mhmxs peldaja nagyon jo.
De peldaul:

Tegyük fel, hogy van ez üzleti szabályod, miszerint ha az egyik műveletem eredménye 0, akkor nem kell csinálni semmit, míg ha nem 0 (hanem tetszőleges érték), meg kell hívni egy másik komponenst az eredmény duplájával.

Ekkor egy osztályod, amely megvalósítja ezt az üzleti szabályt.
Mi kell a szabályhoz?

  1. A rendszer, amit tesztelek
  2. A másik komponenst, akit a rendszer meghív
  3. Az eredménykód.

Mivel a szabály kétféle viselkedést ír le, két tesztünk lesz.
Az egyik esetben a 0, a másik esetben egy biztosan nemnulla, de tetszőleges értékkel hívjuk meg.


class Test() {

class MyClass sut; // system under test
class MyCollab collabMock; // collaborator

@Before
public void setUp() {
sut = new MyClass();
collabMock = mock(MyCollab.class);
sut.setCollab(collabMock); // I don't like injectmocks.
}

@Test
/**
* Test the business rule, that when we are called with a 0 result code, the collaborator is not called at all.
*/
public void testZeroResult() {
 // given
 int resultCode = 0;
 // when
 sut.call(resultCode);
 // then
 verifyZeroInteractions(collabMock);
}

@Test
/**
* Test the business rule, that when we are called with a non-0 result code, the collaborator is called with the double of the result code.
*/
public void testNonZeroResult() {
 // given
 int resultCode = 100; // ensure it is positive 
 // when
 sut.call(resultCode);
 // then
 verify(collabMock).collabMethod(Matchers.eq(2*resultCode));
}

}

Ez szépen ellenőrzi azt a business logikát, hogy ha resultCode nem-nulla, nem szabad hívni a collabMock-ot egyáltalán - nincs vele interakció.
Ha pedig nem 0, akkor mindenképpen a resultCode kétszeresével hívjuk a collaboratort.

Ami itt a lényeg:
Az osztály implementációján kívül nem teszteljük a rendszer többi komponensét, minden ki van mockolva.
Tehát a tesztünk ténylegesen csak azt ellenőrzi, hogy az osztály implementálja-e ezt az amúgy primitív üzleti logikát.

azt nem tudod tesztelni, aki az Utilt felhasználja

Nem a Utilt használják, hanem az add függvényt, a Util az gyakorlatilag csak namespace. Az add függvény meg egy pure függvény, ami a tesztelhetőség szempontjából a legjobb (nézz utána a funkcionális programok tesztelhetőségének).

Ha az Util static, akkor nem tudod tesztelni azt az interakciót, amikor az Utilt a hívó kód meghívja, miszerint helyes paraméterekkel hívja meg.

Rossz kód rossz tesztelése. Itt a tesztelésnek semmi köze sincs az Util.add függvényéhez, itt a paramétereit akarod tesztelni.

Igen, azt akarom tesztelni, hogy a paraméterei jók. Viszont ha a kódom közvetlenül hítja az Util.akármit()-t, akkor azt nem tudom verifikálni, hiszen nem tudom proxyzni a dolgokat.

Tudom, hogy a pure function jó dolog, nem is erről van szó.
Viszont én most nem a pure function-ömet akarom tesztelni, hanem egy osztályom interakcióit más osztályokkal.
És az interakció attól még lehet helyes, hogy azok az osztályok, akikkel kapcsolatban vagyok, önmagukban nem helyesek.

"Itt a tesztelésnek semmi köze sincs az Util.add függvényéhez"
Ahhoz valóban nincs, de ahhoz, hogy verifikálni tudjam a dolgot a Util módosítása nélkül (mert ettől is unit teszt valami), ahhoz az kell, hogy az Utilt tudjam mockolni, és a mockolt példányt átadni a tesztelendő osztálynak.
De ha a tesztelendő osztály direktben hívja meg az Utilt, és nem lehet neki átadni azt, akkor meg vagyok lőve, nem tudom ellenőrizni az interakciót.

Ha Mock-ot (Stub-ot) használsz, akkor rosszul tervezett a kódod.
A fenti esetnél is megmutatkozik két fő probléma:

  1. Az implementációt teszteled. Tudod, hogyan van a tesztelendő kódod implementálva és eszerint írod a tesztet.
    Itt meg van hívva az Util.add függvény és erre létrehozol egy Mock-ot, hogy teszteld jó paraméterekkel hívják-e meg az Util.add-ot.
    Ha korrekt módon változik az implementáció, akkor hibát jelezhet a hibátlan kódodra. A fenti példánál pl. kicseréled az Util.add(x, y) hívás x + y -ra.
    Máris nem fog meghívódni elvárt módon a Mock és hibát kapsz, holott korrekt a kódod.
  2. A tesztelés célja félresiklik. A fenti esetben a(z egyik) cél, hogy meghívódik-e az Util.add. Holott ez nem kellene, hogy célja legyen az adott tesztnek.
    Ismét, ha kicseréled az Util.add-ot x + y-ra, akkor nem is fog teljesülni egy cél és hibát kapsz.
  1. Nem, nem tudom, hogy hogyan működik az implementáció. Csak azt tudom, hogy hogyan van megtervezve, hogyan kellene működnie. Például a terv szerint szükséges lehet az Util.add meghívása mindenhol a rendszerben az x+y helyett, mert az Util.add implementál olyan dolgot, amit az x+y nem (például validál). A tesztben csak azt feltételezem, hogy az előírásoknak (specifikációnak) megfelelően működik
  2. Viszont a rendszer specifikációja azt mondja, hogy az Util.add(x,y) számítja ki az eredményt. Lehet, hogy ez x+y, lehet, hogy ez totál más. Én csak azt verifikálom, hogy a rendszer valóban az Util.add(x,y)-t használja
  3. A teszt ellenőrzi azt, hogy a rendszer valóban használja-e a kollaborátorait a rendszer designnak megfelelően (lehet, hogy nem véletlenül létezik az Util.add absztrakció, lehet, hogy több implementációja is van). Ha az Util.add-ot használó implementáció kicseréli az Util.add meghívását egy konkrét implementációra, a tesztem ezt észreveszi, és szól, hogy az üzleti logikám az elvárt absztrakciót nem használja)
  4. De, a célja a tesztnek az, hogy a metódus viselkedését leírja. Ha ennek a viselkedésnek része a collaboratorokkal való együttműködés, akkor ezt is le kell tesztelni
  5. "Ismét, ha kicseréled az Util.add-ot x + y-ra, akkor nem is fog teljesülni egy cél és hibát kapsz." Épp ezaz. Hogy a tesztemben NEM cserélem ki. Ha meg az implementációja módosul az osztálynak, aki az Utilt használja, akkor azt kideríti a teszt azonnal. Az Util implementációja akármi lehet, azt vizsgálom, hogy ezzel az implementációval jól együttműködik-e a kódom.
  6. "Holott ez nem kellene, hogy célja legyen az adott tesztnek.". De, lehet, hogy ez pontosan célja ennek a tesztnek. Mert az Util.add nem véletlenül létezik. Egy OOP rendszerben midnen osztálynak van létjogosultsága, valamely üzleti igényt modellezi. Ilyen lehet az, hogy számokat nem adhatunk csak úgy össze, hanem azokat összeadás előtt validálni kell. És akkor mindenkinek, akitől elvárjuk, hogy összeadást használ, az Util.add-ot kell használnia. És ezt szépen le is teszteljük. Hogy értsd: OOP rendszerben nem hozunk létre csak úgy Util osztályokat. Mindegyik osztálynak létjogosultsága kell legyen a domain modell alapján, meg kell testesítenie valamilyen szabályt.

Ezért unit teszt ez. A logikáját teszteljük annak, hogy helyesen összekötözött rendszerünk jól együttműködik a collaboratoraival. És ezek a collaboratorok nem véletlenül léteznek, mindegyik 1-1 absztrakciót testesít meg. Az Util.add() lehet, hogy például validálja a bemeneteit, miszerint mindegyiknek pozitívnak kell lennie, míg egy x+y nem csinálja ezt. Épp ezért létezhet az Util.add(), de ő csak egy absztrakció. Így el KELL várnom a kódtól, hogy használja, MINDIG. Ha meg nem használja, és x+y-t ír a programozó, akkor bizony ezt a teszt ki fogja szúrni.

Lehet, hogy en latom rosszul, de ez inkabb integracios teszt semmint unit teszt. Marmint, az unit teszt lenyegenek a lenyege az, hogy ellenorizzem, hogy az adott X osztalyom Y metodusa adott bemenetre adott kimenetet ad-e, vagyis prediktalhato-e a mukodese es az a mukodes megfelel-e a specifikacioban eloirtaknak. Az osztalyok, rendszerek kozotti egyuttmukodes inkabb integracios teszt, vagyis hogy az adott osztaly hogyan interaktal mas osztalyokkal.
--
Blog | @hron84
Üzemeltető macik

Nem. A unit teszt egy adott metódus előírt viselkedését teszteli le, hogy az megfelel a specifikációknak, vagy nem.
És attól egységteszt, hogy csak ezt a metódust tesztelem, azonban ennek a metódusnak minden lehetséges viselkedését.

Épp ezért kell kimockolni az összes függőséget.
Egy metódus nem csak adott bemenetre ad ki adott kiementet. Hanem dolgozhat, sőt, dolgoznia kell a bemeneti paraméterekkel (ezek a bemeneti paraméterek ezért léteznek), például a bemeneti paraméterként kapott osztályokkal, az előírt módon. El kell kérniük a bemenetektől értékeket (ez is interakció), át kell adniuk nekik kiszámított értéket (ez is interakció).

Például a metódusom paraméterül egy Loggert (akár argumentumként, akár osztályváltozóként) és egy null-t, keletkezik egy NPE, és nekem ellenőriznem kell, hogy amikor NPE keletkezik, akkor az osztály azt logolja-e a Loggeren keresztül (mert elvárt mondjuk, hogy minden kivételt logolni kell).
Ezért a tesztben adok neki egy mock Loggert, meg egy nullt, és megnézem, hogy elvártan interakcióba lépett-e a Loggerrel.
Itt bizony a logsor az előírt kiement része az előírt bemenetre.

OOP-ben nem csak X->Y típusú függvények vannak, hanem metódusok, amely más osztályokkal metódusokon keresztül interakcióba lépnek. És a unitteszt során azt vizsgálod, hogy egy adott osztályod egy adott metódusa az előírt interakciókat végzi-e el. Mert valójában ezek az interakciók azok, amelyek a metódus "kimenetei", nem csak a visszaadott érték.

Persze, amit te mondasz, azok sima procedurális X->Y típusú függvények, ott valóban azt ellenőrzöd, hogy adott kiementre adot bemenet keletkezik-e. De nem ettől unittest a unittest. Hanem attól, hogy a rendszer egy osztályának a viselkedését függetlenül teszteled a rendszer minden más osztályától.

Az integrációs teszt meg tkp. a DI container tesztje: azt teszteljük le, hogy az amúgy önmagukban jól működő osztályokat helyesen kötjük-e össze.

Nem, nem tudom, hogy hogyan működik az implementáció. Csak azt tudom, hogy hogyan van megtervezve, hogyan kellene működnie. Például a terv szerint szükséges lehet az Util.add meghívása mindenhol a rendszerben az x+y helyett, mert az Util.add implementál olyan dolgot, amit az x+y nem (például validál).

Ebben az esetben a specifikáció megadja, hogy hogyan implementáld. Ebben az esetben a logikája az lenne, hogy validál és összead (mindegy mi és hogyan), pl. int nev(int, int) throws ValidationException

Ha tesztelendő kódodban Util.add(x, y) szerepel vagy pl.:


if (x > 100) throw new ValidationException();
int result = x + y

az teljesen mindegy.

Ha a logikát teszteled, akkor azt, hogy x > 100 esetén hiba jön, más esetben x és y összege.
Ha az implementációt, akkor meg azt, hogy hívódik-e az Util.add(x, y). Ha hívódik, de nem jót számol, vagy nem jön exception, az itt most elsikkad, pedig ez lenne a lényeg.

"Ha hívódik, de nem jót számol, vagy nem jön exception, az itt most elsikkad, pedig ez lenne a lényeg."
Az az Util.nev() specifikációjának a lényege, nem az Utilt hívó kód specifikációjának a lényege, hogy az Util jól számol-e vagy nem.
Amikor az Utilt hívó kódot ellenőrzöd, neked biztosítanod kell, hogy az Util működjön - valahogy. Lényegtelen, hogy hogyan, azt az Util tesztje ellenőrzi.
Neked csak azt kell ellenőrizned (ennél a viselkedésnél, amikor is egy kódom az Utilt használja), hogy:

  1. Valóban felhasználja az Utilt.
  2. Valóban használja-e az Utillal való interakció eredményét. Ehhez neked kell kívülről a tesztből kontrollálnod, hogy az Utillal való interakció eredménye micsoda. Nem hagyatkozhatsz az Util implementációjára, attól függetlenül kell működnie
  3. Valóban lekezeli-e a ValidationException-t, ha éppen ilyen interakcióra kerül sor az Utillal

Ez nem procedurális programozás, nem valamiből kiszámítunk valamit (igen, vannak ilyen metódusok is, ezeknek primitív típusok a bemenetei mindig), itt legtöbbször objektumok közötti elvárt interakcióról van szó.

Hogy még egyszer:
Igen, a logikát teszteljük.
És ha a logika előírja, hogy márpedig az Utilt használni KELL (mert ez az üzleti logika), akkor azt tesztelni kell.
Van, hogy a logika annyit ír elő, hogy "Ebben a lépésben két számot össze kell adni, meg kell szorozni kettővel. Ha bármely szám nagyobb, mint 100, akkor validációs hibát kell dobni."
Ez esetben az util osztálynak önmagában még nincs is értelme - összesen egy helyen van ilyen feltétel az üzleti logikában, segond.
Amikor eljön az az üzleti logika is a rendszerben, hogy
"Akármikor, amikor két számot össze kell adni, azt validálni kell", akkor erre az üzleti előírásra válaszul születik meg az Util.add(), és a rendszer specifikációja (ami ideális esetben tesztekkel van formalizálva), ezt ki fogja kényszeríteni: minden metódusba belekerül az Util mint paraméter, olyan helyen, ahol az adott metódus specifikációjában két szám összeadása szerepel. És az összeadás lecserélődik Util.add() hívásra a specifikáció szerint.

És innentől kezdve mindenkitől, aki két számot ad össze a rendszerben, elvárjuk, hogy használja az Util.add()-ot. Mivel a specifikációnak a része.

Nem véletlenül bemenete az Util a metódusnak, ennek vagy az osztály adattagjai között, vagy a függvény paraméterei között jelen kell lennie, ha az üzleti logika ezt diktálja.

Persze értem a problémádat, miszerint viselkedést kellene tesztelni mindig, és jó az, ha egy adott összeadás nem használ Utilt, hanem elvégzi, amit az Util amúgy is elvégezne. Azonban nem, valójában specifikációnak megfelelést kell tesztelni mindig. És ha a rendszer specifikációja tartalmazza az Util használatát, akkor azt kell használni.
Mert mondjuk a hívó kódnak nem szabad tudnia innentől kezdve a specifikáció szerint, hogy az Util mit csinál és hogyan csinálja.

Az az Util.nev() specifikációjának a lényege, nem az Utilt hívó kód specifikációjának a lényege

Én az Utilt hívó kódról beszélek, nem az Util.nev vagy add kódjáról.
Egy függvénynek, metódusnak a lényege tömören annyi, ahogy adott inputra milyen állapotot hagy maga után, nem pedig az, hogy közben milyen függvényeket, metódusokat hív.

Közben látom, írtál még hozzá:
Azonban nem, valójában specifikációnak megfelelést kell tesztelni mindig. És ha a rendszer specifikációja tartalmazza az Util használatát, akkor azt kell használni.

Ezzel nem mondasz ellent nekem. Ezt írtam az egyik első hozzászólásomban is, hogy ha Mock/Stub-ot használsz, akkor rosszul tervezett a kódod. Rosszul tervezett kód tesztelésére ideális a Mock/Stub, de ez nem azt jelenti, hogy ugyanazt a logikát ne lehetne jól tervezett kóddal is elérni, ahol viszont már nem kell a Mock/Stub.

Én itt a magam részéről lezárom, nem hiszem, hogy már újat tudnék mondani.

"Egy függvénynek, metódusnak a lényege tömören annyi, ahogy adott inputra milyen állapotot hagy maga után"
Például az adott inputnak a specifikációja az is, hogy a megkapott Util példányból Util.add()-ot kell használni inputként. Mert valójában ez az inputja az adott lépéssorozatnak.

Például az állapothátrahagyás része az is, hogy a paraméterül kapott Loggernek elküld egy LogEntry-t.

Nem, nem az az állapothátrahagyás része, hogy XYZ helyen léteznie kell egy adott formátumú logsornak. Az egy nagyon magasszintű, külső, a rendszer egy konkrét konfigurációját is ismerő specifikáció (amely feltételezi, hogy mely Logger implementációt használjuk), aka integrációs teszt. Viszont ennek a kódnak nem kell ezt tudnia. Azt majd a logger elintézi.
Viszont a tesztnek azt kell ellenőriznie, hogy a kódnak a rendszerkonfigurációtól függetlenül működnie kell jól.

"adott inputra milyen állapotot hagy maga után" hat ha itt mint allapotra az egesz rendszerre gondolok (es nem csak arra az egy nyamvadt objektumra), akkor igen is fontos, hogy "közben milyen függvényeket, metódusokat hív". Ha van mellekhatasa egy metodus hivasanak, akkor bizony a maga utan hagyott allapotba azt is bele kell erteni, hogy milyen side effecteket okozott a rendszerunkben. Pl megfelelo notificationt kuldte-e a felhasznalonak, vagy megfelelo uzenetet loggolt-e ki, es lehetne meg sorolni.

-
Big Data trendek 2016

Pontosan és emiatt vérzik a Mock-os megvalósítás nagyon, mert ezeket szinte teljesen kiiktatja. Nem a Mock-os teszt rész a rossz, hanem amit tesztelnünk kellene, annak a megvalósítása.
pl. pseudo kód:


bool isOk = ExtService.makeThings()
if (!isOk) SmsService.notify("Error")
Date created = ExtService.getCreated()
String email = ExtService.getEmail()
DBService.store(created, email)

Hogyan unit teszteled itt, hogy jó-e a logika?
Stub-bal visszaadsz valamit a makeThings helyett, Mock-kal ellenőrzöd, ha hamisat adtál vissza, ha igazat, akkor Stubokkal adsz vissza created-et és email-t, majd Mock-kal ellenőrzöd, hogy a store-t azokkal hívja-e meg.

" Nem a Mock-os teszt rész a rossz, hanem amit tesztelnünk kellene, annak a megvalósítása."
Ja, hát jól tesztelhető kódot írni nehéz.
De attól még fenntartom, hogy szükség van mockokra. Ugyanis van, amikor nem csak primtiív adatot adsz át egy metódusnak, hanem egy classt is, hogy kezdjen vele valamit.
Ha nem kéne kezdenie vele valamit, akkor minek átadni?
Ha meg kezdenie kell vele valamit, akkor le kell tesztelni az interakciót. És ahhoz meg mockolni kell.

És amúgy igen, ha egy metódusnak az a paramétere, hogy doThis(ExtService, SmsService, DBService), akkor ez azt jelenti, hogy a doThis csinalni fog valamit ezzel a harom osztallyal.
Hogy mit kellene csinalnia, azt doThis specifikacioja tartalmazza, es ugy ellenorzod, hogy ExtService, SmsService es DBService mockokat keszitesz, majd a tesztben ellenorzod, hogy az eloirt modon viselkedett az inputokkal a metodus.
Ugyanis nem veletlenul van ez a 3 parameter a metodusban.

Ha meg kezdenie kell vele valamit, akkor le kell tesztelni az interakciót. És ahhoz meg mockolni kell.

Nem kell mockolni, csak ha van side effect, ha a class immutable, akkor nem kell mockolni. Egy BigInteger mintájú class-t nem kell mockolni, egy StringBuilder mintájút kell.

majd a tesztben ellenorzod, hogy az eloirt modon viselkedett az inputokkal a metodus.

Annak a tesztnek semmi értelme! Azt ellenőrzöd, hogy amit beállítasz az az-e, mint amit beállítottál.

"Annak a tesztnek semmi értelme! Azt ellenőrzöd, hogy amit beállítasz az az-e, mint amit beállítottál."
Nem, nem ezt ellenőrzöm.
És még mindig: szó nincs immutable classokról. Absztrakt interfészekkel való együttműködésről van szó, amelyek valahogyan viselkednek.

Igen, ahogy már írtam is, rengeteg olyan metódus van egy rendszerben, ami immutable -> immutable típusú számítást végez el. Itt nincs is mit mockolni. Azonban a rendszer minden része nem építhető fel immutable komponensekből, ezt még egyetlen FP nyelv sem tudta megoldani, mert valamikor kell kommunikálni (hohó, interakcióba lépni) a rendszer külvilágával. Na, és ez a külvilág az, amit mockolunk.

Nem, nem ezt ellenőrzöm.
Kvázi ez lesz a unit teszted:
Stub-bal visszaadsz valamit a makeThings helyett, Mock-kal ellenőrzöd, ha hamisat adtál vissza, ha igazat, akkor Stubokkal adsz vissza created-et és email-t, majd Mock-kal ellenőrzöd, hogy a store-t azokkal hívja-e meg.

Ez pedig az, amit beállítasz azt ellenőrzöd.

Na, és ez a külvilág az, amit mockolunk.

Az összes olyan osztályt (függvényt) kell mockolni, ami mutable, van side effect-je, pl. StringBuilder nem kommunikál a külvilággal és az összes ilyet mockolni kell.
Ráadásul a külvilággal kommunikálókat se kell feltétlen mockolni, ha lecserélhető tesztelhetőre.

"Az összes olyan osztályt (függvényt) kell mockolni, ami mutable, van side effect-je"
Így van, mivel ez a tesztelendő osztály számára a külvilág. Minden olyasvalaki, aki kollaborator, és változhat az állapota.
Persze szerencsés, ha a rendszerünkben kevés olyan osztály van, aminek a kollaboratorai mutable objektumot, de nem lehet elkerülni.

"Ráadásul a külvilággal kommunikálókat se kell feltétlen mockolni, ha lecserélhető tesztelhetőre."
Ezt kifejtenéd?

Lehet, hogy nem erre gondolt (és hülyeséget beszélek), de néhány ötlet:

pl. van egy lokálisan futó email szerver könyvtár (pl. http://www.icegreen.com/greenmail/), amit tesztelésre találtak ki, akkor nem kell mockolnunk, hanem "valódi" email küldést tudunk tesztelni.

Vagy ha mondjuk egy adatbázisba írunk, akkor akár azt vissza is tudjuk olvasni.

Bár ezek már integrációs tesztek, de külvilággal történő kommunikációt nem lenne érdemesebb integrációs teszt szinten tesztelni?

Külvilág alatt itt egy adott objektum külvilágát értjük: akiknek a létezéséről az objektum tud.
Ezek az objektum metódusainak (a konstruktornak is) a bementő paraméterei.
Amikor egy objektummal együttműködünk, akkor nem szabadna, hogy más helyről menjen információ a rendszerbe (pl. static singletonokból, Context objektumokból stb.). Azaz minden más, ami nem metódus paramétere az osztálynak, az a külvilág. Még egy másik objektum is, amiről nem tud.
Például képzeld el a következőt.
Van egy Foo osztály. Tartalmaz egy doIt(Bar) metódust. Tegyük fel, hogy ezt a metódust egy Whatever osztályból meghívjuk:


myFoo.doIt(myBar);

Joggal várjatjuk el, hogy a metódus végrehajtása során LEGFELJEBB a myFoo és a myBar objektumok állapota változik meg, semmi más. Azaz minden más, ami nem myFoo és nem myBar, az a doIt() során a metódus külvilága. Így kell érteni a külvilág kifejezést. :)

Persze ha Foo eközben szépen kiszól valamely Singletonnak (pl. egy EMailSystem.sendMail-llel), akkor a külvilág megváltozott - pedig a metódus hívásából nem várnánk ezt, hiszen nem adunk át neki EMailSystemet, amivel e-mailt küldhetne, ha akarna.

Külvilág alatt én nem ezt értettem, hanem az IO szempontból külvilágot, pl. adatbázis, fájl írás/olvasás, külső szolgáltatások hívása, ...
A tesztelendő osztályon kívüli, mutable osztályok, amik nem az általam értett külvilághoz tartoznak és nincs is olyan függőségük, azokat könnyen refaktorálhatjuk immutable (side effect mentes) osztályokká.

Persze ha Foo eközben szépen kiszól valamely Singletonnak (pl. egy EMailSystem.sendMail-llel), akkor a külvilág megváltozott - pedig a metódus hívásából nem várnánk ezt, hiszen nem adunk át neki EMailSystemet, amivel e-mailt küldhetne, ha akarna.

Ha paraméterben átadod, akkor sem sokkal jobb a helyzet. Ha side effect-es függvényt (metódust) hívsz, akkor azt mindenképp mockolnod kell, akár a Te fogalmaid szerinti külvilág (nem paraméterből jön), akár nem. Ha meg nem side effect-est hívsz, akkor meg semmiképp sem kell mockolni, akár paraméterben jött, akár nem.

Sőt, ha paraméterben jön egy side effect-es az még rosszabb (itt nincs "külvilág"), pl.


void formattedUser(StringBuilder sb, String id, String name) = {
  sb.append("User (").append(id).append("): ").append(name);
}

Nem tudhatod, hogy milyen állapotban kaptad a StringBuildert és főként azt sem, hogy miközben módosítod, aközben egy másik szál szintén nem módosítja-e.
A fentinél sokkal jobb ez is (itt van "külvilág"):


String formattedUser(String id, String name) = {
  StringBuilder sb = new StringBuilder();
  sb.append("User (").append(id).append("): ").append(name);
  return sb.toString();
}

Ha készítünk egy StringAppender osztályt, ami immutable és minden append függvénye egy új StringAppender osztályt ad vissza, ott mind a két esetben (paraméterként átadva, vagy nem) bátran használhatjuk és nem kell mockolnunk.

"Ráadásul a külvilággal kommunikálókat se kell feltétlen mockolni, ha lecserélhető tesztelhetőre."
Ezt kifejtenéd?

Pl. ha van egy függvényed, ami egy id alapján visszaadja a felhasználót: User getUser(id).
Ha ez a függvény a bemeneti paramétered (vagy ilyen interfacet implementáló osztály), akkor teszt esetén könnyen kicserélheted egy olyanra, ami fiksz User-t ad vissza.

"Nem kell mockolni, csak ha van side effect" ami az esetek 80%-a az en tapasztalatom szerint. Ugyanis ha 1 side effect van a metodusban, akkor az egesznek van side effectje. A pure metodusok a ritka kivetelt kepezik, termeszetesen ott bemeneti parameter alapjan vizsgaljuk a visszateresi erteket. Az ember elkezd mondjuk Haskellt tanulni, aztan nagyon hamar azon kapja magat, hogy monoidokrol meg monadokrol olvas, mert elobb utobb a legbutabb alkalmazas is olvas a konzolrol, lekerdez egy datumot, kiir vagy beolvas egy filet. Helyes tervezessel minimalizalni lehet ezeknek a koret.

-
Big Data trendek 2016

Bar erdetileg statikus tesztelesrol beszeltunk, en ugy ertettem, hogy a szalba melyedve mar altalanossagban beszelunk a tesztekrol, en ennek fenyeben irtam amit irtam. De a konkret esetben mit kell tesztelni?
- tesztelni kell, hogy SmsService.notify() hivodik-e ha nem isOk "Error" parameterrel
- es tesztelni kell, hogy DBService.store() hivodik-e
- ha mondjuk azonos tipusu a ket bemeno parameter, akkor teszteled, hogy megfelelo helyre

Erteni velem a problemadat, megpedig azt, hogy a tesztnek ismernie kell az alany belso szerkezetet. De ahhoz pure-nak kell lennie, hogy erre ne legyen szukseg. Ha ez nem igaz, akkor valami technikaval, plain oroklessel, vagy mockkal (ami szinten egy orokles csak dinamikusan van generalva ;)), vagy a dependenciakat kell bemenetkent adni reszletek. A kerdes az, hogy melyiket egyszerubb implementalni, melyik szemlelettel tud jobban azonosulni a fejlesztocsapat, melyik nyelvkozelibb. Nincs egyikkel se baj, nekunk nagyban egyszerusitik az eletunket a mockok. Sokszor eleg az a bonyolultsag, ami van, nyug lenne meg a tesztek miatt is absztrahalni az osztalyokat.

-
Big Data trendek 2016

"Erteni velem a problemadat, megpedig azt, hogy a tesztnek ismernie kell az alany belso szerkezetet."
Nem. Azt kell ismernie a hivonak (a tesztnek), hogy az alany a parametereivel helyesen jart-e el. Mert nem veletlenul vannak ott azok a parameterek.
Vannak ugyebar immutable parameterek, ott nem is vizsgalunk soha semmit, minek, ugysem tud valtozni az allapota.
Viszont vannak mutable parameterek, valtozik az allapotuk - meghozza jol definialt modon.
Amikor atadjuk egy tesztelendo objektumnak, akkor elvarjuk, hogy pontosan ugy valtoztassa meg a mutable parameterek allapotat, ahogy mi elvarjuk - lehet, hogy eppen az az elvaras, hogy ne valtoztasson, de ezt is lehet verifikalni.
Hiszen a metodus "visszateresi erteke" (azaz a futas eredmenye) az a mutable parameterek allapotaban kezdemenyezett valtozas is.

Persze, csak attol meg az is egy side effect, hogy megvaltoztatta az allapotat, es tudod kell az alany belso mukodeset (es pl mockolhato). Ugyanugy azt a belso mukodest is ismerned kell, hogy nincs side effect, csak az lenyegesen egyszerubb. Amugy a Haskellesek is azt mondjak, hogy nincs pure alkalmazas, csak ok a monadokkal egyertelmove teszik, hogy adott metodusnak van, de ha tesztelni akarod, hogy jol vegezte-e a dolgat, tudod kell mit csinal. Es korbeertunk.

-
Big Data trendek 2016

Egyeb: code review-n megmondjak/kitalaljuk :D
--
"Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live." John F. Woods

Get dropbox account now!

Csak attol fugg, hogy milyen adatokon dolgozik: ha nem a class adataival, akkor nincs helye a classban. Ha nem dolgozik objektum/osztalyszintu adatokon (ie. pure function) akkor nyelvtol fuggoen (mert ez azt hiszem nem volt definialva) legyen egy fuggveny egy kulon namespace-ben, modulban, classban vagy amit a valasztott nyelv lehetove tesz.

Helyes OOP kodot irni nem annyira nehez, csak erdemes egy par dolgot szem elott tartani (hint: http://www.butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod)

Rendes OO nyelvben nincs függvény :) :)

---------------------------------------------
Arch - android - Smalltalk - flamenco

Nincs olyan opció, hogy egyáltalán nem használok statikus metódust. A privát/publikus statikus metódusokat nem minden OOP programnyelv támogatja, ellenben ahogy persicsb írta, tiszta szívás, hogy nem tudod beinjektálni. Ez akkor probléma, ha a factory komplex és rahedli dolgot kell átadni inicializáláskor, amiről a hívó metódus nem biztos, hogy tud. Így pusztán azért, mert nem egy hagyományos objektum, megy a szívás. Nem tudok jó indokot, miért jó, hogy van egyáltalán statikus metódus. A singletonról meg hamar leszokik az ember párhuzamos programozás esetén. :)

Totalisan tokmindegy. Messze a legfontosabb egy kodon a karbantarthatosag (ami tobbe-kevesbe egyenlo az olvashatosaggal, erthetoseggel). Amig ez teljesul, azt hasznal barki, amit akar.

Az a gond azokkal a területekkel, amiket próbaképp írtam, hogy nem tudsz minden esetben elvárt eredményt meghatározni, pl. sztochasztikus rendszerek. Egy jó írás, ami annak idején segített rávezetni áthidaló megoldásra: https://homes.cs.washington.edu/~borning/papers/sevcikova-issta-2006.pdf
--
https://janosfeher.com

Egyetertunk, a teszt kod is resze a kodbazisnak. De ha mindenfelekeppen be kell mocskolnom a kezem, akkor nem kerdes, hogy hol lesz a "fekete magia". Hajlando vagyok a tesztelhetoseg oltaran aldozni, amig az eles kod karban tarthatosagat nem veszelyezteti. Ha igen, akkor felhasznalok minden letezo nyelvi eszkozt _a tesztben_.

-
Big Data trendek 2016

Szerintem oda valo es olyan modon, ahova logikailag tartozik. Ez a programtol fuggoen lehet eltero helyen.
Peldaul el tudom kepzelni, hogy egy sigmoid fuggveny (ami gyakorlatilag egy szambol egy masik szamot csinal mindenfele kulso adat nelkul) egy programban a Math class egy statikus fuggvenye legyen a szogfuggvenyek mellett, egy masikban globalis fuggveny, egy matematikai programban valami sigmoid-szeru fuggvenyek koze tegyek kulon namespace-be, egy mesterseges intelligenciat hasznalo programban meg a neuralis halo osztalyahoz tartozzon (mert legtobbszor ott hasznaljak).

--
"Ne kúrd el mégegyszer a karakterkészletet, mert akkor ez a file is conflictolni fog a merge serveren és ez azért eléggé bosszantó!"

Kovezes bait: oda keruljon, ahol a legtobbet fogjak hivni. Ha van egy muvelet, amit szeretnek kicsomagolni, mert peldaul az adott osztalyon belul gyakran meghivom, de sehol mashol nincs ra szuksegem, akkor hiaba nem dolgozik az osztaly adataival, siman lehet az osztaly (privat) metodusa. Meg csak nem is feltetlen kell statiknak lennie. Ez lehet akar valami adatkonverzio is, peldaul celsiusbol fahrenheitbe atszamolas, mivel nekem peldaul fontos a DRY elv, siman kipakolom az ilyet egy privat fuggvenybe, mert akkor kisebb az eselye, hogy elbokom a kepletet.
--
Blog | @hron84
Üzemeltető macik

neha kenyelmes a static, ha egyutt tudok elni vele, hogy tesztelhetetlen lesz az a kodresz, de amugy kill it with fire :)

--
NetBSD - Simplicity is prerequisite for reliability

Sokan írták, hogy mekkora ördögi dolog a static member függvények használata, és mindenki Java példákat hozott.

Nem tudom, C++-ban gyakran láttam nem factory, de logikailag odaillő public static metódusokat, egészen jó könyvtárakban is. Pl. Qt:

http://doc.qt.io/qt-5/qimage.html#static-public-members
http://doc.qt.io/qt-5/qfiledialog.html#static-public-members

Különösen a második példa nagyon egyértelmű és szép is szvsz.

Ez ellentmondásban van ezzel, amit kicsit feljebb írtál:
Igen, ahogy már írtam is, rengeteg olyan metódus van egy rendszerben, ami immutable -> immutable típusú számítást végez el. Itt nincs is mit mockolni. Azonban a rendszer minden része nem építhető fel immutable komponensekből, ezt még egyetlen FP nyelv sem tudta megoldani, mert valamikor kell kommunikálni (hohó, interakcióba lépni) a rendszer külvilágával. Na, és ez a külvilág az, amit mockolunk.

Itt pure function-ökről van szó, immutable -> immutable típusú számítást végez. Itt nincs is mit mockolni..

ÖÖÖ...aki felhasználja ezt a statikus metódust, az baromira nem pure function, mivel meg fog jelenni egy szép UI ablak, amikor felhasználja azt - csodálatosan megváltozik a rendszer állapota, egy olyan side effectnek köszönhetően, ami csak azért vált lehetségessé, mert a metódus, aki hívta ezt a dialog megjelnító kódot, nem mondta el magáról az API-ban, hogy dialog megjelenító kódot fog hívni.

Történt a külvilággal (a tesztelendő objektum számára külvilággal) egy olyan interakció, amit nem tudsz ellenőrizni az implementáció ismeret nélkül az, aki a tesztelendő objektumot hívta.
Miért kéne nekem tudnom, hogy meg fog jelleni egy dialog? A metódus API-ja nem mondta ezt meg. Ő csak hívott egy statikus metódust, aminek eredményeképpen a user képébe kerül egy dialog.
Ez mitől lenne pure function? Ne vicceljünk már. Az a kód, ami egy statikus dialog készítő kódot hív, eleve nem pure function.

A témához ment. "Joejszaka" megadott két linket példákkal. Nem néztem utána mindnek, elhittem, hogy mind pure function, de valóban nem mind az. Első linknél lévők mind azok, a másodiknál levők egyike sem az.
Így az én hozzászólásomat úgy kell tekinteni, mint ami csak az első linknél levő példákra vonatkozik. Így már érthető?

Attól függ milyen nyelv, illetve milyen OO.

Addig nem érdekel amíg megértem mit csinál. :)

(x) az akkor se legyen static method, kivéve ha a fgv tervezett célja hogy példány nélkül hívogassák

Másodikat jelöltem. Elsőt azért nem, mert leszoktam már kódminőség kapcsán a "mindig"-et tartalmazó mondatokról, mert általában 2-3 héten belül szembejön velem egy ellenpélda :)

A "menjen global függvénybe"-t azért nem jelöltem, mert ez nyelvfüggő, hogy van-e rá lehetőség. Én mondjuk főleg java-ban fejlesztek, ezért globális/csomagszintű függvényeket más nyelvekben is csak viszonylag ritkán jut eszembe használni.

"leszoktam már kódminőség kapcsán a "mindig"" hatalmas +1. Addig minden szep, amig uj cuccot kell fejleszteni, de amikor meglevo architekturaba kell beilleszteni valami ujat, vagy refaktoralni, akkor sokkal konnyebben megalkuszik az ember fia valami kozeputban.

-
Big Data trendek 2016