La gestione degli errori è certamente importante quando sviluppiamo, ma spesso essa sembra quasi "dominare" il nostro codice, tanto da offuscarlo e renderlo illegibile.
L'utilizzo delle eccezioni al posto dei codici d'errore migliora di gran lunga la leggibilità del nostro codice.
Utilizzo dei codici d'errore:
if ($this->userRepository->delete($user) === UserRecord::STATUS_OK) {
if ($this->mailer->send($email) === MailerRecord::STATUS_OK) {
print_r('Email inviata con successo.');
} else {
print_r('Non è stato possibile inviare l`email.');
}
} else {
print_r('Non è stato possibile eliminare l`utente.');
}
Utilizzo delle eccezioni:
try {
$this->userRepository->delete($user);
$this->mailer->send($this->email);
} catch(Exception $e) {
print_r($e->getMessage());
}
Quando definiamo una classe per un'eccezione, dobbiamo pensare al modo in cui essa può essere cachata e utilizzata. Spesso un'unica classe per eccezioni è sufficiente per distinguere una determinata area/situazione nel codice.
Utilizzo superfluo delle classi per le eccezioni:
if (empty($product->getName())) {
throw new InvalidProductNameException();
}
if ($product->getPrice() < 0) {
throw new InvalidProductPriceException();
}
if (empty($product->getUrlKey())) {
throw new InvalidProductUrlKeyException();
}
Utilizzo corretto delle classi per le eccezioni:
if ($product->isValid() === false) {
throw new InvalidProductException($product);
}
Solitamente, quando si verifica un'eccezione, la gestiamo loggando l'errore, mostrando eventualmente un messaggio all'utente e dopodichè proseguiamo con la normale esecuzione del codice. Tuttavia ci sono casi in cui vogliamo gestire l'eccezione in maniera "speciale":
try {
$expenses = $expenseReportDAO->getMeals($employee->getId());
$total += $expenses->getTotal();
} catch (MealExpensesNotFound $e) {
$total += $this->getMealPerDiem();
}
class ExpenseReportDAO {
public function getMeals(int $employeeId): MealExpenses {
// recupera i pasti consumati tramite l'id del dipendente
if (empty($meals)) {
throw new MealExpensesNotFound();
}
}
}
In questo caso, se il pasto viene consumato, diviene parte del totale, altrimenti il dipendente riceve una somma per tale giorno. Questo codice diventa più semplice da leggere, se gestiamo il caso speciale presente nel blocco catch
attraverso una classe a parte (SPECIAL CASE PATTERN).
$expenses = $expenseReportDAO->getMeals($employee->getId());
$total += $expenses->getTotal();
class ExpenseReportDAO {
public function getMeals(int $employeeId): MealExpenses {
// recupera i pasti consumati tramite l'id del dipendente
if (empty($meals)) {
return new PerDiemMealExpenses($employee);
}
}
}
class PerDiemMealExpenses implements MealExpenses {
public function getTotal(): float {
// restituisce il totale per giorno
}
}
Non restituite null
se qualcosa va storto, piuttosto lanciate un'eccezione o restituite un oggetto Special Case come mostrato nell'esempio precedente. L'utilizzo di null
è sconsigliato perché aumenta il carico di lavoro del chiamante che deve verificare se il valore restituito è null
o meno, portando di conseguenza ad avere condizioni if
annidate.
Prima del refactoring:
$employees = $employeeRepository->getEmployees();
$totalPay = 0;
if ($employees !== null) {
foreach ($employees as $employee) {
$totalPay += $employee->getPay();
}
}
Dopo il refactoring:
$employees = $employeeRepository->getEmployees();
$totalPay = 0;
foreach ($employees as $employee) {
$totalPay += $employee->getPay();
}
class EmployeeRepository {
public function getEmployees() {
// recupera la lista dei dipendenti
if (empty($employees)) {
return [];
}
}
}
E' sconsigliato passare null
come argomento (salvo casi particolari), perché questo obbliga al metodo chiamato di verificare il caso in cui l'argomento è null
e il caso in cui non lo è.