Don't throw exceptions in C#. Do this instead

Поделиться
HTML-код
  • Опубликовано: 24 ноя 2024

Комментарии • 637

  • @Tal__Shachar
    @Tal__Shachar 2 года назад +348

    I don't throw my exceptions. I instead keep them for myself in a safe place when they can feel that they are wanted, and feel warm and cozy.

    • @nickchapsas
      @nickchapsas  2 года назад +189

      You sure know how to make them feel exceptional

    • @LostDotInUniverse
      @LostDotInUniverse 2 года назад +1

      Lol

    • @diligencehumility6971
      @diligencehumility6971 2 года назад +9

      If you don't throw exceptions, your boss think you are better, that's why I keep them for myself

    • @Donder1337
      @Donder1337 2 года назад +2

      @@nickchapsas im crying here 🤣

    • @quickben3672
      @quickben3672 Год назад +1

      Lame, I just catch all exceptions and do nothing with them, silent fail is the way to go :p

  • @randomcodenz
    @randomcodenz 2 года назад +482

    I've tried this approach in a production system. The code rapidly becomes unwieldy because you have to check the result at every exit and if the return type of the calling method is different you have to transform that result as well. The deeper the call stack, the more overhead / boiler plate / noise you add to the code purely to avoid throwing an exception. I don't think the maintenance overhead is worth the trade-off unless you desperately need the performance. I went with throwing useful exceptions and mapping them to problem details in middleware. It was easy to understand and follow and even easier to use and maintain the surrounding code in the call stack.

    • @treebro1550
      @treebro1550 2 года назад +19

      We are just now running into that on the project I am working on, we simply have to check every single time if something succeeded.. I think its a good way of stopping the exception bubble up, but yeah we are reverting to letting the parent handle the exception for external calls, db operations, etc.

    • @maybe4900
      @maybe4900 2 года назад +20

      > The code rapidly becomes unwieldy
      Bcs we have Results and have no (>>=). Don't use monads in lang that can't handle it.

    • @endofunk2174
      @endofunk2174 2 года назад +23

      @@maybe4900 Nope.
      Linq's SelectMany is (>>=) which FYI is also known as "bind" or "flatmap" in other languages.
      SelectMany is a monadic operation, whereas Select is a functor operation.
      C# btw copes perfectly fine with functional algebras like functor, monad, applicative functor, etc. Microsoft provides a limited implementation for primarily Linq... for a more comprehensive implementation, consider using a library like Language-ext; or build your own.
      Language-ext supports monadic operations using both dot chained method calls using either Bind or SelectMany; alternatively the library also includes implementations for use of monadic operations using C# Linq query's syntax instead.
      Whilst there are limits to what can be functionally achieved in C# compared to Haskell; none of those limits will typically be a problem in day to day code; and most certainly not for the example described by @randomcodenz.

    • @JS-1962
      @JS-1962 2 года назад +3

      Agree here also, had the same issue also doing an hot fix. Changing the return type would be a massive change in a hot fix. Also need to propagate the exception everywhere across api and ui

    • @PietjePotloodzzzzzz
      @PietjePotloodzzzzzz 2 года назад +4

      Exactly the point I would give. In a system where performance isn't a big issue, you should prefer the solution with throwing custom exceptions instead of the notification pattern style; it will slow you down regarding maintenance because using functions are not always obvious/visible like custom exceptions do, and I like the always valid pattern where the state of an object is valid in any case.

  • @jackkendall6420
    @jackkendall6420 2 года назад +428

    I want a two hour video of Nick explaining what monads are, gradually becoming more and more wild-eyed and frantic as it progresses

    • @LuigiTrabacchin
      @LuigiTrabacchin 2 года назад +4

      That would be intresting

    • @dcuccia
      @dcuccia 2 года назад +11

      As long as it ends with "...and that's it" I'll feel good about it.

    • @orterves
      @orterves 2 года назад +34

      A monad is just a monoid in the category of endofunctors, what's the problem?

    • @dcuccia
      @dcuccia 2 года назад +2

      @@orterves I love Scott Wlaschin

    • @kj2w
      @kj2w 2 года назад

      Are you getting visions of Charlie Day from Its Always Sunny in Philadelphia (IASIP) connecting string to images vibe? Cause I am :D

  • @heinzk023
    @heinzk023 Год назад +53

    This brings us back to pre-exception times where error conditions had to be handled on every level of the call hierarchy. It reminds me of my early "C" or "BASIC" times where there was an "if( result < 0) statement after every function call.

    • @wlbmonizuka
      @wlbmonizuka Год назад

      It's only because that legacy code didn't follow DI practices.

    • @raphaelbatel
      @raphaelbatel Год назад +7

      No. You can still throw exceptions for truly exceptional situations, but avoid doing this for control flow.

    • @acasualviewer5861
      @acasualviewer5861 Год назад +3

      @@raphaelbatel the whole advantage of exceptions is that it modifies your control flow so your code isn't polluted with ifs or in this case a bunch of extra lambdas that just add boilerplate (especially in languages with clumsy syntax like C#)

  • @Rick-mf3gh
    @Rick-mf3gh Год назад +20

    One complaint (of several) that I have about this technique is that you still need to handle thrown exceptions. Your code - and 3rd party libraries - could still throw exceptions. Therefore you have to implement two different failure handling techniques. In Nick's example, you would still need to (e.g.) put a try/catch around the .Match() code.

  • @BrendonParker
    @BrendonParker 2 года назад +251

    Am I the only one that finds the exception based approach easier to read and understand what is happening? The idea that “anything at any point can throw an exception” seems desirable to me, for known business exceptions like “ValidationException”. Once the call stack gets pretty deep, with nested object calls, it seems like you’ll have a lot of code needing to check the Result.Success to determine if it should move forward.
    Wondering what people think about that given perf not being that important.

    • @michawhite7613
      @michawhite7613 2 года назад +16

      Something I like to do is bottle up all my errors until I get back to the controller, and then handle the possible errors. That way, all of my error handling is in one nice spot. In Rust, the ? operator does this for you.

    • @Osbornesupremacy
      @Osbornesupremacy 2 года назад +9

      It's a tradeoff. One could argue that GOTO has advantages as well. Functional programming seems to be about giving up some of the stuff that OOP allows, but experience has shown to be problematic (state mutation, side effects). I currently don't do functional programming, but I understand the problems that it tries to address.

    • @andreikniazev9407
      @andreikniazev9407 2 года назад +18

      Also it easier for monitoring. Wihtout exception your app looks like everything Ok but it is not.

    • @martinjuranek3095
      @martinjuranek3095 2 года назад +3

      If you accept that from now on you use monads, you write "normal" functions or those returning monads and instead of calling them you "bind" them to be used and checking whether first function returned result or exception is done outside your function.

    • @RemX405
      @RemX405 2 года назад +8

      As @Martin Juranek said, this is where the "bind" logic of monads come in. You can just write functions of TResult, and bind them to the Result monad.
      something like
      Result.Bind(func1).Bind(func2).Bind(func3).... and so on. The bind method will make sure the Result is success before calling the next method and transforming to the next type and so on. Your code will be a cascade of Result, which will shortcircuit as soon as IsSuccess = false. You can then handle the Result type at a layer where it make sense and unwrap it, where you will do the logic if success or exception.

  • @jammycakes
    @jammycakes Год назад +63

    Result might be useful for a limited number of use cases such as validation, but advocating it as a replacement for exceptions in general is a case of "those who fail to understand exceptions are condemned to reinvent them, badly."
    There are two very good reasons why exceptions were invented in the first place. First, they convey a lot of important information, such as diagnostic stack traces, and secondly, in 99% of cases, they do the safe and correct thing by default.
    Error conditions, whether reported by means of an exception or Result, indicate (or at least they should indicate) that the method you called was not able to do what its name says that it does. In such a situation, it is almost never appropriate to just carry on regardless: if you did, your code would be running under assumptions that are incorrect, resulting in more errors at best and data corruption at worst.
    Yet Result, as with return codes that predated exception handling, makes this incorrect and potentially dangerous behaviour the default. This means that every single call to a method that returns Result needs to be followed by a whole lot of repetitive boilerplate code. And you need to do this right the way through your entire codebase, not just in your controllers.
    Most of the time, what you will be doing in response to a failed Result is simply returning another failed Result up to your method's caller. But this is exactly what exceptions give you out of the box anyway. The whole point of exceptions is to take this repetitive boilerplate code and make it implicit. In the minority of cases where that isn't the appropriate behaviour, try/catch/finally blocks give you a way to override it to do things such as cleaning up or attempting to recover from the situation.
    Now to be fair, there are a couple of possible use cases for Result. It might be useful if you have one specific error condition that you expect to be encountering frequently, such as invalid input or requests for nonexistent resources, and that you need to handle there and then in a specific way. So it's probably fine for such things as validation. But it should most certainly not be used as a replacement for exceptions in general.

    • @alexandredaubricourt5741
      @alexandredaubricourt5741 Год назад +3

      What you are saying totally applies to Go, where you need a bunch of extra tools/linter in order not to forget to handle errors, else your app will have an undefined state/behaviour, however Results in Rust are great because 1. You always know when your method can error (if I could get $1 for everytime I got an unhandled exception in my life..) and 2. You're forced to handle the exception in order to get your result. Honestly I'd like to see Rust Results in c#. However exception are not that bad, I just think it's a lot harder to spot where your code can fail

    • @acasualviewer5861
      @acasualviewer5861 Год назад +3

      I have to agree with you strongly. I think this is a typical case of a young programmer with a new toy that thinks that something is better because its new.

    • @Ashalmawia
      @Ashalmawia Год назад +1

      for me, exceptions are for program errors (ie: indications of errors in programming). Result (or custom alternatives which I use) is not for that. if you want to return a meaningful message back to the user OR a value of some kind, what do you do? do you throw exceptions for every message that you might want to show the user? what about cancelling? do you throw an exception for that?
      I don't even ask this completely rhetorically, because that could actually work and I do wonder about it. I just had this conversation on reddit and once I started asking about concrete examples they stopped responding when before they said to use exceptions for everything, but then that "cancel" should not be an exception. well then, what do you return for cancel? a bool flag? how about a more strongly typed Cancel value that indicates Cancel.Yes/Cancel.No explicitly so you don't get the logic wrong? or how about a Cancel type that means either to cancel or to get a real value? but then... maybe you could throw a CancelException and your method could actually just return T... would that be better? I'm not actually sure...

    • @acasualviewer5861
      @acasualviewer5861 Год назад +3

      @@Ashalmawia No. Exceptions are for anything beyond the happy path. If its not part of the happy path then it's an exception.
      You can safely assume that an operation will work. If it doesn't you throw an exception.
      That's how languages like Smalltalk did it (one of the first to introduce exceptions). Java further formalized it with checked vs non-checked exceptions (which was rejected by C# designers).
      But the idea is that your code shouldn't be polluted with a bunch of checks to see if things succeeded. Your code can just simply explain it's algorithm.
      For example, when you open a file, you expect it to open. If it doesn't then that's exceptional. And you handle it with an error handler somewhere (not necessarily right then and there).
      Exceptions make code cleaner and more predictable. With Java you can make them formally predictable with checked exceptions (so you don't forget).
      This new retro style of handling errors is just a step back in error handling to the dark ages of C. No matter how much lipstick you put on it. The only thing that's a bit better is they force you to face the errors. But this new fashion of saying exceptions are absurd is just people too young to remember how things were before them.

    • @Ashalmawia
      @Ashalmawia Год назад +1

      @@acasualviewer5861 ok but what if you ask the user for a filename, but the user cancels out of the file open dialog? is that an exception?
      also, I don't know how true it is that "you can just explain your algorithm". you still need to throw exceptions all over the place just as you would return alternative results. and you still need to catch them, and try/catch blocks are one of the ugliest structures in programming.

  •  2 года назад +155

    This is basically the same age old discussion of implicit return via exceptions or explicit checks for error statuses. C++ went the way of exceptions, Go went the way of checking for return values. I can see people preferring one over the other. I prefer exceptions because you are working with a language / platform which favors exception so integrating with 3rd party libraries is easier because exception is commonly used (unlike Result / Maybe / Optional / whatever functional flavor you like). In addition to this you will probably always have to take care of exceptions so you might as well use them as well.
    As for performance I would consider this a a kind of micro-ptimization which is not really needed with an example application that was provided.

    • @FlumpStudiosUK
      @FlumpStudiosUK 2 года назад +6

      Great comment!

    • @thethreeheadedmonkey
      @thethreeheadedmonkey 2 года назад +20

      You haven't actually mentioned any of the main reasons not to use exceptions in your post, so it's deceptive.
      The performance part is covered in this video - it's 80% faster (if all you have are "exception" states) to avoid them.
      The more important part to my mind, is that exceptions represent "teleporting" in the program flow. There's no way to know where your exception will be handled without knowledge of the components you are connected to (and the ones they are connected to, recursively). It is antithetical to functional programming and having transparent, honest contracts in your code.
      Basically exceptions are the bane of any clean code base, and they should be dealt with appropriately at the boundaries to third party or "native" code so that they do not get propagated deep into your code base and business logic.
      That is not to say exceptions do not have a place. Exceptions should represent *bugs in your code*. They should tell the programmer that something really bad happened, or something *exceptional* (like losing network connection for a full hour) occurred.
      This also mitigates the costs of exceptions, since at these times, you definitely do want a stack trace and probably have some special magical handling that is irrelevant to your business logic.

    • @cla2008
      @cla2008 2 года назад

      @@thethreeheadedmonkey lol@ how is 19.5 20% of 34.3? you mental?

    • @christoph6055
      @christoph6055 2 года назад

      100% agree.

    •  2 года назад +21

      @@thethreeheadedmonkey IMHO there is not that much practical difference between try/catch and returning Result. In both cases your probably want to short-circuit your normal logic and return some sort of an error. If you do not then you are probably catching the exception anyway.
      I do not understand your statement that there is no way to know where your exception will be handled - the same can be stated for Result, the caller can either analyze the error and do something about it (catch) or return the error because he does not know how to deal with it (no catch). Yes, the caller must explicitly handle the error path but in C# it makes the code look very weird with bunch of Match methods with lambdas everywhere ... and as I've stated it the result probably comes out the same as using exceptions. If C# had some sort of native operator support and Result-like type (like await for Task) then I could see myself using the Result-like type. What I've seen is that people end up writing some sort of wrapper methods (like Nick does at the end) which basically ends up mimicking the try/catch fall-through because (again repeating myself) you do want to fall-through.
      I can understand the point of view that we should have separate 'expected' from 'unexpected' types of errors (like Java does with checked exceptions or C++ with exception specifications) but this is (IMHO) kind of subjective - is failing validation expected and should we should return true/false or should it raise an exception?
      Again, I'm ignoring the performance aspect of it as I do not consider it relevant here (if I were making a real-time game engine then I would not be having exception anywhere ... for LOB application, I'm fine).

  • @sleeper789
    @sleeper789 2 года назад +92

    Funny, we do exactly this and are seriously considering just going back to exceptions and a custom filter. I'm really starting to think the trade off isn't worth it. 99% of the time I just want to shortcircuit the rest the logic and return an error, and that's exactly what an exception is designed to do.

    • @nickchapsas
      @nickchapsas  2 года назад +9

      Then do that. I'm always for returning early. But think about your code. Would you have a label and a goto to make your code directly to the return point without considering anything else in the flow? That sounds like a huge code smell to me. Is it simpler to have the exception filter? Absolutely and it also looks cleaner in surface level, but as someone whose seen both approaches for more than 2 years in different project, I can honestly say that the Result approach worked way better.

    • @sleeper789
      @sleeper789 2 года назад +14

      @@nickchapsas Well, if the only thing between the goto and label would be a bunch of code that's going "Yup, it's an error, pass it along," then yeah, yeah I would use a goto and a label. It would eliminate a bunch of extraneous error handling from code that doesn't need it.

    • @nickchapsas
      @nickchapsas  2 года назад +4

      @@sleeper789 On any layer? In any method? In any delegate and function? Sounds a bit of a red flag to me.

    • @sleeper789
      @sleeper789 2 года назад +33

      ​@@nickchapsas In the context of an HTTP endpoint? Sure. No matter which layer or method you are in you still know you are in an HTTP handler context. You know what's going to happen if you throw. The calculus is different if you're writing a different type of application, but we aren't talking about desktop apps or whatever. 99% of the time I encounter an error in an HTTP handler the request is unrecoverable and I just want to shortcircuit everything, no matter what layer I am in, and return the appropriate error. Exceptions do that with the least amount of fuss.

    • @EdubSi
      @EdubSi 2 года назад +14

      @@nickchapsas Ye I have seen both too and we are moving forward with the expections. They suck performance wise but other then that, cleaner internal APIs and that's worth a lot.

  • @Ailurophopia
    @Ailurophopia Год назад +3

    I love this kind of approach ... as long as language supports it. It works nicely in Rust and Haskell that both have discriminated unions, 'match' is a keyword and have some operators to reduce boilerplate (>>= in Haskell or ? in Rust). If language forces you to handle all error cases properly then it results with much more correct code.
    In C# however, code becomes bloated quite quick. If you use `async/await` it becomes even more clumsy as it hinders fluent API chains that involve it. Or if one arm of match calls an async function, it affects the type of other arms (e.g need to wrap in Task.FromResult).
    Also, video completely ommits the cost of happy path. After all in happy path, a struct is passed with reference to a an object or an exception and some enum value. It should be allocated on stack ... but with async it will be boxed, and unboxed possibly a few times. Allocations have its cost. This kind of approach reduces cost of error path, but makes happy path more slightly costly. There is a point where this is performance improvement to use Result instead of Exceptions, but this needs to be benchmarked, not assumed. Not to mention that for most applications performance is not a primary concern.

  • @whatisgosu
    @whatisgosu 2 года назад +105

    Outside of what already have been mentioned this approach generates a ton of boilerplate code in a bigger application. I don't think its a way to go, I personally prefer exceptions.

    • @Micro-Moo
      @Micro-Moo Год назад +8

      Absolutely agree. The root fallacy of the author is thinking of exceptions as failures. Exceptions are not errors and are not failures. They are exceptional situations, whatever they are. If you have such situations in your own code, you should better develop exception types and throw exceptions. If not, you don't throw anything. In all cases, you handle the exceptions. That's it.
      I am generally very skeptical about the authors of limiting rules and patterns. The developer should be focused on the ultimate goals of the development. And none of these goals can be about pleasing those authors of the "concepts". What we see here is: the author uses his own very limited experience and over-generalizes it. Besides, I doubt his reasons are valid even for his particular development. Look at the code: you will see enough signs of the code written without enough thinking and enough experience. The "magic number" anti-pattern along tells a tale...

    • @triebb
      @triebb Год назад +5

      @@Micro-Moo Agreed. I think a validation exception is a poor example to demonstrate this. A validation error should not be an exception since it is just a common error. While this approach looks reasonable to capture common / expected errors, a blanket statement that exceptions should not be used is misleading IMO

    • @Micro-Moo
      @Micro-Moo Год назад +2

      @@triebb I would say validation is a fair example of something used to present the choice between exceptions and other approaches. It is simply... too narrow to be representative.
      Misleading is the attempt to over-generalize such a case.

    • @gileee
      @gileee 7 месяцев назад

      @@triebb .NET throws a ValidationException when validation fails. If you don't want to throw a ValidationException then you're in the same boat of "if(validationResult.Error) return early" and then in the calling method you have the same code, which is the same Result pattern you see here. Because a controller isn't the only place where you might call a Validate(object) function you can't really just return an IResult or ActionResult.

  • @EduardQualls
    @EduardQualls 2 года назад +6

    As someone who had to confront C's _setjmp_ / _longjmp_ terror during the 80's and 90's (with the compiler differences [and the platform-dependent implementations of things _called_ exceptions] they could expose), my point of view has not changed in C#: if error conditions are known/predictable/constrained, use/check return values and handle them sensibly and readably, as locally as possible; if error conditions are *_exceptional,_* use exceptions.

  • @ronsijm
    @ronsijm 2 года назад +75

    I'm using the first approach of throwing exceptions and catching them in a middleware, yea.
    The issue with the second approach is that every method needs to have code for handling invalid results. The exception could come from like 5 layers deep... having to handle exceptions in every layer and step back/ return naturally is just annoying.
    I know it's not really a popular opinion, but to me code seems a lot cleaner when it mostly only has to handle the happy-flow. And using exceptions to basically do a longjmp to a middleware is just easier.
    Plus I'm assuming that 90%+ of the calls are going to follow the happyflow. So from a "exception only" benchmark it seems like you can handle errors 3 times faster, great. But my applications are not aimed towards users doing everything wrong and being able to tell them that _slightly faster_

    • @nickchapsas
      @nickchapsas  2 года назад +16

      You don't need to handle those results on every layer. Results have implicit operators so you can just return them you don't have to handle them on every return. Only if you need to map them to something else. If your counter point to this is, mapping object, then you already violate your own rule but having a cross cutting concerns that ignores all your layers. As you saw in the video, the happy path code didn't change at all.
      And on the last point, you don't know how the user will use your all. What stops me from spamming your app with bad requests and tanking your performance? Validation is part of normal app flow so if you throttle it you're hurting the UX.

    • @ronsijm
      @ronsijm 2 года назад +27

      @@nickchapsas well what I mean is, what you're doing at ruclips.net/video/a1ye9eGTB98/видео.html - you have a `_validator`, then in your `CreateCustomer` you have to check the validation result and return when not-happy. Same for the `_githubService`, you call it, check result, and then return when not-happy.
      In an "exception based flow" you wouldn't be doing that. CreateCustomer just does the happy flow. It calls the validator, and if the validator doesn't throw it continues with the flow. Then it gets a "GitUserCreateResult" and possible calls the next service, and the next etc, depending on how big the create flow is. The `_validator` or `_githubService` would be throwing the exceptions internally (from whatever layer the exception is detected)

    • @zibizz1
      @zibizz1 2 года назад +9

      @@ronsijm I agree with you I also go with the exception way. If this is only for cases you do not expect like missing the required argument it is ok. Code is much cleaner, easy exit option, and if something is not ok you get the proper stack trace. However, if you catch some of these exceptions in the middle then for sure it is wrong, you can only do it on some global level. If you make catching the exception as part of logic then it is entirely wrong.

    • @tjakevladze
      @tjakevladze 2 года назад

      Exceptions are very costly, so not ideal for control flow at all. It's probably better to control your classes and methods if you can and let exceptions only get thrown when you are arent/cannot be aware of when or what causing them to be thrown.

    • @evilpigeon4976
      @evilpigeon4976 2 года назад +3

      @@nickchapsas That would be a denial of service attack, for which there are many vectors, and perhaps is outside the scope of this discussion.

  • @notpingu
    @notpingu 2 года назад +88

    Most of the times I've seen this pattern, it had the unfortunate side effect of hiding errors and exceptions because calling code almost never does anything useful with the Result object if the result is not Success. When a user clicks a button and the process behind that button goes wrong, I'd rather have an exception than simply having nothing happen, and you have to check all your backends to see whether the button click actually occurred. The calling code can only work with a Result object if it knows what to do with it, and any error message in the Result object is probably not suitable to show to the user anyway.

    • @michawhite7613
      @michawhite7613 2 года назад +7

      In Rust, the Result type has an unwrap method, which does exactly what you're describing. If the result is ok, it returns the value, otherwise, it panics.

    • @EddieVillamor
      @EddieVillamor 2 года назад +5

      If the calling code doesn't have a path for a failure case then doesn't that mean the caller doesn't care about the failing case anyway?
      It sounds to me like while you can have an api return a non throwing result, the caller should always have a path to elegantly handle all flows and the api will merely expose what is necessary. You wouldn't have to dig through if you know where the failing case is handled and can be easily pointed to it in your IDE.
      Imo this is very good for expected failures, and not unexpected ones. i.e. Useful to let subclasses of Exception be wrapped by a Result, but an unexpected Exception can still be thrown since that one is the one that would need more digging.

    • @seannewell397
      @seannewell397 2 года назад +6

      Smells like a code smell just as bad as an empty or useless catch block imo; either is equally bad regardless of exceptions vs Result

    • @powerclan1910
      @powerclan1910 2 года назад +6

      @@seannewell397 it all depends on the usecase, endusers prefer a nonworking button over the application crashing.
      And since you need to manually unwrap results, you kind of can't forget about errors, you need to explicitly ignore them, which sounds like a + to me, and less bugs

    • @Denominus
      @Denominus 2 года назад +3

      The Result approach works, but it's only really safe and easy to use if the language itself supports discriminated unions. The language can layer on some syntax sugar for quickly dealing with Result types as well (Rust).
      When C# gets DU support, working with these kinds of types will be A LOT better.

  • @Funestelame
    @Funestelame 2 года назад +2

    Really loving the LanguageExt library. I started using it for the Option but I'm always learning new stuff by using it. Seems like Result will be clearer to use rather than Either. Thanks for this video!

  • @dgschrei
    @dgschrei 2 года назад +22

    The main problem with exceptions is that they are very expressly named that and everybody keeps ignoring that.
    Exceptions are supposed to be exceptional. They are for when your code does weird stuff you didn't expect or guard against and you actually can't recover to a defined state on your level of execution. At that point the exception basically becomes a hail mary you throw up the stack in the hopes that someone higher up the food chain actually has enough knowledge about the application state to recover the application to a defined state. (or at least ad additional logs about what exactly went wrong to help you diagnose the failure) .
    If your Exceptions happen at a frequency where the performance impact of using them is a concern to you you are already doing it wrong, because then clearly they are not a rare event and you're using Exceptions for control flow at that point which application code never should do.
    The TPL and async await do use exceptions for control-flow (e.g. TaskCanceledException) because they literally have no better way to achieve this feature. But e.g. a call to a server timing out and throwing a TimeoutException is just something you have to expect and therefore handle gracefully. And not by rethrowing the exception you got from the framework up the chain.

    • @timwood5995
      @timwood5995 2 года назад +1

      This is such a great comment - too many thrown Exceptions IMHO are just lazy on the part of the developer. Our coding standards explicitly say don't throw exceptions. As I was taught on day one of my career (a v. long time ago), "Exceptions are just GOTO without RETURN". I can see how they might be useful in a Web context tho.....

    • @protox4
      @protox4 2 года назад

      About async await having no better way to achieve control flow, I wrote an issue on csharplang github to address fast cancelations without exceptions that had 33 up votes so far. Fast exceptions are uninteresting.

    • @Thial92
      @Thial92 2 года назад +1

      Absolutely and lots of people argue against that by saying "throw fast, throw hard". That's just pure laziness and/or lack of experience, nothing more. I've been using my self written IResult / IResult interfaces which expose very simple properties like IsFail, IsFailOrNull, IsFailOrNullOrEmpty and my every method returns that interface. It also generates a minimalistic call stack in case of failures with class names and lines using Caller attributes.

    • @protox4
      @protox4 2 года назад

      @@Thial92 "throw fast, throw hard" aka "fail fast, fail hard" really just means throw in exceptional cases and don't try to code defensively. Failing fast and early makes it so that bad code is more easily spotted and can be fixed faster. It's not the same thing as attempting to use exceptions to handle business logic.

    • @Thial92
      @Thial92 2 года назад +1

      @@protox4 Of course you should not do a bunch of extra handling in the case of failures but all you are doing is still just offloading the exception to a different layer. I've seen "fail fast, fail hard" repeatedly being used as an argument for throwing exceptions left and right or not handling anything at all.

  • @evilpigeon4976
    @evilpigeon4976 2 года назад +7

    If performance is crucial, then this is the ideal approach. It's very rare for this to be a bottleneck though. How many people in the comments are dealing with a web app needing to handle 1000s of validation errors a second? Does this approach make a difference with client-side validation and happy path?
    Even with this approach, you still need a strategy for handling exceptions for "expected" scenarios, for example database constraint violations and concurrency errors. These types of errors should arguably not return 500 status, but 409, so you end up needing to implement your exception filters anyway! So ultimately this adds significant overhead for small teams.
    As is mentioned elsewhere, this pattern will pollute all your service layers and require you to check for errors all the way up the chain. I think what's telling is how few popular libraries implement this pattern. It's not so bad if you have a pretty flat CQRS architecture I suppose.
    Full disclaimer: you're still my favourite Nick xox.

  • @r14958
    @r14958 2 года назад +1

    I am late to this conversation, but I do not understand why people are looking at this like an either/or situation. Someone mentioned CSharpFunctionalExtensions by Vladimir Khorikov. He is very clear in his courses and blogs that the Result class should only be used to handle expected errors (DB unreachable, user types in a search term with no matches). Why throw something as disruptive as an exception when the problem can be easily dealt with? For unexpected errors, like when the "contract" is broken between the caller and sender (wrong type is returned, or null when that response is not allowed, etc.), then he recommends throwing an exception.

  • @DavidJohnsonFromSeattle
    @DavidJohnsonFromSeattle 2 года назад +11

    Your only justification is a performance improvement in the error case? Hard pass... Follow the conventions of your language and libraries. Doing weird stuff like this makes it difficult to maintain code.

    • @DavidJohnsonFromSeattle
      @DavidJohnsonFromSeattle 2 года назад

      @@altkev A handler? You mean an exception handler? A core concept of the language? Sorry, but that is in fact the problem that I have with his suggestion. He basically says that he doesn't like exception throwing and is doing a weird special case with his code to avoid it in one circumstance. NO THANK YOU

  • @cartsp
    @cartsp 2 года назад +11

    I do the exact same thing using language ext Either , works great although I find myself having to explain it to everyone who has to work on the code. It would save you having to do the more verbose new Result(validationException), you would just return validationException in your case, if it was an Either.

    • @XXnickles
      @XXnickles 2 года назад +8

      The "having to explain it to everyone who has to work on the code" was the deal breaker for it in my company (I only ended using it in one project with not good results in terms of collaboration) The concept is great and I love functional patterns to work with data flows. But when you are in a team environment, if people don't embrace the pattern, it is going to be extremely hard to work with it in the long road. That why I resorted back to domain exceptions, aggregation and interception to "model" error flows

    • @cartsp
      @cartsp 2 года назад +2

      ​@@XXnickles exactly, I've tried to ask them not to worry about the mapping of the either object to the type/error but it keeps coming back to bite when people are writing tests etc and want to get at the values. I also won't be using it again, anytime soon, for the very same reason.

  • @efrenb5
    @efrenb5 2 года назад +6

    Great points. I've been using the "Result" approach for years now to great effect. Response codes are a lot easier to control compared to exceptions, so the code is not only faster, it's also easier to read and maintain.
    I do use a custom "Result" implementation which is more flexible that the one shown here.

  • @zvado
    @zvado 2 года назад +1

    Uncle Bob in Clean Code say to prefer throwing then using error codes because it is subtle violation to CQRS because it promotes commands being used as predicates in if statements.

  • @CoderCoronet
    @CoderCoronet 2 года назад +1

    Nice stuff. Exceptions are exactly what the name implies: Something you were not expecting to happen. If it is something your are expecting, than it is business and you should return a result instead.

    • @dwhxyz
      @dwhxyz Год назад +2

      It's interesting that neither Nick or the other commenters discuss this. Validation errors are validation errors and not exceptions!

  • @donparkison4617
    @donparkison4617 2 года назад +1

    All the people saying that Exceptions are better due to readability are missing the point. Exceptions are heavy and have a high cost on performance. If your api is fast enough using exceptions, then great! But if you need to increase the speed of your api, this is a great way to do it.
    Great video Nick. Thank you.

  • @oldwhitowl
    @oldwhitowl 2 года назад +13

    Hi Nick, I use both a result object (csharpfunctionalextensions) and a global exception handler. That way I can still handle and log exceptional exceptions.
    Love the videos!

  • @anderslinden4701
    @anderslinden4701 2 года назад +5

    I did not try this, but it seems rock stable to "throw" an exception that wll only reach one level in the stack hierarchy as you are suggesting instead of an unknown number of levels, ultimately killing the application if you are unlucky. Great improvement that will result in improved stability. I do not care that much about the performance improvements (they are not the selling point at least). Great video!

    • @acasualviewer5861
      @acasualviewer5861 Год назад

      You solve this with a catch-all.. the whole advantage of exceptions is that you don't have to write a ton of boiler plate for every method call. Your code is just cleaner.
      This monad business is just a fad.

    • @saymehname
      @saymehname 10 месяцев назад

      @@acasualviewer5861 Monads are over 30 years old what are you on about

  • @jimread2354
    @jimread2354 2 года назад +6

    Personally, I like Exceptions in human time and return value models like this for computer time. Exceptions are great for handling error conditions because you can include the data necessary for calling functions to adapt, retry, or worst case build a chain of messages for support to debug and users to know something went bonk. But a server system doesn't need clever messaging and can easily act on return codes which are MUCH faster than exceptions. The other beauty of exceptions is the immediate halt of execution because you can write your code as though everything is hunky-dory and know that if x is 0 you're not introducing NaN into the data stream when you execute 3 / x. That's a stupidly simple example, but you see the point. And for clarity, they're not really "go tos" they're more like interrupts. In fact, IIRC, in C or C++ they were implemented with a specific interrupt codes at the OS level so that it would halt program execution and dump to the exception handling logic.

  • @TiagoCastilhos
    @TiagoCastilhos 2 года назад +2

    If you think about production scenarios, the exception won't be that often, as you mentioned. Exceptions have a price and it's not cheap, that's a fact. If you run stress tests on a code that throws exceptions versus a code that works with result types, exception code will always imply consuming more resources. One thing that's very clear in your refactoring is that your code became much more complex since you have to test results on every scenario instead of handling an exception, and it will end up requiring more unit tests and a harder code to read. My opinion on this one is: If you require EXTREME performance on your code, where every millisecond counts and it must run on very limited hardware, working with results will bring more benefits. Any other scenario won't make that difference, so for general software development, handling exceptions will be the easiest way and will result in an easier code to read and test.

    • @aschwinwesselius
      @aschwinwesselius 2 года назад +1

      I agree. In case something goes sour, having an additional one-third of performance loss is of lesser concern. Both to the user and to the business. It's not nice, but nobody will notice anyway because the attention will go to what went wrong not how fast it went wrong.

  • @anatolia23
    @anatolia23 2 года назад +1

    I didn't know this library. I usually prefer to use throw helper pattern instead of throwing exception directly like 'ArgumentNullException.Throw'. This prevents compiler to generate significant amount of assembly code. Thanks for the video!

  • @oggatog3698
    @oggatog3698 2 года назад +16

    The result monad seems really good at surface level, but having worked with it in production code I do not recommend it. There's a bump in complexity of code, of debugging, of using projection to switch between return types, and of refactoring to accommodate the new result monad and removing the result monad.

    • @vladimirlazarevic9792
      @vladimirlazarevic9792 2 года назад +1

      The little bit of complexity added is absolutely worth not having null values, having better control over your return values and added benefits of working with higher abstraction code. There is a reason basically every mainstream language these days avoids exceptions like plague and uses Result, Option, Either, or whatever construct to handle errors. There is a learning curve that comes with using it, but you'll have a much safer code, resulting in fewer bugs.

    • @Micro-Moo
      @Micro-Moo Год назад +3

      First of all, the monad concept does not contradict the exceptions. Elimination of throwing exceptions is very bad advice. It is an attempt to throw out the benefits of great technology on a poorly scholastic basis. The right approach is thinking of exceptions as exceptions, not "failures" or "errors". If you have something exceptional to throw, you should do it. In other words, developers should use their own heads, instead of being blindfolded and following rules and patterns without deep thinking.

  • @jegtugado3743
    @jegtugado3743 2 года назад +32

    Well this improved performance is only for exceptions. Ideally there will be minimal cases in Prod. I think the already available framework components for handling exception is acceptable.

    • @clementdurazzo4774
      @clementdurazzo4774 2 года назад +1

      That’s a valid point IF you respect the basic principle that an exception has to stay an exception.
      Except that I came across a LOT of projects with exception anti-pattern workflow implementation… 😥

    • @ftrueck
      @ftrueck 2 года назад

      Well, you just need a script kiddie with a little while-true loop to screw your server‘s resources. I think this is a valid enough reason to even consider this single use case.

    • @PelFox
      @PelFox 2 года назад +1

      @@ftrueck At that case I would add a rate limiter for your API so not a single person can break your system with a simple script.

    • @ftrueck
      @ftrueck 2 года назад

      @@AbNomal621 until you are affected by it. Then you ask yourself why you did not think about it earlier...

  • @josephwilliamson7350
    @josephwilliamson7350 2 года назад +2

    GOTOs can go anywhere but Exceptions just bubble straight up. I think this makes them drastically easier to understand than GOTOs that can go anywhere. The main issue with exceptions is novice programmers don't know where to put Try Catch blocks and you need to unit test them. Result types don't really need unit tests, the compiler let's you know whether you've handled success and failure paths or are just passing the result along.

  • @ScottBlomquist
    @ScottBlomquist 2 года назад +2

    At 8:17, you could get away with the implicitly typed `return new(validationException)` instead of `return new Result(validationException)`.

  • @RealCheesyBread
    @RealCheesyBread Год назад +2

    I actually went this route when developing my Azure Functions app, and it ended up being my most regrettable decision. I ultimately refactored things to use the exception model. Now because Azure Functions does not support middleware at the moment, even with dependency injection (as of this comment), I used an aspect to catch all exceptions.
    Normally I don't use aspects as they very quickly make code difficult to understand and difficult for knowledge transfer to others who start working on the code, but aspects like that which act like a form of middleware and have a very simple and well-defined effect are quite useful! I also use an aspect to perform parameter null-checks in methods where it's necessary.

  • @skypravda
    @skypravda 2 года назад +19

    this performance tradeoff in most cases doesn't matter because most of requests are valid. The idea behind introducing exceptions in the first place was to get rid of this C-like 'result values' which generate noise in the code so eventually most of the code is just verifying these 'results'. This isn't just worth it.

    • @nickchapsas
      @nickchapsas  2 года назад +5

      You don't know that actually. What stops me from spamming your API with bad requests and wasting your resources. Nothing. Also, the code quality argument is way stronger than the performance argument. Exception handling in central places for specific domain concerns is bad. You wouldn't use goto in your code. This isn't any different.

    • @jackkendall6420
      @jackkendall6420 2 года назад +4

      I also agree the performance doesn't matter, but the code quality does. The issue with C's result values is that they're typically 0s and 1s or bools which are inherently ambiguous. The functional approach demonstrated in this video is strongly-typed and far less prone to misinterpretation. Yes, you have to manually check the results of your operations with this -- that's a bonus! I don't want to forget to check if a method returns null, or false, or 0, or throws an exception. I want the compiler to force me to do the right thing, that's what it's for...

    • @skypravda
      @skypravda 2 года назад +2

      @@nickchapsas I agree that there are specific cases when workarounds based on paradigmas from other languages (which don't have exceptions) makes sense. Such cases should be revealed and addressed in real-world tests using performance tools etc. The reason why I think that using exceptionless approach everywhere is a bad idea: this is not free. It leads to worse code which is harder to maintain and understand. In the examples you've provided there were too few levels and that's why the issues aren't clearly visible. Just imagine 5 levels of abstractions and the error which occurred on the lowest level, it's becoming complicated to properly handle the error without using exceptions and the code is degrading to become more noisy. Although, I am not a big fun of another extreme approach- using 'central' exception handler for the whole app. By using exceptions wisely errors can be handled properly by the corresponding abstraction layer which has enough information about the state and exception. As for the argument related to fighting the spammers, to some extent it makes sense, but, just in my opinion, this is a good example of the issue which can't and shouldn't be resolved by using only technical measures. Why? Because even if we make a method work 100 times faster it will just make the spammer use more resources for DoD. Anyway, thank you for your channel, I've watching your videos for a long time and they all are great and full of new ideas!

    • @DisturbedNeo
      @DisturbedNeo 2 года назад +4

      @@nickchapsas The SysAdmin is the one stopping you from doing that if they’ve done their job properly. Whether or not you can spam a server with bogus / bad requests is a configuration issue and has nothing to do with the code.

    • @nickchapsas
      @nickchapsas  2 года назад +1

      @@DisturbedNeo SysAdmin? I can't remember the last time I worked with a Sys admin

  • @clementdurazzo4774
    @clementdurazzo4774 2 года назад +2

    We use OneOf to do exactly that but with more versatility on the response as it’s a generic that can be used with anything as a responses, and not just a single type.
    A good example is a proxy where you can manage multiple response type and have multiple action depending on the http status.

    • @clementdurazzo4774
      @clementdurazzo4774 2 года назад

      And you don’t have to create an exception when a simple ErrorType can be enough.

  • @olegvorkunov5400
    @olegvorkunov5400 Год назад

    The issue with performance while throwing an exception is that during the throw, the system collects a full stack trace. If C# implemented an exception type or new throw concept without collecting stack trace, it would be much better, than doing awkward not truly real return type and avoiding constant checks: if result.isSuccess everywhere where you use Result method call. A new throw technique without collecting stack traces would still interrupt execution. In that case, performance would be even better than with Result.

  • @craigfreeman8225
    @craigfreeman8225 2 года назад +1

    Extension method addresses my thoughts on cross cutting concerns more or less, performance difference is interesting, but what I think its really important to note here that exception stack traces come from where it was thrown, but this never throws. Logging is a cross cutting concern and no doubt people have middleware doing that also logging a stack trace. So with this approach you would now have to consider how you will be logging and how to ensure your whole team is doing a good enough job to capture really meaningful data, which you should probably be doing anyway but its just a point to consider.

  • @cesarcmc
    @cesarcmc 2 года назад +1

    I have been using "Maybe monad" approaches for more than 10 years. My eyes hurt with anything handled on middleware handlers, and no comments on returning null as api responses when sthing crashes. There are so many benefits on using monads vs exception handling free will e.g. null handling, validations, code readability/maintenance, performance, etc. Plus standardizing coding requests/responses across all codebase.
    Its difficult to change people's mindset when they have been applying same old style patterns during all career, but there is defo way more benefits than disadvantages on using this approach.
    Good video.

  • @ablues15
    @ablues15 12 дней назад

    Nick often as rock solid advice, but advocating Result is not one I support.
    Throw exceptions in exception cases, simple. Do it in the wrong cases you get problems. Result is not the answer. Writing software still requires skill and knowledge and indiscriminately applying blanket rules will get you bad outcomes.

  • @redslayer973
    @redslayer973 Год назад +1

    This video is so exceptional, it really changed the way I work with exceptions

  • @macgyverswissarmykni
    @macgyverswissarmykni 2 года назад +1

    Been using LanguageExt for years, absolutely love it. Major downside is how quickly code becomes verbose, but at the same time it also allows code to potentially be more expressive (and thus, readable).

    • @thethreeheadedmonkey
      @thethreeheadedmonkey 2 года назад +1

      I've been considering introducing it in my code bases - what would you say are the biggest concrete obstacles in team buy-in?

  • @mohammedelsuissey1745
    @mohammedelsuissey1745 2 года назад +1

    At some cases you have to sacrifice performance to achieve better results, for example in the exception middle-ware I can log the stack trace, I can inject Http context and log request id, or current logged in user, and more
    So sometimes in real life use cases you need this over performance, especially in sensitive projects like fintech or such.
    Thanks though that was enlightening.

    • @creativemanix
      @creativemanix 2 года назад

      Yes in fintech api's I do see using result pattern makes the code so complicated. Also Polly retry policy can be applied to exceptions for retry

  • @enricobuonanno
    @enricobuonanno 2 года назад +9

    Hi Nick, thanks for this video and many more which are really useful. Although I agree with the basic idea in this video, there is a problem with using `Task`. Because both `Task` and `Result` are monads, and there is a natural transformation from `Result` to `Task`, it makes little sense to have them nested. Simply put, you could just use `Task.FromException` to signal a validation error, simplifying your API to simply return `Task`, and without throwing exceptions.
    From a pedagogical point of view, it would be better to use a synchronous code to introduce the `Result` type. I'm also not so keen on the `Result` type having an `Exception` as the "left" type, since `Exception` is exceptional, whereas validation errors are part of normal use cases. I have a much more detailed discussion of all these ideas in my book on Functional Programming in C#, which you might be familiar with.

    • @nickchapsas
      @nickchapsas  2 года назад +1

      The problem with the nested monad problem is that it's a limitation of C# and I agree that it looks dodgy, but that's why I didn't explain what a monad is in the video. Because in the context of C#, it doesn't really matter

    • @Lammot
      @Lammot 2 года назад

      Fancy meeting you here!
      If anyone cares - the book is very good. Not without some flaws, sure, but it's an easy recommend.
      Still have to finish it though, have 7 more chapters to read. :>
      ps: awaiting a code with a task.fromexception will result in exception being thrown.

    • @derekkaneshiro3935
      @derekkaneshiro3935 2 года назад

      Functional Programming in C# is like a pot of gold at the end of the rainbow.

  • @Nochdarus
    @Nochdarus Год назад +1

    Totally liked it as a primarily C person, who just always returns error codes.
    I guess technology does evolve in a spiral.

  • @Corrup7ioN
    @Corrup7ioN 2 года назад +2

    To me, the biggest problem with throwing exceptions is that you never know what exceptions could be thrown by downstream code that you might need to handle (unless you document possible exceptions all the way through the call hierarchy). Whilst using this Result library is a step in the right direction in that it tells you that downstream code might return a failure, you still have no idea what types of failures could be returned. Personally I prefer using the OneOf library for more customisable union types like OneOf for example as the return type when trying to create some entity. And then in the controller, something like:
    return result.Match(
    created => Created(...),
    validationFailed => BadRequest(...),
    duplicate => Conflict(...),
    );
    And I tend to create custom overloads for these standard controller result methods that I can just pass each of my individual result types to and have them formatted appropriately. The nice thing is that you *always* know what type of expected failures can be returned and can choose how to deal with them, although it does become a bit onerous if every method starts returning these things.

    • @nickchapsas
      @nickchapsas  2 года назад +7

      Yeah OneOf is a great alternative as well. I've talked about it in this channel before and people were actually a bit negative about it. Can't wait for DUs to be a native feature of C#.

  • @SchadenNZ
    @SchadenNZ 2 года назад

    Great solution Nick!
    I have tried doing this in the past using OneOf (allows returning "One of" a defined list of result types). It worked but in the end just proved too much extra effort to define the possible results from each service call and then handle each type.
    This seems like the same pattern but using result in langue-ext is a less clunky implementation. Nice find!

  • @alim.karim.
    @alim.karim. 2 года назад

    It is a good video, GOlang is fast, actually following this approach. Go usage error as string to pass through rather than panicking directly.

  • @kishortiwari1348
    @kishortiwari1348 2 года назад +2

    Those validations must be done in the controller level(simple validations) & for business level validations, I prefer exceptions. With wrapping exceptions like not sure if the stacktrace points to right place where it broke.

    • @triebb
      @triebb Год назад

      Agreed. IMO the Business layer should assume input was already validated at the input (controller level). If the input is not already valid, then throw an exception. I think exceptions should generally tell you something is wrong with your code (eg business layer validations don't match controller validations) and not that something is wrong with user input.

  • @wojciechwilimowski985
    @wojciechwilimowski985 2 года назад

    (web API view) IMO using result is great if you have fallback logic or failure cleanup logic. If you don't have any of those, you're going to have to return an error status anyway and the Result is just noise

  • @KibbleWhite
    @KibbleWhite 2 года назад +24

    This is the way forwards for sure. Exceptions should be reserved for, well exactly that, exceptions.

    • @Dxpress_
      @Dxpress_ 2 года назад +2

      Well, there's an argument to be made on what exactly _is_ an exception.
      - The program expects an email address.
      - The user sends something that's not an email address.
      - The program cannot follow the intended path due to invalid input.
      Is this not an exception? I can see why one would throw here.
      Edit:
      Since the above example isn't too realistic (input validation can just be done with attributes & such), here's a better one:
      - The user wants to create a new resource (in a database or wherever).
      - The program reads the user's roles/claims permissions & determines they are not authorized to do so.
      - The program cannot follow the intended path.
      A user tries to perform an operation that they are unauthorized to. Sounds like an exception to the intended flow to me.

    • @christoph6055
      @christoph6055 2 года назад +1

      Would you consider the path, where a user is unauthorised to access a resource, to be unintentional? Is that really an exceptional, unexpected behaviour of your application?
      In my opinion, this is also an intended execution path. It’s also intended that users get notified when they input an incorrect E-Mail address.
      To me, exceptions occur only in actually unintended situations. OutOfMemory, logic error which for example leads to a devision by zero or a null reference exception.
      Those are cases that the developer didn’t expect. Therefore, those are exceptions.

    • @pilotboba
      @pilotboba 2 года назад +2

      @@Dxpress_ It's all in your POV. You can argue that exceptions are only for things you don't EXPECT to normally happen.
      Invalid or unauthorized requests are pretty much expected and common.
      For me, authorizing the request is part of the normal flow. Validating the input is part of the normal flow.
      Look at the aspnet MVC pattern for validation. When the request is bound to the input parameter and there is a binding issue or validation error, it doesn't through an exception, it puts the failure information in the modelvaildation object that you check with IsValid.
      Loss of Network connection, hard disk full, etc aren't normally expected. It's exceptional that this would happen. It's also hard to check ahead of time to ensure something won't fail.
      But, do what makes you happy.

  • @willemschipper7736
    @willemschipper7736 2 года назад +2

    Imo exceptions are a lot better when something unexpected has gone wrong like a fatal error, and not when you expect something might go wrong like with user input validation

  • @pjuliano9000
    @pjuliano9000 2 года назад +1

    Think about the world of C before C++. There existed set jump in long jump. The purpose was that you could have deeply nested layers of code in which you encountered an error and to propagate that up would be Heynis with return codes. Exceptions provide the ability to be down layers deep and fail fast without propagating return codes up and up and up to higher layers

  • @birb1947
    @birb1947 14 часов назад

    Thank you, that's right, throwing exceptions is dangerous. You never know who they might land on

  • @pg_pete
    @pg_pete 2 года назад +2

    I implement this pattern. Exception handling should be nothing more than "attempting to expect the unexpectable", like running out of memory or the connection to your backend is faulty. Using the 'result'-pattern, you can follow the business logic in a straight line. To me, it's undesirable to suddenly jump out of the current stack frame and see an error popup in the front-end. It's more transparent than having one exception filter with catches for every 'expected' error. People who say that a performance drop is negligible or acceptable are imho lazy. You don't have to go out of your way to micro-optimize something, but you definitely should not go out of your way to accomplish the opposite. Something being negligible NOW might not be later (short-term thinking)..and it's also not a good excuse to implement a bad practice. It even says so in the Microsoft documentation.

  • @IAmFeO2x
    @IAmFeO2x 2 года назад

    Very good video. Show us how much performance is lost by throwing and catching exceptions.

  • @fswadling
    @fswadling 7 месяцев назад

    I worked on a codebase once where exceptions were used as part of the happy path a lot. Hated it, discovered scott wachlins series on rop, and fell in love with the idea. I ended falling down the f# rabbit hole and found myself on an f# team where this style of error handling was the norm. Then I discovered the drawbacks; its verbose and you’ll never cover all the possible error cases; and found myself in the frankly weird position of advocating the use of exceptions in f#. Id say that ultimately theres a time and place for both approaches

  • @nilsberghs3
    @nilsberghs3 Год назад

    I have to disagree with this one. I don't doubt the benchmark but as Nick said not every request will be a failure. In most situations they will even be a minority and, on projects I have worked on, their performance can be safely ignored.
    For some projects this will definitely work but once you need to start nesting Result it becomes messy. If your function which returns Result uses a function that returns Result that uses something that returns result,... you end up doing a lot of result checking conversions between result types.
    I do somewhat agree that throwing exceptions can made code harder to follow, but it only does that in 'Exceptional' situations, while the Result makes code a lot harder to read in normal situation.

  • @ryanobray1
    @ryanobray1 Год назад

    Great video. I waffle on this topic probably in every other project. Your approach doesn't feel dirty or inconsistent like the approaches I tend to bounce between. Admittedly, some of my waffling has to do with why Java uses the "throws" keyword and whether such a concept should exist in dotnet.

  • @tomheijtink8688
    @tomheijtink8688 2 года назад +2

    The main advantage is that your code contract is not hidden in some xml comment but an actual part of your code. This is the only way your contract isn't lying about what it is doing or what could happen. Any exception thrown is a violation of your contract. It's basically the fine print nobody reads and likes.
    I've read some comments complaining about having to check the results. But you would have to do that anyway. The one that throws can't assume the application should stop right there. That's up to the caller to decide what to do when the callee fails. If the exception has been added after you've consumed the method you have no idea what has to happen in case it fails. It's just lazy and crazy IMO.

    • @sedlja4605
      @sedlja4605 Год назад +1

      Finally someone brought up the point about contract completeness! That was the first aspect on my mind when I dabbled into monads - your contract then tells what can go wrong and in ideal case knowing the innards of the implementation is not necessary.

  • @creativemanix
    @creativemanix 2 года назад +1

    Oh this is exception, means thrown on rare cases. The business layer can throw validation exceptions, but the API layer need to do model validation before passing to business layer. The business layer throws when API layer missed to do the validation. The UI layer also need to implement this validation for better experience. So this is not a good comparison. I would write API in golang where this is default pattern. In C# throwing is ok, that's how the language designed. Using result pattern in C# makes less readable....

  • @rogiawos8737
    @rogiawos8737 Год назад

    You could create in your models properties HasErrors and Dictionary. IIn case check at the top function that model is valid and you can create it, if no you pass validation error(Dictionary.Values) in json answer. And there is no need to use any Exception

  • @bnotorious1
    @bnotorious1 2 года назад

    This is gold! Thank you so much Nick. I will refactor my code in the project I'm working for my company.

  • @DummyFace123
    @DummyFace123 Год назад

    Shoot, the primary benefit of doing this is actually just simplifying flow. The performance is is too. Awesome!

  • @johnandzelik3204
    @johnandzelik3204 2 года назад

    A couple of thoughts here. 1. Throwing exceptions is completely valid when unexpected happens. (I.e. file i/o, null ref, etc..) try catch log throw exception. 2. For retrieval (if x == null) now there is a use case for a result return type, again after logging the error. Within api's fluent validation will validate the incoming request, and should reject automatically if setup correctly

    • @nickchapsas
      @nickchapsas  2 года назад

      No one said otherwise at any point. That's very clear even from the intro. The video focuses only on cases where it's used as control flow.

    • @johnandzelik3204
      @johnandzelik3204 2 года назад

      @@nickchapsas well from what I saw your service layer is checking on request validation. I did not see any looging of errors even when exceptions were being thrown. Which is why I gave my opinion. You should not validate at the service layer., the incoming request should be rejected immediately with appropriate messages. Your video did not adequately demonstrate the different use cases for both within an api design. (20 years experience) have a great day

  • @AlFasGD
    @AlFasGD 2 года назад +5

    12:50 19483 / 34396 = 0.566... > 1/2
    Not as worse as < 1/3, but still bad and the difference shows

  • @ronaldabellano5643
    @ronaldabellano5643 2 года назад

    I love it! Suggestions with performance test is the best!

  • @georgekerwood9100
    @georgekerwood9100 2 года назад

    Great video, thank you. I enjoyed the space and presentation, made for an enjoyable watch.

  • @Milk-gw1zl
    @Milk-gw1zl 2 года назад

    "Dont use exceptions". And uses it inner validator. 👏 I think here u just need dont use exception and just return message validation. And you dont need write any classes

  • @FarukLuki111
    @FarukLuki111 2 года назад +16

    What’s the performance difference on the „happy path“? You showed us the error and therefore the Exception case.
    I know this Video is about the exception topic (and I like this „other“ approach) but I am curious about the performance difference on happy paths .
    IMO being „faster“ when having an Exception is fine and I love your example but I never ran into issues/complains being (too) slow in error-cases.

    • @clementdurazzo4774
      @clementdurazzo4774 2 года назад

      The more time your api is using to respond, wether it’s an error or not, the more ressources you use, and you have to scale your application accordingly.
      When resource is money, like in the cloud word, time is of the essence regardless of the scenario.
      That said, it also work with a limited resource service. And when you can’t have the luxury to expand, resource still is an important issue.

    • @blackTmsk
      @blackTmsk 2 года назад +2

      @@clementdurazzo4774 It may be the case when you optimize an exception path by the cost of a happy path.

    • @clementdurazzo4774
      @clementdurazzo4774 2 года назад +1

      @@blackTmsk I’m more a OneOf user to use a more functional approch to deal with that kind of scenario.
      The truth is, your api could respond multiple responses. It’s logic your workflow should deal with multiple response and have method that can respond multiple “type” depending on the scenario. This way you don’t deal with “exception” but with “functionnalType”

    • @blackTmsk
      @blackTmsk 2 года назад +2

      @@clementdurazzo4774 I meant only the fact that this approach could make your happy path slower. Maybe it doesn't, it's hard to say without tests.

  • @MMMM-vc5oi
    @MMMM-vc5oi 2 года назад

    It is only simple callback, even worse than promise based approach. For complex scenarios, you probably face nested callbacks.
    For validation, I agree. It can be helpful

  • @WillApplebyUK
    @WillApplebyUK 2 года назад +1

    My philosophy has always been that exceptions are for unexpected scenarios, i.e. they should be 'exceptional' cases. Things like validating email address formats or even checking for duplicate usernames are not exceptional situations, so I wouldn't code them as such. The other issue with the exception flow demonstrated at the beginning here is that it's not intuitive that there is middleware to turn the exception back into a JSON response, you'd have to dig through the code to understand how this works. Using return values makes for a more linear flow and is easier to follow, especially for less experienced developers.

  • @drgusman
    @drgusman 2 года назад +1

    Exceptions should never be used to control regular program flow (as many people do) but be reserved to real problems that need to be debugged, and that's because whenever you throw an exception a stacktrace is created, that's what causes the exception control to be so slow compared to a result return, .net retrieves tons of information about the execution status of the program including many stackframes to be able to trace the executed code, that's only relevant when you need to debug your code, in any other case your program does not care at all at which line some exception was generated, it only cares about the type to use it as a result, what is a complete waste of resources.

  • @kostasgkoutis8534
    @kostasgkoutis8534 2 года назад

    I'm surprised to see this amount of negative comments. I get the criticism that basically all function interfaces return results/options/eithers and become more context aware that something N layers deep might blow up, but I would choose explicit over implicit structure of the code any time of the day. Let's add discriminated unions with case classes as well to push exceptions out as much as possible.
    PS: LanguageExt is simply an extraordinary library, it simply blows my mind how much dedication and hard work has been put into it. Paul Louth, and the rest of the contributors, if you read this, my sincerest respects!!

  • @wh33lers
    @wh33lers 2 года назад

    Thanks for sharing. Haven’t considered this approach yet, but my application heavily relies on exceptions. Will definitely take a look and see if it improves the situation. 👍

  • @NFSHeld
    @NFSHeld Год назад

    You know what? I've never seen your videos, but last week I re-invented the wheel by creating my own "Result" type.

  • @enquiresandeep
    @enquiresandeep 2 года назад +1

    I think it can add more value if the api needs to throw multiple validation exceptions , that way your code doesn’t exits out when it encounters the first THROW . But it will need a change to aggregate the Result before returning it .

  • @amimartian
    @amimartian 2 года назад

    An exception is exactly that - an exceptional state - i.e. something infrequent. Performance at that point is less of a concern.
    An operation which routinely expects a negative outcome (i.e. incorrect username), should handle this without the use of exception.
    However, the contraption you build is an overkill for such a scenario.

  • @Chris-pw7xm
    @Chris-pw7xm 2 года назад +1

    Personally, I use the visitor pattern to communicate the response from the application layer. This again will be translated to an IResult (or in this case an ActionResult) from the infrastructure layer.

    • @nickchapsas
      @nickchapsas  2 года назад +2

      As long as you don't blindly throw exceptions from any layer to catch them in the API layer, you have a better solution

  • @kia3d
    @kia3d Год назад

    Awesome video, to me also using middleware for exceptions make the code realy complex to follow. I have never used what you proposed here, but I have a extension created myself which does the same job. What I didn't know and never tried though, was the performance punishment.
    Thanks for sharing

  • @LuigiTrabacchin
    @LuigiTrabacchin 2 года назад +7

    Ok, but there is one thing that get's in the way here: the exception is an exception, should not happen all the time, the test is spamming with wrong requests, that is more similar to a dos attack than a real scenario... hence a way to delay requests from same ip, session, api key (definetely the way) , whatever would be way more useful than this

    • @DryBones111
      @DryBones111 2 года назад +2

      Yes, and to further the argument; domain exceptions are expected failures of code. They are not exceptional in the sense that failure to connect to a database is an exception. You should be planning for failure within the problem domain.

  • @ОлександрОлександрович-е4о

    Functional-style programming is our future. Fantastic video Nick. It is rather funny, that I have naturally evolved to the same conclusions and even similar extension for controller in my work. However now I use CSharpFunctionalExtensions for a bit more monad honey.

    • @Micro-Moo
      @Micro-Moo Год назад +1

      It is only the illusion of functional programming and functional style. Bad analysis and bad advise.

  • @ВладиславДараган-ш3ф
    @ВладиславДараган-ш3ф 2 года назад +5

    If your web API calls to some heavy business logic or even internal services, that can take WAY much time, so performance hit from using exceptions is neglected anyway. Also I don't understand why the code flow is worse to understand or follow with exceptions. If u throw, it goes up on call stack, until it will be cought (by your code or framework) or unhandled (of course this should be avoided).

    • @ВладиславДараган-ш3ф
      @ВладиславДараган-ш3ф 2 года назад

      My point is your benchmark can't be more synthetic, and of course you shouldn't throw anything on validation, because validation fail is not an exceptional situation.
      In real world however Result-approach will cause too much visual clutter in code.

    • @ВладиславДараган-ш3ф
      @ВладиславДараган-ш3ф 2 года назад

      Unfortunately C# still doesn't support discriminated unions, and all this "Result" stuff is just mimicking this mechanism and poorly

    • @nickchapsas
      @nickchapsas  2 года назад

      It’s worse because it’s not obvious. All I see I someone throwing an exception. The middleware might not even be part of my codebase. It is obscure and hard to debug behaviour. It’s like using a goto to a different file. Just a red flag

    • @ВладиславДараган-ш3ф
      @ВладиславДараган-ш3ф 2 года назад +2

      @@nickchapsas ok, but this is debatable, because exceptions are native mechanism and pretty much every OOP-dev should be aware of it, and use it wisely.
      I'm not a fan of Java-way with throwing every time, but some situations are really exceptional, like dropped connection to DB or similar serious failure. And in that kind of situations I as developer don't wont to mix them with BL errors and wrapping them in Result like nothing happened.
      This debate from that perspective is very similar to "Returning BL errors from API with 200", and I understand pro's and con's, but ...

    • @luvincste
      @luvincste 2 года назад

      to me all of this comes down to discipline vs an imperfect and striving world: are you disciplined enough to apply the same techniques to all of your methods/classes? are you disciplined enough to use your own types of exceptions for different kinds of errors? are you disciplined enough to document them? and to catch and recover from all the exceptions that the methods you are calling throws (the ones you need to)? part of us will answer with no, too much time, too much to study, and son, so we're here to discuss about the middle grounds

  • @prettywiththelightsout
    @prettywiththelightsout 2 года назад +3

    Hmmm. I'm torn on this. I like the concept, but I also like throwing specific result exceptions in my service layer ala throw new ApiServiceException(Exception ex, int statusCode) and letting the filter deal with it. I try to not depend on a lot of packages like this, but I'm curious. I may give it a go and see how I like it. Always love your content though Nick!

  • @bondarenkodf
    @bondarenkodf 2 года назад

    Wow, thank you for the library - language-ext. I've started using such a method as you described many years ago developing mobile apps. With a slightly different approach, but anyway.

  • @chesthar
    @chesthar 2 года назад +1

    While it looks cool and faster to use some third-party libraries over the built-in functionality, this comes always with a price. I'm personally against using something without thorough examination if your project really needs it, the team who will work on the project to agree, and potentially at the beginning the code reviews might take longer than usual because it's a new thing, and also any new joiner will have to reserve more time get familiar with this approach, and when the project depends on something external you should add the risk of potentially adapting to breaking changes which is a big deal when you have to come with a deadline for delivery and planning for the project itself. @Nick Chapsas can you make a video about when you should use a third-party library and when not, and what to consider before you do the final choice? I think it would be an interesting topic for everyone.

  • @TheRoundedTable
    @TheRoundedTable Год назад

    I prefer making purpose based custom result wrappers, if I have "expected" failure states. For example, in this case we could use the classic ModelStateDictionary. It serves the same purpose as a result wrapper, but is also validation specific.
    I understand it's far from the same thing as the demonstrated monad. However, it makes it so that you can customize your wrappers and their use becomes a part of business logic instead of being wrapped in a generic contract.

  • @dimitris470
    @dimitris470 Год назад

    Yeah totally agree that the exception and middleware acts like a hidden quasi event dispatcher, and leaks business code all over the place. The mapping between success/error/result is a matter that should be handled in a self-contained Service and encapsulated from your controllers and framework glue code. The Result TBH does not feel natural for code that should be easily readable by C# people.

  • @TimMattison
    @TimMattison 2 года назад

    Exactly what I needed this morning, thanks!

  • @noelfrancisco5778
    @noelfrancisco5778 2 года назад

    This is a very helpful tip, something i can use in my boilerplate. Thanks :)

  • @ebrahimmansur9815
    @ebrahimmansur9815 2 года назад +1

    I love your content mate ...... its a good approach which i always use in other frameworks like flutter
    i like to make it a generic type with to values [left and right ] the idea is to pass in 2 option in which a you return left T if error and right T if success and call the result method to pass in a 2 callback functions.....i hope it helped.
    class ResultWrapper {
    late TLeft _leftType;
    late TRight _rightType;
    final bool _isLeftType;
    ResultWrapper._();

    ResultWrapper.left(TLeft type) : _isLeftType = true;
    ResultWrapper.right(TRight type) : _isLeftType = false;
    void result(Function(TLeft left) onLeft, Function(TRight right) onRight) =>
    _isLeftType ? onLeft(_leftType) : onRight(_rightType);
    bool get isLeft => _isLeftType;
    bool get isRight => _isLeftType != true;
    }

    • @nickchapsas
      @nickchapsas  2 года назад +1

      Thanks! This looks more like a Either/Option/Optional type than a Result one. The result one is very specific about the error side of things. Same concept and still a monad but different purpose.

    • @ebrahimmansur9815
      @ebrahimmansur9815 2 года назад

      @@nickchapsas
      totally agree brother , loving what your doing with the channel

    • @codeforme8860
      @codeforme8860 2 года назад

      I have seen people use trupals in the past

    • @reymarkandog1441
      @reymarkandog1441 2 года назад

      May you explain more what is left and right approach?

  • @CarmenSantiNova
    @CarmenSantiNova 2 года назад +7

    Even though I see the benefits of this approach, which I used before I started using exceptions, I see some limitations as well.
    One of which is the bubbling concept of exceptions.
    In my code I have multiple layers in my business code. Services using services and so on.
    That means that if a nested service throws it will bubble all the way to top. This wouldn't be possible with this approach.
    Instead I would wrap my throwing code with the following in those cases I do want to control the flow of the code. Basically a shorthand of try-catch...
    var result = Result.Handle(() => myOriginalMethod());

  • @AaronShumaker
    @AaronShumaker Год назад

    The problem with all of these roll-your-own approaches to error handling is they require the entire application to adopt this pattern which isn't realistic in practice, and what happens in practice is we inherit a project that already has one of a thousand different approaches to roll-your-own error handling that vary in quality. Usually the quality is the lowest common denominator of the quality of the author's approach and the quality of the implement's skill, with a roll of the dice as to whether the implementer selected a good or bad roll-your-own approach from a random article/video. It would be great if someone evangelized what is supported out of the box, but rarely implemented correctly because it is drowned out by all of these: just having a root exception handler, never catching exceptions in intermediate layers, using throw; appropriately to preserve stack traces, and using .Data to add contextual information for the root exception handler to log instead of sprinkling log statements all over the app.

  • @mohitkumar-vm6zl
    @mohitkumar-vm6zl Год назад

    Them : How complex code you want.
    Him: YES

  • @akimbbo_upnext
    @akimbbo_upnext 2 года назад +1

    Im doing something similar. I have my own type Result and T for me are enums which indicate what went wrong during processing request. Also my folder structure is more 'domain' oriented so my top level folders are like Users/Books/Orders etc. By doing that i can also fit those enums in folder where i place files related to specific process.

  • @ivandrofly
    @ivandrofly 2 года назад +1

    4:20 Benchmark API endpoint
    6:15 LanguageExt.Core (monad)

  • @XXnickles
    @XXnickles 2 года назад +2

    Unfortunately most of the OO languages do not have any other way to model errors with another mechanism that is not exceptions. Also, composing objects is "hard" (the solution has been typically Interfaces + DI), which also add to the problem. Language ext is a "patch" to bring more functional patterns, but unfortunately I found most people a) do not understand them and go back to the procedural way and b) Language ext is extremely verbose (mostly because C#). For those reasons, I just used in a project and not used anymore, too many drawbacks for team work that don't catch up with the benefits. The "Today Available" implementation for this is F#, but if they finally implement discriminate unions in C#, I think we will finally be able to see more functional patterns go mainstream in the dotnet work and being embraced instead of rejected by default

  • @MatthewCrawford
    @MatthewCrawford 2 года назад +5

    My training taught me that exceptions were for actual errors and not to ever be used for application logic
    We should not be lazy and use exceptions instead of properly implementing business logic

  • @swedishprogrammer
    @swedishprogrammer 2 года назад +1

    Awesome content, thanks for that! 😍

  • @nicholash8021
    @nicholash8021 12 дней назад

    99.9% of the time, we don't throw exceptions (they are, after all, exceptions), so the performance benefits are not worth the trouble of writing all this boilerplate code and polluting my service layers with result wrappers while bypassing the reason exceptions were invented in the first place. However, this video was useful because it highlights some tricks that you might use in other scenarios.