2016 Stack Overflow Developer Survey - eredmények

Címkék

A Stack Overflow bejelentette "2016 Stack Overflow Developer Survey" felmérésének eredményét. Az idei kérdéssort 173 orszából több mint 50 ezer tag töltötte ki. Egyebek mellett - szerintük - kiderül belőle, hogy a

  • a JavaScript maradt a világ legnépszerűbb programozási nyelve.
  • a React a leggyorsabb ütemben fejlődő technológia a Stack Overflow-n, de a Swift is robbantott. Az Objective-C visszaesett.
  • a Rust a legkedveltebb programozási nyelv.
  • a Visual Basic a legrettegettebb programozási nyelv.
  • a fejlesztők 46%-nak nincs számítástechnikai vagy azzal kapcsolatos BSc-je (Bachelor's degree), ezért a Stack Overflow szerint az a munkáltató, ami ilyen végzettséget követelne meg ha fejlesztőt keres, jobb ha átgondolja stratégiáját.
  • stb.

Az eredmények itt olvashatók.

Hozzászólások

"a fejlesztők 46%-nak nincs számítástechnikai vagy azzal kapcsolatos BSc-je (Bachelor's degree), ezért a Stack Overflow szerint az a munkáltató, ami ilyen végzettséget követelne meg ha fejlesztőt keres, jobb ha átgondolja stratégiáját."

Meg azert is kene atgondolni, mert ezeknek a legnehezebb elmagyarazni, hogy a setter lenyege a validalas, mert ok az iskolapadban csak azt lattak, hogy


private status;

public setStatus(status)
{
  this.status = status;
}

// ... (another method)
  if (newstatus >= 1 && newstatus <= 3) {
    this.setStatus(newstatus);
  }

Meg azert is at kene gondolni, mert egyed-kapcsolat modellben gondolkodnak, ha CREATE TABLE-t kell irni, ami erre tokeletesen alkalmatlan. Ha mar mindenaron rajzolni is akarnak melle, nem kellett volna ujramenni vizsgazni, ha UML-es tetelt huztak :(

Meg azert is kene atgondolni, mert olyan emberek tudnak adatbazissal kapcsolatos layerekrol nagy pofaval okoskodva beszelni, akik aztan olyan inkonzsisztens adatbazist csinalnak, hogy olyat meg ember nem b*szott.

Meg azert is at kene gondolni, mert az, hogy 7-8 tantargybol volt nekik objektum orientalt programozas, az keves volt nekik ahhoz, hogy megertsenek egy 4 soros singletonos getInstance() methodot (oke, meg kell erteni melle egy static membert is, na). :(

Meg azert is kene atgondolni, mert ezeknek van a legkevesebb fogalmuk arrol, es nekik a legnehezebb elmagyarazni, hogy valojaban mit is kene dokumentalni es mit nem.

De biztos hasznos volt levizsgazni abbol, hogy hany laba van egy elavult processzornak.

"érdemes lenne az arra alkalmatlanokat a programozástól eltanácsolni még időben."

Igen, ez jó lenne, de még így is munkaerőhiány van. Sok businesspeople számára meg inkább jobban megfelel a bugos, karbantarthatatlan szoftver, ami valamire azért mégiscsak jó (mondjuk az esetek 95%-ban nem hibázik), mint az, ami 0%-osan használható, mert humánerőforrás hiány miatt el sem készül.

Na most amiről te beszélsz az Java, esetleg C# meg SQL. Az itt felmértek nagy része HTML "programozó" aki JS-t használ. Ahhoz tényleg nem kell diploma csak némi szépérzék. Mondjuk már ez sem nagyon igaz mert HTML5-ben már nagyon komoly appokat lehet csinálni.
Mindegy, a világ változik, a Facebook meg a Gugli a középsuliból szedi a ki a gyerekeket akik hajlandók programozni. A főiskolásoknak meg fizetnek, hogy hagyják ott a sulit.
Hogy itthon maradjunk: nézz rá a HUP-ra. Imádom ezt az oldalt, meg tudom trey el van havazva melóval, ennek ellenére 1-2 html srácot rá kellene állítani az átírásra, hogy legyen normális mobilos felülete amit lehet olvasni meg könnyen kommentelni. Sosincs elég JS programozó, még több kell. Diplomával vagy anélkül.

--
GPLv3-as hozzászólás.

JS programozobol inkabb azert van ilyen sok, mert egy nagy ganytenger az egesz JS, ami mellesleg pofatlanul tul sok mindent enged total felulirni. Raadasul egyre tobb a write-only JS kod, lassan kinevezhetjuk a 2010-es evek Perljenek. Mar eleve az hanyas ahogy namespace-t definialsz benne. Nem csoda hogy nagyobb cegek JS libjei is megallitjak egymast valtozoutkozes miatt(!) ha mindkettot betoltod. Aztan beraktal 10 JS libet, mind definial egy tengernyi event listenert, nemelyik "closest div"-re hogy meg jobban triggerelodjon ott, ahol nem szamitasz ra, aztan fejlesztes helyett napokig debuggolod a sok esetben csak egy minify-olt forrasfajlban elerheto hugytengert (mert hat ne foglaljon mar sok helyet meg ne legyen sok request darabszamra se).

Raadasul ahogy megjelent az npm, a bower es a reszponziv design, elmult 2 ev arrol szolt, hogy hirtelen mindenki telehanyta a githubot a sajat dependency helljevel, amit mar egy honapra ra se tudsz ujra feloldani, de legalabb a CV-jukben jol mutat a link. Es eljutottunk oda, hogy mig nem is olyan regen tobb backendes kellett a piacnak mint frontendes es tobbet fizetett, ma 3x annyi JS programozo kell tobbszor annyiert, mint minden mas egyuttveve, es a munkaidejuk nagy reszet nem valos fejlesztessel, hanem szarlapatolassal toltik. El nem hinned mi sok penzert vettunk fel majdnem rossz szajizzel egy JS devet, mert legalabb ugy tunt, hogy mar mashol sikerult lenyegesen nagyobb szartengert ellapatolnia mint a mienk. Aztan mashova is felvettek... sokkal tobbert... pedig nagyon nem kinaltunk keveset es annyira jo se volt.

"Nem attól Singleton valami, mert privát konstruktora, és class-level (aka static) publikus getInstance() instantiate() metódusa van"

Es megkaptuk a factory-t ;)

Amugy igen, Singleton arra kell, amibol egy van es mindig egy is lesz, es ebben biztosak is vagyunk. Volt mar, hogy at kellett irnom a getInstance() -t getInstanceWithHash(Connection.getHashFor(getConnetionStringFor('defaultdb')))-re pl., szoval ismerem a singleton limitacioit es hogy hogy lehet azt megkerulni :D

Dependency injection meg szep es jo, de nehezebben karbantarthato es csunyabban nez ki, ha emiatt 30 dolgot kell atadni a konstruktornak (lattam ra peldat, a szerzo ezt komolyan gondolta, ettol fuggetlenul esszel ez is hatarozottan jo).

Szoval a tortenet roviden: mindent esszel, egyik pattern sem svajci bicska (bar szerintem ezzel nem mondtam neked se ujat).

Az a setter/getter, ami nem csinál semmit, az lehetne publikus field is. Semennyire nem rejt el adatot, csak máshogyan lehet elérni azt.
Nem biztosítja például az invarianciák meglétét. Egy setter igenis validáljon, sőt: ha van esetleg több, egymástól függő property, akkor bizony a setterben van üzleti logika is: ha A property értéke a lett, akkor B property értékét b-re állítja. Mert ezt követeli meg az üzleti logika: ha A értéke a, akkor B értéke csak b lehet. És ezt az A settere tudja egyedül MINDEN esetben, a többi kódtól függetlenül biztosítani, senki más.

Persze ehhez el kell szakadni a JavaBeans káros value class/anemic domain model fogalmaitól, és rendes domain modelt használni.

Lásd: http://www.martinfowler.com/bliki/AnemicDomainModel.html

Az anemic domain model egy hihetetlenül káros dolog.

Rejtett subscribe.

Ezzel az eggyel azért vitatkoznék:

Az a setter/getter, ami nem csinál semmit, az lehetne publikus field is.

Még ha nem is validál (mert pl. még nincs mit validálnia), szvsz. akkor is érdemes settert rakni köré és dokumentálni, hogy ha nem tetszik neki, amit kap, akkor dobhat valamilyen Exception-t. Így API változás nélkül később bevezethetőek újabb megszorítások, míg ha kezdetben publikus field, akkor minden kliens osztályt módosítani kell.

BlackY
--
"en is amikor bejovok dolgozni, nem egy pc-t [..] kapcsolok be, hanem a mainframe-et..." (sj)

Ha szemantikai változásod van, akkor is minden klienst módosítani kell (valójában az is API változás, még akkor is, ha binárisan kompatibilis marad).
Ha egy függvényhívás szemantikája megváltozik (például az, hogy milyen esetekben dob hibát, vagy milyen értéket vár), akkor az inkompatibilis változás, akkor is, ha binárisan kompatibilis marad a régi kliensekkel.

Épp ezért teljesen mindegy, hogy szemantikailag vagy binárisan töröd el a kompatibilitást (és vezetsz be public field helyett setter).

Ha szemantikai változásod van ... valójában az is API változás

Mit tekintünk annak? pl. egy triviális példa, egy User osztály email propertyje, aminek a dokumentációjában szerepel, hogy ha érvénytelen (elsőre legyen mondjuk RFC821-nek nem megfelelő), akkor dob egy IllegalArgumentException-t. Ha ezen felül bevezetsz egy új követelményt, hogy nem tartalmazhat + jelet a localpart (mert bár nem illik nézni, de így a leggyakoribb address extension karaktert kiszűrted), akkor az API változás?

A klienst nem kell módosítanod, mert az eddig is a User osztályodra bízta az érték validálását és eddig se tudott sok mindent kezdeni arral, hogy érvénytelen egy érték (miért volt érvénytelen? formai hibás? garantáltan nem létező [nincs se MX se A rekord (ez ugye mind a klienstől, mind a User mögött levő validálótól független!)]? egy dinamikusan változó feketelistán szerepel a domain part? visszajött egy bounce? ... ez mind belefér az "érvénytelen e-mail cím" definíciójába).

És akkor van-e különbség eközött a helyzet között és mondjuk aközött, hogy utólag mégiscsak beleteszel egy API-ba egy irányítószám-ellenőrzést, ahol eddig nem volt ("no-op setter"), csak annyi megkötés, hogy először az országot kell beállítani (pont azért, hogy később bele tudd tenni)? Ha az API leírásban korábban szerepelt, hogy dobhat kivételt, ha (biztosan) érvénytelen értéket kap a setZip, akkor volt-e szemantikai változás attól, hogy ezt elkezdted betartatni [akár csak néhány országra, ahol biztosan ismered a zip formátumot]?

BlackY
--
"en is amikor bejovok dolgozni, nem egy pc-t [..] kapcsolok be, hanem a mainframe-et..." (sj)

Azért szemantikai változás, mert írhatsz olyan kódot, ami az egyik esetben elhasal, a másik esetben meg nem, attól függően, hogy melyik verzióját használod a libnek (plusz validáció van, vagy nincs). Mondjuk elhal egy unit teszt, ami eddig lefutott. Ez azt jelenti, hogy változott az API, mivel változott a metódusod jelentése. Méghozzá visszafelé nem kompatibilis módon: szűkebb értékkészletet fogad el, mint eddig elfogadott.

" Ha az API leírásban korábban szerepelt, hogy dobhat kivételt, ha (biztosan) érvénytelen értéket kap a setZip, akkor volt-e szemantikai változás attól, hogy ezt elkezdted betartatni [akár csak néhány országra, ahol biztosan ismered a zip formátumot]?"

Hogyne lett volna: A korai verzióval nem halt el egy kódom, most meg elhal ugyanarra az inputra. Az egy dolog, hogy a linker és már egyebek (classloader) számára binárisan nincs inkompatibilis változás.

Lásd: https://en.wikipedia.org/wiki/Cluster_(spacecraft)#Launch_failure
Egy Ariane 4 platformot használtak Ariane 5 rakétához. Mivel az Ariane 5 inputja bővebb volt, ezért hibázott a kód. Pedig "semmilyen" API változás nem történt.

Hogyne lett volna: A korai verzióval nem halt el egy kódom, most meg elhal ugyanarra az inputra.

Akkor egy kicsit továbblépek: a Single Responsibility Principle miatt a User osztályból kiszervezed az e-mail cím tényleges validálásáért felelős kódrészletet egy külön osztályba, szépen interfész mögé zárva. Aztán dependency injectionnel adod a User példányoknak a tényleges validáló példányokat (így ugye a User is jobban tesztelhetővé válik, mert a validálást ki tudod mockolni).

És innentől kezdve a User osztályod semmit nem fog tudni arról, hogy mi minősül érvényes e-mail címnek (nem az ő feladata, ahogy egyébként tényleg nem), akár futási időben változtathatod, hogy mi minősüljön érvényes e-mail címnek.

Akkor megint API break az, hogy mondjuk a konfig fájlban(!) átütöm, hogy nekem mégsem a NoopEmailValidator kell, hanem egy olyan, ami szépen végigcsinálja az RFC ellenőrzést, a DNS kéréseket stb?

Lásd: https://en.wikipedia.org/wiki/Cluster_(spacecraft)#Launch_failure
Egy Ariane 4 platformot használtak Ariane 5 rakétához. Mivel az Ariane 5 inputja bővebb volt, ezért hibázott a kód. Pedig "semmilyen" API változás nem történt.

Ez két okból azért más [ha jól értem, bár eléggé messze áll tőlem az űrkutatás]: 1) itt pont az volt a gond, hogy nem validált valamit az egyik alrendszer, amit kellett volna és 2) ebben a helyzetben speciel a kliensnek pontosan tudnia kell, hogy a másik alrendszer miért jelzett hibás és ebben az esetben mit lehet tenni, hogy javítva legyen.

