First, is there a risk of
Masking errors without taking meaningful action ?
Probably not, since either the exception is passed to the caller (so the caller is responsible for a meaningful action), or the exception is logged, which is probably the intended action here.
Is there a sign of
use of error handling for control flow ?
The answer is IMHO also no, this would only be the case if DoSomethingDangerous
throws an exception for a non-exceptional situation, but there is no indication for this in the code presented in the question.
So, if these two points are not the problem, is there another issue?
I guess the intention of the author was to make the code reusable in two different contexts:
one where exceptions are allowed to pop-up to the caller, which for example could result in displaying a message box in the UI, which disrupts any outer processing
one where exceptions shall not disrupt the outer control of flow (for example, in an asynchronous batch processing)
So far this could be a reasonable requirement. But is it possible to improve something? Of course, the obvious thing here is to avoid the code duplication (already suggested by @ErikLippert in a comment). If you need to keep the semantics, it should look like this:
public void DoSomething(bool safe=true)
{
try
{
DoSomethingDangerous();
}
catch(Exception ex)
{
if(!safe)
throw ex;
// Log error & carry on
}
}
The other maintainability problem I see here is that the exception handling now happens at two different levels of abstraction, that is something I would avoid whenever possible, since it can become easily error prone. Solving this, however, requires to refactor the code first the way suggested by @DavidArno, and then refactor to outer code to call DoSomethingSafely
only in the batch processing context and DoSomethingDangerous
only in "UI mode".
This however, can result in a lot of refactoring. If there are several intermediate layers between the outer "batch controller" or "UI controller" and the shown method, it can become really hard to avoid additional code duplication inside the calling layer. The reason for this is, you will probably be forced to implement two different versions of the calling code, one for each context. One of these versions will use DoSomethingSafely
in a specific way, and one using DoSomethingDangerous
in a similar way, but with some differences in regards to error handling and maybe the other context. So to make a decision if that is the right approach, you need to look careful at the calling code.
Finally, here is another approach using Dependency Injection. The "Logger" itself could be an injected object (maybe it should not called "Logger" then, but something like "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);
}
}
}
That way, the parametrization of the exception behaviour is not done by a boolean parameter to the function any more, but by passing a different logger into the class. That fits typically better to the requirement of making the function behave differently in different contexts, and it avoids the usage of a flag argument as a function parameter as well.