Anemic Domain Model: Pattern or Anti-Pattern?

SOLID principles, Test Driven Development, and functional programming techniques are getting more and more ingrained into the modern programmer’s mind.

That’s obviously a good thing. But it comes with a price: the loss of good old Object Orientation Principles.

One symptom of this trend is the rise of the Anemic Domain Model Anti-Pattern (ADM). An ADM contains data classes without logic and (service) classes containing logic but no data. In fact the code is procedural. In contrast a Rich Domain Model (RDM) is truly Object Oriented: classes contain data and behavior.

AnemicDomainModel Anti-Pattern

The fundamental horror of this anti-pattern is that it’s so contrary to the basic idea of object-oriented design; which is to combine data and process together. The anemic domain model is really just a procedural style design – Martin Fowler

So, ironically, the SOLID principles for Object Orientation will lead to less Object Orientation when applied too rigidly.

According to this blog post from 2014, this doesn’t seem to be a problem:

The Anemic Domain Model is no anti-pattern it’s a SOLID design

The cons of an ADM:

  • Complexer design with more classes
  • Domain logic spread over multiple services
  • Unconstrained mutation of the business model

In my opinion the last point is a very serious flaw of using an ADM.

The pros of an ADM:

  • SOLID principles lead to ADM
  • ADM better supports automatic testing
  • ADM models are more flexible (smaller independent building blocks)

The first argument is actually a non-argument. SOLID principles are not an end in itself. If SOLID principles (meant for OO!) lead to less OO they are flawed!

Not being able to automatically test your code and swap it for something else is a serious problem. But these are straw man arguments, since it is still possible to use an RDM and rely on object composition and dependency injection.

There is no such thing as a single thruth in programming. Programming is a balancing act. I’ll illustrate this with a link to yet another blogpost from 2016 on this subject:

Anemic vs. Rich Domain Objects—Finding the Balance

Advertenties
Geplaatst in Uncategorized | Een reactie plaatsen

Protected Varation: SOLID wordt SPLID

SOLID is een acroniem voor een vijftal ontwerp-principes. De O in SOLID staat voor Open/Closed Principe (OCP): Een module moet open zijn voor uitbreiding, maar gesloten voor aanpassing.

Dit principe laat zich lastig uitleggen en komt nog uit een tijd dat implementation-inheritance nog geen ‘bad smell’ had.

Jon Skeet komt nu met een beter hanteerbaar principe als alternatief voor OCP: Protected Variation.

Lees meer in zijn blog post: The Open-Closed Principle, in review

Geplaatst in Uncategorized | Een reactie plaatsen

Privacy Statement Psalmboek App

English:
Psalmboek.nl for Windows 8 does not collect or publish any personal information. The internet connection is only used to download the organ accompaniment.

Nederlands:
Psalmboek.nl voor Windows 8 verzamelt of publiceert geen persoonlijke informatie. De internetverbinding wordt uitsluitend gebruikt voor het downloaden van de orgelbegeleiding.

Geplaatst in Psalmboek.nl | Een reactie plaatsen

De onzin van Story Points in relatie tot Velocity

Het bepalen van een Velocity met behulp van Story Points is fundamenteel fout!

In de wereld van Scrum slaat het gebruik van Story Points behoorlijk aan.

Story Points zijn een manier om om te kunnen gaan met onzekerheid in een project. Op die manier heb je iets meetbaars, maar hoef je tegelijk geen uitspraken te doen over de tijd dat het kost om iets te implementeren.

Nu is er op zich niet veel in te brengen tegen het gebruik van Story Points, zolang je ze uitsluitend gebruikt om User Stories onderling te vergelijken. Het kan een goed hulpmiddel zijn bij het aanbrengen van prioriteiten. Ook kan het duidelijk maken dat een User Story te groot is en opgedeeld moet worden.

Waar het echter fundamenteel fout gaat is, wanneer Story Points opgeteld gaan worden om hiermee een Velocity te meten.