BlackY
--
"en is amikor bejovok dolgozni, nem egy pc-t [..] kapcsolok be, hanem a mainframe-et..." (sj)

"Akkor megint API break az, hogy mondjuk a konfig fájlban(!) átütöm, hogy nekem mégsem a NoopEmailValidator kell, hanem egy olyan, ami szépen végigcsinálja az RFC ellenőrzést, a DNS kéréseket stb?"
Ez egy szemantikus törés, bizony. Ugyanis a NoopValidator meg RFCBasedValidator csak formailag ugyanazt az interfészt implementálja, szemantikailag meg nem.

Mondjuk például ellentmond a Liskov substitution principle-nek.

Van egy EmailValidator interfészed, N implementációval.
Akkor bármikor, bármilyen inputra a következő kódnak mindig ugyanúgy kell viselkednie a hívó szempontjából:

emailValidator.isValid('a@b.c')

Mert ha különbözőképpen viselkednek a hívó szempontjából, akkor az azt jelenti, hogy valójában nem ugyanazt az interfészt implementálják szemantikailag, csak formailag.

Hogy egy másik, közismert példát mondjak: Javaban ha implementálod az equals-t, akkor az equals szemantikáját is be kell tartanod, nem csak a formáját. Ha nem tartod be az equals szemantikáját (rosszul implementálod), akkor például nem működhet egy Set-ed, egy List-ed, amelyek felhasználják az equals szemantikáját, és feltételezik, hogy aki implementálja az equals-t, az helyesen teszi.
Azért az eléggé ciki, amikor egy hívónak tudnia kéne, hogy akit hív, az hogyan van implementálva.
Hogy értsd: az Ariane-nál is ez volt a probléma. Tightly coupled rendszer, az IMU-nak tudnia kellett, hogy milyen környezetbe ágyazzák be, és emiatt (az IMU implementációs hiányossága miatt) nem lehetett beágyazni egy formailag (de nem szemantikailag!) ugyanazt az interfészt (hogy úgy tessék, API-t) használó rendszerbe.

