6

I was watching the following video by Vladimir Khorikov, which recommends to "Refactoring Away from Exceptions" pluralsight.com - Applying Functional Principles in C# - Refactoring Away from Exceptions and instead using a Result object. You can also find a blog about it here: enterprisecraftsmanship.com - Functional C#: Handling failures, input errors

To summarize it, the recommendation is to prefer returning a result object then throwing an exception. Exceptions should be used to signalize a bug only. The arguments for this approach are the following:

  • Methods which throws exceptions are not "honest". You can't recognize if a method is expected to fail or not, by looking at its signature.
  • Exception handling adds a lot of boiler plate code.
  • When exceptions are used to control the flow, it has a "goto" semantic, where you can jump to specific line of code.

On the other hand return values can be ignored (at least in C#), which exceptions can not.

Is it a good idea to refactor a existing enterprise application in this direction? Or is a less radical approach the better one? (I belive that it make sense for sure to avoid Vexing exceptions by using return types for method like ValidateUserInput(string input))

Note that Are error variables an anti-pattern or good design? is a similar question. The difference is, that I am not talking about "Error by magic values" (returning a error code or even worse null) which is clearly an anti pattern. I am talking about the pattern presented by Vladimir Khorikov, which doesn't have the same drawbacks like just returning a primitive error code. (For example: Result objects have a error message, like exceptions does)

Jonas Benz
  • 475
  • 1
  • 5
  • 8
  • "*On the other hand return values can be ignored (at least in C#), which exceptions can not.*" `try { DoSomething(); } catch {}`. One exception completely ignored... ;) – David Arno Jul 17 '19 at 08:11
  • @DavidArno you are right, but you have to explicitly ignore Exceptions. Return values can be ignored implicitly. – Jonas Benz Jul 17 '19 at 08:14
  • 1
    In my view, exceptions are overused by most developers and the try pattern, result objects etc are a better solution in many cases. But this is a deeply controversial view as it challenges the habitual practice of those developers. So if you do adopt result objects, expect many stuck-in-the-mud's to criticise your decision. – David Arno Jul 17 '19 at 08:15
  • 2
    Also, to really make good use of result objects, they ideally should be discriminated unions (DU's) that can then be pattern matched based on the result. To do that in C#, you'll have to wait until C# 9 is released as DU's are loosely planned for that version. – David Arno Jul 17 '19 at 08:16
  • 1
    "*Return values can be ignored implicitly.*". True, though you can use a Roslyn analyser extension to the compiler to warn/error on such ignoring of return values and to mandate they be explicitly ignored through discards. – David Arno Jul 17 '19 at 08:18
  • @gnat Thanks for the possible duplicate. I tried to explain, why I think it is not the same in the question itself. – Jonas Benz Jul 17 '19 at 08:31
  • @DavidArno Excellent point, I can't wait for real DU's to come to C#, having gotten a taste of them in F#. – Graham Jul 17 '19 at 12:46
  • 1
    @JonasBenz As far as refactoring goes, I wouldn't alter an old code base for any kind of stylistic change (like this). Just try out Result on your next project. – Graham Jul 17 '19 at 12:50
  • @JonasBenz: Do you mean "*than* exceptions? – JacquesB Jul 17 '19 at 13:25
  • @JacquesB yes, thanks and sorry for my bad English. I fixed it. – Jonas Benz Jul 17 '19 at 13:30
  • 1
    `Methods which throws exceptions are not "honest". You can't recognize if a method is expected to fail or not, by looking at its signature.` -- That's why Java has checked exceptions. – Robert Harvey Jul 17 '19 at 14:19
  • `Exception handling adds a lot of boiler plate code.` -- So do a lot of other coding "best practices." – Robert Harvey Jul 17 '19 at 14:19
  • 1
    `When exceptions are used to control the flow, it has a "goto" semantic, where you can jump to specific line of code.` -- Don't use exceptions for flow control. – Robert Harvey Jul 17 '19 at 14:19
  • 1
    Finally what does "is it a good idea" mean? Good idea to whom? Read [If there are two ways of approaching a task, how should one choose between them?](https://softwareengineering.stackexchange.com/a/351357/1204) – Robert Harvey Jul 17 '19 at 14:32

3 Answers3

7

There are use cases for both exceptions and result objects, so "cleanliness" of the code is dependent upon the use case. It all depends on how critical it is to stop the execution of the program.

Exceptions are the hand grenades of programming. Pull the pin and throw it. The only thing to keep your application from blowing sky high is to catch it and handle it (and we'll imagine for a moment you can put the pin back in to keep it from exploding). Exceptions communicate that you have encountered an error condition such that you absolutely cannot continue operation, and must halt immediately.

Result objects communicate something differently. They say "one or more things went wrong, and someone needs to correct them before continuing." Result objects can also say "Things went fine, but a few little warnings you should know about are ..." The intent of a result object is track errors and warnings so they can be reported back to an end user or some other form of output, like a log file. An operation that returns a "results" object is usually fine with failure, because some other process can recover from the failure.

Result objects provide feedback so another process or person can correct errors, and attempt the operation again. Exceptions are meant to halt the program execution before real damage is done to data.

The exception to using exceptions would be if damage (real or virtual) would be done by allowing the exception to propagate unchecked. For instance, if crashing the application would cause a loss of data then you might be better off catching the exception, and returning a results object. More importantly if a crashed program would cause physical, psychological or financial harm to people or animals, then you definitely do not want to leave exceptions unchecked. Again, a "results" object or "error code" is the right way to go, so the error can be corrected, and the operation attempted again.

Greg Burghardt
  • 34,276
  • 8
  • 63
  • 114
7

the recommendation is to prefer returning a result object than throwing an exception

This is bad advice IMHO, because they should be used in different circumstances. Using a result object when an exception is most appropriate is bad.

If ValidateUserInput is not able to perform its task because (for example) the process is out of memory, then throwing an exception is the right thing to do. Returning a value that indicates that the input is either valid or invalid would obviously be wrong since we don't know that. Calling "out of memory" a validation error would be misleading, since it is not an error in the input. Creating three cases: valid, invalid and out-of-memory would be bizarre.

Exceptions should be used to signal when a method is not able to complete its task. Result objects is for when a method is able to complete its task, but the result can falls into different cases.

For example a Search() method might return an option type with a case when a match is found and another if no match is found. But it should throw an exception if the database is disconnected, there was a timeout or anything else which means the search couldn't be performed.


So what about the argument in the linked blog post, which say you should always use result-objects instead of exceptions?

The post suffers IMHO from a a symptom I would call language envy. It tries to shoehorn patterns from a different language without considering the different context of C#. Typically you end up with worst of both languages. Some points to consider:

  1. .net still have exceptions. Whether you like it or not, the framework and 3rd party libraries will still throw exceptions.
  2. .net exception are unchecked which means you don't know at compile time exactly which exceptions a method may throw.

This means you can't really get the purported advantages by turning all errors into a result-object. For example:

Methods which throws exceptions are not "honest". You can't recognize if a method is expected to fail or not, by looking at its signature.

If you think this is an issue, it is not going away. The framework and 3'rd party libraries still don't advertise what exceptions they may throw, which means your code could also pass on an unadvertised exception.

If you consider this a problem, consider using a language like Java instead, which does have checked exceptions - or a language without exceptions.

Exception handling adds a lot of boiler plate code.

No more that encoding errors in result values! Result values require boilerplate to wrap, unwrap and to pass up the up the call stack - something which happens automatically (no boilerplate) for exceptions.

In a language like Rust with uses result-objects throughout, it was quickly realized how much boilerplate it required, so a shortcut syntax (first the try macro, then the ? operator) was introduced to alleviate that. Haskell has the do-syntax. C# does not have anything similar, and doesn't have macros, so you have to suffer the boilerplate.

The article ends up rewriting everything into non-idiomatic C# with a chain of OnSuccess calls which end up being much more complex than the initial code it was supposed to simplify! (The article cheats a bit by have some needless redundancy in the "before" code which is magically gone in the "after" example.)

More importantly, the initial problem which the code attempted to solve, the transaction rollback, could be more elegantly solved in an idiomatic way by using a using-block. The use of RollbackLastTransaction seem like is could be vulnerable to race conditions.

When exceptions are used to control the flow, it has a "goto" semantic, where you can jump to specific line of code.

This is a red herring because it is recognized as an anti-pattern in C# to use exceptions for control flow. That is not their purpose. But in any case, comparing exceptions to GOTO is a misunderstanding of why GOTO's are considered bad in the first place. If an exception is like a GOTO, then so is a return.

Bottom line: Choose the language which you prefer or which is most appropriate for the task. Then understand the design principles and idioms of the chosen language, and use them to your advantage instead of fighting them,

JacquesB
  • 57,310
  • 21
  • 127
  • 176
3

Methods which throws exceptions are not "honest". You can't recognize if a method is expected to fail or not, by looking at its signature.

Compared to [older versions of] Java this was argued to be one of .Net's biggest failings. Java laces Exceptions right into the Method signature; .Net doesn't.

Exception handling adds a lot of boiler plate code.

Only when it is done wrongly.

You add an Exception Handler where you can do something useful with that Exception, not just because it's a "good idea" not to "leak" Exceptions. (This sort of boiler-plating is a very Bad Idea).

  • When exceptions are used to control the flow, it has a "goto" semantic, where you can jump to specific line of code.

"Goto semantic"? OK.

"Specific line of code" - not so much.

You should throw Exceptions not knowing where or even if they will be caught. if you're using them to break out of loops and such like, then you're misusing them.

Exceptions are the coding equivalent of "I give up!" and hoping that someone else will step in to help.

If you have a language that doesn't expect to throw Exceptions, like C, then returning Results is probably OK. But .Net and Java expect you to throw Exceptions and, if you try to force a Results strategy onto them, you will lose because, eventually, they will throw an Exception by themselves, blowing your carefully constructed call and return structures apart.

Phill W.
  • 11,891
  • 4
  • 21
  • 36
  • 1
    It's funny about the difference in perspectives. The .NET folks thought checked exceptions were one of Java's biggest failures, and decided exceptions were best kept in documentation. Like any sort of feature, it all depends on the use case. I've even wished we could have it both ways in C#. Sometimes I wish the compiler could tell me I'm missing a critical error condition, and other times I'm frustrated by checked exceptions in Java requiring try-catch blocks when I don't have a need to handle an exception. – Greg Burghardt Jul 17 '19 at 12:39
  • 3
    @Greg: "I'm frustrated by checked exceptions in Java requiring try-catch blocks when I don't have a need to handle an exception". It's been a while, but I thought Java's model was "Handle it or Pass it along". You catch the Exception and deal with it (so your callers don't know /anything/ about it) or you tell those calling methods that "you" might throw (or, at least, "emit") that Exception. There shouldn't be any additional catch clauses (unless you're wrapping/re-throwing the Exception at a process boundary); just let the Exception cascade. – Phill W. Jul 17 '19 at 13:25
  • That is true, but it is annoying to constantly add this fact to the method signature. And to be honest, it's not a big deal, it's just one of those little annoying things. – Greg Burghardt Jul 17 '19 at 14:19