[OO algemeen] Contracted methods bijhouden

Pagina: 1
Acties:

  • Gwaihir
  • Registratie: December 2002
  • Niet online
Ik ben aan een forse applicatie begonnen die lang en vele uitbreidingen mee moet gaan. Eén van de dingen die ik veel genoemd zie (en zelf eerder ook al tegenaan gelopen ben) is de (terechte) angst voor het aanpassen van onderdelen omdat dit zo onzichtbaar ver door kan werken: een bepaalde publieke method kan immers op vele plekken gebruikt worden..

Eén manier om hier mee om te gaan is het eenmalig goed bedenken wat van een method verwacht wordt en hierop te unit testen. Maar toch.. je komt regelmatig later nog iets tegen wat nog 'ongedefineerd' is gebleven en weet je dan echt zeker dat je programma nergens op dit onbedoelde (en wellicht ongewenste) gedrag voortbouwt?

Uitgaande van het idee van een 'class reference card' lijkt het me daarom handig in een database vast te leggen welke klassen met welke methods uit een andere klasse een contract hebben. Toch kom ik het systematisch bijhouden van zulke afhankelijkheden (of iets in die richting) nergens tegen. Het uitwerken van dergelijke kaarten zie ik in bijvoorbeeld RUP alleen genoemd tijdens het uitwerken van het (use-case) ontwerp, waarna ze voor mijn gevoel gedoemd zijn te verdwijnen in de stapel 'nauwelijks nog gebruikte laat staan actueel gehouden' krabbels.

Vandaar mijn vraag aan andere ontwikkelaars: is dit zinnig? Zo ja, zijn er 'standaard' methoden om dit te doen; is het bijvoorbeeld iets wat al in Java/PHP Documentor ingebakken zit (en daarom weinig specifieke aandacht krijgt op het net)? Of is dit een van die dingen die het 'nice to have' niveau echt niet overstijgt en waar je dus wellicht beter je tijd niet in kunt steken?

  • prototype
  • Registratie: Juni 2001
  • Niet online

prototype

Cheer Bear

Birdie schreef op zaterdag 07 januari 2006 @ 00:58:
Ik ben aan een forse applicatie begonnen die lang en vele uitbreidingen mee moet gaan. Eén van de dingen die ik veel genoemd zie (en zelf eerder ook al tegenaan gelopen ben) is de (terechte) angst voor het aanpassen van onderdelen omdat dit zo onzichtbaar ver door kan werken: een bepaalde publieke method kan immers op vele plekken gebruikt worden..
Dat is meteen de kunst (nahjah, niet DE kunst) van object georienteerd ontwerpen. De afweging maken tussen veel afhankelijkheden naar een specifiek onderdeel of toch maar kiezen voor het even rampzalige redundancy. Dat laatste sowiesow NOOIT ivm onderhoudbaarheid, echter dependencies die ver doorwerken is imho gelijk aan een kasteel bouwen op te weinig steunpilaren. De gulden middenweg kies ik dan vaak door de Adapter pattern te gebruiken, waardoor de functionaliteit van een veelgebruikte klasse nu losser in verband staat en ietwat flexibeler is geworden; mocht in de toekomst de adaptee aangepast moeten worden, dan kan nog altijd gekozen worden om de adapter wel of niet mee te laten veranderen. Natuurlijk staat niet elke situatie dit toe.
Eén manier om hier mee om te gaan is het eenmalig goed bedenken wat van een method verwacht wordt en hierop te unit testen. Maar toch.. je komt regelmatig later nog iets tegen wat nog 'ongedefineerd' is gebleven en weet je dan echt zeker dat je programma nergens op dit onbedoelde (en wellicht ongewenste) gedrag voortbouwt?
Het gevaar bij contracten opstellen is dat je ze te streng definieert. Het is vaak veel gunstiger deze wat losser te definieren, om te voorkomen dat je jezelf in de vingers snijdt door 'achteraf gezien' te hoge eisen gesteld te hebben. Je kunt ze altijd in subclassen/wrappers nog in enige mate herdefinieren en ook daar op unit testen.
Uitgaande van het idee van een 'class reference card' lijkt het me daarom handig in een database vast te leggen welke klassen met welke methods uit een andere klasse een contract hebben. Toch kom ik het systematisch bijhouden van zulke afhankelijkheden (of iets in die richting) nergens tegen. Het uitwerken van dergelijke kaarten zie ik in bijvoorbeeld RUP alleen genoemd tijdens het uitwerken van het (use-case) ontwerp, waarna ze voor mijn gevoel gedoemd zijn te verdwijnen in de stapel 'nauwelijks nog gebruikte laat staan actueel gehouden' krabbels.