Mert ha különbözőképpen viselkednek a hívó szempontjából,

Hogy értelmezed itt a viselkedés kifejezést? A teljes f(x1,[x2,...]) -> v hozzárendelésre pontosan kell egyezniük vagy a contractben vállaltaknak megfelelően kell működniük?

Mert a fenti példánál maradva az EmailValidator.isValid(String) contractja szólhat úgy, hogy akkor és csak akkor fog igazat visszaadni, ha nem tudja bizonyítani, hogy az e-mail cím érvénytelen (mert ezt tényleg csak úgy tudná, hogy kiküldi a visszaigazoló levelet és megvárja, hogy rábökjenek a linkre...), ekkor viszont mindegyik implementáció helyes és teljes és szabadon cserélhetőek.

Ha viszont elvárod, hogy ha adott állapotban, adott argumentumokkal a leszármazott osztályok pontosan ugyanazt adják vissza, akkor pl. az összes hashCode() alapú collection-t bukod, mivel mindegyiknek az Object#hashCode-ot kellene visszaadnia.

BlackY
--
"en is amikor bejovok dolgozni, nem egy pc-t [..] kapcsolok be, hanem a mainframe-et..." (sj)

"Ha viszont elvárod, hogy ha adott állapotban, adott argumentumokkal a leszármazott osztályok pontosan ugyanazt adják vissza, akkor pl. az összes hashCode() alapú collection-t bukod, mivel mindegyiknek az Object#hashCode-ot kellene visszaadnia."

Nem, nem ugyanazt kell visszaadnia, hanem be kell tartania a szemantikát. És le is van írva, hogy a hashCode-nak mi a szemantikája: nem várja el, hogy az Object.hashCode() értékével kell megyegyezzen, hanem
"Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.
•If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result. "

Ennyi. És erre a contractra írhatok én unit testet, használhatom, és minden conform hashCode() implementációval működni fog az összes tesztem.
Az persze lehet, hogy egy unit testet valójában nem erre a contractra írsz meg, hanem használsz olyan feltételezéseket, ami nincs benne ebben a contractban, például kikötheted a tesztben, hogy egyezzen meg bármelyik hashCode() az Object.hashCode()-dal, de ez sehol nincs leírva a contractban.

"hozzárendelésre pontosan kell egyezniük vagy a contractben vállaltaknak megfelelően kell működniük?"
Ha a contract definiálja, hogy hogyan kell kinéznie a kimenetnek bitre pontosan, akkor igen, pontosan meg kell egyeznie a hozzárendelésnek.
Például nem tudsz kétféle koszinuszfüggvény implementációt úgy csinálni, hogy ne egyezzen meg a kimenet. Mert ha két cos implementáció (például egy CPU-only meg egy FPU-only) ugyanazt kell, hogy visszaadja mindig. Nem lehet más, mert akkor bugos: nem felel meg a specifikációnak.

Ha egy EmailValidator.isValid() specifikációja csak annyi, hogy "Validálja az e-mail címet" akkor az egy gagyi specifikáció: készíthetsz olyan implementációkat, amelyek ugyanarra az inputra teljesen máshogy viselkednek a hívó szempontjából. És ez rossz, mert nem felcserélhetőek az implementációk - akkor meg minek az interfész? Nem testesít meg semmit valójában.

Ha mondjuk az a specifikációja, hogy "RFC822 szerinti validációt készít", akkor minden EmailValidator implementációnak RFC822 szerinti validációt kell készítenie.

Vagy hogy egy ténylegesen kézzelfogható példát mondjak:
Ha mondjuk egyik napon a hálózati áram 230 V helyett 450 V lenne, akkor hiába ugyanaz a csatlakozó és az aljzat (azaz formailag ugyanaz az interfész), szemantikailag nem ugyanolyan: bedugsz egy 230 V-ra méretezett eszközt és megsül.
Az, hogy egy interfész formailag nem változik, attól még lehet inkompatibilis változás az API-ban: az API nem csak formaiság, szemantikája is van.

Például nem tudsz kétféle koszinuszfüggvény implementációt úgy csinálni, hogy ne egyezzen meg a kimenet.

+/- a contract-ben vállalt hibahatár (Math.cos: The computed result must be within 1 ulp of the exact result.)
Ha mindkettő tartja a hibahatárt, de az FPU-only valamivel pontatlanabb, attól még cserélhetők, ha bit szinten nem is ugyanazt hozzák (és hogy ez egy hosszabb számítás végére orbitális különbségeket jelenthet).

Ha egy EmailValidator.isValid() specifikációja csak annyi, hogy "Validálja az e-mail címet" akkor az egy gagyi specifikáció

