OOP a pravidla pro konstruktor

anonym

Re:OOP a pravidla pro kontruktor
« Odpověď #30 kdy: 03. 06. 2018, 16:56:08 »
Když si v testu zavoláš new A(), jsi v zadeki, protože se ti zavolá B a ty to nijak nemůžeš ovlivnit.
Ano, protože voláš B když ho volat nechceš. S mockováním to nesouvisí s testováním také ne.

Proto už z hlediska testování je na nic, když se v konstruktoru spustí nějaký návazný proces - ten musí být v metodě.
Když bude v metodě, tak z hlediska testování bude úplně stejný problém. Z hlediska mockování taky. Vůbec nic jsi tím nevyřešil.

A než přemýšlet, co se stane, tak raději budu dávat do konstruktoru pouze inicializace objektu.
Je mi líto, ale programování je o přemejšlení.

Když objekt nejprve inicializuju, pak v něm můžu mockovat potřebné věci. A pak terpve spouštím metodu, kterou chci testovat.
Ne, nemůžeš. Tím, že jsi to přestěhoval z konstruktoru do metody jsi vůbec nic nevyřešil. Furt ti to vypálí díru do zdi, furt to nemůžeš mockovat, furt to nejde testovat. Ten problém je totiž jinde.


Jestli ty se jen blbě nevyjadřuješ.

Dávat logiku do konstruktoru je IMHO naprosto v pořádku. Samozřejmě je nesmysl, dát do konstruktoru logiku "pal", když tu logiku chceš vykonat až zavoláním metody inst.pal(). Ale to absolutně nijak nesouvisí s pravidlem, který jsi postuloval na začátku. To považuju za zcela chybné.

Podle mě nemáš pravdu a přijde mi dost podivné to, co píšeš - jako kdybys to nikdy nedělal. Třídu B, která vypálí díru do zdi, budu mít jako private field v A. V testu v konstruktoru dojde pouze k inicializaci třídy A. Následně si namockuju tu třídu B v A (vytáhnu si ten private field reflexí a dám mu tam namockovanou verzi). Až potom zavolám a.hvezdaSmrti321Ted();, která uvnitř zavolá fake objekt this.b.lazerPal();.


BoneFlute

  • *****
  • 1 859
    • Zobrazit profil
Re:OOP a pravidla pro kontruktor
« Odpověď #31 kdy: 03. 06. 2018, 21:19:02 »
Když si v testu zavoláš new A(), jsi v zadeki, protože se ti zavolá B a ty to nijak nemůžeš ovlivnit.
Ano, protože voláš B když ho volat nechceš. S mockováním to nesouvisí s testováním také ne.

Proto už z hlediska testování je na nic, když se v konstruktoru spustí nějaký návazný proces - ten musí být v metodě.
Když bude v metodě, tak z hlediska testování bude úplně stejný problém. Z hlediska mockování taky. Vůbec nic jsi tím nevyřešil.

A než přemýšlet, co se stane, tak raději budu dávat do konstruktoru pouze inicializace objektu.
Je mi líto, ale programování je o přemejšlení.

Když objekt nejprve inicializuju, pak v něm můžu mockovat potřebné věci. A pak terpve spouštím metodu, kterou chci testovat.
Ne, nemůžeš. Tím, že jsi to přestěhoval z konstruktoru do metody jsi vůbec nic nevyřešil. Furt ti to vypálí díru do zdi, furt to nemůžeš mockovat, furt to nejde testovat. Ten problém je totiž jinde.


Jestli ty se jen blbě nevyjadřuješ.

Dávat logiku do konstruktoru je IMHO naprosto v pořádku. Samozřejmě je nesmysl, dát do konstruktoru logiku "pal", když tu logiku chceš vykonat až zavoláním metody inst.pal(). Ale to absolutně nijak nesouvisí s pravidlem, který jsi postuloval na začátku. To považuju za zcela chybné.

Podle mě nemáš pravdu a přijde mi dost podivné to, co píšeš - jako kdybys to nikdy nedělal. Třídu B, která vypálí díru do zdi, budu mít jako private field v A. V testu v konstruktoru dojde pouze k inicializaci třídy A. Následně si namockuju tu třídu B v A (vytáhnu si ten private field reflexí a dám mu tam namockovanou verzi). Až potom zavolám a.hvezdaSmrti321Ted();, která uvnitř zavolá fake objekt this.b.lazerPal();.

Už ti rozumím. K tomu ti mohu říct jediné - nedělej to tak.

