A SOLID alapelv alkalmazása elindít az FP útján?

Az OOP programozók gondolom hallottak már a SOLID alapelvről, minden estre, ha fel kellene eleveníteni, akkor van egy nagyon jó blog post, janoszen blogtársunk tollából. Én is az ott leírtakat használtam az írásomhoz.

A SOLID-ból nekünk igazából most csak a SID (gonosz kisgyerek a Toy Story-ból;) az érdekes. Nézzük ezeket egyesével:

  • S: Single Responsibility Principle (Egy felelősség elve).
    Egy osztály vagy modul egy, és csak egy felelősséggel rendelkezzen (azaz: egy oka legyen a változásra).

Ha ezt az alapelvet alkalmazzuk, akkor egyre inkább olyan osztályokat készítünk, amiben vagy csak adat van vagy csak függvény, abból is csak egy. A fenti blog post alábbi mondata is ezt erősíti:
"Szervezzük át tehát a Student osztályt úgy, hogy önmagában csak egy adattároló legyen, és a különböző feladatoknak külön osztályai legyenek"

Vegyük észre, hogy ez az elv szembe megy az OOP egységbezárás (encapsulation) alapelvével!

Példa OOP:


class Student {
}
class GradeBook {
    def addGrade(student: Student, grade: Int) = { ... }
}
class StudentRecords {
    def changeStudentName(student: Student, newName: String) = { ... }
}

Példa FP:


class Student {
}