Fentebb írtam egy specifikációt, hogy "akkor és csak akkor ad vissza igazat, ha nem tudja bizonyítani, hogy a megadott e-mail cím nem képes levelet fogadni". A User contractje eleve úgy szól [ugye a refaktorálás előtt még róla és az ő klienséről volt szó], hogy a felhasználó élő, érvényes e-mail címét tartalmazza, vagyis az, hogy ezt milyen szinten tartatod be (pont azért, mert nem tudod _ténylegesen_ betartatni) egy szerintem időben változtatható, akár cserélgethető implementációjú kérdés. A User contractjének viszont azt tartalmaznia kell, hogy a klienseinek fel kell készülnie, hogy az e-mail címet érvényesíti és elutasíthatja (még ha abban az időben, amikor az első kliense készül, a User a NoOp-ot használja is).

Az, hogy egy interfész formailag nem változik, attól még lehet inkompatibilis változás az API-ban: az API nem csak formaiság, szemantikája is van.

És itt kezdődik a parttalan jogászkodás, hogy mi számít inkompatibilis változásnak: ha bármi változik, vagy ha valami úgy változik, hogy a contract-ot többé nem tartja be bizonyos inputokra. Ha bevezetek egy AlwaysFalseEmailValidator-t és azt kezdi el használni a User, az egyértelműen egy inkompatibilis (bugos) változtatás, mivel korábban elfogadott, valid e-mail címeket többé nem fogad el. Ha viszont váltok az RFCValidator-ról a DomainValidator-ra, akkor annyiból inkompatibilis a változás, hogy a korábbi bar@example.foo-t használó unit test elhasal, viszont korábban pont a bar@example.foo-s unit teszt volt az, ami nem tartotta magát a User interfészhez (érvénytelen e-mail címet adott meg, akár szándékosan, akár nem).

Ha mondjuk egyik napon a hálózati áram 230 V helyett 450 V lenne, akkor hiába ugyanaz a csatlakozó és az aljzat (azaz formailag ugyanaz az interfész), szemantikailag nem ugyanolyan: bedugsz egy 230 V-ra méretezett eszközt és megsül.

Nem is ezt mondtam, hogy bármi előjel nélkül hirtelen kezdjünk el e-mailt validálni. Onnan indult az egész, hogy szerencsésebb kitenni a gettert/settert és ráírni, hogy dobhat Exception-t, ha invalid; vagyis az áram szolgáltató a nulladik naptól közölte, hogy amikor úgy gondolja ő válthat 450V-re, úgyhogy a kliens készüljön fel rá.

BlackY
--
"en is amikor bejovok dolgozni, nem egy pc-t [..] kapcsolok be, hanem a mainframe-et..." (sj)

"Onnan indult az egész, hogy szerencsésebb kitenni a gettert/settert és ráírni, hogy dobhat Exception-t, ha invalid"
Az, hogy throws Exception, az semmitmondó: egyszerűen teljesen felesleges elkapni, mert nem tudod, hogy milyen hiba történt.
Ugyanaz, mint ahogy önmagában egy file is semmitmondó, jelentése csak akkor less, ha meg is adod a file formátumát. Semmitmondó dolgok meg feleslegesek: nem hordoznak valójában információt.

Esetleg azt mondhatod, hogy throws EmailValidationException, és ez az Exception tartalmaz leírást arra, hogy milyen lehetséges hibák vannak: invalid localpart, invalid domain, missing @ character etc.
De ez meg már megint az interfész része.

Az egy dolog, hogy az implementáció nem fog sosem hibát dobni, de két rendszer interfészelésénél nem az implementáció a lényeg, nem az implementációhoz írod a kódot, hanem az interfészre programozol.

"viszont korábban pont a
bar@example.foo
-s unit teszt volt az, ami nem tartotta magát a User interfészhez (érvénytelen e-mail címet adott meg, akár szándékosan, akár nem).
"

De, tartotta magát az interfészhez amennyiben a teszt nem hasalt el, hiszen az interfész szerint ez egy valid e-mail cím volt a specifikációban.
Hogy jobban értsd: nem az implementáció határozza meg az interfészem szemantikáját, ez rossz dolog. Hanem az implementációnak kell megvalósítania az interfész specifikációját.

"Fentebb írtam egy specifikációt, hogy "akkor és csak akkor ad vissza igazat, ha nem tudja bizonyítani, hogy a megadott e-mail cím nem képes levelet fogadni"."
"Ha viszont váltok az RFCValidator-ról a DomainValidator-ra, akkor annyiból inkompatibilis a változás,"
És az RFCValidator és a DomainValidator közül egyik ezt a specifikációt nem teljesíti, ha az egyiket használva a unit test nem hal el, a másikat használva meg igen, így nem cserélheted ki őket csak úgy. Hogy értsd: te itt valójában az interfészt változtattad meg, méghozzá úgy, hogy az implementáció diktálja az interfészt, azaz a hívónak tudnia KELL, hogy milyen implementáció van az interfész mögött. És ez rossz dolog: tightly coupled rendszert hoz létre.

Még egy gondolat: "a felhasználó élő, érvényes e-mail címét tartalmazza, vagyis az, hogy ezt milyen szinten tartatod be (pont azért, mert nem tudod _ténylegesen_ betartatni)"
Ha valamit nem tudsz ténylegesen betartani, akkor az egy olyan üzleti feltétel, ami értelmetlen.
Mintha azt mondanád az üzleti feltételben, hogy a gravitációnak felfelé kéne gyorsítani a testeket, és nem lefelé. Értelmetlen feltétel, amit lehet különféle módokon relaxálni: oké, használhatunk rakétát a felfelé irányú gyorsulás emulációjára, használhatjuk az atmoszféra felhajtóerejét stb.
De ettől még az üzleti feltételed értelmetlen marad, és valójában csak gányolsz.

Ha valamit nem tudsz ténylegesen betartani, akkor az egy olyan üzleti feltétel, ami értelmetlen.

Hogy tartatod be, hogy amikor egy User-t rögzítesz, annak az e-mail címe:
1) szintaktikailag megfelelő (egyszerű)
2) létező e-mail cím és tud fogadni levelet (egy levél eldöntheti)
3) _tényleg_ ahhoz a személyhez tartozik, akit a User objektumod reprezentál? Értsd: engem, Kis Bélát ki akadályoz meg abban, hogy regisztráljak Tóth István nevében a rendszeredbe a toth.istvan@akarmi.hu e-mail címmel, amit én hoztam létre, én használok, következésképp simán vissza tudom igazolni?

A Tóth Istvánnak szóló jelszó emlékeztető mégis nekem fogod küldeni - vagyis a rendszer feltételezi, hogy mindhárom feltétel teljesül, holott nem.

De ugyanígy, lehet, hogy teljesen korrekten regisztrálok ma a rendszeredbe, bejelentkezem az SMS-es hírlevéllistádra, és holnap felmondom a mobil előfizetésem. Pár hónap múlva a ti rendszeretek szerint Kis Bélának küldött SMS-ek Tóth Gábornál fognak landolni...