Private field je private. Z pohledu zvenčí neexistuje. Tudíž se netestuje. Ani reflexí, ani nijak. Tudíž to co děláš je špatné, ne proto, že by byla špatná logika v konstruktoru, ale proto, že je špatné pokoušet se nějak nabourávat do privátních fieldů, které jsi jakkoliv nasetovat - ať už v konstruktoru, nebo jinak.

Vlastně máš pravdu - tohle jsem nikdy nedělal. Ani by mě to nenapadlo :-) (Dobře, kecám, určitě kdysi dávno mě to napadlo, a možná jsem to i zkusil, ale pak mě klusic sežrali, co že to dělám za prasečiny.)

BoneFlute

  • *****
  • 1 859
    • Zobrazit profil
Re:OOP a pravidla pro konstruktor
« Odpověď #32 kdy: 03. 06. 2018, 21:30:09 »
Mám pocit, že se tu anonym poněkud nešťastně vyjadřuje. Takže jen uvedu, co si představuju pod logikou v konstruktoru:

Kód: [Vybrat]
class A:
    private engine
    constructor(strategy, opts):
        if strategy AND len(opts) > 1:
             this.engine = strategy.many(opts)
        elif strategy AND len(opts) = 1:
             parser = new Parser()
             this.engine = strategy.many(parser.parse(opts))
        elif strategy AND empty(opts):
             throw new Exception()
        else:
             this.engine = new D()

    invoke(args):
        this.engine.invoke(args)

Sorry, nenapadlo mě teď z fleku nic složitějšího. Ale třeba anonym uvede, co si představuje, že za logiku do toho konstruktoru nepatří.
« Poslední změna: 03. 06. 2018, 21:34:23 od BoneFlute »

Puritan

Re:OOP a pravidla pro konstruktor
« Odpověď #33 kdy: 03. 06. 2018, 22:03:10 »
Konstruktor by nemel obsahovat zadnou logiku (prestoze obcas je jednodussi podlehnout lennosti a neco do nej dopsat). Od konstruktoru se ocekava, ze inicializuje objekt, tj. priradi objekty privatnim fieldum a zkontruluje, ze tyto fieldy nejsou null, kdyz byt nemaji. Kdyz se pisou nejdrive testy a teprve pote implementace (napr. TDD), primo vas to k tomu vede. Idealni stav je, kdyz se do kontruktoru skrze rozhrani injektuji konkretni implementace z vnejsku. Sam objekt potom neni zavisly na konkretnich implementacich jednotlivych fieldu. Je to dobry zpusob, jak psat udrzitelny kod.

Situaci, kterou popisujete v prikladu (class A), lze resit takto:
1) interface A - pouze public rozhrani
2) class AbstractA (private, nebo package private - viditelna pouze ve factory)
3) class FactoryA, ktera v ruznych metodach vraci konkretni parametrizace uplne nastavene A.

Vhodnym pojmenovanim metod FactoryA, ktere budou poskytovat plne inicializovane rozhrani A, poskytnete uzivatelum factory prehledny a snadno pouzitelny nastroj pro vytvoreni instanci A. Dana Factory muze skryvat ruzne strategie parametrizace puvodni tridy.

Povzdech: Je skoda, ze se na skolach dodnes neuci technologie programovani. Potrebne zkusenosti clovek ziska az po delsi dobe: praci na legaci kodu, kde nejprve vidi priklady a dusledky spatne psaneho kodu, pote novych projektech, pod vedenim kvalitnich team-leaderu.

Kit

Re:OOP a pravidla pro kontruktor
« Odpověď #34 kdy: 03. 06. 2018, 22:13:23 »
Kód: [Vybrat]
$object->validate($validator);

Jaký je důvod, aby validaci řídil objekt, který je validován a ne validátor? Tzn. proč bys dal metodu validate() do objektu a ne do validátoru?

Ze sémantického hlediska mi přijde přirozenější a správnější říct: „validátore, zkontroluj mi tenhle objekt“ a ne „objekte, běž se nechat zkontrolovat k validátorovi a pak mi řekni, jak jsi dopadl“.

Důvodem, proč to dělat obráceně by mohla být viditelnost polí, ale to podle mého není platný argument, protože by to narušovalo zapouzdření – pokud se objekt navenek (tzn. public a protected pole a metody) chová správně, tak bychom ho měli považoval za validní – co si dělá uvnitř v privátních polích a metodách, to je jeho věc a nás to nemusí zajímat.

Objekt si uvnitř zavolá ten validátor a předá mu takové hodnoty, které chce zvalidovat. Klidně i několikrát.

Analogická je i státní kontrola. Nekontroluje přímo, ale uloží povinnost provozovateli, aby si nechal zkontrolovat vozidlo a poskytne mu k tomu seznam STK.

