verantwoordelijkheid waar, in dubio
Code (php)
Toen bedacht ik vandaag... is het wel correct dat foo() rechtstreeks de locked property aanspreekt. Ligt de verantwoordelijkheid of de class gelockt is niet bij is_locked()? Stel bijv. dat in de toekomst het niet alleen van de property locked afhangt, maar ook nog van een andere variabele, dan werkt de code niet meer zoals het zou moeten. Zou het daarom niet zo moeten, vroeg ik me af?
Code (php)
Op zich zou je denken dat dit beter is, omdat nu daadwerkelijk is_locked bepaalt of de class op slot zit.
Maar... als je die lijn doortrekt, dan zou je dus ook niet dit krijgen...
Code (php)
Maar dit...
Code (php)
Immers, de verantwoordelijkheid of iets bestaat hebben we bij has() neergelegd.
Trek je deze lijn nog verder door, dan zou je niet dit krijgen...
Code (php)
Maar dit...
Code (php)
Maar sla je dan niet te ver door vraag ik me af? Wie kan er iets zinnigs over zeggen?
Wat je doet is jouw keuze. Globaal gesproken heb je 4 opties:
1. Je kan de verantwoordelijkheid bij de getters/setters leggen. Dat betekend dat alleen deze bij de property mogen en de rest deze methods moet gebruiken (in het kort: private properties en getters/setters voor alles).
2. Je kan de verantwoordelijkheid binnen de klasse leggen, dus alles wat in de klasse gebeurd is de verantwoordelijkheid van de klasse. Hij moet er alleen voor zorgen dat dingen buiten de klasse er niet bij kunnen (in het kort: private properties en getters/setters voor buiten).
3. Je kan de verantwoordelijkheid bij de hele klasse hierarchy leggen. Hierdoor heeft dus zowel de klasse als de subklassen vrij toegang tot de properties (in het kort: protected properties en getters/setters voor buiten).
4. Je kan de verantwoordelijkheid bij de applicatie leggen. Hierdoor heeft dus iedereen vrij toegang tot de property, als jij zo stom bent om daar iets verkeerd in op te slaan is het lekker je eigen schuld (in het kort: public properties).
Mijn vraag is, op het moment dat je een bepaalde method maakt, is die method dan automatisch binnen die class altijd verantwoordelijk voor de betreffende functionaliteit, dus los van visibility.
Ik kan in een method foo het volgende zetten:
Code (php)
Nu voegen we een has() method aan de class toe:
Code (php)
Oké, nu hebben we dus een aparte method in het leven geroepen waarmee we zowel van buiten als binnen de class kunnen bepalen of een bepaalde key/id bestaat.
Nu is de vraag... door de komst van die has() method... is daarmee die has() method eindverantwoordelijk geworden om te bepalen of een key/id bestaat? Waar ligt nu die verantwoordelijkheid?
Mag de foo() method zelf gaan kijken via isset() of de betreffende key/id bestaat? Of ligt deze verantwoordelijkheid, als gevolg van het bestaan van een has() method, nu volledig bij de has() method en zou de foo() method er dus als volgt moeten uitzien?
Gewijzigd op 29/03/2014 21:20:18 door Ger van Steenderen
Zo wil je toch niet programmeren? ;-)
Dit klopt inderdaad wat jij zegt. En vandaar ook mijn vraag. Uhm... ik vind dit lastig om uit te leggen.
Nou, stel dat we in een later stadium een keer die has functie aanpassen naar zoiets:
Code (php)
En de functie foo is nog steeds dit:
Dan correspondeert dat niet meer met de aangepaste has() method. Dan ontstaat er dus een probleem. Snap je wat ik bedoel?
Quote:
isset($this->data[$id]) && $data[$id]['legal'] === true
In dit specifieke geval (maar iets soortgelijks zou zomaar kunnen opgaan in elke denkbare situatie) zou ik de functionaliteit splitsen in een functie has() en een functie has_legal(). Het is anders niet mogelijk om onderscheid te maken tussen het niet voorkomen van een id, en het wel voorkomen, maar niet legaal zijn.
Gewijzigd op 29/03/2014 22:15:22 door Willem vp
Toevoeging op 29/03/2014 22:28:47:
En laten we ook meteen het begrip pseudo-code de wereld uit helpen. Pseudo-code betekend niet PHP code zonder praktisch voorbeeld. Psuedo-code is een praktisch voorbeeld weergegeven in een simpele weergave, zo dat je makkelijker kan nadenken over het praktische voorbeeld, ipv met moeilijke script grammatica te werken. Dan focus je namelijk teveel op syntax en kun je niet meer goed over de applicatie opzich nadenken.
Dat legal was puur een voorbeeld om aan te geven dat de has() method met iets anders wordt uitgebreid. De invulling ervan is verder niet zo relevant. Het was slechts een voorbeeldje.
@Wouter:
>> welke gek gaat nou een waarde controleren in een functie alleen controleert of een bepaalde variabele/parameter aanwezig is?
Wat bedoel je? Het kan toch zijn dat je van buiten de class wilt weten of een waarde bestaat? Daar maak je dus een has method voor.
Het voorbeeld van is_locked hierboven is overigens dus een praktisch voorbeeld waar ik het over heb.
Code (php)
Zouden jullie de controle of de class gelocked is doen zoals je hierboven in de add() method zit? Of zouden jullie het zo doen?
Code (php)
Gewijzigd op 29/03/2014 22:39:50 door Ozzie PHP
Lijkt mij een gewoon alles door elkaar gooien.
Blijft nog steeds dat je dingen dubbelop doet:
Ik maak is_locked public zodat je van buitenaf kunt testen of de class wel of niet op slot zit:
Ik snap wat je bedoelt met dingen dubbelop doen. Dat is ook exact de kern van mijn vraag.
Een functie kan heel makkelijk kijken of de class property locked true of false is, maar mijn vraag is of een willekeurige functie dat zomaar zou moeten doen. Mag een willekeurige functie zelf die property raadplegen, of is het netter als deze functie gebruik maakt van de is_locked functie. Ook al komt het precies op hetzelfde neer. Dat is dus precies waarom ik deze vraag stel.
Ik ga even een belachelijk voorbeeld geven nu... maar ik probeer daar iets mee duidelijk te maken waar deze vraag betrekking op heeft. En hopelijk snap je dan wat ik bedoel.
Nogmaals, het is vrij belachelijk, maar het gaat om de gedachte die erachter zit.
Stel we hebben deze functies:
- add
- delete
- is_locked
De add en delete functie kunnen alleen gebruikt worden als de class NIET is gelocked.
Nu gaan we aan deze functies poppetjes hangen:
- add -> adriaan
- delete -> dennis
- is_locked -> bert
Bert is de bewaker van de data class. Stel je de data class voor als een grote loods.
Situatie 1)
Adriaan wil iets neerleggen in de loods. Pompiedom... daar komt ie aangewandeld. Adriaan loopt naar bewaker Bert en vraagt of de loods open is. Bert loopt naar de deur. Hij drukt de klink naar beneden, en ja hoor de loods blijkt open te zijn. Adriaan legt snel zijn spulletjes neer. Een paar minuten later komt ook Dennis aanlopen. Ook hij gaat naar Bert. Bert controleert weer of de loods open is. De loods is open en Dennis haalt iets weg uit de loods.
Situatie 2)
Adriaan wil iets neerleggen in de loods. Pompiedom... daar komt ie aangewandeld. Hij ziet Bert wel staan maar negeert hem. Adriaan voelt aan de deurkruk. De loods gaat open en Adriaan legt z'n spulletjes neer. Dennis is ook weer van de partij. Ook hij negeert Bert. Voelt of de loods open is en haalt er wat spullen uit.
Op een goede dag wordt de loods ook gebruikt om gevaarlijke chemische stoffen uit te testen. Aan bewaker Bert is gevraagd of hij voortaan niet alleen wil testen of de deur niet op slot zit, maar ook of er geen giftige gassen aanwezig zijn.
Situatie 3)
Adriaan en Dennis komen bij de loods. Hallo Bert, kunnen we erin? Nee, nu even niet zegt Bert want de loods is gevuld met gevaarlijke chemische stoffen. Oké, zeggen Adriaan en Dennis. We komen later wel even terug.
Situatie 4)
Adriaan en Dennis staan voor de loods. Ze negeren bewaker Bert. Ze doen de deurkruk naar beneden en lopen gezellig pratend de loods binnen. "Erghh... wat gebeurt er... wattisssdiitt...". Boem, ze vallen allebei dood op de grond. Ze zijn omgekomen door de chemische stoffen.
Zo... mooi verhaal hè :-)
In situatie 4 ging het dus mis. Als ze Bert (de is_locked method) hadden geraadpleegd was er niks aan de hand geweest en was alles goed gegaan. En dat is dus de kern van mijn vraag. Bij wie ligt de verantwoordelijkheid? Mag zo'n add/delete functie zelf kijken of de deur op slot zit, of ligt die verantwoordelijkheid bij bewaker Bert, ofwel de is_locked functie?
Gewijzigd op 29/03/2014 23:20:44 door Ozzie PHP
Het moeilijke van voorbeelden is dat je deze nooit 100% kunt overplaatsen. Je kan een voorbeeld bijv. gebruiken om deelvraag 1 van onderwerp A te verklaren, maar dat zelfde voorbeeld kan totaal niet opgaan voor deelvraag 2. Voorbeeldje: De deeltjes gebruiken andere deeltjes om krachten aan elkaar door te geven. Een afstotende kracht kun je mooi uitleggen door 2 mensen op ijs een basketbal te laten overgooien, ze zullen dan allebei uit elkaar gaan. Een aantrekkende kracht is op deze manier echter helemaal niet uit te leggen.
Een voorbeeld moet je dus alleen gebruiken als je 100% zeker bent van het onderwerp waar je over praat. Anders kan je hem misschien wil op verkeerde momenten gebruiken. Je gaat dan een onderwerp verzinnen om jouw mening te ondersteunen, niet om de waarheid te verkrijgen.
Daar zou je in situatie 3 en 4 een Bert hebben om te controleren of de deur op slot zit, en een Sjaak om te bepalen of er geen giftige dampen in de loods hangen.
Anders gezegd: een functie die is_locked heet, moet alleen kijken of de deur op slot zit. Wil je ook controleren op rondwalmend mosterdgas, dan moet je daar een andere functie voor bedenken. is_safe_to_enter() of zo.
Uiteraard staat het je vrij om een overkoepelend controleorgaan is_accessible() te maken die achtereenvolgens is_locked() en is_safe_to_enter() uitvoert. Maar in dat geval zou wel alle toegang via die is_accessible() moeten lopen. Bij de Belastingdienst kom je tenslotte ook niet bij Bert voordat de receptie je heeft doorgelaten.
Toevoeging op 29/03/2014 23:37:00:
Je zou natuurlijk ook een briefje op de deur kunnen hangen waarin gewaarschuwd wordt voor gevaarlijke gassen...
>> Je gaat dan een onderwerp verzinnen om jouw mening te ondersteunen, niet om de waarheid te verkrijgen.
Wouter, ik vind het vervelend/lastig als je dit soort suggestieve opmerkingen maakt. Ik weet dan niet of je me wil helpen of juist niet.
Ik zit geen onderwerp te verzinnen om mijn mening te ondersteunen. Ik stel een vraag, die blijkbaar moeilijk is uit te leggen en daarom probeer ik analogieën te bedenken om het probleem duidelijk te maken.
@Willem:
Kijk, daar kan ik wat mee. Nu vertel je iets wat me een stapje verder brengt. Als ik je dus goed begrijp, zeg jij eigenlijk dat (in dit geval) de locked parameter het resultaat is van één taak, en dat je die locked parameter dus gerust vanuit een andere functie mag raadplegen. Correct?
Ozzie PHP op 29/03/2014 23:37:47:
Kijk, daar kan ik wat mee. Nu vertel je iets wat me een stapje verder brengt. Als ik je dus goed begrijp, zeg jij eigenlijk dat (in dit geval) de locked parameter het resultaat is van één taak, en dat je die locked parameter dus gerust vanuit een andere functie mag raadplegen. Correct?
Ja, met als kanttekening dat je eigenlijk per situatie moet bekijken of dat handig/verstandig is.
Om maar weer een beetje door te borduren op het voorbeeld met de loods:
Je zou een class kunnen zien als een team/afdeling. De functies zijn medewerkers/collega's.
Binnen een team weet je vaak ook wel waar je collega's mee bezig zijn. Vooral in kleinere bedrijven is het niet ongebruikelijk dat iemand zelf naar de loods loopt om er iets in te stoppen of uit te halen. Als er een extra veiligheidscheck gedaan moet worden in verband met de mogelijke aanwezigheid van gifgas, dan zal er een memo worden rondgestuurd aan het team waarin de procedure wordt uitgelegd. Iemand die in de loods moet zijn, weet dan dat als hij de deur van slot heeft gehaald, hij ook even moet kijken of het kanariepietje niet met zijn pootjes omhoog ligt (oude mijnwerkerstruc). En als iemand het toch niet durft, kan hij altijd aan een collega vragen of die meeloopt.
Oftewel: binnen een class hoeft het geen probleem te zijn als methods rechtstreeks van private properties gebruik maken. Dat betekent dan wel, dat als er ooit iets wordt gewijzigd in de procedures, alle methods nagekeken/gewijzigd moeten worden. Bij kleinere classes is dat nog wel te overzien. En het is natuurlijk ook geen probleem als een method wél gebruik maakt van een andere method om die properties te bewerken.
Bij grote bedrijven zul je een Bert hebben die de loods beheert. Als een medewerker in die loods moet zijn, moet hij zich altijd eerst bij Bert melden. Hij haalt dan de spullen uit de loods die de medewerker nodig heeft (of stopt erin wat de medewerker meebrengt). De medewerker zelf komt in principe nooit in de loods, maar blijft aan het bureau van Bert staan tot de spullen zijn opgehaald.
Dit komt overeen met een class waarvan de methods nooit rechtstreeks de private properties benaderen die onder verantwoordelijkheid van een andere method vallen. In plaats daarvan vragen ze de informatie intern op via de verantwoordelijke method.
Het aanroepen van zo'n method brengt overhead met zich mee (en het benaderen van de loods via Bert ook, want je moet als medewerker eerst aan Bert vertellen wat hij moet ophalen). Wanneer een class wat groter wordt, is die overhead vaak goed te verdedigen omdat het nog veel meer kost om fouten te corrigeren die zijn ontstaan door buiten de procedures om te werken.
Een ander voordeel van toegang via een centrale Bert() is wanneer Bert meerdere handelingen moet uitvoeren: slot controleren, gasmetingen uitvoeren, etc. Je hoeft dan alleen Bert maar te vertellen wat er is gewijzigd en de andere medewerkers/methods hoeven daar geen weet van te hebben. Een abstractielaag dus.
Overigens is dat geen garantie dat er nooit iets hoeft te wijzigen. Het kan zijn dat de directie bepaalt dat medewerkers niet meer rechtstreeks naar Bert mogen, maar eerst toestemming moeten hebben van hun teamleider (of het zelfs de teamleider moeten laten doen). Voor een class betekent dat, dat de methods die Bert() aanroepen toch herschreven moeten worden.
Als vuistregel zou ik dus zeggen: wanneer je class niet al te groot is, laat dan de methods zelf aanrommelen met de properties. Heb je een grotere class, doe dat dan niet.
Je zou nu als argument kunnen geven dat je van tevoren niet kan bepalen of een class altijd klein zal blijven, waardoor je dus de methods nooit rechtstreeks de properties laat benaderen. Echter, als een class die klein is opgezet in een later stadium flink groeit, loont het waarschijnlijk de moeite om de boel een keer te redesignen.
Gewijzigd op 30/03/2014 00:45:24 door Willem vp
Het kwartje is wel gevallen. Het begon met jouw voorgaande opmerking en deze opmerking maakt het gehaal af. Dus dank daarvoor!!
Als laatste ben ik nog even benieuwd. Wat versta je onder een grote class, over hoeveel regels praat je dan (ongeveer)?
Ozzie PHP op 30/03/2014 00:54:24:
Wat versta je onder een grote class, over hoeveel regels praat je dan (ongeveer)?
Geen flauw idee, ik OO niet ;-)
Maar goed, dit soort issues heb je ook in procedurele code. Die grens ligt denk ik ook niet scherp en is niet alleen afhankelijk van het aantal regels code, maar ook van het aantal methods/functies, de complexiteit van de interactie (komt 1 aanroep van Bert overeen met 1 statement, of zijn het er misschien wel 10?), de dichtheid van de code (hoeveel gebeurt er in 1 regel?) en vooral ook je eigen referentiekader. Sommige programmeurs vinden 250 regels code al veel en andere vinden 5000 regels nog klein.
Als ik naar mezelf kijk, dan schat ik in dat ik de interne communicatie in mijn software wat formeler ga aanpakken vanaf zo'n 1000 regels code (ruwweg 16 kantjes A4).
Ooooh... hehe... schrijf jij dan gewoon alle code onder elkaar?
Ozzie PHP op 30/03/2014 01:33:45:
Jij OO't niet?
Nou, ik zal niet zeggen dat ik nooit aan OO doe (ik vind dat trouwens een beetje een Teletubbiekreet: Oh, oh...) maar het is inderdaad niet het eerste instrument waar ik naar grijp als ik iets maak. Dat kan ook te maken hebben met het feit dat ik vaak software maak waar ik OO minder bij vind passen. Het is best mogelijk die software in OO te schrijven op zo'n manier dat het goed werkt, maar de manier waarop het is geïmplementeerd is dan wellicht niet de meest logische (je kan best een schroef in de muur krijgen met een hamer, maar of het logisch is?). Als vuistregel hou ik altijd aan: als je data niet gemodelleerd kan worden op een relationele database, dan moet je geen OO gebruiken.
Wat ik een van de grote nadelen aan OO vind, is de neiging om je software te gaan over-engineeren, wat weer kan leiden tot inefficiënte code. Vooral bij programma's die niet groter zijn dan een paar honderd regels is het toepassen van OO vaak vergelijkbaar met het gebruiken van een kanon om op een mug te schieten.
Dat wil overigens niet zeggen dat als je geen OO gebruikt je je code gewoon onder elkaar pleurt (zoals jij lijkt te suggereren). Er zijn meer manieren dan alleen OO om je code te structureren. ;-)
Daarnaast zijn er situaties waar OO echt niet het juiste vehikel is. Niet alle datatypes voldoen bijvoorbeeld aan het substitutiepricipe van Liskov, en het is dan best lastig om in een OO-taal iets te schrijven waarmee je jezelf als programmeur niet volledig belachelijk maakt.
Misschien moet ik dat toelichten met een voorbeeld. ;-) Stel, je hebt een class 'vogel' met subclasses als 'duif', 'mus' en 'spreeuw'. Per definitie erft een subclass alle eigenschappen van de superclass. Omdat alle vogels kunnen vliegen, heeft de superclass 'vogel' dus een method 'vliegen()'.
Tsja, en vervolgens wil je subclasses 'pinguïn' en 'struisvogel' toevoegen... Die erven de method vliegen() terwijl die helemaal niet van toepassing is.
Nu zou je nog kunnen zeggen dat dat betekent dat je de class 'vogel' verkeerd hebt gemodelleerd en dat je eigenlijk eerst subclasses 'vliegvogels', 'loopvogels' en 'vreemdevogels' had moeten bedenken, maar dat was in het begin misschien helemaal niet relevant. Kom je weer op het punt over-engineeren: wat je ook bedenkt, je kunt altijd wel iets bedenken om het nóg abstracter/uitgebreider/ingewikkelder te maken. Bovendien maak je een model van de werkelijkheid, en die werkelijkheid kan ook nog veranderen. Je kan wel proberen dat allemaal mee te modelleren, maar ergens moet je een grens stellen, anders krijg je op een gegeven moment de situatie dat je voor een simpele write('Hello world') 75 duizend regels code nodig hebt.
Gewijzigd op 30/03/2014 17:23:04 door Willem vp
>> Dat wil overigens niet zeggen dat als je geen OO gebruikt je je code gewoon onder elkaar pleurt (zoals jij lijkt te suggereren). Er zijn meer manieren dan alleen OO om je code te structureren. ;-)
Wat doe je dan? Gewoon heel veel bestanden en telkens includes gebruiken???
Er is ook nog iets als functioneel programmeren. En ook procedureel kun je met goed nadenken tot goede code omzetten. Procedureel !== Italiaanse spaghetti code.