Een Story Point is een relatieve maat voor diverse zaken, zoals complexiteit, benodigde inspanning en onzekerheid. Er worden dus gegevens uit meerdere dimensies platgeslagen in een getal. Dit getal heeft alleen betekenis in relatie tot Story Points in andere User Stories. Story Points zijn verder absoluut niet bedoeld om hier een eenheid van tijd mee aan te geven.

Na afloop van een Sprint worden deze relatieve getallen bij elkaar OPGETELD. Van deze optelling wordt dan vervolgens gezegd dat dit de ontwikkelsnelheid is (Velocity). In een volgende sprint zouden dan evenveel story points gerealiseerd kunnen worden.

Wiskundig gezien slaat dit nergens op. Er is geen enkele reden om te veronderstellen dat een aantal Story Points met een bepaalde som een vergelijkbare hoeveelheid werk oplevert als een andere combinatie van Story Points met dezelfde som.

Uiteindelijk blijkt dat Story Points toch als maat voor werktijd gehanteerd worden door de meeste klanten/opdrachtgevers, terwijl dit expliciet niet de bedoeling is.

Ga maar na: als in een Sprint van twee weken met vier man (320 uur) 160 Story Points ‘gerealiseerd’ worden en dit ook maatgevend is voor een volgende Sprint van twee weken, dan kun je de logische conclusie trekken dat een manuur binnen dat team gelijk staat aan 0,5 Story Points.

De voorstanders van deze methode zullen direct aangeven dat dit niet de bedoeling is. Maar de vraag is wat dan wél de bedoeling is? Hoe je het wendt of keert: de Velocity wordt gehanteerd als maatgevend voor wat er in een Sprint (van twee of drie weken) gerealiseerd kan worden. 

Mijn aanbeveling zou zijn om toch maar weer in uren te gaan schatten. De PERT-methodiek is daarbij een statistisch meer verantwoorde manier om toch met onzekerheid om te kunnen gaan. En ook dan geldt dat een normale optelling van urenschattingen per taak of User Story niet mogelijk is.

Zie ook: Story Points and Velocity: Recipe for Disaster

Geplaatst in Agile, Scrum | Tags: , , , | 2 reacties

Technische schuld. Voorkom een crisis

De laatste tijd zijn schulden volop in het nieuws. Europa lijdt onder een schuldencrisis. Dit artikel heeft niet de pretentie om dáár iets aan te kunnen veranderen.

Een schuldencrisis-in-het-klein kan ook veroorzaakt worden door technische schuld. Hoewel niet bedreigend op grote schaal, kunnen projecten er veel last van hebben. Het is namelijk een schuld waarover rente betaald moet worden in de vorm van inefficiënte projecturen. Voor de meeste projectmanagers blijft deze schuld echter verborgen. En zolang de klant nog in staat is te betalen, hoeven we ons er niet zo druk over te maken.

De parallel trekkend met Europa: zolang het economisch goed gaat, maken we ons niet druk om wat uitstaande schuld. Dat is typisch iets ‘voor later zorg’. Maar zodra het economisch minder voor de wind gaat, blijkt die schuld ineens erg wezenlijk en kun je daar behoorlijk mee in je maag zitten.

In slechte tijden kán technische schuld ervoor zorgen dat projecten niet levensvatbaar zijn, omdat de te betalen ‘rente’ gewoonweg te hoog wordt. De klant is niet meer bereid of in staat om de benodigde prijs te betalen.

Met dit artikel beperk ik me tot het begrip technische schuld binnen het vakgebied software engineering. Daarbij richt ik me op het herkennen van technische schuld en het aflossen van teveel aan technische schuld om hoge rentebetalingen te voorkomen.

Wat is technische schuld?

De term technische schuld (technical debt) is afkomstig van Ward Cunningham, die in 1992 voor het eerst het verband legde tussen technische complexiteit en schuld:

Shipping first time code is like going into debt. A little debt speeds development so long as it is paid back promptly with a rewrite… The danger occurs when the debt is not repaid. Every minute spent on not-quite-right code counts as interest on that debt. Entire engineering organizations can be brought to a stand-still under the debt load of an unconsolidated implementation, object-oriented or otherwise.

Dit verband vloeit voort uit de wet die Meir Manny Lehman in 1980 formuleerde (Lehman’s Law):

As an evolving program is continually changed, its complexity, reflecting deteriorating structure, increases unless work is done to maintain or reduce it.

De term technische schuld is dus ontstaan binnen het vakgebied software engineering. En dat is niet zo vreemd; met software kun je op duizenden manieren hetzelfde resultaat bereiken; daarmee wordt het een programmeur erg makkelijk gemaakt om foute keuzes te maken.

Voor minder goede code wordt een prijs betaald. Op korte termijn kan minder goede code voordeel opleveren, omdat een project daardoor sneller klaar is. Op langere termijn zal de ‘schuldenlast’ zodanig groeien dat de code niet meer te onderhouden of uit te breiden is zonder grote investeringen in de structuur van de code.

Hoe ontstaat technische schuld?

Er bestaan grofweg twee oorzaken voor het ontstaan van technische schuld:

· Onbewust
· Bewust

Onbewuste schuldopbouw is meestal het gevolg van onervarenheid en/of te weinig kennis. Deze vorm van schuldopbouw is het gevaarlijkst, omdat niemand het beseft en er dus ook geen maatregelen tegen worden getroffen. Onervarenheid kan daarnaast ook zorgen voor slechte tijdschattingen vanwege het niet onderkennen van projectrisico’s als gevolg van slechte code.

Bewuste schuldopbouw ontstaat meestal vanwege tijdsdruk, soms met een commercieel motief: sneller opleveren levert meer geld op. Dit kan een verstandige keuze zijn, mits iedereen er zich van bewust is en er geld gereserveerd wordt om deze schuld op een aanvaardbaar niveau te houden.

Bewuste schuldopbouw kan ook ontstaan vanwege gemakzucht: waarom ‘moeilijk’ doen als het ook makkelijk kan. Eigenlijk is dit ook een vorm van onkunde, omdat de ‘makkelijke’ manier op langere termijn juist meer inspanning kost.

Herkennen van technische schuld

Het duurt soms lang voordat je aan de ‘buitenkant’ merkt dat er sprake is van technische schuld. Zodra de volgende symptomen zich aandienen, is het eigenlijk al te laat:

· Er wordt door programmeurs geklaagd over slechte of bij elkaar gehackte code.
· Elke verandering leidt tot een opeenvolging van nieuwe fouten in de software (en kan showstoppers tot gevolg hebben).
· Fouten die verholpen leken, duiken vaak weer op.
· Er treden regelmatig fouten op in code die eerst prima werkte.

Gelukkig is het ook mogelijk om veel vormen van technische schuld in een veel eerder stadium te herkennen. Door je eigen code regelmatig te (laten) reviewen kunnen de volgende zaken aan het licht komen:

· Code smells
· Anti-patterns

Code smells zijn symptomen in de source code van een programma die mogelijk de indicatie zijn van een dieperliggend probleem.

Anti patterns zijn veelvoorkomende ontwerp-‘patronen’ die niet effectief zijn en/of contraproductief werken op de langere termijn.

Het vinden van code smells en anti-patterns die daadwerkelijk bijdragen aan de technische schuld is een kunst op zich, want er zijn situaties waarin een afwijkende keuze juist het beste werkt.

Aflossen van technische schuld

Technische schuld ontstaat beetje bij beetje. Het is de opeenhoping van kleine afwijkingen ten opzichte van een in aanvang smetteloze architectuur.