Podobně funguje i formátování výstupu:
Kód: [Vybrat]
$object->format($style);


Bill

Re:OOP a pravidla pro kontruktor
« Odpověď #35 kdy: 03. 06. 2018, 22:17:50 »
Když máte složitou logiku konstrukce objektů, vytvořte statickou metodu s příhodným názvem, třeba LoadFromConfigurationFile.

Pokud myslíte statickou metodu ve stejné třídě, tak i s tímto přístupem silně nesouhlasím. V rámci jedné třídy se pak míchají dvě zcela odlišná rozhraní - jednak "obecný" interface objektu a dvak způsob konstrukce objektu, který je zcela poplatný  konkrétní aplikaci ve které se objekt používá a jejím pravidlům - formátu konfigurace, mapování konfigurace na parametry, apod. Nehledě na to, že většinou nechci, aby mi konstruktor každého dílčího objektu načítal znovu a znovu  konfigurační soubor a řešil problémy s tím spojené, konfigurační soubor načtu na začátku aplikace jednou a v případě problémů se do konstrukce objektů, které jsou na něm závislé, vůbec nepouštím.

Nesouhlasím s tím přesto, že se uvedený postup asi občas využívá - LoadFromConfigurationFile, NewFromCommandLineOptions, ....

Jestli se vám to nelíbí, můžete si samozřejmě vytvořit jakoukoli další třídu, takže budete volat třeba MyClassBuilder.LoadFromConfigurationFile. Kdyby mi to vývojář napsal, asi bych se ho zeptal na praktický význam (žádný není), ale nijak bych mu v tom nebránil. Nikdy jsem zatím nenarazil na žádné reálné potíže s umístěním podobných metod přímo do třídy, definující můj objekt.

BoneFlute

  • *****
  • 1 859
    • Zobrazit profil
Re:OOP a pravidla pro konstruktor
« Odpověď #36 kdy: 03. 06. 2018, 23:12:49 »
Konstruktor by nemel obsahovat zadnou logiku (prestoze obcas je jednodussi podlehnout lennosti a neco do nej dopsat). Od konstruktoru se ocekava, ze inicializuje objekt, tj. priradi objekty privatnim fieldum a zkontruluje, ze tyto fieldy nejsou null, kdyz byt nemaji.

Tomuto já říkám legaci kód :-)

anonym

Re:OOP a pravidla pro kontruktor
« Odpověď #37 kdy: 04. 06. 2018, 09:27:36 »

Podle mě nemáš pravdu a přijde mi dost podivné to, co píšeš - jako kdybys to nikdy nedělal. Třídu B, která vypálí díru do zdi, budu mít jako private field v A. V testu v konstruktoru dojde pouze k inicializaci třídy A. Následně si namockuju tu třídu B v A (vytáhnu si ten private field reflexí a dám mu tam namockovanou verzi). Až potom zavolám a.hvezdaSmrti321Ted();, která uvnitř zavolá fake objekt this.b.lazerPal();.

Už ti rozumím. K tomu ti mohu říct jediné - nedělej to tak.

Private field je private. Z pohledu zvenčí neexistuje. Tudíž se netestuje. Ani reflexí, ani nijak. Tudíž to co děláš je špatné, ne proto, že by byla špatná logika v konstruktoru, ale proto, že je špatné pokoušet se nějak nabourávat do privátních fieldů, které jsi jakkoliv nasetovat - ať už v konstruktoru, nebo jinak.

Vlastně máš pravdu - tohle jsem nikdy nedělal. Ani by mě to nenapadlo :-) (Dobře, kecám, určitě kdysi dávno mě to napadlo, a možná jsem to i zkusil, ale pak mě klusic sežrali, co že to dělám za prasečiny.)

Už se přesně tady na tento problém vedla samostatná diskuze. Ano, private field zvenčí neexistuje, ale doprčic, NE PRO UNIT TESTY! To je úplně normální a NUTNÉ, že máš třídu a v ní jako private atribut něco, co musíš namockovat. To nemusí být laserpal, stačí aby to byl třeba Socket nebo zápis do souboru na disku!

Phi

Re:OOP a pravidla pro kontruktor
« Odpověď #38 kdy: 04. 06. 2018, 10:01:56 »
Citace: anonym
Ano, private field zvenčí neexistuje, ale doprčic, NE PRO UNIT TESTY! To je úplně normální a NUTNÉ, že máš třídu a v ní jako private atribut něco, co musíš namockovat.
Privátní field unit testy netestují a ani by neměly. Testuješ kontrakt - veřejné rozhraní a mockuješ okolí třídy: parametry konstruktoru, parametry veřejných metod, případně veřejné rozhraní jiných tříd.

