Volgens mij kun je inderdaad wel zeggen dat het aantal controles van condities met controlestructuren zoals
if en
switch afneemt wanneer je die condities formaliseert in abstracties. Klinkt misschien erg theoretisch, maar dat valt bij nader inzien wel mee als we in de praktijk duiken.
Terug naar een eerder voorbeeld. Stel, we willen het geslacht van een persoon vastleggen. Daar blijkt een norm voor te bestaan:
ISO/IEC 5218 Information technology - Codes for the representation of human sexes.
We hebben alleen een
tiny int nodig:
<?php
declare(strict_types=1);
class Person
{
public int $gender = 0;
}
?>
Prachtig! We hebben een compacte class die zo min mogelijk geheugen gebruikt, zonder naïeve getters en setters. En de marketingafdeling kan trots zeggen dat onze software aan internationale standaardaarden voldoet.
Maar is deze code wel duidelijk? Kan een collega die deze class opent zien wat hij of zij met
$gender kan doen? Snapt iedereen wel wat die 0 is?
Misschien moeten we onze ontwerpbeslissingen documenteren:
<?php
declare(strict_types=1);
/**
* @sse https://www.iso.org/standard/36266.html ISO/IEC 5218:2004
* @see https://en.wikipedia.org/wiki/ISO/IEC_5218 ISO/IEC 5218
*/
class Person
{
/**
* @var int $gender
* ISO/IEC 5218 compliant gender as an integer. MUST be 0 (Not known),
* 1 (Male), 2 (Female), or 9 (Not applicable). Defaults to 0.
*/
public int $gender = 0;
}
?>
Beter, maar we kunnen met documentatie alleen niet voorkomen dat de class verkeerd gebruikt wordt. De oude standaardoplossing in PHP is dan de controle over een property delegeren aan een setter.
In modern PHP zie de oude oplossing er bijvoorbeeld zo uit:
<?php
declare(strict_types=1);
/**
* @see https://www.iso.org/standard/36266.html ISO/IEC 5218:2004
* @see https://en.wikipedia.org/wiki/ISO/IEC_5218 ISO/IEC 5218
*/
class Person
{
/**
* @var int $gender
* ISO/IEC 5218 compliant gender as an integer. MUST be 0 (Not known),
* 1 (Male), 2 (Female), or 9 (Not applicable). Defaults to 0.
*/
protected int $gender = 0;
public function setGender(int $gender): void
{
if ($gender === 0 || $gender === 1 || $gender === 2 || $gender === 9) {
$this->gender = $gender;
} else {
throw new \ValueError('Invalid gender');
}
}
public function getGender(): int
{
return $this->gender;
}
}
?>
Deze PHP-code
lijkt op het eerste gezicht acceptabel. Zo ziet PHP er wel vaker uit, dus je leest er gedachtenloos overheen. En de code werkt voorlopig goed, dus vooral niet aankomen.
If it ain't broke, don't fix it!
De code heeft echter een aantal problematische afhankelijkheden (
dependencies):
1. De code is afhankelijk van externe standaarden. Hier is het weliswaar een open standaard en een internationaal erkende standaard, maar dat is en blijft een externe afhankelijkheid die door anderen wordt bepaald. Als de standaard verandert, hebben we een probleem.
2. We hebben de afhankelijkheid deels moeten uitschrijven in documentatie, anders is het ontwerp niet duidelijk. Zo ontstaat een tweede afhankelijkheid: een goed begrip en correct gebruik van de code is afhankelijk van de documentatie. En we weten allemaal dat niemand documentatie goed of helemaal leest.
3. Om verkeerd gebruik van de property
$gender tegen te gaan, hebben we een setter moeten toevoegen. En omdat de property niet meer
public is ook nog een getter. Dat maakt de code ongeveer twee keer zo langzaam: test het maar. En het maakt onderhoud van de code duurder: je hebt niet langer slechts één property, maar ook nog twee methoden. We kunnen dit eventueel oplossen door een factory of builder verantwoordelijk te maken voor het setten ven properties, maar dat introduceert dan weer een andere afhankelijkheid.
4. We kunnen bij wijzigingen niet slechts de code aanpassen, maar moeten ook altijd de documentatie bijwerken. De ervaring leert dat dat laatste vaak niet gebeurt. En daarmee krijgen we een ongewenst maar bekend fenomeen:
Code comments are useful lies.
Afhankelijkheden maken code slecht leesbaar, traag, foutgevoelig, moeilijk te veranderen, duur in onderhoud, enzovoort.
We proberen afhankelijkheden daarom zoveel mogelijk te beperken, te internaliseren en expliciet te maken. Dat kan, bijvoorbeeld, met een
enum:
<?php
declare(strict_types=1);
enum Gender: int
{
case NotKnown = 0;
case Male = 1;
case Female = 2;
case NotApplicable = 9;
}
class Person
{
public Gender $gender = Gender::NotKnown;
}
?>
Niet zaligmakend, wel véél beter.