ugrás a tartalomhoz

Ruby tervezési hiba?

Joó Ádám · 2010. Már. 16. (K), 20.25
A Ruby nyelv egyik legszebb vonása az, hogy minden nyelvi konstrukció rendelkezik értékkel. Nem csak a hagyományos kifejezések helyettesíthetők be egyetlen objektummal, de más nyelvekben szokatlan módon a vezérlési szerkezetek is.

Ez a gyakorlatban az adott szerkezetben utoljára végrehajtott utasítás értékének visszaadását jelenti.

Ennek köszönhetően megtehetjük például, hogy egy feltételt jobbértékként kezelünk:
permission_level = if session.admin? then
  2
else if session.logged_in? then
  1
else
  0
end
Hasonló filozófia érvényesül a függvények visszatérési értékénél is, minek köszönhetően egy kilvasófüggvény nem több, mint:
class User
  def admin?
    @admin
  end
end
Kényelmesnek tűnik, azonban elég súlyos biztonsági kockázatot rejt. Vegyük az alábbi metódust:
class User
  def delete
    @##kukac##db.execute 'DELETE FROM users WHERE id = ?', @id
  end
end
Amennyiben az execute tagfüggvény visszatérési értéke self (mondjuk hogy láncolható legyen), vagyis az objektum, úgy ez lesz a delete visszatérési értéke is, osztályunk felhasználója pedig máris szabadon hozzáfér az adatbázishoz: ez az adatrejtés vége.

Részemről ezt elég súlyos tervezési hibának érzem, mert könnyű megfeledkezni az explicit megadandó nil értékről. És még ha az ember észben is tartja, fárasztó beirkálni és hülyén is hat a végén: elvégre ha a semmit adom vissza, azt minek kiírni? Így most ahelyett, hogy amikor kell, akkor return érték-et írnék, amikor nem kell, írom a return nil-t.

Ráadásul, míg a vezérlési szerkezeteknél ez a lehetőség jól olvasható kódot eredményez, úgy egy függvénydefinícióban véleményem szerint a return elhagyása kifejezetten ront az értelmen.
 
1

Ez funkcionális

Török Gábor · 2010. Már. 17. (Sze), 10.10
Ez funkcionális programozásbeli vonás, koránt sem tervezési hiba, pláne nem a Rubyé. FP-ben nem megengedhető, hogy egy függvénynek ne legyen visszatérési értéke, ha másképp nem, akkor az utoljára kiértékelt kifejezés lesz az.

Mindazonáltal elég ügyetlen megközelítés, hogy egy számodra szokatlan viselkedésből ilyen messzemenő következtetéseket vonsz le. Azért kényelmetlen neked return nil-nel zárni a függvényeket, mert nem FP-ben gondolkozol és kódolsz, különben úgy írnád a függvényeidet, hogy azoknak legyen visszatérési értéke. Amint eléred ezt, üdvözlöd majd, hogy nincs szükség mindig azt explicit definiálnod.
9

FP vs. OOP

Joó Ádám · 2010. Már. 18. (Cs), 01.03
Oké, de számomra a Ruby sokkal inkább tűnik objektumorientált nyelvnek, mint funkcionálisnak. Márpedig ugye egy OOP nyelvben objektumokon végzek műveleteket, amik megváltoztatják azok belső állapotát – miért kéne bármit is visszaadjanak?
2

Ezt a kérdést nem is értem

prom3theus · 2010. Már. 17. (Sze), 11.48
"elvégre ha a semmit adom vissza, azt minek kiírni?"

Ugyanazért, amiért a nullát is, mint számot. A "semmi" egy speciális "valami". Vezérlési szempontból a "semmi" ugyanúgy érték, mint bármi más. Az, hogy ezt egyes nyelvek kényelmesen átlépik, nem jelenti azt, hogy ez lenne a logikus út. A PHP-ban például minden függvény és eljárás null-t ad vissza ha nincs visszatérés meghatározva. Én ezt nem tartom helyesnek. Még egy, a típusokat annyira lazán kezelő nyelvben sem, mint a PHP. Ha az ember odafigyelve (tervezve) fejleszt, amúgy sem fordulhat elő, hogy egy függvény ne vezérelten térjen vissza értékkel, vagy hogy egy eljárás néha adjon visszatérési értéket, néha meg ne. A null, mint alapértelmezett visszatérése minden eljárásnak és függvénynek viszont egy hasznavehetetlen dolog, ami valószínűleg változna, ha a PHP valaha az életbe kinőné a kőkorszakot - inkább akkor pedig adja vissza $this-t vagy self-et, mert a null-al kitörölhetem (bocsánat).
3