Egyszerűen vannak olyan körülmények, amiket sehogy nem lehet automatizáltan elkerülni (vagy rohadt mód fognak utálni a felhasználóid a naponként "még mindig a tied ez az e-mail/szám" levelekért/SMS-ekért), amiknél a legjobb, amit tehetsz, hogy az ÁSZF-be beleteszed, hogy csúnyán nézünk rád (ha szándékos)/szépen nézünk rád (ha nem szándékos, és szól róla).

BlackY
--
"en is amikor bejovok dolgozni, nem egy pc-t [..] kapcsolok be, hanem a mainframe-et..." (sj)

"Egyszerűen vannak olyan körülmények, amiket sehogy nem lehet automatizáltan elkerülni (vagy rohadt mód fognak utálni a felhasználóid a naponként "még mindig a tied ez az e-mail/szám" levelekért/SMS-ekért), "
Ha nem lehet elkerülni, akkor felesleges rá automatizmust építeni.

A szoftverek nem másra valók, mint a való világban lévő munkafolyamatok gépesített támogatására. Ha valamit nem lehet gépesíteni, akkor teljesen felesleges szoftverkövetelménnyé tenni azt, ez ilyen egyszerű.
És te most azt mondod, hogy van egy nem gépesíthető követelményed, és nekem bizonygatod, hogy ez a követelmény nem kényszeríthető ki szoftverrel. Körbe-körbe jársz: az üzleti követelményed nem gépesíthető (azaz szoftveres szempontból értelmetlen), és bizonygatod, hogy erre a követelményre nem lehet jó szoftveres megoldást csinálni. Láss csodát: tényleg nem lehet.

Ha nem lehet elkerülni, akkor felesleges rá automatizmust építeni.

Akkor nálad ha elfelejtettem a jelszavam, személyivel be kell sétálnom a cég székhelyére és kedvesen mosolyognom a recepciósra, hogy ugyan adjon már egy másikat?

Ha valamit nem lehet gépesíteni, akkor teljesen felesleges szoftverkövetelménnyé tenni azt, ez ilyen egyszerű.

Nem mondtam, hogy explicit követelmény a fentinél, hogy a Usernél _utólag_ is garantáltan élő e-mail címünk legyen (ez az a része, amit nem tudsz előírni, mert tőled független), de ha arra a címre küldesz ki jelszócsere linket, akkor van egy ilyen implicit feltételezésed. Az esetek nagyon nagy részében fog működni? Igen. Lesz olyan, aki közben mondjuk ISP-t váltott? Biztos. Lesz olyan, aki a munkahelyi címével regisztrált és azóta háromszor váltott munkahelyet? Lesz. Lesz olyan, aki váltott a gmail-ről yahoo-ra (vagy fordítva) és AZT a jelszavát is elfelejtette? Naná. Ettől még a te rendszeredben ott lesz az a feltételezés, hogy a User.getEmail()-re küldött leveleket megkapja.

És te most azt mondod, hogy van egy nem gépesíthető követelményed, és nekem bizonygatod, hogy ez a követelmény nem kényszeríthető ki szoftverrel.

Én azt mondom, hogy vannak olyan feltételezések (nem feltétlenül követelmények) a rendszerről, amik nem feltétlenül helyesek. pl. ott van maga az SMTP, az átlaguser feltételezi, hogy az egy hűde retek biztonságos valami, hogy garantáltan megérkezik a címzetthez, amit elküldenek, hogy tényleg attól jött egy-egy levél, aki a Feladó mellett szerepel, hogy amit nagyon össze-vissza formáztak, az a fogadónál is pont úgy fog megjelenni. Igazak ezek a feltételezések? Dehogy igazak.

--

Te végig követelményekről beszélsz, én pedig (akár implicit, akár explicit, akár vélt, akár valós) feltételezésekről a rendszerről, a komponenseiről, a kezelt adatok minőségéről stb. (és ezek egymásról alkotott feltételezéseiről). Ilyen feltételezés az, hogy valami nem null (ami lehet, hogy annak a követelménynek a következménye, hogy az adott elem kötelező). Ilyen feltételezés az, hogy egy elem azonosítója érvényes GUID (ami lehet annak a követelménynek a következménye, hogy egyértelmű azonosítót kell adni természetes azonosító nélkül). És ilyen feltételezés, hogy ha küldesz arra a címre egy levelet, az a user postafiókjában landol (ami annak a követelménynek felel meg, hogy a userek tudjanak jelszóemlékeztetőt kérni)...

BlackY
--
"en is amikor bejovok dolgozni, nem egy pc-t [..] kapcsolok be, hanem a mainframe-et..." (sj)

"Akkor nálad ha elfelejtettem a jelszavam, személyivel be kell sétálnom a cég székhelyére és kedvesen mosolyognom a recepciósra, hogy ugyan adjon már egy másikat?"
Ki mondott ilyet? Amúgy teljesen biztonságos jelszócsere autoatikus, anonimizálható rendszerekkel nincs is. De senki nem is mondta, hogy egy számítógépes rendszertől (pl. a HUP felhasználókezelésétől) MINDIG elvárjuk a teljesen biztonságos jelszócserét. Vannak olyan rendszerek, ahol ezt nem várjuk el, valahol meg elvárjuk. Valahol elég a jelszavas belépés, máskor meg elvárod a kétfaktoros autentikációt.

"Ettől még a te rendszeredben ott lesz az a feltételezés, hogy a User.getEmail()-re küldött leveleket megkapja."
Miért lenne ott? Nem tudom, honnan vonsz le ilyen következtetést: mivel eleve az a követelmény, hogy a User.getEmail() reprezentálja a felhasználó bizonyítottan hozzá tartozó e-mail címét, az értelmelen szoftverkövetelmény (eleve az egész e-mail rendszerben e-mail accountokról van szó, és nem valós személyekről: például az abuse@example.com kihez tartozik? Ha megosztom valakivel egy e-mail account jelszavát, akkro az az ő e-mail accountja, vagy az enyém?), ezért a rendszerről levonni egy olyan következtetést, amit már eleve nem fog tudni teljesíteni, majd mutogatni, hogy "nézd, nem teljesíti a szoftver azt a tulajdonságot, ami nem is volt követelménye", totálisan értelmetlen.

Egy-egy rendszerről felesleges vélt vagy valós feltételezéseket tenni, amikor szoftvert fejlesztesz. Egy rendszer teljesen megismerhető az alapján, hogy milyen követelmények vannak vele szemben, és ezen követelményeket mennyire teljesíti.
"pl. ott van maga az SMTP, az átlaguser feltételezi, hogy az egy hűde retek biztonságos valami, hogy garantáltan megérkezik a címzetthez, amit elküldenek, hogy tényleg attól jött egy-egy levél, aki a Feladó mellett szerepel, hogy amit nagyon össze-vissza formáztak, az a fogadónál is pont úgy fog megjelenni. Igazak ezek a feltételezések? Dehogy igazak."
És miért nem igazak? Mert az SMTP követelményei között ezek a kívánalmak sosem szerepeltek. Ha ismered egy rendszer követelményeit, akkor pontosan tudod a korlátait is. Ezért kell követelményekről beszélni: amit elvárunk a rendszerrel szemben.
Azért beszélek követelményekről, mert a szoftvert nem csak úgy magában készítünk, hanem annak célja van: elvárunk tőle dolgokat, amit csináljon, ezek a követelmények, amelyeket a szoftvernek teljesítenie kell.