Het lukt niet altijd om de opbouw van technische schuld op voorhand te voorkomen, omdat de negatieve gevolgen van bepaalde keuzes pas gaandeweg een project duidelijk worden. Het is echter wel van belang om technische schuld af te lossen ruim voordat het uit de hand gaat lopen. Het regelmatig aflossen van technische schuld moet daarom een vast onderdeel vormen van het normale ontwikkelproces:

· Focus op kwaliteit van architectuur en ontwerp (niet-functionele eisen!)
· Review code regelmatig
· Refactor de code vaak (minimaal na elke review)

Volgens Wikipedia betekent refactoring:

Code refactoring is “disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior”, undertaken in order to improve some of the nonfunctional attributes of the software. Typically, this is done by applying series of “refactorings”, each of which is a (usually) tiny change in a computer program’s source code that does not modify its functional requirements. Advantages include improved code readability and reduced complexity to improve the maintainability of the source code, as well as a more expressive internal architecture or object model to improve extensibility.

Bij elke refactoring wordt een deel van de technische schuld afgelost, met als gevolg dat de code beter te onderhouden en uit te breiden is.

Om te kunnen refactoren is wel kennis nodig van de belangrijkste ontwerpprincipes. Deze principes kunnen worden samengevat met het acroniem ‘SOLID’. Het gaat om de volgende principes:

· Single Responsibility Principle (SRP)
· Open-Closed Principle (OCD)
· Liskov Substitution Principle (LSP)
· Interface Segregation Principle (ISP)
· Dependency Inversion Principle (DIP)

Deze principes zijn er voornamelijk op gericht om de afhankelijkheid tussen componenten te verkleinen (low coupling) en daarbij componenten te gebruiken die een goed afgebakend doel dienen (high cohesion).

Een ander belangrijk principe is ‘DRY’: Don’t Repeat Yourself.

Verstandige toepassing van deze principes leidt tot uitbreidbare, onderhoudbare en herbruikbare code.

Helaas zijn hier weer mitsen en maren bij te bedenken. Zo kost het maken van generieke en herbruikbare componenten erg veel inspanning. Het is bijvoorbeeld niet nodig of nuttig om een component dat één keer wordt gebruikt, herbruikbaar te maken. Zeker omdat herbruikbaarheid pas getoetst kan worden na veelvuldige toepassing. (Robert Glass’ Rules of Three).

Ook bestaat het gevaar van ‘teveel’ architectuur door het onnadenkend overal toepassen van deze principes. Dit zorgt dan juist weer voor slecht te begrijpen code die daardoor juist niet goed te onderhouden is. Het is daarom altijd raadzaam om de volgende acroniemen achter de hand te houden:

· YAGNI (You Ain’t Gonna Need It)
· KISS (Keep It Simple Stupid)

Er bestaat geen standaardrecept waarmee alle technische schuld uitgebannen kan worden. Elke situatie is uniek en vraagt om een specifiek recept met kennis en ervaring als basis-ingrediënt.

Voorkom een crisis

Met een beetje inspanning en alertheid is technische schuld goed binnen de perken te houden en kan een lokale schuldencrisis voorkomen worden. Zowel softwareontwikkelaars als projectleiders moeten wel beseffen dat dit alleen kan wanneer er ruimte vrijgemaakt wordt in de planning om code reviews en refactorings te kunnen uitvoeren.

Verder lezen

Technical debt – Wikipedia
http://en.wikipedia.org/wiki/Technical_debt

Design Debt – Jim Shore
http://jamesshore.com/Articles/Business/Software Profitability Newsletter/Design Debt.html

Paying Down Your Technical Debt – Jeff Atwood
http://www.codinghorror.com/blog/2009/02/paying-down-your-technical-debt.html

Code smell – Wikipedia
http://en.wikipedia.org/wiki/Code_smell

Code refactoring – Wikipedia
http://en.wikipedia.org/wiki/Code_refactoring

SOLID – Wikipedia
http://en.wikipedia.org/wiki/SOLID_(object-oriented_design)