Nekem meg ez nem lenne logikus

zzrek · 2010. Már. 17. (Sze), 12.16
Nekem meg ez nem lenne logikus: miért pont $this-t, vagy selfet vagy mit adna vissza, ha semmit nem határoztam meg, hogy adjon vissza? Ha nem foglalkoztam a visszatérési értékkel, akkor valószínűleg azért tettem, mert nem akarom felhasználni. Ha mást akarok, hogy visszaadjon mint a "semmitse"-t, akkor foglalkozom vele, és megadom hogy mit is akarok. Szerintem a null az pont logikusabban jelzi hogy nem tettem semmit a visszatérési érték érdekében és pont arra való hogy "ki se törölhessem vele".
(Természetesen azzal egyetértek, hogy a "semmi" az fontos visszatérési érték -- de tervezni kell vele, és nem csak "úgy", és ez igaz bármi más visszatérési értékre)
4

Te határoztad meg

Török Gábor · 2010. Már. 17. (Sze), 12.46
miért pont $this-t, vagy selfet vagy mit adna vissza, ha semmit nem határoztam meg, hogy adjon vissza?

De, te határoztad meg, hogy fejlesztési környezetként a Rubyt választod, az adott függvényben pedig utolsó kifejezésként a delete függvényt hívtad meg, ami (Ceriak írása szerint, és egy Rubyt ismerő programozó számára tudvalevőleg) self-fel tér vissza.

Ennek az egész posztnak nem sok értelmét látom. Előbb ismerjünk meg egy eszközt, és amennyiben a módszertana, tervezése megfelelő számunkra, utána kezdjük el használni.
6

Nem a Rubyról beszéltem...

zzrek · 2010. Már. 17. (Sze), 13.51
... mert arra a mondatra reagáltam, hogy "...ha a PHP valaha az életbe kinőné a kőkorszakot - inkább akkor pedig adja vissza $this-t vagy self-et, mert a null-al kitörölhetem". (Igen igazad van, én határozom meg a környezetet, csak épp itt a PHP volt kiválasztva a kritikára nem a Ruby, és ott a null a szokás, és ennek a logikusságát ecseteltem az adott környezetben)
11

A poszt értelme

Joó Ádám · 2010. Már. 18. (Cs), 01.19
Ennek az egész posztnak nem sok értelmét látom. Előbb ismerjünk meg egy eszközt, és amennyiben a módszertana, tervezése megfelelő számunkra, utána kezdjük el használni.


Szerintem rögtön a rosszat feltételezed. Én kifejezetten szeretem a Ruby-t, nem lehúzni akartam, pusztán találtam benne egy tervezési sajátosságot, ami hibának tűnt, és magamat nem tudtam meggyőzni az ellenkezőjéről. Az egyik hozzászólónak azonban sikerült, de erről majd ott.
5

Ha egyszer feltehetően

prom3theus · 2010. Már. 17. (Sze), 13.38
Ha egyszer feltehetően úgy sem akarod használni a nem meghatározott visszatérést, akkor nem mindegy, hogy null-t, self-et, $this-t, vagy kiscicát ad vissza? :-)

Én csak azt mondtam, hogy a null-nál a self vagy a $this OO szempontból praktikusabb alapértelmezett visszatérési érték lenne, ahogy nem OO eljárások esetén inkább a false-t tartanám szerencsésnek ugyanerre. Ennek belátásához szerintem kell kb 3 év PHP-s tapasztalat OO és nem OO szempontból, de ez mind csak a saját szubjektív véleményem.

Sőt - ahogy erre fentebb is utaltam - ezt az egész marhaságot, hogy eljárásnak visszatérési értéke lehet random fejlesztői hülyeség függvényében, eleve nem támogatom. A függvénynek legyen kötelező visszatérnie meghatározottan és minden esetben, az eljárás pedig ne térhessen vissza semmivel, mert onnantól nem eljárás. Ezért tartom kreténségnek, hogy nincs a PHP-ban szétválasztva egymástól ez a két fogalom a nyelv szintjén. (bár ellenérvként ezen álláspontom ellen szól, hogy ettől se lenne sokkal kevesebb figyelmetlen PHP fejlesztő a világban)