" Ilyen feltételezés az, hogy egy elem azonosítója érvényes GUID (ami lehet annak a követelménynek a következménye, hogy egyértelmű azonosítót kell adni természetes azonosító nélkül). "
Ez a feltételezés akkor igaz, ha megnézed a szoftver követelményeit, és látod, hogy követelmény, hogy az elem azonosítóinak GUID-nak kell lenniük. Ha ez a követelmény nincs leírva, akkor nem tehetsz ilyen feltételezést.

Azért fontos a követelményekről beszélni, mert ez az egyik nagyon fontos eleme a szoftvernek. Ugyanis jó és pontos követelmények nélkül nem lehet jó és pontos szoftvert készíteni.
Az egész TDD és unit testing is erről szól: kódban fejezed ki a szoftvertől elvárt követelményeket, hogy a követelmények ellenőrzése automatizált legyen.
Amikor azt mondja egy szoftvermérnök, hogy "write tests first", akkor igazából azt mondja, hogy tudnom kell a szoftverrel szemben támasztott követelményeket ahhoz, hogy implementálni tudjam azt.

Sajnos nem, az én kommentemben levő qoute (u - o csere) a rossz, már ott szétcsúszik az egész (Hupper nélkül is, FX-es Hupperrel semmi különös nem történik, leszámítva amit egy tiszta Chromiummal is).

BlackY
--
"en is amikor bejovok dolgozni, nem egy pc-t [..] kapcsolok be, hanem a mainframe-et..." (sj)

És még egy teljesen más oldal:

https://www.link-intersystems.com/blog/2011/10/01/anemic-vs-rich-domain…

Viszont egy kérdés: van egy osztályom, ami mondjuk egy person-t reprezentál. Ezt a person-ből mondjuk egy pdf-et kellene generálni byte[] formátumba. Vajon a PDF generáló kódot azt a Person osztályba rakjuk bele, vagy pedig csináljunk egy külön osztályt erre a célra? Illetve még egy kérdés, mert itt bizony azt mondják, hogy mondjuk egy service-en keresztül kéne ezt elérhetővé tenni, amit belepakolunk az osztályba.

http://jblewitt.com/blog/?p=241

Szóval, a kérdés az, hogy mi van akkor, ha egy olyan pdf-et kell generálnunk, amiben mondjuk Car és Person is szerepel. Annak a generálását hova raknátok?

"Szóval, a kérdés az, hogy mi van akkor, ha egy olyan pdf-et kell generálnunk, amiben mondjuk Car és Person is szerepel. Annak a generálását hova raknátok?"

Szerintem ezt te is tudod, csak nem mondtad ki: mivel single responsibility van, ezért ennek egy külön osztály kell:
egy olyan PDFReport interfész implementáció, ami ezt a PDF-et generálja.

A PDF generálás nem tartozik sem a Car, sem a Person osztályhoz: hiszen a PDF generálás csak akkor kell, hogy módosuljon, ha maga a report specifikációja módosul. Ha maga a Person és a Car visszafelé kompatibilis módon változik, attól még a Car+Person PDF reportot készítő class nem szabad, hogy módosuljon. Épp ezért ez egy külön osztály.

Igen, ezért lebegtettem meg a kérdést, hogy esélyes, hogy a PersonCarPDF az egy külön osztály lesz. De egy sima person-t és car-t hogyan érdemes szépen megoldani, és miért?
Próbálom iteratívan levezetni, kíváncsi vagyok a véleményekre...

Naiv implementáció:


class Person{
String name;
...
}

class Car{
String rendszam;
...
}

class PDFGeneratorService{

public static byte[] generatePDF(Person p){
...
}

public static byte[] generatePDF(Car c){
...
}

public static byte[] generatePDF(Person p, Car c){
...
}

}

Ennek ugye van egy olyan előnye, hogy ha PDF generálás kell, akkor egy fileban kell keressem, viszont a single responsibility elvet üti, mivel a PDFGeneratorService az
generál car-t is és person-t is, és carperson-t is, ráadásul szépen kövérre fog hízni, ráadásul egyszerre nem tud több fejlesztő pdf generáláson dolgozni. Próbáljuk ezt szétszedni:


class PersonPDFGenerator{
public byte[] generatePDF(Person p){...}
}

class CarPDFGenerator{
public byte[] generatePDF(Car c){...}
}

class PersonCarPDFGenerator{
public byte[] generatePDF(Person p, Car c){...}
}

public class PDFGeneratorService{

public static PersonPDFGenerator personGenerator = new PersonPDFGenerator();
public static CarPDFGenerator carGenerator = new CarPDFGenerator();
public static PersonCarPDFGenerator personCarGenerator = new PersonCarPDFGenerator();

public static byte[] generatePDF(Person p){
return personGenerator.generatePDF(p);
}

public static byte[] generatePDF(Car c){
return carGenerator.generatePDF(c);
}

public static byte[] generatePDF(Person p, Car c){
return personCarGenerator.generatePDF(p, c);
}
}

Ezzel ugye annyit nyertünk, hogy nem egy osztályban van beleírva az összes PDF generálás, hanem több osztályban van szétdobva, ezért pl. 2-3 fejlesztő is dolgozhat egyszerre a pdf generáláson, egymástól függetlenül. Az az előny megmaradt, hogy van egy service osztályunk, és mindig ahhoz tudunk fordulni, viszont ez egy person-nál nem feltétlenül intuitív (tudni kell, hogy nyomtatni a PDFService-ből tudunk).

Próbáljuk ezt továbbvinni:


interface IPersonPDFGenerator{
public byte[] generatePDF(Person p);
}

class Person{

IPersonPDFGenerator pdfGenerator;

public byte[] generatePDF(){
pdfGenerator.generatePDF(this);
}
}

Az IPersonPDFGenerator pedig valamikor átadásra kerül az osztálynak (gondolom ide jönne a dependency injection). Viszont, továbbra is az a gond, hogy mi van akkor, ha person-t és car-t is akarunk nyomtatni, ha jól értem, ettől függetlenül ugyanúgy kellene a service osztály, de ha már van egy service osztály, csak akkor meg visszatértünk az előző lépéshez... Hogyan kellene ezt szépen megvalósítani?

"De egy sima person-t és car-t hogyan érdemes szépen megoldani, és miért?"

Attól függ, hogy a rendszered domainspecifikus fogalmai mit igényelnek egy Car-tól meg egy Person-tól. Egy autós játékban máshogy modellezel egy Car-t, mint egy autónyilvántartásban.

