Per prima cosa, c'è il rischio di
Masking errors without taking meaningful action ?
Probabilmente no, dal momento che l'eccezione viene passata al chiamante (quindi il chiamante è responsabile di un'azione significativa), oppure viene registrata l'eccezione, che è probabilmente l'azione desiderata qui.
C'è un segno di
use of error handling for control flow ?
La risposta è IMHO anche no, questo sarebbe solo il caso se DoSomethingDangerous
lancia un'eccezione per una situazione non eccezionale, ma non c'è alcuna indicazione per questo nel codice presentato nella domanda.
Quindi, se questi due punti non rappresentano il problema, c'è un altro problema?
Suppongo che l'intenzione dell'autore fosse di rendere il codice riutilizzabile in due contesti diversi:
-
in cui le eccezioni sono autorizzate a comparire nel chiamante, che ad esempio potrebbe comportare la visualizzazione di una finestra di messaggio nell'interfaccia utente, che interrompe qualsiasi elaborazione esterna
-
quello in cui le eccezioni non devono interferire con il controllo esterno del flusso (ad esempio, in un'elaborazione batch asincrona)
Finora questo potrebbe essere un requisito ragionevole. Ma è possibile migliorare qualcosa? Ovviamente, la cosa ovvia qui è evitare la duplicazione del codice (già suggerita da @ErikLippert in un commento). Se hai bisogno di mantenere la semantica, dovrebbe apparire così:
public void DoSomething(bool safe=true)
{
try
{
DoSomethingDangerous();
}
catch(Exception ex)
{
if(!safe)
throw ex;
// Log error & carry on
}
}
L'altro problema di manutenibilità che vedo qui è che la gestione delle eccezioni ora avviene a due diversi livelli di astrazione, cosa che eviterei ogni volta che sia possibile, poiché può facilmente diventare soggetta a errori. Risolvere ciò, tuttavia, richiede di rifattorizzare il codice prima come suggerito da @DavidArno, quindi di rifattorizzare il codice esterno per chiamare DoSomethingSafely
solo nel contesto di elaborazione batch e DoSomethingDangerous
solo in "modalità UI".
Ciò tuttavia può comportare un sacco di refactoring. Se ci sono diversi livelli intermedi tra il "controllore batch" o "controller UI" esterno e il metodo mostrato, può diventare davvero difficile evitare la duplicazione del codice aggiuntiva all'interno del livello chiamante. Il motivo è che probabilmente sarai costretto ad implementare due diverse versioni del codice chiamante, una per ogni contesto. Una di queste versioni utilizzerà DoSomethingSafely
in un modo specifico e una che utilizza DoSomethingDangerous
in modo simile, ma con alcune differenze in termini di gestione degli errori e forse di altro contesto. Quindi, per prendere una decisione se questo è l'approccio giusto, è necessario prestare attenzione al codice chiamante.
Infine, ecco un altro approccio che utilizza l'iniezione delle dipendenze. Lo stesso "Logger" potrebbe essere un oggetto iniettato (forse non dovrebbe chiamarsi "Logger", ma qualcosa come "ExceptionProcessor"):
class MyClass
{
IExceptionProcesser exProc; // gets injected through the constructor
public void DoSomething()
{
try
{
DoSomethingDangerous();
}
catch(Exception ex)
{
// Log error, or rethrow
exProc.Process(ex);
}
}
}
In questo modo, la parametrizzazione del comportamento dell'eccezione non viene più eseguita da un parametro booleano alla funzione, ma passando un altro programma di registrazione nella classe. Ciò si adatta in genere meglio alla necessità di far sì che la funzione si comporti diversamente in contesti diversi e evita anche l'uso di un argomento flag come parametro di funzione.