Tiszta kód
janoszen egy másik fórumszálban linkelte egy OOP példakódját egy kezdő kérdésére válaszolva, ehhez lennének kérdéseim és hozzáfűznivalóm, ezért kiemeltem egy külön témába.
A programból le lehet vonni azt a következtetést, hogy OOP-t ott lehet érdemes használni, ahol egy bizonyos funkciót legalább kétszer megvalósítunk, mert ad egy szabványos interface-t, amivel tudunk kommunikálni.
Viszont itt jön a kérdés, hogy hol találkozunk ilyen bonyolultságú projekttel? Hol van szükség arra, hogy ugyanolyan típusú adatokat (mondjuk blogbejegyzés) két vagy több különböző adatforrásból halásszunk össze?
Hol van szükség arra, hogy akár többféle router is lehet a rendszerben? Ha viszont csak egyet használ a projekt (márpedig a fenti blogmotorban csak egy van), akkor miért van általánosan megvalósítva? Hisz így egyrészt van egy minimális overhead, másrészt plusz munkával jár. De ki tudja, hogy mit hoz a jövő? Mi lesz, ha a mostani router interface nem megfelelő? Akkor lehet refaktorálni, azaz fölösleges melót tett bele az, aki a jelenlegit írta.
Felmerülhet az is, hogy miért keverednek a php és a sablon fájlok? Mi közük van egymáshoz? De ez apróság.
Kérdés, hogy tényleg olyan kivételes helyzet, ha olyan konfigurációs beállítást kérdezünk le, ami nem létezik? Ettől a program futásának meg kell állnia?
Kérdés, hogy miért van több kivétel definiálva, amikor nincsenek kifejtve, azaz a kódjuk megegyezik?
Kérdés, hogy a BlogPostInteractor miért példányosítja a kétféle lekérdezési módot? Mi van, ha egy adott oldalon csak az utolsó n darab bejegyzésre van szükségünk, de az egyes posztokra nem? Ez is egy apró overhead.
Kérdés, hogy ha a kivételek típusa (Exception) megjelenik a fájlnévben, akkor az interface-eké miért nem?
Kérdés, hogy kezdőknek ez alapján kéne elkezdenie programozni?
---
Értem én, hogy példakód, de azért ez egy kezdő számára ijesztő lehet. Végeredményben nem csinál mást, mint egy vagy több blogposztot lekérdez, a nettó kód ebből legfeljebb egy-két kilobájt, ezzel szemben itt a szerveroldalon az src könyvtár mérete negyvennégy kilobájt. Ez azt jelenti, hogy két kilobájt kódhoz negyven kilobájtnyi bloat jár OOP alatt? Nyilván ez erős túlzás, de mit is kapunk cserébe? Szüksége van erre egy kezdőnek, de akár egy átlagos weboldalnak?
Mi nem blogmotort gyártunk, hanem vállalatirányítási rendszert, számlázással, pénzüggyel, meg hasonló dolgokkal. De nálunk, ha bekerül valami az adatbázisba, az az adat "készen" van, amikor legközelebb szükség van rá, ritka kivételtől eltekintve nem kell formázgatni, hanem lekérdezés után egy az egyben megy ki a kliensre. De akkor minek tennénk bele egy objektumba? Miért kéne minden adattaghoz ezek után getter meg setter?
Vagy például a hibakezelést úgy valósítottuk meg, hogy hibakód plusz hibaüzenet, amibe opcionálisan be lehet helyettesíteni az értékeket. Megírtuk egyszer, nem kell minden típusú hibához új php fájlt létrehozni.
Vagy mondjuk kezelünk többféle kimenetet (json, pdf, fájl stb.), de elintéztük egy egyszerű
Szóval ez a blogmotor így szerintem nem clean code, hanem tiszta bloat, az ágyúval a vérebre tipikus esete. Nem véletlenül kérdeztem évekkel ezelőtt, hogy hol van az a határ, ahol érdemes OOP-t használni? Nálunk egy helyen lehetne: a biztonsági mentésben lehet választani, hogy ftp-vel vagy scp-vel működjön – minden más funkcionalitás csak egyszer szerepel a kódban, nincsenek ismétlések.
■ A programból le lehet vonni azt a következtetést, hogy OOP-t ott lehet érdemes használni, ahol egy bizonyos funkciót legalább kétszer megvalósítunk, mert ad egy szabványos interface-t, amivel tudunk kommunikálni.
Viszont itt jön a kérdés, hogy hol találkozunk ilyen bonyolultságú projekttel? Hol van szükség arra, hogy ugyanolyan típusú adatokat (mondjuk blogbejegyzés) két vagy több különböző adatforrásból halásszunk össze?
Hol van szükség arra, hogy akár többféle router is lehet a rendszerben? Ha viszont csak egyet használ a projekt (márpedig a fenti blogmotorban csak egy van), akkor miért van általánosan megvalósítva? Hisz így egyrészt van egy minimális overhead, másrészt plusz munkával jár. De ki tudja, hogy mit hoz a jövő? Mi lesz, ha a mostani router interface nem megfelelő? Akkor lehet refaktorálni, azaz fölösleges melót tett bele az, aki a jelenlegit írta.
Felmerülhet az is, hogy miért keverednek a php és a sablon fájlok? Mi közük van egymáshoz? De ez apróság.
Kérdés, hogy tényleg olyan kivételes helyzet, ha olyan konfigurációs beállítást kérdezünk le, ami nem létezik? Ettől a program futásának meg kell állnia?
Kérdés, hogy miért van több kivétel definiálva, amikor nincsenek kifejtve, azaz a kódjuk megegyezik?
Kérdés, hogy a BlogPostInteractor miért példányosítja a kétféle lekérdezési módot? Mi van, ha egy adott oldalon csak az utolsó n darab bejegyzésre van szükségünk, de az egyes posztokra nem? Ez is egy apró overhead.
Kérdés, hogy ha a kivételek típusa (Exception) megjelenik a fájlnévben, akkor az interface-eké miért nem?
Kérdés, hogy kezdőknek ez alapján kéne elkezdenie programozni?
---
Értem én, hogy példakód, de azért ez egy kezdő számára ijesztő lehet. Végeredményben nem csinál mást, mint egy vagy több blogposztot lekérdez, a nettó kód ebből legfeljebb egy-két kilobájt, ezzel szemben itt a szerveroldalon az src könyvtár mérete negyvennégy kilobájt. Ez azt jelenti, hogy két kilobájt kódhoz negyven kilobájtnyi bloat jár OOP alatt? Nyilván ez erős túlzás, de mit is kapunk cserébe? Szüksége van erre egy kezdőnek, de akár egy átlagos weboldalnak?
Mi nem blogmotort gyártunk, hanem vállalatirányítási rendszert, számlázással, pénzüggyel, meg hasonló dolgokkal. De nálunk, ha bekerül valami az adatbázisba, az az adat "készen" van, amikor legközelebb szükség van rá, ritka kivételtől eltekintve nem kell formázgatni, hanem lekérdezés után egy az egyben megy ki a kliensre. De akkor minek tennénk bele egy objektumba? Miért kéne minden adattaghoz ezek után getter meg setter?
Vagy például a hibakezelést úgy valósítottuk meg, hogy hibakód plusz hibaüzenet, amibe opcionálisan be lehet helyettesíteni az értékeket. Megírtuk egyszer, nem kell minden típusú hibához új php fájlt létrehozni.
Vagy mondjuk kezelünk többféle kimenetet (json, pdf, fájl stb.), de elintéztük egy egyszerű
switch
-csel, nincs ezerfelé szétszabdalva, egy helyen ott van minden, tökéletesen működik.Szóval ez a blogmotor így szerintem nem clean code, hanem tiszta bloat, az ágyúval a vérebre tipikus esete. Nem véletlenül kérdeztem évekkel ezelőtt, hogy hol van az a határ, ahol érdemes OOP-t használni? Nálunk egy helyen lehetne: a biztonsági mentésben lehet választani, hogy ftp-vel vagy scp-vel működjön – minden más funkcionalitás csak egyszer szerepel a kódban, nincsenek ismétlések.
janoszen
+1
Nem
Fiatal a szakmánk nagyon, nem néz olyan hosszú múltra vissza mint mondjuk az építészet vagy a könyvelés, szóval nagyon hasznos az ha minél több nézőpontot megvitatunk.
Sokszor
Amire a repóhoz kapcsolódó cikk rá kívánt világítani az az, hogy az alkalmazásod különböző rétegei között érdemes egy fajta leválasztást megteremteni azért, hogy le lehessen cserélni a mögöttes működést. Hogy ezt interfacekkel és objektumokkal teszed meg, vagy header file-okkal és structokkal, az ebből a szempontból halálosan mindegy.
Számomra (személyes preferencia) ebből a szempontból sokat segít ha egy erősen és szigorúan tipusos nyelvben fejlesztek, mert már az interfacek összedobálásánál kiderül, hogy hoppá, azt úgy ott nem lehet. (Ezért szeretném, ha végre lenne normális IDE a Hacklanghez és ezért gyűlölöm teljes szívből a PHP tömbkezelését.)
Ami azt a kérdést illeti hogy mennyire gyakran fordul elő a modulok cseréje: az én életemben gyakrabban mint gondoltam volna. Régebben sokszor inkább addig tekergettem a kódot amíg a "régi" modulba belehegesztettem a kívánt plusz funkcionalitást. Ennek aztán az volt a következménye, hogy egy pár ilyen módosítás után szembe nézett egy hétfejű sárkány. Amióta absztrakciós réteget teszek mindenhová egyrészt jobban végig gondolom hogy milyen absztrakciónak van értelme, másrészt sokkal könnyebb egy modult inkább öröklődés útján kiterjeszteni vagy komplett lecserélni mint módosítani. Ráadásul egy ilyen absztrakció, ha normálisan van megcsinálva, gyakorlatilag semmibe nem kerül teljesítmény szintjén.
Itt visszakanyarodnék a blogmotor kérdésére: nagyon kevés olyan megrendelőt láttam életemben, aki a projekt elején elmondta volna hogy mit kér, és az abból született leírás pontosal lefedte volna a későbbi funkcionalitást.
Ha pl. egy listázó oldalról van szó, akkor simán előfordulhat az, hogy a megrendelő a projekt 80%-ánál még azt kéri, hogy mi lenne ha ott megjelenítenénk a kommentek számát, vagy ajánlott cikkeket, vagy mittudomén. Persze, kérünk tőle több pénzt, több időt, de ha emiatt a kódbázis nagy részéhez hozzá kell nyúlni, akkor az sokkal többe kerül, mintha a kódbázis darabjai kiterjeszthetőre vannak megcsinálva.
És legyünk őszinték: a megrendelő mindig kér még valamit. Én meg olyannal nem találkoztam, akár műszaki akár nem műszaki, aki ne kért volna egy számára látszólag triviális módosítást, ami aztán egy full refaktorba eszkalál ha nincs kellően szegmentálva a kód.
Egébként már össze akartam dobni egy real world projektet megnézésre erről, csak egyszerűen olyan szinten el vagyok úszva, hogy az valami elképesztő.
Kiterjeszthetőség
Természetesen nálunk is elő szokott fordulni, hogy valamit le kell cserélni, de ezt általában akkor szoktam megtenni, ha
1, van egy működő kód,
2, kérnek egy újítást, azt leprogramozom,
3, ha a kettő összevonható/van bennük közös, akkor azt kiemelem függvénybe
Tesztekkel nagyon szépen lehet ellenőrizni, hogy a függvény vagy metódus paramétere az adott típusú-e. Ha a type hintnek nem felel meg, akkor php7 alatt kapsz egy kivételt, ha nincs type hint, akkor meg a teszt jelez, de a kódban valahol ígyis-úgyis le kell kezelni az esetet. Én ezért a type hintnek nem sok értelmét látom.
Ellenőrzöd
Mert ha ezt nem teszed, akkor gyakorlatilag egyetlen védelmi vonalad van, és ha ebben van egy bug ami váratlan tipusú adatot hoz létre, az tovább gyűrűzhet más modulokba és a végén akár biztonsági problémát is okozhat.
Ellenőrzés
1, ami kívülről jön,
2, ami belülről jön.
Ami kívülről jön, azt leellenőrizzük, és úgy tesszük a memóriába/adatbázisba.
Ami belülről jön, az egy ellenőrzött adat (hisz úgy került a memóriába vagy az adatbázisba); ha létezik, akkor helyes, ha nem létezik (pl. opcionális beállítás), akkor hibát dobunk vagy alapértelmezett értéket használunk.
Ellenőrzés
Nem
tehát feltételezitek, hogy
Fő a magabiztosság. :-)
Válasz
Itt két fajta iskola van. Szerintem, az a helyes, hogy vagy átadunk egy default értéket, vagy pedig lekezeljük azt hogy nem kaptuk meg a konfigurációs opciót.
Ha nem kezeljük le, akkor azzal a feltételezéssel élnék, hogy a program nincs felkészítve a nem létezésére, tehát a program futása álljon le egy értelmezhető hibaüzenettel ami alapján könnyű javítani.
Kivételek
A kivételek nevére lehet catch-elni. Ha pl. egy SQL absztrakciós réteget írok, akkor simán el akarhatom kapni mondjuk a duplicate key exceptiont, noha semmiben nem különbözik bármelyik másik SQL exceptiontől.
Hibakezelés
Ugyanígy megszakad a futás, ha felhasználói hiba történt, például hiányzik valamilyen fontos adat a számláról.
Szóval mi mindent el tudunk intézni egy hibakód és hibaüzenet párossal, ha baj van, ha pedig nem olyan nagy, azt a kód lekezeli.
Ezért nem értem a nevesített kivételeket.
Pl
Igen ám, de van ahol a UserNotFoundExceptiont el kell kapni és másképp kell kezelni, pl. mappelni kell egy AuthenticationFailedException-re. Itt két megoldásod van:
a) nevesíted az Exceptionöket
b) elkapod az összes exceptiont és reménykedsz, hogy a Junior kolléga az else ágban nem felejti el újra dobni az exceptiont az esetben ha olyan hibakóddal találkozik amire neki nincs szüksége.
Vagyis az a) opció:
Ez különösen akkor jön szembe, ha kicsit bonyolultabb a jogosultság-kezelésed, ez esetben ugyanis sokszor kell mappelni a belső hibaüzeneteket publikus hibaüzenetekre hogy ne legyen info-leak az API-ból.
A probléma tovább hatványozódik ha több adatbázis motort kell támogatnod, vagy ne adj Isten olyan adatbázist ami nem relacionális és nincsenek ilyesmire hibakódja, pl. LDAP. És igen, én eleve több DB motort használok, fejlesztőkörnyezetben pl. HSQLDB-t hogy ne kelljen külön SQL szervert indítanom és egy repülőn is tudjak fejleszteni anélkül, hogy az aksikimélő üzemmódot kikapcsolnám. Baromi kényelmes az, hogy az IDE-ben rányomok a "Run" gombra és nem csak az alkalmazás indul el, hanem az embedded DB is amit szépen be is inicializál tesztadatokkal.
Az exception bár nem
a) a különböző típusú hibákat a megfelelő absztrakciós szinten tudd lekezelni (vagyis akár több stack frame-mel feljebb)
b) egy hibát ne tudj ignorálni, azzal valahol valamit kelljen kezdeni
c) magával hozza a hiba jelentkezésekori stack tracet
Ennyivel tud kb többet, mint a hibakód. Hogy miért van neve? Amiért a függvényeknek is. Milyen jelentése van 3 szinttel mélyebben annak, hogy a hibakód 4 volt? Mi van, ha több metódus is mondhatja azt, hogy a hibakód 4, de eltérő jelentéssel?
Egyedi
Hogy garantálsz egyediségét
Korábban használtunk MySQL-t,
A hibakezelésünk, így jobban belegondolva, hasonló a kivételkezeléshez, mert teljesíti azokat, amit felsorolsz. A hibákat bármelyik felsőbb szinten tudjuk kezelni, bár a rendszer jellegéből adódóan erre nincs szükség.
Továbbra se látom ez hogy
És akkor hiba kezelés kikényszerítése, stack trace, context infók csatolása meg mindig sehol nincs...
Egyszerű
Csak rávilágítanék, hogy itt
A hibák lekezelése pedig továbbra is azon függ, hogy a fejlesztő a hívó oldalon megvalósítsa a hibakezelést, kikényszerítés nincs.
Kivétel
- Az esetek 80%-ában elég visszaadnunk egy hibakódot,
- 18%-ban egyéb, változó paraméterekkel egészítjük ezt ki, például behelyettesítendő változók (változó számú), űrlapelemek azonosítói, egyéb értékek,
- 2%-ban backtrace,
- A hibaüzeneteket akárhány nyelven kell megjeleníteni.
PHP-ban az egyes új kivételeket mindig deklarálgatni kell, a paraméterlistájuk pedig kötött./**
* Sets the address that has caused this error.
* @param INETAddress $address
*/
public function __construct(INETAddress $address, $required) {
parent::__construct('Address ' . get_class($address) . ' is not of required type: ' . $required);
}
}
Ezt janoszen egyik kódjából másoltam be, mert épp ezt találtam. Egy csomó munka minden egyes új hibatípushoz új php fájlt felvenni.
Nálunk nem kell kiegészítgetni semmit, csak a megfelelő táblába kell felvenni az új hibakódhoz tartozó hibaüzeneteket.
Nyilván le lehet programozni a kivételkezelést teljesen általánosan, de akkor ugyanott vagyunk, ahol a part szakad.
Amit idéztél, az hibakezelés.
Kezelés
Mutass már egy példát légy
Példa
null
vagy 1000, akkor minden rendben volt, ha 1000 feletti, akkor galiba történt. A hívó visszakap egy eredményt, amiből ahibakod()
függvény kiveszi a hibakódot, és az alapján eldönthetjük, hogy mit csinálunk, de szinte mindig a hívónak adjuk át a vezérlést.A hibák többségénél nincs szükség logolásra és a stack-re, ahol igen, ott a
hiba_general()
nevű függvény beleteszi a válasz tömbbe.Vagyis a hibakódot a
Igen
return 2522;
ekvivalens areturn array('hibakod' => 2522);
-vel.Ezért tényleg egyszerűbb, mint létrehozni n darab kivételt.
Sosem probléma... :)
Többnyire az egyszerűséggel érvelsz a strukturált / procedurális programozási szerkezet mellett. Emiatt viszont bennem kb ugyanaz a kérdés vetődik fel, mint BlaZe - ben: ha azt gondolod, hogy így egyszerűbb, akkor miért tűzdeled teli a kódod if / else / elseif ágakkal random helyeken, gyakorlatilag kódot duplikálva, ha minderre adottak nyelvi eszközök (, amiket akár procedurális megközelítésben is lehetne használni)?
Ezen a ponton rengeteg hibalehetőséget tesztek bele. Nem csak magatoknak, hanem egy esetlegesen becsatlakozó új kollégának még jobban..
Tényleg?
Már megint az az érzésem, hogy hülyének nézel. Ha az ember bizonyos cselekedeteinek az a következménye, hogy kap egy nagy pofont az élettől, akkor előbb-utóbb változtatni fog. Ha nem változtat, akkor hülye, ha nem kap pofont, akkor pedig nem cselekszik rosszul.
Szerintem nem hülyének néz,
Amellett, hogy mindig felhozod az OOP ellen a teljesítmény overheadet. Itt amiről beszélsz, az sokkal nagyobb overhead. Asszociatív tömbök repkednek ok nélkül, azokban lookupolni kell, és szét kell branchelni a kódot a helyes kezelés miatt, plusz user függvényeket kell bevezetni és meghívni. Mindezt azért, hogy ne kelljen létrehozni 1-2 filet, ami 2 kattintás egy IDE-ben. Ezek miatt az érveid szakmailag megalapozatlanok. Az meg pláne, hogy MÉG nem okozott problémát. Ami még nem okozott gondot, de a programozó figyelmén múlik, és van rá egyszerű megoldás, be se engedjük a kódba...
Köszi
Nem véletlenszerű
if (!$siker) {
return hiba_general(5000);
}
A
return 2523;
és athrow new IsNotAdminException;
begépelése pontosan ugyanannyi figyelmet igényel.Szóval ilyen megérzésekre, hogy "MÉG nem okozott problémát, ezért megalapozatlan" meg "amiről beszélsz, az sokkal nagyobb overhead" nem szoktam adni.
Más: kerestek kezdő programozókat? Egy ismerősöm szeretne átnyergelni Javára.
@ minek?
Az ftp_connect megteszi a maga kis warning-ját, ha nem jó valami, ezt miért nyeled le? Adott esetben csak abból tudhatod meg, hogy mi is volt a baj.
(Hoppá, most nézem, hogy nem is triggerel hibát, akkor szimplán felesleges a @.)
Akikkel viszont én eddig dolgoztam, azok közül még senki sem akadt, aki megkérdőjelezte volna egy jól belőtt IDE hasznát.
A példádnál maradva, ha én begépelek annyit, hogy 'th', már ajánlja is, hogy 'throw', ha ráütöm az ENTER-t, szóközzel és helyesen kiírja, következő ajánlat mindjárt a 'new', tehát csak +1 ENTER kell, ezután annyit írok, hogy 'IsN', máris kapom a teljes listát azokról a létező osztályokról, amik Exception - leszármazottak és így kezdődik a nevük, alattuk pedig azokat (ha vannak), amiknek csak szerepel a nevében ez a három betű.
Tehát nagyon - nagyon keveset elegendő tudnom a hibáról, amit dobni szeretnék.
A Te 4 számjegyedből nagyon könnyű egyre is rosszul emlékezni - és már borult is minden, azt se tudjuk, mi történt.
Nagy számok
Izgalmas lehet például egy ekkora legördülő listából kiválasztani az épp aktuális kivételt.
Nem olyan nehéz
3 vagy 4 féle exception van összesen, ezek közül egy olyan, amit frontendre is ki kell vezetni. Ehhez kötelező a státuszkód is, ami megegyezik a HTTP státusszal. A szövege nyelvesített, meglévő nyelvi kulcsokból kell kiválasztani.
Ezt ha ügyesen építed fel, 20 elemnél soha nem látsz többet a legördülő listáidban és tetszőlegesen bővíthető.
Ha jól emlékszem, volt még külön User-rel kapcsolatos (ez is HTTP státusszal), adatbázishiba és általános.
Utóbbi kettőnél is HTTP státuszkódból választottunk.
Nem biztos, hogy pontosan emlékszem, és nem is lehet ennyire röviden leírni egy teljes hibakezelést, de a lényeg az, hogy főként ha van bő 300 féle hibád, akkor érdemes csoportosítani.
Sok
Megérzés...
Viszont kíváncsi lennék, hogy szerinted az alábbiak közül melyik illik az általad propagált megoldásra: szebb, rövidebb, áttekinthetőbb, biztonságosabb, fókuszáltabb, gyorsabb. És akkor itt csak egységes hibakezelésről volt szó. Ha minden hívásra le kéne külön kezelni a hibát, vagy el kéne kezdeni még feljebb buborékoltatni a hibát valamiért, akkor még borzasztóbb lenne ez az ifelgetés.
Én az elsőt választanám,
return func() + func() + func() + func();
-nál nem tudod megmondani, melyikfunc()
hívásban volt.Viszont jobban szeretem a lapos függvényeket, mint az egymásba ágyazott struktúrákat.
$res1 = func_assoc();
if ($res1['code'] != 1) {
goto vege;
}
$res2 = func_assoc();
if ($res2['code'] != 1) {
goto vege;
}
$res3 = func_assoc();
if ($res3['code'] != 1) {
goto vege;
}
$res4 = func_assoc();
if ($res4['code'] == 1) {
return $res1['result'] + $res2['result'] + $res3['result'] + $res4['result'];
}
vege:
$GLOBALS['errors']++;
}
De tudod exceptionnél is, ha
Az összes általában hozott OOP elleni érveddel szembe megy a fenti példakód.
Igen
global $errors;
$osszeg = 0;
try {
$osszeg += func();
} catch (Exception $ex){
$errors++;
}
try {
$osszeg += func();
} catch (Exception $ex){
$errors++;
}
try {
$osszeg += func();
} catch (Exception $ex){
$errors++;
}
try {
$osszeg += func();
} catch (Exception $ex){
$errors++;
}
return $osszeg;
}
Ott kezd érdekessé válni a történet, amikor a feladatok között vannak mellékhatásokkal járó műveletek, például adatbázisban kell matatni, és azokat hiba esetén vissza kell vonni.
?
Exceptionök meg képesek hordozni információt, ha kell.
Kivételek
:)
Értsd jól:
Szerintem a gond az, hogy egyszer érvelsz pl az IDE ellen (300-as lista), majd ha kapsz az érvedben szereplő problémára megoldást (csoportosítás), akkor kitalálsz egy másikat, ami miatt az neked nem felel meg (felhasználónak ugyanazt kell látni, nem elég a http státusz, stb). Nem igazán konstruktív a gondolkodásmódod, ha az lenne, már rég megtaláltad volna az eddig elhangzott példák közül azt, ami Neked hasznos.
Ezek csak minták, amikben többen többféle dolgot megmutattunk, hogy Mi hogyan csináljuk / csináltuk. Nyilván egyik sem lesz egy az egyben Neked is jó.
Viszont nem is keresed bennük a számodra hasznos dolgokat, így nem sok értelme van megvitatni...
Az összes általában hozott
Többen többször leírtuk. Én
De tudod exceptionnél is, ha
Viszont, ha nem számít, hogy hol keletkezett a hiba, akkor meg inkább így oldanám meg:
global $counter, $errorModulo;
if ($counter++ % $errorModulo != 0) {
return $counter;
}
}
function calculate_assoc(){
global $errors;
$tomb = array(func_assoc(), func_assoc(), func_assoc(), func_assoc());
if (!in_array(null, $tomb)) {
return $tomb[0] + $tomb[1] + $tomb[2] + $tomb[3];
}
$errors++;
}
<troll>
Mert ez rövidebb, áttekinthetőbb, gyorsabb, nehezebben elrontható, fókuszálja a fejlesztőt a business logic fejlesztésére?
Vagy mert csakazértis? :)
</troll>
Nem
Mivel a mi rendszerünkben ez egy fontos információ, ezért a hibakódos megoldást választottuk.
Továbbá a kliensnek is kiküldjük a hibakódot – tehát ennek valahol mindenképp ígyis-úgyis meg kell jelennie. Ha telefonon felhívnak, a kód alapján könnyebb és gyorsabb azonosítani a dolgot, mint ha felolvasnák a szöveget, mivel ez sajnos nem mindenkinek megy.
Exception-el is át lehet adni
Ha tényleg úgy gondolod, hogy van, ami nem megoldható kivételekkel, de a te módszereddel igen, akkor írj ilyen példát! Kíváncsian várom!
Nem
Ezzel szemben a hibatömbös megoldás jóval rugalmasabb és kevesebb munkával jár.
Szerintem átláthatóbb lesz a
Szerintem ha több projektet csinálsz, akkor te is másolod a megoldások egy jelentős részét, pl a bejelentkezést, stb. A hibakódokkal gondolom ugyanígy jársz el. Szóval olyan nagy különbség nincs aközött, hogy másolod a kivétel fájlokat vagy hogy másolod a táblázatodat. Egyébként nem tudom te hogy vagy vele, de én az olyan dolgokon, amiken nem kell gondolkodni, nem jellemző, hogy számottevő időt veszítek. Ilyen pl egy új kivétel fájl létrehozása és az osztálynév begépelése. Az egész megvan néhány másodperc alatt. Sokkal munka igényesebb a tényleges model kitalálása, vagy egy kódrészlet megtalálása egy olyan kódban, ahol nehéz követni, hogy mi mit csinál és a keresett kód szét van szórva ezer helyre.
Hogy mit nyersz vele, arra
Nem kell minden egyes hibának külön exception osztály. Ha kell a kód, átadod az exceptiönnek, ennyi.
Továbbra is fenntartom, hogy amit csináltok, az egy architekturális probléma. A hibakezelés és a hiba riportálás összemosása nem szerencsés. Dobsz egy belső hibát, amit a program megfelelően lekezel, majd a megjelenítési rétegben a hibát átmappeled user friendly üzenetté, és/vagy kóddá. Ez két külön dolog kell legyen. Nálunk is vannak belső, külső kódok, hibaüzenetek. De ezeknek semmi köze nincs a hibakezeléshez, csak a riportáláshoz.
Ez a "hibát ott kezeljük,
Szerintem
Van egy mutatvány, amit ha végig csinálsz, több táblában több adatnak kell az üzleti logika szerint változnia.
De a valahanyadik változás beleütközik valamibe (mondjuk duplicate entry for xy key), ebből derül ki, hogy a mutatvány nem végrehajtható, rollback, és vmi user friendly üzenet kell.
Ilyenkor bizonyos hibákat helyben kezelsz, a mutatvány try / catch blokkjában.
Mindez nem jelenti szó szerint azt, konkrétan ott kezeled, ahol kiváltódik, mert nyilván a db insert / update valahol máshol van kódolva, nem a blokkodban.
Szerintem ez (is) az egyik kódszervezési probléma Gáboréknál, attól, hogy procedurális, még lehetne sokkal jobban strukturált.
C
Megegyezik
throw new Exception(2522);
teljesen ekvivalens areturn 2522;
-vel.A kódunkban a legfontosabb, hogy a hibák (megfelelően) le legyenek kezelve, ez magasabb prioritást élvez, mint az olvashatóság. Ez nem jelenti azt, hogy olvashatatlan a programunk, általában az egyes nagyobb blokkok végén szokott lenni egy
return
.Hogy ez architekturális probléma-e vagy sem, nem tudom, még (: )) nem volt belőle baj az évek során. Elég sokan ezen elvek mentén kezelik a hibáikat.
Számomra viszont a throw new
Így
Nem
A legkisebb ellenállás útja a különbség. Ha exceptiont dobsz, a programozónak külön dolgoznia kell, ha az exception ellenére folytatni akarja a futást, míg ha figyelmen kívül hagyja, akkor lerohad a program. Ezzel szemben a return esetén a legkisebb ellenállás útja az, hogy tovább fut a program, és külön erölködni kell azon, ha a hiba hatására le kell rohadnia a programnak.
Vagyis a Te megoldásodnál programozónak tudatosan erölködnie kell azon, hogy minden hibát megfelelően kezeljen, és egyetlen benézés vezethet hibás adatokon végzett műveletekhez. Márpedig a programozó időnként lusta, nem ivott még kávét, és egyébként is már az esti WOW-ozáson jár az esze, vagy csak simán és egyszerűen ember, és emberek hibáznak.
Ezért jók az exceptionök, ezért jó a szigorú és statikus típusosság, külön erölködnöd kell, hogy figyelmen kívül tudd hagyni a hibát, a legkisebb ellenállás útja egyben a helyes út is.
Ez nem azt jelenti, hogy procedurálisan nem lehet nagyon tisztességes és kultúrált kódot írni, hiszen évtizedekig csináltuk, csak azt jelenti, hogy a produktivitásod egy jelentős része olyan dolgokra megy el, amit egyébként a compiler / futtatókörnyezet megoldana helyetted. Rutinnal kiküszöbölhető, de csak korlátozottan, és nehezebb lesz új embert felvenni.
Ez így nagyon igaz!
Amit én is próbáltam neked érzékeltetni:
A programozó is ember, és mint ilyen, annyit tesz meg, amennyit muszáj. Nagyon jól írja János, hogy "erőlködnöd kell". Ez egy helyes út.
Ez így nem igaz
Használhatod magát a "gyári" Exception osztályt is, ott van benne gyárilag is a helye a kívánt kódodnak.
Egyáltalán nem kötelező minden egyes hibának külön leszármazottat gyártani.
Ha a jelenlegi db-t használó függvényeidet ki szeretnéd okosan váltani vele, elegendő egyetlen saját exception-t deklarálnod, amiben megvalósítod a db-vel a kapcsolatot.
Más kérdés, hogy így még nem fogsz tudni kezelni minden hibát, mert pl ha a db-hez nem tudsz csatlakozni, akkor kilőtted a hibakezelésed is. Ez mondjuk a jelenlegi megoldásodnak is az egyik hibája.
Pedig az első a hosszabb, és
Más: kerestek kezdő
A return 2523; és a throw new
Ez azért egy bold statement. A számnál plusz egy lépés átgondolni, hogy az "is not admin"-hoz melyik szám tartozik, egy kezdőnek meg be kell tanítani a számkódolást. Erre írtam korábban, hogy használj rá nevesített konstansokat ahelyett, hogy csak így odavetsz egy integert, mert könnyű hibázni, de látom nem jutott el hozzád.
Az overhead-el kapcsolatban én úgy vagyok, hogy amíg nem bizonyítja egyik vagy másik oldal benchmarkkal, hogy bármit is számít, addig nincs értelme foglalkozni vele.
Szerintem nem hülyének néz,
Ha már ide értünk, rengetegszer felhozza azt az érvet is, hogy feleslegesen hosszú és bonyolult az oop kód. Hát itt pont arról van szó ennél az if halomnál, hogy feleslegesen hosszú és bonyolult lesz tőle a kód...
Gondok
Amelyik hibát nem kezeled, az gyakorlatilag nincs is - szemben az akár egyedi Exception - ökkel. Azt ha nem kezeled, megy a standard hibalogba, stb. "Néha" a típusosságnak is vannak előnyei...
Más dolog használni egy rendszert, és megint más fejleszteni. De a felhasználók közül is kikerülhet egy új "Manyika néni", aki aztán kikattintgatja neked azt, amit még soha senki..
Mondják azt is, hogy a hülyének meg a részegnek van szerencséje, ezt nem tudom, de személyes tapasztalatom is az, hogy nem szabad elkényelmesedni attól, ha egy ideig nem kapsz pofont, mert tökéletes szoftver nincs, ki fog jönni újabb bug, legfeljebb kicsit később.
Csak még1 megjegyzés a hülyével kapcsolatban. Ha valakit én hülyének gondolok, vagy akár csak eléggé nem hozzáértőnek, akkor ezt megmondom neki, és nem folytatok vele szakmai vitát.
Nem gondollak hülyének, de van egy - két olyan (elég mélyen gyökerező) ideád, amik - szerintem - gátolják a szakmai fejlődésed, mert tévesek. Ezekkel amikor szembesülök és van elég türelmem hozzá, akkor megpróbállak velük szembesíteni, megpróbálom megmutatni, hogy mi bennük a téves.
Ha ez Téged sért, akkor elnézést kérek, mondd meg és nem teszem többet (viszont el se fogom olvasni, amiket ezzel kapcsolatban írsz).
Amelyik hibát nem kezeled, az
Vagy kiderül, hogy lehet máshogy is csinálni.
Bocsi
Ez szerintem eléggé veszélyes hozzáállás.
Persze sok fejlesztő van, aki ilyesmikkel igyekszik bebiztosítani a helyét, hogy ezt vagy azt "csak ő tudja megcsinálni", de előbb - utóbb ha a tulajdonosnak van egy kis esze, ezeket a függéseket felszámolja.
De igen.
A minimális kivételkezelés nálam 3 fő részből áll:
- Központi hibakezelés: nem csak Exception-t, hanem minden hibát kezel, logol, ha olyan, amit a User-nek (is) szánunk a kimenetre, akkor gondoskodik arról is, hogy kijusson.
- Szükséges számú egyedi Exception-ök implementálva (nem feltétlenül hibatípusonként másik, lehet más szempont szerint is csoportosítani, mint pl. megjelenik-e usernek, stb).
- Azokon a helyeken, ahol valamilyen okból el kell kapni valami más programrész kivételét (try / catch), ott kizárólag azt a kivételt kezelni, amire számítunk (erre is jó az egyedi class), minden további kivételt a catch ágban kötelező újra dobni, hogy a központi hibakezelő megkaphassa.
Ha ezek teljesülnek, akkor a maximális szintű infóm meglesz, mikor a Manyika néni bekattintja "a rettenetest". Ahogy eddig láttam, a Te rendszeredben pedig annyi infó lesz, amennyit a Manyika néni sikítozik a telefonba, mert nem várt hibára nincs megoldásotok.
Emiatt (és több korábban említett okból) én javaslom, hogy ha nem is rögtön, de gyorsan térjetek át egy jobb, egységesebb hibakezelésre, érdemes felhasználni, amit a nyelv nyújt.
A cipőt is fel lehet húzni a kezedre is, de úgy kicsit nehéz gépelni, és ha a lábadra nem húzol, akkor az utcán járni kényelmetlenebb. De ettől még húzhatod a kezedre.
Van benned egy nagy adag fixed mindset. Ragaszkodsz hozzá. Szerintem ennyi a gond.
nem feltétlenül
Ez tök jó gondolat, eddig még nem jutott eszembe. Tudnál erről többet írni?
Jobb nyelveknél eleve el se kapja azokat, amiket nem sorolsz fel a listán.
Fentebb írtam
Ha igaz, php (7) se kapja el amit nem akarsz, de szerintem átláthatóbb, ha van egy utolsó
} catch (Exception $e) {
ág, ahol továbbdobod, mert akkor látszik, hogy "itt felettem ezeket kezeljük, ha más történik, megy tovább a nóta". Illetve ugye nem ritka eset, mikor egy DB tranzakciót vissza kell rollback-elni, ha bármi történt, ezért is érdemes meghagyni.Én azt hiszem a kezelt
A hibákat a helyükön
Persze sok fejlesztő van, aki ilyesmikkel igyekszik bebiztosítani a helyét, hogy ezt vagy azt "csak ő tudja megcsinálni", de előbb - utóbb ha a tulajdonosnak van egy kis esze, ezeket a függéseket felszámolja.
Amint a "minimális kivételkezelés"-nél írsz, az nálunk is úgy van. Csak rugalmasabb.
Pont ez a gond
Persze, értem én, hogy
Hiányzik:
A Te megoldásoddal az (is) a baj, hogy ha kihagyom az
if (nincs_hiba())
részt, akkor kvázi megy tovább az élet - akár rossz irányba - rossz adatokkal, csak mert fatal error nem történt..1 Meghal a db szerver (ilyenkor Neked semmilyen hibakezelésed nincs, mert az is függ a db - től)
2 Elgépelsz egy változónevet, emiatt random helyen egy null - ból próbálsz adatokat beszúrni a db - be (esetleg be is megy az üres rekord!)
3 foreach egy null v bool v egyéb értéken
4 bármilyen E_NOTICE vagy E_WARNING szintű hiba
- stb stb
2 - 4 olyan hiba, ami miatt a program futása nem áll meg, viszont a helytelen működés miatt létrejöhetnek invalid adatok a db - dben, ami aztán később még nagyobb gondot fog okozni és debugolhatsz hetekig...
Minden olyan hiba "nem várt", amire nem készültél fel pl egy try / catch blokkban. Nálatok kb a hibák 90% - a lehet ilyen, mert ha jól láttam, konkrét hibakódokra if - elsz. Így csak nagyon picike részt lehet lefedni. Vagy hatalmas spagetti lesz 300 féle hibával.
if (van_hiba(1234))
sorokat. Akkor egységes és jól működő, ha pl én odamegyek, elolvasom a doksikat (ha vannak), hogy mit hogyan kell fejleszteni nálatok, ezután csinálok egy tök más, semmilyen szabályt be nem tartó modult vagy bármilyen kódot, és Te pl az automata tesztek futtatása után pontosan felsorolod, hogy melyik szabályt hol szegtem meg és emiatt nem műxik. Helyette csináljam így vagy úgy, akkor jó lesz.Ezért is mondtam, hogy egy esetleges új fejlesztővel sok gondotok lenne.
SZERK.:
Félreértések elkerülése végett önmagában az nem baj, ha lehet (akár sok féle) nem várt hiba, a gondok ott kezdődnek, ha erről nincs elég infó, és / vagy a program futása nem áll meg, pedig meg kéne (pl mentésre, sőt kiküldésre kerülhet rosszul kitöltött számla, de ez csak egy példa kiscsillió közül).
Azt sose gondold, hogy "nálunk nincs nem várt hiba, mert mi mindenre fel vagyunk készülve", mert ez lehetetlen. Ahol usertől vagy másik szoftvertől jönnek inputok, ott nem tudsz mindenre felkészülni.
Többen is jeleztük, hogy ez
try { ... } cache ()
-ben van, de ha mondjuk egy belső függvényben, ahol adatbázisműveletek vannak, és nincsenek helyben lekezelve, akkor inkonzisztens állapot lesz az eredmény, árva rekordok, ilyenek. Vagy egy állandóan futó szolgáltatásban lefoglalsz egy file descriptort, de elfelejted felszabadítani (szerencsére a PHP-ban legalább az ilyen problémák nem jönnek elő).2 Elgépelsz egy változónevet
Van egy saját hibakezelőnk, ami minden PHP hibát logol.
Hát nem
Ez csak akkor lenne igaz, ha az lenne a célom, hogy az esetleges hibákat eltussoljam... Pont az ellenkezője igaz erre a szemléletmódra, vagyis az elsődleges cél az, hogy ha a programozó (akár "vadi új junior") rosszul csinál valamit, az derüljön ki nagyon gyorsan, és mindenképpen az élesítés előtt.
Ha levédi az összes programrészt (amit nem kellene), az mindjárt arról árulkodik, hogy (ne haragudj meg érte!), hogy nem bízik meg a saját tudásában és / vagy nem látja át a folyamatokat, és valahogy keresztül akarja vinni a saját egyéni elképzelését egy számára ismeretlen rendszeren.
Ha azonban ért hozzá, akkor csak és kizárólag ott alkalmaz védett blokkot, ahol neki külön egyedi kezelésre van szüksége. Ahol nincs, ott bízik abban, hogy ha valami hiba történne, arról időben tudomást szerez, és javítja a saját kódját.
Kicsit leegyszerűsítve, leszűkítve jó példa erre, amikor valamilyen üzleti szabály nincs teljesen lefedve adatbázis szinten, mondjuk az x. műveletnél jelentkezik csak pl foreign key error, de emiatt vissza kell vonni másik y műveletet is.
Ilyenkor (is) érdemes try / catch, de az alapvető hozzáállás éppen az, hogy ez minél ritkábban kelljen.
Tök jó emellett a "statikus hibatömb", csak
- Nem a legjobb megoldás, hogyha - ezek szerint - több helyről is kell tudni azonosítani egyetlen hibát;
- Egy hiba = egy esemény, régen rossz, ha ezt többféleképp kell(ene) kezelni.
Összességében nekem úgy tűnik, hogy a szoftver tervezésekor (amennyiben volt ilyen) kifelejtődött, hogy mire van nyelvi (és jó) megoldás, és mire nincs. Egyébként az összes valamirevaló nyelvben (a nagyon alacsonyszintűek, pl Assembly kivételével) van valamilyen jól használható hibakezelés. Php-ben is. Szerintem egyszerűen nem akarjátok használni - de valós indokot még nem láttam rá, hogy miért.
Na, csak megkérdezem mégis...
Példa
'hibakod' => 1000, //sikeres művelet
'eredmeny' => $szerkezet,
);
Én nem lepődtem meg, valami
Így
return HIBAKOD_NEM_ADMIN;
}
Igen, erre gondoltam.
DB-ből?
Szóval ez nem biztos, hogy jó ötlet...
Ezek szerint a
Az, hogy mit logolsz ki, és milyen hibát kezelsz, az két külön dolog. Senki nem tart vissza attól, hogy csinálj olyan exceptionöket, aminek egy objektum, vagy asszociatív tömb az egyik paramétere, amiben változó mennyiségű, minőségű tartamat tudsz átadni a hibakezelőnek.
Egy jó exception szerintem definiálja a hiba kódját és fogadjon hozzá hibaüzenetet. Egy hibakódot több helyen is felhasználhatsz más üzenettel. Én pl rendszeresen használok HTTP exceptionöket, pl.: NotFoundException, aminek a hibakódja 404. A hibaüzenet viszont erőforrásonként változhat. Pl.: user esetén: User not found; product esetén: Product not found, stb. A loggerem pedig catch blokkban elkapja az exceptiont, loggolja a státusz kódot, az üzenetet (ami programkód szinten definiálódik, pl constansban), a trace-t és egy szerializált objektumot. Ezzel megfelelően egységes hibakezelést valósítok meg szerver és kliens oldalon is, mert kevés a hibakód, egységesek, és minden fejlesztő kolléga tudja, hogy mire gondolt a költő.
Kódok
Megvalósít
Nem példányosít, megvalósít. A BlogPostInteractor osztály a megvalósítása a nevezett két interface-nek. Az interfaceket egybe is lehetne tenni, de ekkor minden megvalósításnak mindkét függvényt kellene biztosítania, és simán lehet egy olyan, hogy a legutóbbi bejegyzések mutatásánál valamiféle cache-t veszünk igénybe, míg a teljes post betöltésénél nem. (Kitaláltam valamit.)
Ez így ilyen formában nem igaz, ugyanis a másik függvény nem fut le, a betöltött bytecode pedig amúgy is cachelésre kerül. A különbség mérhetetlenül kicsi és én olyasmit optimalizálok amit mérni is tudok. Feltételezések alapján nem optimalizálok.
Interface
Izlések és pofonok. Az interfacek ez esetben a Boundary, stb. szavakkal vannak megjelölve mint absztrakciós rétegek, de akár mögé is teheted az interface szót ha úgy tetszik. Ebben nem szeretnék állást foglalni, mindenki csinálja úgy ahogy jól esik. Az IDE amúgy is megmondja mindenről hogy micsoda.
Kezdő
Nem gondolom azt, hogy egy kezdőnek objektum orientált módon kellene elkezdenie programozni, és azt sem gondolom, hogy a kódszervezés mikéntje nem feltétlenül kezdő probléma. Lássuk be, még mi "szeniorok" sem feltétlenül tudunk megegyezni arról, hogy hogyan kellene ezt jól csinálni. Ez egy módszer, nem A módszer.
Vállalatirányítás
Én nem fejlesztek vállalatirányítási rendszert, de sejtésem szerint egy ilyen rendszerben nagyon szigorú és ritkán módosuló szabályhalmaz szerint működik minden.
Ezzel szemben egy nem technikai ügyfélnek írt webmotor, vagy induló fázisban levő startup dashboard-ja rengeteget változik, és sokszor előfordul, hogy egy meglevő funkcionalitást meg kell tartani de közben már új funkcionalitást is kell biztosítani.
A settert, mint egy későbbi cikkben írtam is, el kell felejteni hosszú távon. (Immutable data structures.) Lehetne olyan osztállyal is dolgozni ami pusztán publikus változókból áll (mint egy struct), de akkor nehéz mellé csomagolni apróbb utility függvényeket amik közvetlenül az adott objektummal kapcsolatosak. Ezen felül nehéz megvalósítani az immutabilitást.
Változik
Egy vállalatirányítási rendszernél pedig a fő kérdés az, hogy milyen széles a felhasználói kör és nekik milyen sűrűn és mennyire változik a működésük.
Általánosságban én inkább azt mondanám, hogy mindkét terület módosításigénye közel azonos a "nem technikai ügyfélnek írt webmotorral", más kérdés, hogy Gáborék rendszerét hányan és kik használják.
Jo de
Miután mi megtehetjük, mi abszolut "lean" módon kezeljük a módosításokat, sokszor hetente tekerünk át valamit a rendszeren. De ez nem lenne lehetséges ha nem lenne agyonszegmentálva.
Így van
Változások
Tehát így működik a megjelenítés:
Az adatmegjelenítés így nagyon egyszerű és nagyon gyors.
Ahol pedig adatokat szerkesztünk, a szerkesztőmezőkhöz tartozó rövid parancssorokkal dolgozunk, amiket tudunk másolni vagy függvényszerűen hívni.
Látod
Ezen felül a kód nagy része elosztottan, több szoftverben fut, szóval közös libraryket is kell kezelni és tesztelni, ami eleve szükségessé teszi nagyon sok funkció (pl. adatlekérdezés) absztrahálását.
Switch
Ez teljesen jó akkor, ha egy terméket gyártasz egy helyen való futáshoz, és Te magad szabályozod a fejlesztési ütemtervet. Tőlem most olyat kértek az egyik projektnél, hogy a rendszer minden tájékában szeretnének nem csak PDF, hanem Microsoft Word kimenetet is. Na és itt jön be az, hogy az ember inkább csinál egy absztrakciós réteget ami szépen lekonvertálja a kívánt kimenetre a cuccot, mint hogy ezer helyen beletegyen switcheket.
Egy
1, feldolgozzuk a bejövő kérést, ami közvetve vagy közvetlenül megmondja a kimenet típusát
2, lekérdezzük a megváltozott adatokat, ha szükséges
3, a kimenet típusa alapján előállítjuk, amire szükség van (json, pdf stb.) - itt van a
switch
Ezzel az egésszel csak arra szerettem volna utalni, hogy így szerintem jóval rövidebb és rugalmasabb a kódunk, mint OOP-vel.
A switcheléssel, ifeléssel
Az ilyen kód unit tesztelhetősége sem épp ideális, pont ugyanezért. A tesztelelendő esetek száma megsokszorozódik, a metódusok komplexitása az egekbe szökik.
Az if-ek kapcsos zárójelei
Két éve írtam erre egy nagyon jó példát, az alapján el lehet dönteni, hogy az if-es vagy az objektumos megközelítés az átláthatóbb. Persze, nyilván ez szubjektív kérdés.
A kiszervezés nem csökkenti a
Nem
Az ifek maguk a komplexitás.
Ilyenek: cyclomatic complexity, path coverage.
Oo-val valahogy így nézne ki
Bloat
A linkelt kódot ne úgy nézd mint egy blogmotor, hanem inkább tekints rá úgy, mint egy falazásilag kész házra: be vannak húzva a kábelek mindenhova, fel vannak húzva a falak, de még messze nincs kész. Ez egy váz, amire az ügyfél igényei szerint fel lehet aggatni a cuccokat és sokszor az a nagyobb meló.
Az OOP azért fontos, mert rengeteg eszköz van hozzá és még a PHP is biztosít hozzá valamilyen szintű szigorú és erős típusosságot, míg a procedurális és funkcionális programozásra nagyon kevés kényszerítő és minőségellenőrző eszköz létezik. Javaban lehet pl. funkcionális programozást folytatni úgy, hogy a függvények paraméterei kényszerítettek, nem csak úgy random adogatunk át dolgokat és ezt használom is nagyon lelkesen.
Csak pár vélemény
Szerintem egy kezdőnek a legjobb, ha a programozást nem bármiféle webfejlesztéssel kezdi, hanem valamilyen igazán alacsony szintű nyelven, pl assembly. Persze száraz, nehéz, sok munkával kevés látszat, de ha az alapvető processzor - memória - fájlrendszer működéssel nincs tisztában valaki, akkor a magasabb szintű nyelveken sem fog tudni elég jól érvényesülni.
Utána érdemes valami egyszerűbb strukturált nyelv, pl valamilyen basic, a "jó" öreg turbopascal, vagy egyéb. Még nem OOP, de már nem csak bitek meg bájtok vannak, hanem némi segítség vezérlési szerkezetekre, függvényekre, nem neked kell feltalálni az adattípusokat.
Utána valamilyen valóban OOP nyelv, mert még a PHP 7.1.8 sem teljesen az, bár 7.0 óta nagyot lépett afelé.
A (nagyon) kezdő programozónak sincs rá szüksége, csak mikor már érdeklődik iránta, vagy belefut olyan problémákba, mint itt.
Összességében akkor van rá szükség, ha így átláthatóbb, tesztelhetőbb és könnyebben karbantartható kódot tudsz írni (ne haragudj, de rajtad kívül kb mindenki).
Továbbra is az az érzésem, hogy egyszerűen nem érted a hasznosságát, ami egy dolog, de ez miért hozza magával, hogy kampányolsz ellene?
SZERK: Időközben míg én egy commentben leírtam pár észrevételt, Janoszen tételesen és precízen válaszolt, köszi! :)
»hol találkozunk ilyen
Mindenütt. Egy tök egyszerű CMS (mint a példa) esetén is megéri: átláthatóság, továbbfejleszthetőség, működési biztonság miatt. (Itt nekem úgy tűnik, hogy nagyon sok dolog van, amit azért tartasz felesleges rossznak, mert pusztán nem érted, hogy mire való.)
A továbbfejleszthetőség egy teljesen megfoghatatlan dolog, hacsak nem látsz a jövőbe. Én nem látok. Fogalmam sincs, mit fognak kérni, ezért nem tudom, melyek azok a részek, amit általánosan, parametrizálva kell megírni.
Hogy biztonságosabban működik-e a projekt, az sem feltétlenül állja meg a helyét. Mik a mérőszámok?
Biztos vagy benne, hogy te érted, mire valóak és hol használhatóak hatékonyan a különböző paradigmák?
Az interface feladata épp az, hogy egységes kapcsolódási felületet hozzon létre, tehát ha bármilyen routing szabály változása miatt új router-t kell írni, annak felhasználása ugyanúgy történjen. Pont azt a problémát oldja meg, amitől tartasz.
Épp ezért fölöslegesnek tartom ezt előre definiálni és megvalósítani.
Szóval nem értem. De szeretném. Ha nekünk sikerült más módszerekkel dolgozni, akkor lehet, hogy másnál is működhet?
Tehát a problémát többféleképp is meg lehet oldani. Miben különböznek a körülmények?
Elvek
Az OOP egy eszköz. Ugyanúgy, ahogy a procedurális vagy funkcionális programozás is az. Sőt mi több, akár egy kódban is lehet használni mindegyiket. Olyan adathalmazod van ahol jól jön a csatolt funkcionalitás és az adatok elrejtése? Használj objektumot. Buta egyszerű függvényed van? Írj egy buta egyszerű függvényt. Olyan feladatod van amiben el akarod kerülni a mellékhatásokat? Írj funkcionális kódot. Elég csak megnézni a Pythont, tök másképp struktúrálja a kódját és ott nem számít eretnekségnek egy függvényt írni osztály helyett. Teljes békességben elvan egymás mellett a példányosított webszerver és egy zsák függvény. Sőt mi több, szerintem egy jó fejlesztő némi gyakorlás után bármilyen paradigmában képes jó kódot írni.
Azt viszont látni kell, hogy különböző nyelvek különböző szinteken támogatják az egyes paradigmák használatát. Procedurálisan akarsz programozni Javaban? Lehet, csak épp nem erre van kihegyezve a nyelv. Funkcionálisan PHP-ban? Háááááát.... sok sikert, hiányoznak hozzá még az alapvető eszközök is. Satöbbi.
A probléma a procedurális programozással leginkább ott van, hogy nagyon kevéssé köti meg a kezedet, nagyon kevés korlátot lehet szabni a berhelésnek, és szerintem ezért mozdult el a piac az OOP irányába. A fejlesztők felének kevesebb mint 6 év tapasztalata van, és az OOP kényszerítő eszközei némileg kordában tartják a szerteszaladó juniorhalmot.
Ettől függetlenül látni kell azt, hogy a mai napig van olyan NAGY számlázási rendszer amit Cobol hajt. Miért? Mert a sok junior által legyártott Java kód 1000x annyi erőforrást eszik. Ez nem azt jelenti hogy minden procedurális kód kötelezően gyorsabb, sőt. De volt ott olyan programozó aki megcsinálta hatákonyan. Mert értett hozzá.
Nem gondolom azt, hogy bárkinek állást kéne foglalnia bármelyik paradigma mellett. Sőt, szerintem egy jó programozó legalább minimális szinten gyakorlott és képes tűrhető kódot gyártani bármelyik paradigmában. Feladathoz az eszközt. Szétszaladó juniorhalomhoz OOPt, statikus és szigorú típusosságot, nádpálcát. Nagy JavaScript projektekhez funkcionális programozást, stb. És mindenkinek azt, amiben megtalálja a boldogságot és a sikert.
Ucsó! :)
Mindenkinek meg van, hogy mit lát egyszerűbbnek, mi az átláthatóbb, stb.
Ezért is kérdeztem azt is, hogy mit csinál egy odavetődött junior nálatok.
Mert pusztán ez a fő lényeg:
- adott feladatra biztosan lehet jó megoldást találni többféle paradigma alatt is (akár egyszerre több paradigmát használva is)
- a továbbfejleszthetőség (és átláthatóság, stb) "mérőszáma" az, hogy mennyire és milyen hatékonyan tud hozzányúlni egy most odakerülő fejlesztő (senki sem garantálja, hogy a szoftver életciklusa alatt a teljes stáb ugyanaz marad)
- a szoftver ára a teljes életciklusban legjobban a továbbfejleszthetőségen múlik
- a szoftver minősége szintén (mert ha rosszul fejleszthető, sog bug és hegesztgetés lesz az ára a módosításoknak)
Így annak megítélése, hogy "a mi kódunk jóval egyszerűbb", korántsem ennyire egyszerű, hogy pl hány fájlból vagy hány fv-ből áll.
Hogy másnál mennyire működhet a módszeretek, én nem tudom, de leginkább szerintem úgy lehetne kideríteni, hogy odaültetsz a kód elé egy juniort és egy seniort pár napra, minden dokumentáció nélkül. Mit ért meg belőle? Mert Janoszen "szuperbonyolult 45 fájlból álló agyoninterfészelt" (mindössze) blogmotorját úgy látom, hogy többen is hamar átláttuk és megértettük.
Nyilván ha most én állnék neki hasonlónak, akkor lennének jelentős különbségek (pl return type declaration), de az is biztos, hogy OOP lenne.
Nincs azzal baj, ha Te nem kedveled / nem érted / feleslegesnek tartod ezt a paradigmát, de kérlek ez még ne legyen elegendő ok arra, hogy másokat lebeszélj róla.
Hasonlóan Jánoshoz, nekem is nagyon jó mankó; PHP - re vetítve nekem nagyon tetszik a 7.0 - tól kezdődő irányvonal, amivel nagyot lépett a nyelv a szigorúan típusos (számomra ez a "valódi-") OOP felé.
én azt gondolom, elég jól
[quote]Annyi pici kérdésecske, hogy ha az adatbázisod (szerver) állt le pl, vagy néhány tábla "eltűnt" belőle, akkor a Te hibakezelőd honnan veszi a hibaüzenetet?
Ha nulláról kéne írni egy ilyet, akkor abban lenne egy függvény az adatbázishoz való kapcsolódáshoz, egy a lekérdezések futtatásához, egy-egy pedig az utolsó n darab és egy poszt lekéréséhez. Tegyük fel, hogy a sablonozás ugyanúgy twig-gel menne.
Szóval melyiket könnyebb átlátni, a monstrumot vagy az előző lekérdezésben leírt két kilobájtos programot? És most tekintsünk el attól, hogy ezt OOP vagy procedurálisan valósítottuk meg.
Tipusos
Honnan tudod hogy nem? En Java, Python, Hacklang, PHP vonalon mozgok. Ebbol pontosan egy nem erosen (es statikusan) tipusos.
Még1 kérdés Gábor:
valamilyen_globalis()
függvényt, aminek ettől megváltozik a kimenetének a struktúrája?Teszt
Kérdés
Dehogy
Mondjuk
Ha injektálod a függvényeid
Én mondjuk annyira nem vagyok híve az egységtesztnek, az integrációs teszteket sokkal jobban szeretem bdd jelleggel cucumber-el. Kevesebb meló van velük, ha később hozzá kell nyúlni, rugalmasabbak, de azért közel sem annyira erősek, mintha unit test van mindenre. Az olyan projektekre kifejezetten jók, ahol nem kell annyira precíz munkát végezni. Annak idején még prohardver fórumban próbáltam felvetni, hogy kéne tesztelni minden appot, meg hogy tdd vagy bdd alapon kellene csinálni minden projektet. Elég csúnyán le lettem osztva, hogy az mekkora plusz munka lenne már egy egyszerű pár soros projektnél is, de szerintem ha bdd-vel csinálják, akkor minimális befektetéssel elég szépen le lehet fedni, hogy működik e minden funkció még egy hobbi projekten is, és visszajön a rá fordított idő, ha esetleg valaki kénytelen debuggolni.
Köszi :)
TDD-t tekintve nem igazán egyezik a véleményünk, szerintem rendkívül hasznos, főleg nagyobb projektnél és / vagy csapatmunkában.
Magamon azt tapasztaltam, hogy ha jól csinálom (~ mindig előbb elfailel a teszt, utána fejlesztek), akkor az több előnnyel is jár, emellett maga a "programkód legyártása" annyira rövid idő lesz, hogy szinte észre se veszem.
- Hasznos, hogy az igény oldaláról gondolom át először a fejlesztés során is az adott feladatot, sokkal jobban, hogy jó tesztet tudjak írni.
- Sokkal jobban tudok kódot szervezni is teszt írás közben vagy után, mert másik szemszögből is átgondolom ugyanazt a feladatot.
- Későbbi módosításnál nagyon jól tud jönni egy failed teszt, sokkal jobb, mint egy kiélesített bug, netán security hole. :) Igen, ehhez módosításkor a unittest-et is megfelelően utána kell húzni.
- Egyedül az tud lélekromboló lenni, ha utólag kell valamit tesztekkel lefedni, ilyet is csináltam sprinteken keresztül, de ezért is kárpótolt valamennyire az a biztonságérzet, hogy ezentúl nem csak "gondolom formán" és valamennyire manuálisan tesztelve kerül élesítésre a szoftvernek egy nagyon sokat használt része.
Azt gondolom, hogy aki(k) szerint "az mekkora plusz munka lenne", azok vagy nemigazán jól csinálták, vagy nem látják kellően azt a hosszabb távú megtérülést, amit a ki sem élesített esetleges hibák hoznak az ügyfél által bejelentett, hajat tépve gyorsan meghegesztett bugok helyett.
Azzal egyetértek, hogy BDD lehet egyfajta középút TDD és semmi közt, illetve minden jobb, mint a semmi.
Inkább UI teszthez volt szerencsém ehhez is, nekem nagyon tetszett, bár nem igazán tudom szigorú kategóriába sorolni, amit fejlesztettünk benne. Kb a korábbi, minden egyes élesítés előtt manuálisan végignyomkodott checklisteket programoztuk le, ami a tesztelőknek 0.5 - 1 óra volt korábban, az (a build folyamatba építve automatikusan) néhány perc alatt meg lett. De ettől még a backend egységtesztje ugyanolyan fontos - szerintem.
BDD-vel teljes mértékben ki
Ha UI-t akarsz tesztelni, akkor karma+nightwatch a legjobb, amit eddig néztem, de akkor még a nightwatch kiforratlan volt. Szerintem azóta már beérhetett. Azon kívül még ott van a sikuli is, ha biztosan tudni akarod, hogy nem csúszik szét az oldal a különböző böngészőkön, esetleg játékot akarsz tesztelni vagy valami svg grafikont vagy ilyesmit.
Ha régi kódhoz nyúlsz, de nem kell módosítanod azt, csak használni, akkor egy anti-corruption layer-el el tudod fedni az implementációját, és tudod a saját modelledet alkalmazni, illetve erre az ACL-re tudsz teszteket is írni, így nem kell a régi kód felületére megírnod a tesztet. Ha bele kell nyúlnod, akkor nincs ekkora szerencséd, kénytelen vagy teszteket írni, ha nincsenek. Ez spagetti kódnál különösen fájdalmas, annál akár még az UI tesztek irányából is el lehet indulni, hogy lásd, hogy nem töröd el a kódot, amikor megpróbálod szétszórni osztályokba.
Na ez az oka
Vagy minden egyes apró módosításnál futtatod a tesztet (ami ugye a gyakorlatban kb lehetetlen), vagy bevállalod, hogy
Ezért nekem még kicsit idegen, de meglátjuk mit hoz a jövő, lehet egyszer kipróbálom.
Nyilván ha meglévő spagettit akarnék tesztelni, hát örülök bárminek, amivel sikerül egy kicsit is lefedni. :)
minden egyes apró
Ajánlom figyelmedbe ezt a könyvet, ebben pontosan úgy megy a refactoring, mint ahogy fentebb írtad. Először megnézed, hogy megy e a teszt, aztán minden apró módosításnál újra lefuttatod, hogy lásd, nem e törtél el valamit. Jobb, mint revertelni fél óra írkálás után, mert nem tudod miért nem működik a kód. A tesztek egyébként TDD-nél és BDD-nél is gyorsak kellenek, hogy legyenek, pont emiatt. Ha integrációs teszteket írsz adatbázishoz, ehhez-ahhoz, akkor azokat külön kell venni, mert azok lassúak. Adatbázisnál lehet gyorsítani rajtuk, ha tranzakciót használsz és állandóan rollback-ezel, de a teszt adatbázis felépítése így is időigényes szokott lenni.
Egyébként a spagetti kóddal kapcsolatban sem az a legnehezebb, hogy hogyan írjál tesztet hozzá, hanem, hogy sűrűn előfordul, hogy a tesztek nem működnek, mert alapból hibás a kód, és ha belenyúlsz, akkor előfordul, hogy még jobban eltöröd. Na ez a része az, ami még nekem sem megy. :D
Így
lehet részletesebben?
Attól függően, hogy megfelel-e interface-nek:
- Igen: semmi gond, a felhasználó egyéb programrészeknek nem szabad, hogy baja legyen (vagy eleve rossz az interface)
- Nem: meg kell vizsgálni, hogy maga az interface még jó-e (vagy változtatni kell), az adott (speciális) problémára nem kell-e új, esetleg származtatott osztályt írni, etc-etc.
Itt én inkább a visszaadott értékre gondoltam, mint a paraméterezésre, és hát ez még php 7.1.x alatt is lehet ugye tömb, aminek aztán bármiféle elemei lehetnek, és épp attól törik el a felhasználó kódod, hogy kikerül belőle egy bizonyos kulcs, vagy bekerül egy olyan, amire nem számítasz. De sok egyéb lehetőség is van, pl ha nem használod ki a return type declaration-t, akkor simán rá is foreach-elhetsz valahol egy null-ra vagy string-re, bármire.
Interface-el pedig ki tudod kényszeríteni, hogy az implementáció helyes típust adjon vissza.
És ez még csak egy pofon egyszerű hibapélda volt, mégis ezer helyen bele lehet futni, ha nem figyelsz rá.
Teszt
public $tag = array();
}
$objektum = new klassz();
$objektum->tag = null;
foreach ($objektum->tag as $kulcs => $ertek) {
PHP Warning: Invalid argument supplied for foreach()
}
Objektumok adattagjai is lehetnek bármik, tehát az ellenőrzést nem úszod meg.
A PHP és a JS egyik legnagyobb előnye a statikus típusos nyelvekhez képest, hogy az asszociatív tömbökbe dinamikusan tehetsz bele új kulcsokat.
Fáradt vagyok most átrágni
Közben átolvastam, írom
Az absztrakció megfelelő kialakítása mindig plusz munkával jár, át kell gondolni, nem csak odavetsz valamit a monitorra. Nagyjából ugyanez az előnye is, átgondolt, a jövőben már nagy valószínűséggel nem kell hozzányúlni az interface-hez. Ezzel szemben ha átgondolatlanul csinálsz valamit absztrakció nélkül, akkor sokkal nagyobb az esélye annak, hogy néhány új funkció hozzáadásánál refaktorálni kell az egész osztályt metódus nevekkel mindennel együtt, és törik az összes tőle függő kód. Természetesen ez is bevállalható, ha nem sokan függnek az adott osztálytól és/vagy nem várható, hogy változtatni kell rajta bármit a jövőben. Nem muszáj minden huszadrangú osztályt interface-el tökéletesen kidolgozni. Szerintem.
A minimális overhead, amit felhoztál érvként premature optimization-nek számít, ha webes programozásról beszélünk.
A kivételeknél nem a kód a lényeg, mert általában a törzse üres szokott lenni, hanem hogy a típus alapján tudjuk elkapni azokat, amiket el akarunk egy adott szinten, a maradékot meg tovább tudjuk engedni. Egyébként a csoportosításra ezeknél lehet üres interface-t is használni.
Cserébe azt kapod, hogy minden osztályról pontosan tudod, hogy mit csinál, és ha bővíteni kell valamit funkcióban, akkor nagyon gyorsan és egyszerűen meg tudod tenni azt szemben egy spagetti kóddal. Egy kezdőnek nem biztos, hogy szüksége van rá, de ha mondjuk egyedi webshop fejlesztésébe vágja a fejszéjét, akkor már jobban jár vele, ha megtanulja, különben könnyen bedőlhet az átlag struktúrált kódra (Pistike spagettije) jellemző gyenge karbantarthatóság miatt a projekt. Sajnos sokszor találkozni ilyen "majdnem kész" projektekkel, ahol elfogyott a lendület és amik menthetetlenek, mert annyira tele vannak hibával.
Ami kijön az adatbázisból, az sokszor messze van még egy view model-től, és alakítgatni kell rajta attól függően, hogy milyen formában akarod megjeleníteni, pl táblázatban, grafikonon, és így tovább. Csak egy nagyon egyszerű példa, ha van mondjuk egy mátrixod, azt is le tudod írni két különböző modellel, attól függően, hogy oszlopok vagy sorok szerint csoportosítod az értékeket:
Szerintem nincs ilyen határ, bármekkora méretnél lehet oo programozni. Ha nem valami általános blog motort vagy keretrendszert csinálsz, akkor pl nem kell bele annyi absztrakció, mert overengineering lesz a vége és a legtöbb helyen úgysem fogod sosem lecserélni az osztályokat. Azzal a kijelentéseddel egyetértek, hogy ilyen esetben egy csomó plusz munkát beleraksz a semmiért. Azt hiszem ez ilyen know-how lehet, rá kell érezni a helyes arányra ebben is. Olvastam néhány könyvben annak idején, hogy egy osztály legyen annyira kicsi, amennyire csak lehet, de nem értek ezzel egyet én sem, mert a végén a kód nagy részét az fogja kitölteni, hogy egy soros osztályokat deklarálsz. Sokszor egyszerűbb egy nagyobb osztályba beleszórni, amit akarsz, aztán ha később bonyolódik a helyzet, akkor feldarabolhatod, kialakíthatsz interface-eket, és így tovább, ahogy a helyzet hozza. Ha csak egy prototípust vagy proof of concept-et csinálsz, akkor meg még tesztek nélkül is el lehet indulni, később ha bevált a dolog, meg be lehet pótolni az ezzel kapcsolatos hiányosságokat, és utána lehet refaktorálni, hogy tisztább legyen a végeredmény. Ezek persze többé-kevésbé az én véleményemet, eddigi tapasztalataimat tükrözik, biztosan van, aki jobban tudja...
Egyébként maga a kód egy-két fájlba belenézve szerintem szép.
Pontosan
Két fejlesztő ezért nem fog sosem egyetérteni abban, hogy hol húzódik a clean code határa (mármint ha lenne éles határ), mert mindenkinél picit más a "helyes arány". De ez jól is van így, ne legyünk egyformák.
Tesztek
Igen
Szoval akkor teszteltek vagy nem teszteltek PHP-ban?
+1
A programból le lehet vonni
Az OOP (és a clean code) erőssége, hogy specifikus contextekben dolgozik a fejlesztő, aminek a kikényszerítése nyelvi elemekkel van megtámogatva. Ez nem azt jelenti, hogy a specifikus context elérhetetlen nem OOP paradigmával.
Ha már a teljesítmény is szóba került, egy értelmes compiler tudja, hogy ha csak egy implementáció létezik, akkor ott még csak teljesítmény vesztés se nagyon legyen. Class Hierarchy Analysis, monomorphic és inlining a kulcsszavak. És ott van még a CPU-k branch predictionje is.
Csak ismételni tudom magam:
Vegyük a blogmotorból a routert, amiből egy darab van és minden kérés elején lefut. Erre írok egy függvényt:
...
}
$eredmeny = utvalaszto($_POST);
function teszt_utvalaszto() {
$eredmeny = utvalaszto(array());
assert($eredmeny);
}
Ebben az egész témában számomra nem az a lényeges, hogy OOP vagy sem, hanem hogy ha valamit ennyire egyszerűen is meg lehet oldani, akkor miért nem így csináljuk?
Ezt a kört már megfutottuk
Amúgy a C-s könyvtárak is definiálnak "interface"-eket. Nem véletlenül.
Gondolom szándékosan a
Pont ma olvastam egy jó cikket , te jutottál eszembe róla az örökös oop vagy struktúrált vitáiddal.
Pöcögtetés
Szerintem nézz bele hogy
Megint a plusz munka mellett érvelsz ugyanis (egyéb problémák mellett).
dupla karbantartás
Akkor valójában nem is az éles kódot teszteled, és emberi odafigyelésen múlik, hogy a teszt mennyire hasonló kódot tesztel az éleshez képest... :(
Márpedig az ember tévedhet, és szokott is.
Bár nem jellemző, de ha elég
Gondolom ezek a "teszt függvények" a mockjaid, amiket be tudsz az egyes teszteseteknél kívülről állítani, hogy hogyan viselkedjenek. Esetleg használsz valamilyen mocking libet erre? Nem világos, hogy hogyan oldod meg, mondjuk én nem vagyok annyira jártas a strutúrált php eszközeiben, én csak a PHPUnit-ot ismerem, de abban sem nagyon tudok olyan részről, ami függvények ilyen formán történő kimockolását csinálná.
Így
Ezeket lehet unit tesztelni.
A modulok felett vannak az API parancs gyűjtemények, amelyek a modulok függvényeinek hívásából állnak.
Itt már integrációs teszteket kell végezni.
Ezek szerint csak az
Igen
Akkor tényleg unit tesztelsz.
Gondolat
Javítja a gondolkodást, segíti a "big picture" helyes megtalálását.
Sok mindenről szó volt, helyenként egész részletekbe menően, de jó lenne ebből kialakítani egy értékesebb vázat.
Neked is segítene jobban megérteni azt a sok "hülyeséget", amit itt különálló szálakon összehordtunk.