Igazából nem a Person-t, a Car-t nyomtatod ki, hanem egy PersonReport/CarReport dolgot. Amely tudja, hogy minek kell szerepelnie a Person/Car tulajdonságai közül a reportban. Azt
sem a Person, sem a Car nem szabad, hogy tudja.
Talán egy ember a valóságban tudja, hogy neki hogyan kell kinyomtatnia egy dokumentumot saját adatairól az autókereskedés számára? Dehogy tudja. A valóságban is: az autókereskedő elkéri az információkat a személytől, majd kinyomtatja azt.

Szerintem a te modelledben vannak: Car, Person, CarReport, PersonReport, CompoundReport domain objektumok, mindegyik reprezentál valamilyen valós világbeli (aka kézzelfogható) dolgot. Például a CompoundReport tartalmazhatja egy személy egy autójának szerződési adatait: egy személy egy másiktól mikor vette meg azt az autót stb.

Na, ha ezt OOP osztályokra akarjuk lefordítani úgy, hogy rugalmasak is legyünk (például megengedjük, hogy ne csak PDF formátumú report legyen), akkor valahogy így járhatunk el:
A PersonReport, CarReport, CompoundReport osztályok tudják, hogy milyen információkat kell összeállítaniuk az egyes Person/Car/Person+Car példányokból a reporthoz.

Ebből a CompoundReport egy absztrakt osztály, tudja, hogy neki igazából egy "purchaseDate, seller, buyer, licensePlate" információhalmazt kell kinyernie az átadott 2 Person és 1 Card példányból, majd azt valamilyen módon szerializálni.

És van neki egy abstract byte[] print() metódusa, aminek van PDFCompoundReport, HTMLCompoundReport implementációja, amelyek tudják, hogy a CompoundReport állapotát hogyan kell szerializálni egy bytesorozattá.

És amikor te egy CompundReport/PersonReport stb. példányt készítesz, akkor mondjuk azt így teszed:

CompoundReport aPdfCompoundReport = new PDFCompoundReport(seller, buyer, car, purchaseDate);
byte[] pdfDocument = aPdfCompoundReport.print();

Természetsen a PDFCompoundReport példányosítása történhet Factory-n keresztül, történhez DI-vel, ez már lényegtelen most, a lényeg itt annak a felismerése, hogy a reportok önmagukban is létező, érvényes domain objektumok, sem a Car, sem a Personnak nem részei. Mint ahogy a valóságban is így van.

Ez a második linked egyáltalán nem arról, hogy anemic domain model + service layer, csak nem ismeri fel.

Ott, amikor elkezdi a User classt normális domain objektummá tenni, akkor azt a specifikációs követelményt önti kód formába, hogy "képesnek kell lenni egy-egy felhasználó számára (amely felhasználó egy név és e-mail cím párost jelent) üzenetet küldeni".
És igen: az üzenetet a felhasználónak küldjük, épp ezért a User API-jának a része az, hogy üzenetet kap. Az, hogy HOGYAN kap üzenetet (azaz miként valósul meg a MessageService) az megint más kérdés.
Épp ezért a User mint magasszintű absztrakciójú class definiálja, hogy neki szüksége van egy MessageService-re, hogy üzenetet lehessen neki küldeni, és a MessageService-nek ez és ez az API-ja. Aztán ezt a magasszintű API-t majd más alacsonyabbszintű megvalósítások implementálják, ez egy példa a dependency inversionre.

Amúgy a Step 1-ben van leírva (csak nem felismerve) a probléma:
"This may be as a result of an explicit design decision or, more probably, it comes from the fact that certain pieces of logic just cannot be implemented in the domain entity because it is a persistent class and has no internal references to external services"
Ez egy rossz megközelítés. A domain objektumot köti annak I/O-jához: a persistent class nem maga a domain objektum. Csak egy eszköz arra, hogy az objektumunk állapotát valamilyen I/O rétegen keresztül elmentsük, hogy később az állapotot visszatölthessük.

Aki a persistent entityket domain entitynek tekinti, az rossz megközelítés. A persistent entity class csak egy I/O réteg, nem maga a domain model.

"Ez egy rossz megközelítés. A domain objektumot köti annak I/O-jához: a persistent class nem maga a domain objektum. Csak egy eszköz arra, hogy az objektumunk állapotát valamilyen I/O rétegen keresztül elmentsük, hogy később az állapotot visszatölthessük. Aki a persistent entityket domain entitynek tekinti, az rossz megközelítés. A persistent entity class
csak egy I/O réteg, nem maga a domain model."

Ezt a részt ki tudnád fejteni bővebben?

A legjobban talán Uncle Bob tudja ezt elmondani:
https://www.youtube.com/watch?v=Nsjsiz2A9mg

Kötelezően megnézendő video.

A lényeg: nem a tárolási réteg határozza meg, hogy az objektumaid mit tudnak és mit nem és hogyan kell kinézniük. A tárolási réteg csak I/O.

Mondjuk a fenti Person/Car példában:
Van egy Person, Car objektumod, amik maguk az autókereskedésbeli domain objektumok. Tartalmazzák az üzleti logikát, stb.
És mondjuk van egy PersistentStorage objektumod (ez már nem az autókereskedés domainje, hanem a perzisztens tárolás domainje), ami tud olyat, hogy PersistentStorage.persist(Person) meg PersistentStorage.restorePersonByName(name) stb.
És lehet, hogy a PersistentStorage-nek van pár implementációja, ami JPA-val, Entity Frameworkkel, tetszőleges DAO-val perzisztálja az objektumot. Ő tudja, hogy a Persontól mit kell elkérni, hogy egyértelműen perzisztálni lehessen: az ő domainje a perzisztencia, és nem a 'business logic'.
Az, hogy a JPAStorage használ PersonEntity-ket a JPA framework által diktált formátumban (sima value bag-ek, logika nélkül), attól még a business objektumaid nem value bagek.
NE a JPA framework korlátja dikálja, hogy hogyan építed fel az üzleti objektumaidat. Mert akkor a JPA-hoz és a JPA korlátaihoz leszel mindig is kötve. Pedig a JPA csak egy I/O device. Semmi köze a valódi üzleti logikádhoz.
Például mekkora szívás lenne abban az esetben, ha a JPA architektúrája diktálja az alkalmazásod architektúráját, JPA-ról áttérni iBatisra, ugye? Pedig maga az üzleti logikád NEM változik meg, miért kéne átírnod az üzleti logikát. Csak a perzisztencialogikád változik meg.

Hogy másként mondjam: nem az adatbázis/tárolási réteg/I/O réteg (pl. REST) felől építjük az alkalmazásunkat.
Hanem a business által definiált objektummodelt készítünk, amihez készítünk REST/DB/whatever interfészeket, amik az üzleti objektumainkat kiajánja HTTP felé, kiajánlja DB felé stb.