Vandaar mijn vraag aan andere ontwikkelaars: is dit zinnig? Zo ja, zijn er 'standaard' methoden om dit te doen; is het bijvoorbeeld iets wat al in Java/PHP Documentor ingebakken zit (en daarom weinig specifieke aandacht krijgt op het net)? Of is dit een van die dingen die het 'nice to have' niveau echt niet overstijgt en waar je dus wellicht beter je tijd niet in kunt steken?
Een hoop buzzwords waar een simpele ziel als ik niet veel wijzer van wordt. ;) Is hetgeen dat je zoekt om relaties aan te duiden niet gewoon de @see tag in java/php doc?
Wat betreft het documenteren van je contracten, Javadoc kent de mogelijkhed om custom tags op te nemen, waarbij over het algemeen @require voor precondties en @ensure voor postcondities gebruikt wordt. Ik denk dat ik je probleem niet geheel begrijp en het huidige alcohol promillage helpt daar ook niet bij, desondanks dat hoop ik je toch in enige mate geholpen te hebben ;)

[ Voor 7% gewijzigd door prototype op 07-01-2006 03:38 ]


  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

als je je code aan mathematische inspectie laat onderwerpen dan is de kans dat je nog speciale gevallen hebt overgelaten nogmaals een stuk kleiner.
bovendien kun je pre-/postcondities opstellen adhv die mathematische test. houdt de programmeur zich daar niet aan, dan is dat zijn probleem.
Dus als je code doet wat jij beschrijft in de documentatie met geldige pre/postcondities dan is er geen enkel probleem.

Enkele dingen kun je wil in het oog houden mbt functies:
In OO ontwerp hou je best je functies zo algemeen mogelijk. Overerven om te specialiseren is namelijk makkelijker dan overerven om restricties in te voeren:
(Voertuig -> Auto is makkelijker als Auto -> Voertuig)

CRC kaarten zijn handig om afhankelijkheden en fouten in je statisch/dynamisch model op te sporen
Ze zijn imo niet echt iets wat je buiten die fases gebruikt en ook al niet voor het doel dat jij beschrijft.
Een functie schrijf je om een bericht te verwerken. Als een functie een nieuw bericht niet kan verwerken dan moet je een nieuwe functie schrijven. (dus als je functie niet nuttig is voor wat je later zou willen, dan moet je hem gewoon herschrijven/een nieuwe schrijven)
Daarnaast is het zo dat als je een functie hebt die voor de gegeven parameters (die voldoen aan de precondities) niet de juiste waarde teruggeeft dan is je functie verkeerd en moet ze gefixt worden. Je kan met testen enkel nagaan of je functie fout is, niet of ze juist is.

ASSUME makes an ASS out of U and ME


  • PrisonerOfPain
  • Registratie: Januari 2003
  • Laatst online: 07-04 13:41
Birdie schreef op zaterdag 07 januari 2006 @ 00:58:
Eén manier om hier mee om te gaan is het eenmalig goed bedenken wat van een method verwacht wordt en hierop te unit testen. Maar toch.. je komt regelmatig later nog iets tegen wat nog 'ongedefineerd' is gebleven en weet je dan echt zeker dat je programma nergens op dit onbedoelde (en wellicht ongewenste) gedrag voortbouwt?
Je UnitTest de methods die er gebruik van maken toch ook? Dan geven de tests uiteindelijk alsnog aan dat er iets gebroken is.

  • Gwaihir
  • Registratie: December 2002
  • Niet online
PrisonerOfPain schreef op zaterdag 07 januari 2006 @ 12:26:
[...]

Je UnitTest de methods die er gebruik van maken toch ook? Dan geven de tests uiteindelijk alsnog aan dat er iets gebroken is.
Ik eh.. ik geloof dat ik het hernieuwd doorlopen van de volledige set tests toch meer als een soort eindcontrole behandel dan een manier om systematisch dat soort 'beschadigingen' mee te volgen. Maar dat klinkt wel goed eigenlijk; met name ook omdat het het systeem het werk laat doen; geen extra bijhouden voor nodig, alleen wat vaker tests draaien. :)
prototype schreef op zaterdag 07 januari 2006 @ 03:33:
Een hoop buzzwords waar een simpele ziel als ik niet veel wijzer van wordt. ;) Is hetgeen dat je zoekt om relaties aan te duiden niet gewoon de @see tag in java/php doc? ..
Ik zal die tags eens bestuderen. :)

Buzzwords.. ehm.. well:
-> 'Class Reference Card' dekt de lading aardig: dat is een kaart waarop je relatief vroeg in het ontwerp proces een klasse beschrijft, ter referentie. Naam, wat hij moet doen, wat hij van andere klassen gebruikt, etc..
-> RUP: een plek waar ik ietwat tot mijn verbazing over dit onderwerp effe geen houvast trof. (Rational Unified Process; een forse set (best practice?) werkwijzen voor softwareontwikkeling waar flink over nagedacht is en nog altijd wordt. Tegenwoordig van IBM.)


HIGHGuY, wat bedoel je precies met mathematische inspectie?

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Birdie schreef op zaterdag 07 januari 2006 @ 00:58:
Ik ben aan een forse applicatie begonnen die lang en vele uitbreidingen mee moet gaan. Eén van de dingen die ik veel genoemd zie (en zelf eerder ook al tegenaan gelopen ben) is de (terechte) angst voor het aanpassen van onderdelen omdat dit zo onzichtbaar ver door kan werken: een bepaalde publieke method kan immers op vele plekken gebruikt worden..
Vaak kan je dependency problematiek oplossen door te werken met layers. Bij een layer is iedere layer afhankelijk van de layer direct onder hem, maar niet van de layer(s) boven hem. Bv de web/gui layer is afhankelijk van de business layer, maar de business layer niet van de gui layer.

