Ozzie PHP op 16/12/2022 17:22:09
Je kunt 2 kanten op. Zoals Ad Fundum zegt, kun je een lege array returnen. Wat mij betreft een mooie en prima oplossing. Je kunt een controle doen met empty() om te zien of er iets in de array zit en vervolgens wel of geen verdere actie ondernemen. Je geeft ook altijd hetzelfde type (in dit voorbeeld een array) terug, en ook dat is netjes en zorgt voor een voorspelbaar resultaat.
Ad zegt ook dat hij hetzelfde doet met strings. Als er geen resultaat is, wordt er een lege string geretourneerd. Naar het eerste voorbeeld:
<?php
public function getFoo(): string
{
if ($some_condition === true) {
return $this->foo;
} else {
return '';
}
}
?>
Dat doet hij bijvoorbeeld bij een geboortedatum:
Ad Fundum op 14/12/2022 19:52:28
- geboortedatum
Een string is een string, lege strings betekent geen geboortedatum. Wederom geen NULL-waarden.
Laten we dat eens uitwerken, inclusief low-budget unittest. ;)
<?php
declare(strict_types=1);
class Person
{
public \DateTimeImmutable $dateOfBirth;
public function getDateOfBirth(): string
{
if (isset($this->dateOfBirth)) {
return $this->dateOfBirth->format('Y-m-d');
} else {
return '';
}
}
}
//> string(0) ""
$person = new Person();
var_dump($person->getDateOfBirth());
//> string(10) "1980-04-01"
$person->dateOfBirth = new \DateTimeImmutable('1 april 1980');
var_dump($person->getDateOfBirth());
?>
Dat werkt zoals verwacht: als er geen geboortedatum is, komt er een lege string uit. Het
lijkt bovendien consistent, want een lege array en een lege string zijn beide
empty().
Maar nu introduceren we twee complicaties, die tonen waarom en wanneer deze systematiek in duigen valt:
(a) Stel, we moeten een
getAge() toevoegen die de leeftijd in jaren als integer retourneert. Wat retourneer je dan als leeftijd onbekend is? Een lege integer? Of 0 want
empty(0) is
true?
(b) Stel, we willen
getDateOfBirth() herschrijven zodat de methode geen string met de geboortedatum meer retourneert, maar een
value object, bijvoorbeeld een
DateTime- of
DateTimeImmutable-object. Wat retourneer je dan bij een onbekende geboortedatum? Een leeg object?
Met een
null in de returns kunnen we het geheel leesbaar, consistent én voorspelbaar houden. En ja, dat is slechts een compromis, maar wel een heel gebruikelijk compromis.
(Terzijde: ik begrijp heel goed waarom Ad een lege string gebruikt voor een ontbrekende geboortedatum. In een grootschalige database wil je namelijk zo min mogelijk nullable kolommen hebben. Bovendien is er een groot verschil tussen "we weten de waarde niet" en "we weten dat de waarde er niet is". Ik wil dat echter lager in het systeem dicht bij de datalaag houden, bijvoorbeeld in een mapper of data access object, en liever niet meeslepen als lege strings in hogere-orde classes in een applicatielaag.)
Ozzie PHP op 16/12/2022 21:36:39
Ik heb geen IT-opleiding gevolgd, maar heb van geschoolde programmeurs wel eens vernomen dat het niet 'mooi' is om op meerdere plekken binnen een functie iets te returnen. Daar zit wel wat in denk ik.
Er zijn inderdaad programmeurs die dat doen, maar die schrijven vanwege die regel soms overbodig complexe én, niet onbelangrijker, inefficiënte code.
Neem dit voorbeeld uit OpenCart. Deze methode controleert of er producten met downloads zijn:
<?php
public function hasDownload()
{
$download = false;
foreach ($this->getProducts() as $product) {
if ($product['download']) {
$download = true;
break;
}
}
return $download;
}
?>
Lelijk. Hier wordt de dubbele vuistregel gevolgd: er is maar één
return en die
return volgt aan het einde na alle operaties. Maar het gevolg daarvan is dat er nodeloos een hulpvariabele wordt geïntroduceerd. En dat er een
break nodig is om uit een loop te ontsnappen.
Het kan ook gewoon zo:
<?php
public function hasDownload(): bool
{
foreach ($this->getProducts() as $product) {
if ($product['download']) {
return true;
}
}
return false;
}
?>
Of als je niet vies bent van een eenregelige
if zonder
else:
<?php
public function hasDownload(): bool
{
foreach ($this->getProducts() as $product) {
if ($product['download']) return true;
}
return false;
}
?>
Twee returns, maar je kunt veel beter zien wat de methode doet.
En dat brengt ons via een omweg terug bij het begin van dit topic. De
return types zijn onder andere ingevoerd omdat je altijd aan de
signature van een methode kunt zien wat eruit komt. Je hoeft helemaal niet in de methode te zoeken naar de
return. En dus is de regel dat die
return altijd voorspelbaar aan het einde staat overbodig geworden.