Oké, köszi, így már kicsit világosabb a dolog. De nem lesz ettől a rengeteg sok layertől feleslegesen bonyolult a rendszer? Pl. egy adatbázis köré épített rendszernél (legyen mondjuk myBatis, vagy JOOQ, hogy a JPA-s önszívatást elkerüljük) teljesen világos, hogy ami osztályom van, az egy-egy sort reprezentál az adatbázisban, és pont. Maga az üzleti logika is adatbázis sorokból képzett objektumokon dolgozik. Igy az architektúrám nagyon egyszerű, könnyen debuggolható, a valóság pedig az, hogy az adatbáziskezelőt a legritkább esetben kell változtatni. Vagy lehet, hogy félreértem..

Miért lenne bonyolult? Nem attól bonyolult valami, hogy sok osztályod van, hanem akkor, ha nem tiszták a szerepkörök: ki kivel van, melyik osztály mit (rossz esetben: miket) reprezentál.

"Pl. egy adatbázis köré épített rendszernél (legyen mondjuk myBatis, vagy JOOQ, hogy a JPA-s önszívatást elkerüljük) teljesen világos, hogy ami osztályom van, az egy-egy sort reprezentál az adatbázisban, és pont."

"Maga az üzleti logika is adatbázis sorokból képzett objektumokon dolgozik."
Nem, az üzleti logika üzleti objektumokon dolgozik, attól teljesen függetlenül, hogy ezek hogyan vannak perzisztálva. Kivétel, ahol maga az adatbázissor az üzleti objektum, de ez csak maguknál az adatbáziskezelőknél van így.

" a valóság pedig az, hogy az adatbáziskezelőt a legritkább esetben kell változtatni."
Azért kell a legritkább esetben változtatni, mert ha a DB köré építed a rendszeredet (és általában sajnos így van), és a business megkérdezi, hogy mennyibe kerülni áttérni Oracle-ről MSSQL-re, és mondasz neki egy vállalhatatlanul sokba kerülő és sokáig tartó DB-cserét, akkor inkább úgy dönt, hogy nem változtat a DB-n.
Mint amikor egy szar frameworkbe vagy belekényszerítve, mert a project elején hoztatok egy rossz döntést, amit kijavítani kurva sokba kerülne, ezért midnenki úgy dönt, hogy marad a szar.
Ez a fajta szemlélet (a DB köré építünk mindent) eredményezi, hogy a DB-t nem cserélgetik: mert az alkalmazás nem engedi meg, hogy cseréljék a DB-t. Pedig sok esetben jó lenne upgradelni, vagy olcsóbb DBMS_re váltani, mert megváltozott a licencelés stb. De nem, nem lehet. És az IBM, az Oracle és sok cég ezért keresi magát halálra: mert ők azt akarják, hogy az ő szoftvere köré építsd a tiedet, ezért függj tőlük. És a cég meg a szoftver élettartamáig fizeti a licenceket, mert nem tehet mást: elbaszták az architektúrát, és megfizetik az árát.

Az egyszerű debugolhatóság pedig ott egy fals feltételezés, hogy nem debugolhatóságra tervezünk, hanem egyszerű módosításra. Az, hogy ez complex mintákkal jár együtt (factory, interfészek stb), az az ára az egyszerű módosításnak. Hány olyan kódod van, amit baromi sok idő lenne rendesen refactorálni, csak mert azt feltételezted, hogy "ez így van jól, hiszen sosem fog változni a rendszer". Aztán kiderül: a szoftvernél az igények nagyon gyorsan változnak. Épp ezért könnyű módosíthatóságra tervezünk (forward-compatible architecture).

Az adatbáziskezelő rendszerek közötti váltás (ha normálisan kihasználjuk az adatbáziskezelő feature-jeit) az mindig költségbe kerül. De hogy saját példát mondjak: mysql-ről postgresql-re kb. 1 hét alatt váltottunk, egy több mint 10 éves kódbázissal, szinte alig kellett átírni valamit.

Gondolom azért, mert volt egy réteged (jéé, layerek), aminek volt illesztése Postgresre és MySQL-re. Mert elabsztrahálta azt. És ha mondjuk neked egy hierarchical DB-re és nem relational DB-re kell váltanod, az mennyiben módosítja a nem perzisztenciával foglalkozó (mondjuk úgy, üzleti logikai) kódodat?

Ez a termék erőteljesen többszálú, standard java alkalmazás volt. Voltak "service" osztályok, melyek adatbázis perzisztenciát valósítottak meg (beégetett sql utasításokkal) azt kellett refactorálni. Tehát ilyen szempontból volt layer. Itt pl. viszonylag egyszerű (nem mondom azt, hogy nem drága, de egyszerű) lenne akár másik DB típusra váltani.

De pl. egy új termékünkben (kb. egy CRUD adminisztráló program) az üzleti logika jórésze adatbázisban van implementálva, a check-ek is, nagyon erőteljesen használjuk a postgresql szinte minden feature-ét, és nagyon bejött: iszonyat gyorsan tudunk fejleszteni a korábbi hasonló CRUD keretrendszereinkhez képest, teljesen világos, hogy mi hova kerül, szép, tiszta a kód. Ez pl. teljesen az adatbázis köré van szervezve, itt az átállás nagyon drága lenne. Viszont mi elég speciális piacra dolgozunk, ahol lényegében a hardwertől a softwareig mindent mi szállítunk, szóval ez így azért más.

Jo latni, ahogy a sok javas szivarfust mogul filozofalgat...

Mindekozben a javascript az egyeduli nyelv,
amivel ertelmesen lehet backendet, frontendet, mobilra es desktopra is fejleszteni. Akar egy programozoval, nincs varakozas a masik csapatra.

A dependency hellnek titulalt valami, pedig igazabol egy nagysagrenddel gyorsabban fejlodo kornyezet.
Iszonyatosan felgyorsult a javascript vilag...

---
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....

"Mindekozben a javascript az egyeduli nyelv,
amivel ertelmesen lehet backendet, frontendet, mobilra es desktopra is fejleszteni. Akar egy programozoval, nincs varakozas a masik csapatra."

Mert egy programozo nem tud Javaban JDBC-zni, JSP-zni es Swingezni egyszerre pl? ;) Semmi koze ennek a Javascripthez, mellesleg legtobb programozo nem csak "egy nyelven beszel", mashol is van ilyen hogy "nem kell varni masik csapatra", meg C#-ban es C++-ban is meg tudsz csinalni db connectiontol GUI-ig mindent pl. "nem varva a masik csapatra".

"Iszonyatosan felgyorsult a javascript vilag..."

Meg ezzel parhuzamosan a karomkodasaink is egyre valasztekosabbakka valnak nekunk, programozoknak. ;)

> es Swingezni egyszerre pl? ;)

Ezt ugy kell elkepzelni, mint az abevjava-t? :)
Azt probaltad mar mondjuk egy 3200x1600 pixeles 13"-os laptopon? :)))

---
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....

Az online képzések egyre dominánsabbak lesznek. Amerikaiaknak már állást garantál a udacity, ha elvégeznek valami kurzust náluk. ha nincs állás a képzés teljes árát visszafizetik. Tisztán látszik, hogy ez a jövő. Az egyetemek valami kutató műhelyekké fognak majd válni, ahova nagyon speciális okból megy valaki.