Geplaatst in Softwarekwaliteit | Tags: , , , , | Een reactie plaatsen

Apps voor Windows Phone

Door mij ontwikkelde apps voor Windows Phone:

Psalmboek.nl

Violin Tuner

Geplaatst in Windows Phone | Tags: , | Een reactie plaatsen

C# en C++: DLL’s met callback functionaliteit

In C# is het prima mogelijk om (unmanaged) C++ DLL’s te gebruiken.

Zolang er basistypen (bool, int, double) gebruikt worden in functieparameters en returntypes is er niet veel aan de hand.

Lastiger wordt het wanneer er complexere typen heen en weer moeten tussen managed (C#) code en unmanaged (C++) code. In dit artikeltje wordt bekeken op welke manier functie-pointers kunnen worden uitgewisseld.

Het scenario
Stel we hebben een DLL die een berekening uitvoert. Voor die berekening is het nodig om waardes op te zoeken in een omvangrijke database. Op voorhand is niet bekend om welke waardes het gaat.

Nu zijn er een aantal manieren denkbaar waarop de DLL aan zijn gegevens zou kunnen komen.

  • De DLL spreekt zelf de database aan;
  • De DLL krijgt de relevante tabellen door van de aanroepende partij;
  • De DLL laat het opzoeken over aan de aanroepende partij en gebruikt hiervoor een callback functie.

De laatste optie is handig als de aanroepende partij toch al gebruik maakt van de database.

Bovendien zorgt de keuze van een callback ook voor een betere ‘separation of concerns’. De DLL kan zich puur bezig houden met de berekening en hoeft niets te weten van een specifieke database. Als een DLL op deze manier gevrijwaard kan blijven van ‘oneigenlijke’ functionaliteit, is deze generieker toe te passen en neemt de herbruikbaarheid toe.

De implementatie
Uitganspunt is een C++ DLL met de volgende interface:

typedef double (*getHeight_t) (double lon, double lat);

extern “C”
{
_declspec(dllexport) double shortestGroundDistance(
         double lat1, double lon1,
         double lat2, double lon2,
         getHeight_t getHeight);
}

Deze DLL exporteert de functie shortestGroundDistance. Deze functie accepteert een functiepointer met type getHeight_t. getGeight_t is pointer naar een functie die doubles accepteert (lat en lon) en een double retourneert.
De implementatie van shortestGroundDistance zal de meegegeven getHeight functie aanroepen om daarmee de kortste afstand over de grond tussen twee punten mee te berekenen.

De functie shortestGroundDistance verwacht dus een functiepointer als argument. Een functiepointer in C++ is gelijkwaardig aan een delegate in C#. Een gewone (managed) delegate kan echter niet worden doorgegeven aan een DLL. We moeten gebruik maken van de attribuut ‘UnmanagedFunctionPointer’ om een delegate te krijgen die gelijk is aan een C++ function pointer.

Met deze kennis kan het C# gedeelte geimplementeerd worden:

class Program
{
   [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
   delegate double GetHeightFunction(double lat, double lon);
 
   [DllImport(“CalcDistance.dll”, 
    CallingConvention= CallingConvention.Cdecl)]
   static extern double shortestGroundDistance(
          double lat1, double lon1, double lat2, double lon2,
          GetHeightFunction getHeight);
 
   static double GetHeight(double lat, double lon)
   {
      // .. retrieve height at location lat, lon from a database
      // .. code omitted ..
      return height;
   }
 
   static void Main(string[] args)
   {
      // Testing the DLL. Note that the GetHeight
      // delegate is passed to the DLL.
      double dist = shortestGroundDistance(
          52.0, 5.0, 52.0, 6.0, GetHeight);
 
      Console.WriteLine(string.Format(“Distance = {0}”, dist);
   }
}

Let op de keuze van de juiste ‘calling convention’ bij zowel de delegate als de DLL functie.

Geplaatst in C#, C++ | Tags: | Een reactie plaatsen