anonym

Re:OOP a pravidla pro kontruktor
« Odpověď #39 kdy: 04. 06. 2018, 11:01:15 »
Citace: anonym
Ano, private field zvenčí neexistuje, ale doprčic, NE PRO UNIT TESTY! To je úplně normální a NUTNÉ, že máš třídu a v ní jako private atribut něco, co musíš namockovat.
Privátní field unit testy netestují a ani by neměly. Testuješ kontrakt - veřejné rozhraní a mockuješ okolí třídy: parametry konstruktoru, parametry veřejných metod, případně veřejné rozhraní jiných tříd.

Ano, jenže tohle ti poruší zapouzdřenost. Řekněme, že máš třídu, ve které budeš mít metodu

Kód: [Vybrat]
private zapisSpecialneDoSouboru(String info) {
    // nejaky kod
    this.bufferedWriter.write(str);
    // nejaky kod
}

Tak DOPRČIC, přece nebudeš mít v konstruktoru třídy parametr BufferedWriter jenom proto, abys to potom mohl otestovat? To je úplná kravina, porušuje to zapouzdřenost, vystavuješ ven vnitřnosti třídy. Prostě uděláš to, že bufferedWriter reflexí namockuješ!

SB

Re:OOP a pravidla pro konstruktor
« Odpověď #40 kdy: 04. 06. 2018, 11:33:10 »
Došel jsem ze zkušeností k tomu, že konstruktor je třeba výlučně používat pouze k nasetování stavu objektu.

Asi máte na mysli inicializaci.

Je chyba v konstruktoru provádět jakékoliv výpočty atp., vždycky jsem se tím dostal do problémů a musel jsem to refaktorovat. Máte stejný poznatek? A pokud ano, jak jste na to přišli? Nečetl jsem o tom nikdy v žádné literatuře a přitom mi to přijde jako důležité pravidlo.

Konstruktor je (měla by být) metoda jako každá jiná, liší se jen v tom, že vytváří instanci třídy. Že je při inicializaci třeba něco dopočítat, je běžné. Jak byste si to jinak představoval, závisejí-li vnitřní stavy na vnitřní logice?

SB

Re:OOP a pravidla pro kontruktor
« Odpověď #41 kdy: 04. 06. 2018, 11:38:47 »
Konstruktor by měl uvést objekt do výchozího konzistentního stavu. Zpravidla to vypadá tak, že pouze umístí předané parametry do lokálních atributů a hotovo. Obvykle ta data ani nevaliduji, protože v tu chvíli nevím, která pravidla k tomu budu potřebovat.

Nemusí, ale je to nejjednodušší řešení.
Ale taky může inicializace znamenat složitý výpočet, na to žádné omezení neexistuje.
Neověřením vstupů se dostáváte do té samé situace, jako když objekt neinicializujete a ponecháte jej v nějakém bordelstavu (který pak musíte pochopitelně nějak řešit).

SB

Re:OOP a pravidla pro kontruktor
« Odpověď #42 kdy: 04. 06. 2018, 11:42:14 »
best practice je nepsat konstruktory vůbec, nechat je generovat automaticky. Validace a inicializace definovat pro jednotlivé atributy. Potom máte u všech objektů standardizované parametry a chybové hlášky. V Pythonu používáme všude knihovnu attrs.

:O
Ono je vůbec nejlepší nepsat třídy vůbec a nechat je generovat celé, potom mají standardizované parametry(???) a chybové hlášky(???).

SB

Re:OOP a pravidla pro kontruktor
« Odpověď #43 kdy: 04. 06. 2018, 11:45:49 »
...Když potřebuju validovat nějaká data, udělám z té třídy pojo a validaci té třídy dám bokem do jiné třídy.

A vynalezl jste to sám, nebo někde odkoukal? Pocamrejte se. https://en.wikipedia.org/wiki/Anemic_domain_model
Takže jde to, ale stojí to přesně proti myšlence OOP.

SB

Re:OOP a pravidla pro kontruktor
« Odpověď #44 kdy: 04. 06. 2018, 11:50:41 »
Viz to Unit testování: třída bude mít private metody a já je budu muset mockovat. Jak budeš mockovat, když už při zavolání konstruktoru se ti spustí výpočetní logika?

Mock je objektem, který navenek slyší na stejný protokol, který má zastupovaný objekt. Jeho vnitřek je černou krabkou, tudíž mockování se skytými metodami původního objektu nemá nic společného! Asi proto tu druhou větu vůbec nechápu.