Meestal zet je dan een facade op waar je de inhoud van de layer gecontroleerd achter weg kunt stoppen. Hierdoor beperk je de afhankelijkheden nog meer want alleen binnen de layer is de interne structuur bekend. Verder geeft deze facade je ook de mogelijkheid om bv security of transacties vanuit te coordineren of om bv een remoting laag over te plaatsen.
Eén manier om hier mee om te gaan is het eenmalig goed bedenken wat van een method verwacht wordt en hierop te unit testen. Maar toch.. je komt regelmatig later nog iets tegen wat nog 'ongedefineerd' is gebleven en weet je dan echt zeker dat je programma nergens op dit onbedoelde (en wellicht ongewenste) gedrag voortbouwt?
In principe mag het niet voorkomen dat een functie ongedefinieerd gedrag vertoond. Ik zou dan een exception opwerpen en dit bijhouden in de documentatie. Op deze exceptions kun je ook unittesten.

[ Voor 10% gewijzigd door Alarmnummer op 09-01-2006 10:04 ]


  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

Birdie schreef op zondag 08 januari 2006 @ 22:31:
HIGHGuY, wat bedoel je precies met mathematische inspectie?
Mathematische inspectie is een proces waarin jij of (liefst) iemand anders de code "bewijst".
Dit gebeurt door pre-/postcondities. Niet enkel op functie-schaal maar ook binnen de functie.
Het is de bedoeling dat iemand die de code niet kent, aantoont dat de code juist zal werken, ook al kent die het algoritme niet.

bvb voor een while lus:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int eenFunctie(int* tabel, int n)
{
  //A0

  // veel code die ook bewezen wordt.

  // zoek grootste element
  int i = 0;
  int grootste = 0;
  // A1
  while (i < n)
  {
     // B0
     if ( tabel[i] > grootste)
        grootste = tabel[i];
     // B1
  }
  // A2
  
  // veel code die ook bewezen wordt.
  return iets;
  // A2
}

mathematische inspectie:
A0 (=preconditie van de functie): tabel is een geldige pointer wijzend naar ints, n is het aantal elementen in de tabel.
A1: i en grootste zijn correct geinitialiseerd
-> opmerking: grootste = INT_MIN?? <-
B0: i is een geldige index in de tabel
B1: grootste bevat het grootste tot nu toe gevonden element en de index is opgehoogd
-> opmerking: i wordt niet opgehoogd <-
A2: grootste bevat het grootste element uit de tabel
A3 (=postconditie van de functie): functie geeft ... terug

lijkt behoorlijk zinloos (is het ook imo) maar het kan wel je code zuiveren van nasty dingetjes.
Ik had bvb de code getypt in de overtuiging dat ze correct was (op de INT_MIN na) maar ik was wel gelijk de i++ vergeten en heb die er dus pas bij de inspectie uitgehaald :X

Je kan elke tussentoestand zien als een preconditie van wat erop volgt en als postconditie van wat ervoor komt. Bovendien kan je dus ook bepaalde afleidingen maken:
uit A1 volgt dat i correct(=0) geinitialiseerd op het begin van de tabel
uit B1 volgt dat grootste na iedere iteratie het grootste element aanwijst
uit A1, B0 en B1 volgt dan weer dat alle elementen overlopen (alle met geldige index)
dus kan je concluderen dat in A3 grootste ook effectief het grootste element bevat van de tabel.

voor code als deze lijkt dit zinloos, maar ik denk dat als je het algoritme van je leven vindt, je dit dan wel apprecieert om toch zoveel mogelijk te verifieren dat je code doet wat ze moet doen ;)

ASSUME makes an ASS out of U and ME


  • PrisonerOfPain
  • Registratie: Januari 2003
  • Laatst online: 07-04 13:41
Birdie schreef op zondag 08 januari 2006 @ 22:31:
Ik eh.. ik geloof dat ik het hernieuwd doorlopen van de volledige set tests toch meer als een soort eindcontrole behandel dan een manier om systematisch dat soort 'beschadigingen' mee te volgen. Maar dat klinkt wel goed eigenlijk; met name ook omdat het het systeem het werk laat doen; geen extra bijhouden voor nodig, alleen wat vaker tests draaien. :)
Misschien dat ik te snel aan Test Driven Development dacht bij het horen van de term Unit Tests, maar opzich is het geen verkeerd idee om je er over in te lezen. Zelf vind ik het vrij prettig werken als ik weet dat de Unit Tests zeggen dat ik niets heb gesloopt en dat het doet waar het bedoelt voor is. Je schrijft de Unit Tests namelijk voor je de daadwerkelijke implementatie schrijft, en dan nog probeer je zo min mogenlijk implementatie te schrijven. Zelf draai ik die tests dan ook om de paar regels, dus niet een keer 's avonds of 3 keer per dag, maar vrij frequent.
Pagina: 1