object Student {
    def addGrade(student: Student, grade: Int) = { ... }
    def changeStudentName(student: Student, newName: String) = { ... }
}
  • I: Interface segregation principle (Interfész elválasztási elv)
    Több specifikus interfész jobb, mint egy általános.

    Ha ezt az alapelvet alkalmazzuk, akkor egy interfészt sok metódussal helyett sok interfészt egy-egy metódussal fogunk készíteni. Egy interfész egy metódussal pontosan megfeleltethető egy függvénynek (csak a függvény általánosabb). Ezt láthatjuk is a Java8 féle lambda megvalósításnál is.

    Példa OOP:

    
    trait PrinterInterface {
        def print(document: Document)
    }
    trait ScannerInterface {
        def scan: Document
    }
    

    Példa FP:

    
        type PrinterInterface = Document => Unit
        type ScannerInterface = Unit => Document
    
  • D: Dependency inversion principle (Függőség megfordítási elv)
    A kódod függjön absztrakcióktól, ne konkrét implementációktól.

    Ha ezt az alapelvet alkalmazzuk, akkor interfészeket készítünk a rétegeink közé és azokat hívogatjuk a konkrét megvalósítás helyett, így a funkcionalitás könnyen cserélhető. Fentebb láthattuk, hogy egy interfész megfeleltethető egy függvénynek, így a fenti elv alkalmazása megfelel a magasabb rendű (higher order) függvények használatának.

    Példa OOP:

    
    trait BusinessLogicInterface {
        def getSomeData: Data
    }
    
    trait DatabaseConnectionInterface {
        def query(sql: String): DatabaseQueryResult
    }
    
    class MyController(businessLogic: BusinessLogicInterface) {
        def myAction = {
            val myData = businessLogic.getSomeData()
            //...
        }
    }
    
    class BusinessLogic(databaseConnection: DatabaseConnectionInterface) extends BusinessLogicInterface {
        def getSomeData: Data = {
            val result: DatabaseQueryResult = databaseConnection.query('SELECT ...')
            //...
        }
    }
    

    Példa FP:

    
        def myAction(getSomeData: Unit => Data) = {
            val myData = getSomeData()
            //...
        }
        def getSomeData(query: String => DatabaseQueryResult): Data = {
            val result: DatabaseQueryResult = query('SELECT ...')
            //...
        }
    
  • Összefoglalva: ha valaki jól és következetesen használja a SOLID alapelvek közül a SID elemeit, akkor Ő OOP-ben kvázi funkcionálisan programoz, csak jóval nagyobb körítéssel.

    Hozzászólások

    Elfelejtettem a tanulságot levonni! :)

    Igen, a SOLID (SID) alkalmazása elindít bennünket a funkcionális programozás felé. Ha úgy is tűnik, hogy fáradságos a SID elveket betartani (OOP-ben), akkor is érdemes, mert segítségükkel könnyen módosítható, könnyen tesztelhető, könnyen olvasható lesz a programunk.
    Ha pedig vesszük a bátorságot és megízleljük az FP világát, akkor ezen elvek betartása már nem lesz fáradságos, hanem egyszerű és mókás lesz. Nem véletlen, hogy az FP nyelvek a legkedveltebb nyelvek közé tartoznak, és az sem mellékes, hogy a a fizetések is a legjobbak közé tartoznak.

    Bar a legtobb alapelvet erdemes hasznlani, de a Single Responsibility Principle azert megerne egy beszelgetest. Ki az aki egy metodussal/adattaggal rendelkezo osztalyokra bontja szet a kodot?
    Ez inkabb csak elmeleti lehetoseg de sehol nem lattam szigoruan alkalmazva.

    En inkabb gyakorlatiasabban kozelitem meg a problemat, egy meghatarozott feladatra keszitek osztalyt, pl ha szenzor adatokat kell beolvasni akkor biztosan szetbontom egy manager osztalyra amely birtokolja az osszes szenzort, illetve szenzor osztalyokra. Ha lehetseges akkor azokat is csoportba bontom, szenzor tipusokra. Meg korites. De ennyi.
    Csak a pl szenzor nevhez biztosan nem csinalnek kulon osztalyt.
    Valszeg az IDE beallna a foldbe annyi osztalytol, most is neha lassu a tobb ezer osztalytol pedig SSD-m es i7-em van.

    Itt https://sklivvz.com/posts/i-dont-love-the-single-responsibility-princip… boncolgatja a kollega a hatranyokat ha valakit erdekel.

    "Bar a legtobb alapelvet erdemes hasznlani, de a Single Responsibility Principle azert megerne egy beszelgetest."

    Valamelyiket érdemes használni, valamelyiket nem annyira. Mindenképp érdemes ezekről beszélgetni, mert ugyanazt a dolgot sokféle szemszögből is érdemes megvizsgálni. Az egyes elveket érdemes alkalmazni is, de aztán érdemes időnként levonni a tapasztalatokat is. Az SRP és az Encapsulation egymásnak ellentmondanak, így mind a kettő egyszerre nem lehet jó és követendő. Én az SRP-t jónak tartom, az Encapsulationt inkább rossznak.

    "Ki az aki egy metodussal/adattaggal rendelkezo osztalyokra bontja szet a kodot? Ez inkabb csak elmeleti lehetoseg de sehol nem lattam szigoruan alkalmazva."

    Igen sajnos nem nagyon lehet ilyet látni, viszont olyat annál inkább, hogy egy új funkcióhoz inkább lemásolsz egy osztályt vagy metódust, mert nem mered átírni, nehogy valamit egész máshol elrontson. Ez a kettő viszont összefügg, ha egy osztály többféle felelősséggel bír, akkor az egyik felelősség változtatása elronthatja a másik működését is.

    "Itt https://sklivvz.com/posts/i-dont-love-the-single-responsibility-princip… boncolgatja a kollega a hatranyokat ha valakit erdekel."

    Köszi, érdekes írás! Szerintem érdemes elolvasni, bár nagyon sok mindennel nem értek egyet benne.
    Kiderül belőle, hogy nem is érti az SRP lényegét, bár ezt be is vallja.

    Erről beszél:
    "Egy osztály vagy modul egy, és csak egy felelősséggel rendelkezzen (azaz: egy oka legyen a változásra)."

    Ennek kapcsán az okok között ilyeneket ír, hogy hiba miatti javítás, optimalizálás, ... Ezek nem okok a változásra! Nem "felelőssége" (SRP szerint), hogy hibamentes legyen, vagy gyorsan fusson, a "felelőssége" az, hogy pl. kirajzolja magát a képernyőre vagy mentse magát adatbázisba, vagy kiszámoljon valamit.
    Emiatt, ha egy osztályban (függvényben) több felelősség is van (több mindent csinál egyszerre), akkor az egyik módosítása esetén könnyen elromolhat a másik is. Illetve megérteni is nehezebb, mert egyszerre mindegyiket meg kell értenem, ha szét vannak bontva, akkor külön-külön is megérthetem. Írni csak egyszer írjuk a kódot, viszont rengetegszer olvassuk (megértjük mit csinál). Nem mindegy, hogy ha egy funkcionalitást kell megértenem, akkor tényleg csak azt az egyet kell, vagy a sok felelősség összekeverése miatt sokkal többet.

    A komplexitás fogalmában is nagyon eltér az én véleményem. Szerinte akkor egyszerű, ha együtt van az üzleti logika és a perzisztencia. Szerintem meg akkor, ha külön vannak és egy vékony réteg köti össze őket. Könnyebb megérteni, módosítani külön-külön ezeket, mint együtt.
    Azzal se értek egyet, hogy a sok osztály növeli a komplexitást, ha egybe rakjuk ugyanazt a funkcionalitást, akkor kisebb lesz. Ha van sok egyszerű osztályunk és köztük kevés interakció, akkor szerintem egyszerűbb, mint, ha egy osztály lenne, benne sok interakcióval.

    "Az SRP és az Encapsulation egymásnak ellentmondanak"
    Van ket alapelv es a ketto kozti "egyensulyt" kell megtalalni szerintem. A korulmenyektol is sokminden fugg, en alapvetoen egyetertek a sraccal.

    Egy kis cegnel nem fognak idot adni hogy mindent "szepen" megtervezz. Ugyanez hataridos munkanal. A motto hogy eloszor mukodni kell, majd utana lehet optimalizalni. A befektetoket az elejuk tett eredmenyek erdeklik. Altalaban megertik ha valami bugos vagy kevesbe optimalizalt, ellenben ha nincs elottuk semmilyen eredmeny az regen rossz.

    Nagy cegnel sok eves "lifetime"-al rendelkezo termek eseten a karbantarthatosag nagyon fontos, mert lehet hogy tobb ev mulva nyulnak hozza a kodhoz a szerzo pedig mar nem is dolgozik ott...

    Kollegaval ezt boncolgattuk, es egyetertettunk, hogy barmilyen alapelv addig jo amig mar nem "karos", nincs tulbonyolitva de megis jol meg van tervezve.

    "a "felelőssége" az, hogy pl. kirajzolja magát a képernyőre vagy mentse magát adatbázisba, vagy kiszámoljon valamit."
    Ebben igy ilyen formaban egyetertek, de ezek is osszetett muveletek viszont mar nem erdemes megjobban szetbontani oket tehat a szentbontogatasban egy optimumot elerve erdemes megallni. Ha kesobb megiscsak szukseg lesz jobban szetbontani az osztalyokat akkor refactoring :)

    "A motto hogy eloszor mukodni kell, majd utana lehet optimalizalni."

    Így van, de a "majd utána"-ra már sosincs idő. :)

    "Altalaban megertik ha valami bugos vagy kevesbe optimalizalt, ellenben ha nincs elottuk semmilyen eredmeny az regen rossz."

    Az agilis metodikának ez az egyik alapja, hogy folyamatosan legyen "eredmény", emiatt pedig a legfontosabb kitétel, hogy könnyen módosítható legyen a kód. A kis cégek egyik legnagyobb fegyvere a nagy cégekkel szemben a rugalmasság, ha a megrendelő ezt akarja, akkor így lesz, ha másnap mást, akkor meg úgy, és mindezt a lehető legtakarékosabban.

    "Így van, de a "majd utána"-ra már sosincs idő. :)"

    Ha muszaj akkor nagyon muszaj :) Amugy nalunk eloszor a regi logika es a GUI van atemelve a regibol az uj verzioba. Ezutan jon a refaktoralas. Tehat mindig van egy mukodo verzio. Persze a vegleges verzioban az osszes regi ablak/funkcio ki van cserelve.

    " A kis cégek egyik legnagyobb fegyvere a nagy cégekkel szemben a rugalmasság.." Jah, de pont ott van kevesbe jol megtervezve a kod altalaban. A megrendelo tegnapra akarta tehat gyorsan osszerakjak a cuccot. Unit tesztrol meg ne is almodozzunk.

    Egy osztálynál is főként csak akkor gond, ha több felelősséggel is bír, ha az adott osztály mutable, ha immutable, akkor legalább egyik metódus sem tudja elrontani egy másik metódus működését.

    Gondot igazából csak annyit okoz, hogy kicsit nehezítheti a megértést, mert a függőségi gráfban nem csak egy-egy funkciót hoz magával, hanem többet.

    A több felelősség viszont nem csak osztálynál gond, hanem metódusnál is. A linkelt blogban pl. azt írja, hogy teljesen jó az üzleti logikát összevonni a perzisztenciával. Ha azt egy metóduson belül oldják meg, akkor az nagyon rossz.
    Pl. vegyünk egy rendelés elküldését, ha így van elkészítve, hogy:

    • végigmegy a rendelés tételein,
    • ellenőrzi, hogy ki van-e töltve a tétel mennyisége, ha nincs, akkor hibát ad,
    • kiszámolja a tétel nettót, az áfát és a bruttót,
    • menti a tételt,
    • ellenőrzi a rendelés fejben a vevő adatait, ha hiányzik, akkor hibát ad,
    • kiszámolja a rendelés össz. nettót, áfát, bruttót,
    • menti a rendelés fejet.

    Ebben az esetben ez a metódus három felelősséggel is bír: validáció, üzleti logika (számolások), perzisztencia. Bármely részén akarok változtatni, ahhoz az egész működést meg kell értenem.
    Egyik részén való módosítás elronthatja egy másik rész működését.
    A hibakezelés is bonyolultabb, összekeverednek a felhasználónak szóló (validáció), a fejlesztőknek szóló (üzleti logika), valamint az üzemeltetőknek szóló (perzisztencia) hibák.
    Nem újrafelhasználható, ha pl. csak ellenőrizni szeretnénk az épp bevitt adatokat, vagy a számolás eredményeket egy kimutatásban is szeretnénk használni, akkor vagy kód duplikálás történik (sajnos ez az általános), vagy refaktorálni kell, ami viszont megint új hibalehetőségek behozatalát eredményezheti.
    Nem hatékony a végrehajtás sem, mivel hiba esetén feleslegesen hajtatunk végre számolásokat és perzisztenciákat.
    Ha ehelyett három metódusunk van: validáció, üzleti logika, perzisztencia, akkor a fenti hátrányok egyike sem jelentkezik.

    kis észrevétel: az első sorban a link nem jó helyre mutat