Tehát ha már az utóbbi szigor nem a PHP útja, akkor legalább olyan alapértelmezett visszatérési értékeket használhatna, aminek van hasznossága.
7

Igazad van

zzrek · 2010. Már. 17. (Sze), 14.04
Igazad van, lehet nézni a te szemszögedből is, de szerintem máshogy is lehet nézni: egy eljárás esetén mondhatjuk hogy ez egy olyan függvény, aminek null a visszatérési értéke és nem is lehet más vagyis ha ez egy feature akkor az furcsább feature lenne, ha mást adna vissza, mint nullt (ha már nem térhetne vissza semmivel szerinted se). (Vagy mittomén :-)
De a többivel egyetértek. (A kiscicán kívül, mert az sem lenne logikus :-)
12

False

Joó Ádám · 2010. Már. 18. (Cs), 01.31
ahogy nem OO eljárások esetén inkább a false-t tartanám szerencsésnek ugyanerre


A false egy logikai érték, tehát érték, és nem az érték hiánya, kifejezetten rossz választás volna implicit visszatérési értékként!

(Képzeld csak el, mikor a nem létező tömbindexet lekérdezve false-t kapnál vissza: ezt most akkor beletette valaki, vagy nincs is ott?)
10

Adjon vagy ne adjon

Joó Ádám · 2010. Már. 18. (Cs), 01.14
Ha már a nullát felhoztad: a 5 + 3 - 3 egyszerűsítése után te 5 + 0-t írsz, vagy simán csak 5-öt?

A PHP null-ja, a Ruby nil-je mind arra valók, hogy ahol szükséges, ott tudjam jelezni a semmit. Miért kéne külön jeleznem ott, ahol nincs rá szükség?

Ha egy művelet nem ad vissza értéket, akkor arra szoktuk azt mondani, hogy null-al tér vissza. És miért ne volna létjogosultsága annak, hogy egy művelet ne adjon vissza semmit, ha csak egy objektum állapotát hivatott megváltoztatni. Vagy miért ne volna lehetséges, hogy egyszer adjon visszatérési értéket, egyszer meg ne. Hisz ha pl. olyan kulccsal kérek elemet egy gyűjteményből, amivel nem szerepel benne semmi, akkor egyértelmű, hogy semmit nem kapok vissza.

Azt mondjuk járható útnak tartanám, ha az alapértelmezés $this avagy self lenne, legalábbis egy OOP nyelvben, a bejegyzés lényegét ez nem érinti.
8

A Ruby nem objektum, hanem

kuka · 2010. Már. 17. (Sze), 16.30
A Ruby nem objektum, hanem prototípus alapú, mint a JavaScript. Vagyis metódusok később is deklarálhatóak. Akkor meg nem értem miért kellene valaki azzal küzdjön, hogy egy már létező metódus visszatérési értékével zsonglőrözve férjen hozzá az adatbázishoz. Szerintem nincs itt semmi tervezési hiba.
13

Köszönöm

Joó Ádám · 2010. Már. 18. (Cs), 01.43
Teljesen igazad van, ezt valamiért nem vettem számításba.

Ez viszont felvet egy másik, engem régóta foglalkoztató kérdést: ha a Ruby olyannyira objektumorientált szemléletű, hogy nem is enged nyilvános adattagokat létrehozni, akkor miért engedi, hogy az utólag definiált tagfüggvények hozzáférjenek a változóihoz? Ezzel a megoldással két szék közé a földre esik: egyszerre próbálja az adatrejtést megkövetelő keményvonalas objektumorientáltságot, és a más prototípusos nyelvekben (JavaScript, Self) meglévő bővíthetőséget megvalósítani, de az előbbit csak felületesen valósítja meg, a másikat pedig megnehezíti.

És ezek szerint a JavaScript a closure adta lehetőségekkel erősebb adatrejtésben, mint a Ruby?

Csak filozofálok, de akit érdekel, és jobban tudja, az ne habozzon ismét megcáfolni.
14

Csak filozofálok

zzrek · 2010. Már. 18. (Cs), 10.55
Csak filozofálok én is, de az "adatrejtésnek" nem csak abból a szempontból van jelentősége, hogy 1) csináljunk magunknak szabályokat és azt segítsen betartani (vagyis mangunkat korlátozhassuk vele, vagyis nevelési szándék) 2) és ehhez kapcsolódóan segítsen abban, hogy nehogy olyan módon (pl. véletlenül) olyan változókat átírjunk (stb) amiket nem terveztünk meg?
Hiszen a programozó számára a forráskód rendelkezésre áll: ha úgy dönt, hogy eleve az osztályba tesz egy settert, hát megteszi: akkor már miért ne tehetné meg ezt utólag, prototype alapon?
Az utólag definiált függvény általi hozzáférés mindkettőnek megfelel: egyrészt lehetővé teszi, hogy saját magunk szabályai szerint, terv szerint hajtsuk végre az adathozzáférést, másrészt az "adatrejtés" érvényesül, ha nem definiálunk ilyen függvényt.
(Ebből az következik, hogy ha tényleg így van, akkor az eredeti feltevésed helyesnek látszik, vagyis ha a visszaadott objektumon keresztül "illegálisan" férünk hozzá adathoz, akkor az előre tervezettség védelme bukik, nem elég erős a koncepció)
16

Forráskód

Joó Ádám · 2010. Már. 18. (Cs), 18.20
Hiszen a programozó számára a forráskód rendelkezésre áll


Mi a hétköznapok során így dolgozunk, de azért ez koránt sem egyértelmű. Ugye a fordított nyelveknél egyértelmű a dolog, de a jövőben szinte biztos, hogy interpretáltaknál is fogunk úgy dolgozni, hogy készen kapunk objektumokat, mi, mint harmadik fél. (Javaban több próbálkozás is volt/van operációs rendszer létrehozására, ott tudtommal erősen építenek a nyelvi elérésszabályozásra. De mintha Crockford is a mashupok kapcsán üdvözölné a könyvében a closure-t.)
17

De akkor is

zzrek · 2010. Már. 18. (Cs), 19.46
De akkor is csak ugyanúgy megállapodás kérdése, hogy ne "piszkíts bele a kódba/használd a megadott osztályt változatlan formában", vagy az, hogy "ne bővítsd az osztályt utólag prototype alapon", nem? Legalábbis az esetek nagy részében a megállapodás korlátozza a fejlesztőcsoport tagjait, nem pedig az, hogy fizikailag megváltoztathatatlan számukra az adott kódkönytár. (Amit említesz, az inkább kivétel, mint gyakorlat, nem? Úgy gondolom, hogy általában a szabályozás, a programozási stílus betartása miatt alakítanak ki zárt osztályokat, nem pedig a "fizikai hozzáférhetetlenítés" miatt (?))
18

Konvenció vagy kényszer

Joó Ádám · 2010. Már. 18. (Cs), 20.49
Azért a kompilált nyelveket még ma sem nevezném a szkripttengerben kivételként árválkodó szikláknak :)

Teszemazt a Windows van annyira meghatározó platform, mint a web, egyelőre jobban is, és ott is csak egy API-t kapsz. Amit ott rád kényszerítenek, az ellen nincs apelláta, legfeljebb biztonsági rés.

Én szeretek a jövőben gondolkodni, és szerintem az elkövetkező évtizedekben nagyságrendileg fog eltolódni a munkánk a készen kapott objektumokkal harmadik félként való munka irányába, még akkor is, ha ezek történetesen interpretált nyelv objektumai.

De vonatkoztassunk kicsit el a klasszikus programozástól, hogy egy jelenbeli példán át is megszemlélhessük a problémát: meddig volna működőképes a webszolgáltatások mai ökoszisztémája, ha az általuk nyújtott API csak egy konvenció volna, amit bármikor meg tudnánk kerülni, és mondjuk direktben túrni a Facebook adatbázisát?

Adat-adat, művelet-művelet, API-API, itt is ott is. Csak még gyerekcipőben járunk, de meg fog ez ám komolyodni.
15

ha a Ruby olyannyira

kuka · 2010. Már. 18. (Cs), 15.24
ha a Ruby olyannyira objektumorientált szemléletű, hogy nem is enged nyilvános adattagokat létrehozni, akkor miért engedi, hogy az utólag definiált tagfüggvények hozzáférjenek a változóihoz?

Szerintem a magyarázat ott is van az idézett részben: szemlélet. És nem paranoia.

Csak programozási stílust szorgalmaz, nem pedig rejtegetni segít.

Megjegyzés, hogy az Object.freeze metódus segítségével megakadályozható az osztály későbbi változtatása, tehát újabb metódusok deklarálása.