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

2022 ж. 1 Мау.
244 530 Рет қаралды

Check out my new "Integration testing in ASP .NET Core" course and use code INTESTING1 for 20% off (first 300): dometrain.com/course/from-zer...
Become a Patreon and get source code access: / nickchapsas
Hello everybody I'm Nick and in this video I will show you how you can potentially replace some of your exception throwing in your C# application with a differnent way of handling bad state. It is a concept that is native in other languages like Rust and F#, and it will eventually make its way into C# too but for now, here is the workaround.
Give Language Extensions a star on GitHub: github.com/louthy/language-ext
Don't forget to comment, like and subscribe :)
Social Media:
Follow me on GitHub: bit.ly/ChapsasGitHub
Follow me on Twitter: bit.ly/ChapsasTwitter
Connect on LinkedIn: bit.ly/ChapsasLinkedIn
Keep coding merch: keepcoding.shop
#csharp #dotnet

Пікірлер
  • 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.

    @Tal__Shachar@Tal__Shachar Жыл бұрын
    • You sure know how to make them feel exceptional

      @nickchapsas@nickchapsas Жыл бұрын
    • Lol

      @LostDotInUniverse@LostDotInUniverse Жыл бұрын
    • If you don't throw exceptions, your boss think you are better, that's why I keep them for myself

      @diligencehumility6971@diligencehumility6971 Жыл бұрын
    • @@nickchapsas im crying here 🤣

      @Donder1337@Donder1337 Жыл бұрын
    • Lame, I just catch all exceptions and do nothing with them, silent fail is the way to go :p

      @quickben3672@quickben3672 Жыл бұрын
  • I want a two hour video of Nick explaining what monads are, gradually becoming more and more wild-eyed and frantic as it progresses

    @jackkendall6420@jackkendall6420 Жыл бұрын
    • That would be intresting

      @LuigiTrabacchin@LuigiTrabacchin Жыл бұрын
    • As long as it ends with "...and that's it" I'll feel good about it.

      @dcuccia@dcuccia Жыл бұрын
    • A monad is just a monoid in the category of endofunctors, what's the problem?

      @orterves@orterves Жыл бұрын
    • @@orterves I love Scott Wlaschin

      @dcuccia@dcuccia Жыл бұрын
    • Are you getting visions of Charlie Day from Its Always Sunny in Philadelphia (IASIP) connecting string to images vibe? Cause I am :D

      @kj2w@kj2w Жыл бұрын
  • 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.

    @randomcodenz@randomcodenz Жыл бұрын
    • 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.

      @treebro1550@treebro1550 Жыл бұрын
    • > The code rapidly becomes unwieldy Bcs we have Results and have no (>>=). Don't use monads in lang that can't handle it.

      @maybe4900@maybe4900 Жыл бұрын
    • @@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.

      @endofunk2174@endofunk2174 Жыл бұрын
    • 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

      @JS-1962@JS-1962 Жыл бұрын
    • 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.

      @PietjePotloodzzzzzz@PietjePotloodzzzzzz Жыл бұрын
  • 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.

    @jammycakes@jammycakes Жыл бұрын
    • 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

      @alexandredaubricourt5741@alexandredaubricourt57419 ай бұрын
    • 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.

      @acasualviewer5861@acasualviewer58616 ай бұрын
    • 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...

      @Sahuagin@Sahuagin5 ай бұрын
    • @@Sahuagin 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.

      @acasualviewer5861@acasualviewer58615 ай бұрын
    • @@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.

      @Sahuagin@Sahuagin5 ай бұрын
  • 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.

    @heinzk023@heinzk023 Жыл бұрын
    • It's only because that legacy code didn't follow DI practices.

      @wlbmonizuka@wlbmonizuka Жыл бұрын
    • No. You can still throw exceptions for truly exceptional situations, but avoid doing this for control flow.

      @raphaelbatel@raphaelbatel11 ай бұрын
    • @@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#)

      @acasualviewer5861@acasualviewer58616 ай бұрын
  • 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.

    @DaliborCarapic@DaliborCarapic Жыл бұрын
    • Great comment!

      @FlumpStudiosUK@FlumpStudiosUK Жыл бұрын
    • 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.

      @thethreeheadedmonkey@thethreeheadedmonkey Жыл бұрын
    • @@thethreeheadedmonkey lol@ how is 19.5 20% of 34.3? you mental?

      @cla2008@cla2008 Жыл бұрын
    • 100% agree.

      @christoph6055@christoph6055 Жыл бұрын
    • @@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).

      @DaliborCarapic@DaliborCarapic Жыл бұрын
  • 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.

    @Rick-mf3gh@Rick-mf3gh Жыл бұрын
  • 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.

    @BrendonParker@BrendonParker Жыл бұрын
    • 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.

      @michawhite7613@michawhite7613 Жыл бұрын
    • 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.

      @Osbornesupremacy@Osbornesupremacy Жыл бұрын
    • Also it easier for monitoring. Wihtout exception your app looks like everything Ok but it is not.

      @andreikniazev9407@andreikniazev9407 Жыл бұрын
    • 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.

      @martinjuranek3095@martinjuranek3095 Жыл бұрын
    • 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.

      @RemX405@RemX405 Жыл бұрын
  • 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.

    @sleeper789@sleeper789 Жыл бұрын
    • 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.

      @nickchapsas@nickchapsas Жыл бұрын
    • @@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.

      @sleeper789@sleeper789 Жыл бұрын
    • @@sleeper789 On any layer? In any method? In any delegate and function? Sounds a bit of a red flag to me.

      @nickchapsas@nickchapsas Жыл бұрын
    • ​@@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.

      @sleeper789@sleeper789 Жыл бұрын
    • @@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.

      @EdubSi@EdubSi Жыл бұрын
  • 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.

    @whatisgosu@whatisgosu Жыл бұрын
    • 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...

      @Micro-Moo@Micro-Moo Жыл бұрын
    • @@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

      @triebb@triebb Жыл бұрын
    • @@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.

      @Micro-Moo@Micro-Moo Жыл бұрын
    • @@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.

      @gileee@gileee13 күн бұрын
  • 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!

    @oldwhitowl@oldwhitowl Жыл бұрын
  • 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!

    @Funestelame@Funestelame Жыл бұрын
  • 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.

    @Ailurophopia@Ailurophopia Жыл бұрын
  • 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.

    @EduardQualls@EduardQualls Жыл бұрын
  • 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_

    @ronsijm@ronsijm Жыл бұрын
    • 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.

      @nickchapsas@nickchapsas Жыл бұрын
    • @@nickchapsas well what I mean is, what you're doing at kzhead.info/sun/lJWylZ2dfoZ4bHA/bejne.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)

      @ronsijm@ronsijm Жыл бұрын
    • @@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.

      @zibizz1@zibizz1 Жыл бұрын
    • 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.

      @tjakevladze@tjakevladze Жыл бұрын
    • @@nickchapsas That would be a denial of service attack, for which there are many vectors, and perhaps is outside the scope of this discussion.

      @evilpigeon4976@evilpigeon4976 Жыл бұрын
  • 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.

    @notpingu@notpingu Жыл бұрын
    • 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.

      @michawhite7613@michawhite7613 Жыл бұрын
    • 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.

      @EddieVillamor@EddieVillamor Жыл бұрын
    • 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

      @seannewell397@seannewell397 Жыл бұрын
    • @@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

      @powerclan1910@powerclan1910 Жыл бұрын
    • 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.

      @Denominus@Denominus Жыл бұрын
  • 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.

    @evilpigeon4976@evilpigeon4976 Жыл бұрын
  • 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!

    @anatolia23@anatolia23 Жыл бұрын
  • 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!

    @SchadenNZ@SchadenNZ Жыл бұрын
  • 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. 👍

    @wh33lers@wh33lers Жыл бұрын
  • I love it! Suggestions with performance test is the best!

    @ronaldabellano5643@ronaldabellano5643 Жыл бұрын
  • 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.

    @floppabellic@floppabellic Жыл бұрын
  • Great video, thank you. I enjoyed the space and presentation, made for an enjoyable watch.

    @georgekerwood9100@georgekerwood9100 Жыл бұрын
  • 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.

    @craigfreeman8225@craigfreeman8225 Жыл бұрын
  • This is gold! Thank you so much Nick. I will refactor my code in the project I'm working for my company.

    @bnotorious1@bnotorious1 Жыл бұрын
  • Exactly what I needed this morning, thanks!

    @TimMattison@TimMattison Жыл бұрын
  • 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.

    @dgschrei@dgschrei Жыл бұрын
    • 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.....

      @timwood5995@timwood5995 Жыл бұрын
    • 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.

      @protox4@protox4 Жыл бұрын
    • 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.

      @Thial92@Thial92 Жыл бұрын
    • @@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.

      @protox4@protox4 Жыл бұрын
    • @@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.

      @Thial92@Thial92 Жыл бұрын
  • 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.

    @jimread2354@jimread2354 Жыл бұрын
  • 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.

    @ryanobray1@ryanobray1 Жыл бұрын
  • 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!

    @anderslinden4701@anderslinden4701 Жыл бұрын
    • 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.

      @acasualviewer5861@acasualviewer58616 ай бұрын
    • @@acasualviewer5861 Monads are over 30 years old what are you on about

      @saymehname@saymehname3 ай бұрын
  • At 8:17, you could get away with the implicitly typed `return new(validationException)` instead of `return new Result(validationException)`.

    @ScottBlomquist@ScottBlomquist Жыл бұрын
  • This video is so exceptional, it really changed the way I work with exceptions

    @redslayer973@redslayer973 Жыл бұрын
  • 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.

    @oggatog3698@oggatog3698 Жыл бұрын
    • 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.

      @vladimirlazarevic9792@vladimirlazarevic9792 Жыл бұрын
    • 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.

      @Micro-Moo@Micro-Moo Жыл бұрын
  • 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 Жыл бұрын
    • And you don’t have to create an exception when a simple ErrorType can be enough.

      @clementdurazzo4774@clementdurazzo4774 Жыл бұрын
  • 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.

    @bondarenkodf@bondarenkodf Жыл бұрын
  • 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

    @kia3d@kia3d Жыл бұрын
  • That's a great video Nick !

    @neighborhooddev@neighborhooddev Жыл бұрын
  • 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.

    @efrenb5@efrenb5 Жыл бұрын
  • 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.

    @FarukLuki111@FarukLuki111 Жыл бұрын
    • 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.

      @clementdurazzo4774@clementdurazzo4774 Жыл бұрын
    • @@clementdurazzo4774 It may be the case when you optimize an exception path by the cost of a happy path.

      @blackTmsk@blackTmsk Жыл бұрын
    • @@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”

      @clementdurazzo4774@clementdurazzo4774 Жыл бұрын
    • @@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.

      @blackTmsk@blackTmsk Жыл бұрын
  • Awesome content, thanks for that! 😍

    @swedishprogrammer@swedishprogrammer Жыл бұрын
  • Very good video. Show us how much performance is lost by throwing and catching exceptions.

    @IAmFeO2x@IAmFeO2x Жыл бұрын
  • Great content as usual Nick! Me and my team use LanguageExt for all our code since 3 years and we just love it, what is your opinion about it and more generically about functional programming in C#? Cheers!

    @sergiomaestrello4750@sergiomaestrello4750 Жыл бұрын
  • Absolutely amazing !

    @alexwexov4298@alexwexov4298 Жыл бұрын
  • This is a very helpful tip, something i can use in my boilerplate. Thanks :)

    @noelfrancisco5778@noelfrancisco5778 Жыл бұрын
  • 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.

    @cartsp@cartsp Жыл бұрын
    • 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

      @XXnickles@XXnickles Жыл бұрын
    • ​@@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.

      @cartsp@cartsp Жыл бұрын
  • 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.

    @CoderCoronet@CoderCoronet Жыл бұрын
    • It's interesting that neither Nick or the other commenters discuss this. Validation errors are validation errors and not exceptions!

      @dwhxyz@dwhxyz Жыл бұрын
  • 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.

    @RealCheesyBread@RealCheesyBread Жыл бұрын
  • 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.

    @olegvorkunov5400@olegvorkunov5400 Жыл бұрын
  • 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!

    @prettywiththelightsout@prettywiththelightsout Жыл бұрын
  • It is a good video, GOlang is fast, actually following this approach. Go usage error as string to pass through rather than panicking directly.

    @alim.karim.@alim.karim. Жыл бұрын
  • Thanks! Great info that I can put into use immediately. One question: Which Oh-My-Posh theme are you using here in your terminal?

    @bitbonk@bitbonk Жыл бұрын
  • Great video! Very clarifying by the way... I always struggle using a better approach for exceptions because of business logic and control flow... I will probably try this! Is the code used in this video on your github? Thank you!

    @eddorneles@eddorneles Жыл бұрын
  • Amazing man, love your videos!

    @MrSamisack@MrSamisack Жыл бұрын
  • 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

    @rogiawos8737@rogiawos8737 Жыл бұрын
  • 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.

    @chesthar@chesthar Жыл бұрын
  • 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.

    @r14958@r14958 Жыл бұрын
  • Would be nice to go into the monad part of it, because one of the main reasons for having a monad like this is error propagation (i.e. chaining), just like exceptions. In functional languages, this would be mostly invisible due to do-notation (like async/await). It would look almost like the initial code with exceptions. You allude to it in the video, but I think the main point here is that you have "unknown errors" and "known failures", and you want to be explicit about the known failures. If the result monad is mainly about error propagation, it's not really that different from exceptions imo. But I can see that it's a nice, explicit, standardized way of doing it (as opposed to returning tuples/TryGet/return null, etc)

    @uglypie182@uglypie182 Жыл бұрын
  • 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.

    @AaronShumaker@AaronShumaker8 ай бұрын
  • You know what? I've never seen your videos, but last week I re-invented the wheel by creating my own "Result" type.

    @NFSHeld@NFSHeld Жыл бұрын
  • Great video Nick! I am curious if there was any delay on the happy-path. See you in Oslo!

    @edvin3956@edvin3956 Жыл бұрын
  • 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.

    @josephwilliamson7350@josephwilliamson7350 Жыл бұрын
  • I would be interested in hearing more on when it's appropriate to use this pattern vs throwing the exception. Validation of input is a good fit for this and handing unexpected exceptions from libraries are not. What about network errors accessing your data store or other services? I think one of the major performance improvements is that the system isn't using reflection to generate the stack trace for the exception. I personally almost always use the stack trace to start debugging an issue with a system. I guess I would error on the side of if I really thought the stack trace will help me I would throw the exception.

    @JoeStevens@JoeStevens Жыл бұрын
    • If it's just input validation then why throw the exception? You are already in the top layer so just validate and return the status appropriate to the validation result.

      @nickchapsas@nickchapsas Жыл бұрын
    • To answer your question you have to take the definition of an exception. An exception is an unexpected and most of the time unrecoverable event happening. A user entering a wrong value is something you can expect, so don't use exceptions. I used to write coding guidelines. One of them was "don't use exceptions to write normal business flow of your application" (don't remember the exact wording). So, to come back to the example in the video. I would not name it "ValidationException", becuase it is not an exception. Name it something like "ValidationError", because, thats what it is, a validation error. You expect users to make a mistake, you even write unit tests, to test this (one of the alternative flows). You normally don't write unit tests for "out of memory exception".

      @Kristof473@Kristof473 Жыл бұрын
  • (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

    @wojciechwilimowski985@wojciechwilimowski985 Жыл бұрын
  • 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.

    @cesarcmc@cesarcmc Жыл бұрын
  • 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.

    @enricobuonanno@enricobuonanno Жыл бұрын
    • 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

      @nickchapsas@nickchapsas Жыл бұрын
    • 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.

      @Lammot@Lammot Жыл бұрын
    • Functional Programming in C# is like a pot of gold at the end of the rainbow.

      @derekkaneshiro3935@derekkaneshiro3935 Жыл бұрын
  • 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.

    @mohammedelsuissey1745@mohammedelsuissey1745 Жыл бұрын
    • 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

      @creativemanix@creativemanix Жыл бұрын
  • 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 .

    @enquiresandeep@enquiresandeep Жыл бұрын
  • ITS REALLY WORKED LOL THANK YOU DUDE

    @carlospavajeau7179@carlospavajeau7179 Жыл бұрын
  • Them : How complex code you want. Him: YES

    @mohitkumar-vm6zl@mohitkumar-vm6zl Жыл бұрын
  • Thanks for the video. How about Use case based approach, with inversion of control - when we passed reference to API controller (infrastructure layer) into buisness logic layer method to prepare the operation result (following the DDD and clean architecture principles)?

    @user-ji7qu7bd4w@user-ji7qu7bd4w Жыл бұрын
  • As usual you are very informative. I do have a question. Another youtuber that I watch that teaches on C# that I also respect and learn a lot from, promotes throwing the exception. I have to be honest - I'm not a big fan of throwing the exception, rather prefer something closer to what you do but likely for different reasons. I'm "old school" - that is every routine has one entry and one and only one exit point. Throwing exceptions breaks that rule. His argument for throwing an exception was that you don't have to trust that the upstream logic will handle the exceptions. If it's an error and they don't handle it the program continues and the user is none the wiser even though they may not have the desired results. It enforces handling the exception. I get that and can appreciate that line of thinking. I'm asking - what is your thoughts about his position of enforcing upstream logic to deal with the exceptions?

    @rockymarquiss8327@rockymarquiss8327 Жыл бұрын
  • 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.

    @TiagoCastilhos@TiagoCastilhos Жыл бұрын
    • 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.

      @aschwinwesselius@aschwinwesselius Жыл бұрын
  • Great video Nick, unrelated question, what tool do you use to annotate (arrows\squares) on screen?

    @dorlg6655@dorlg6655 Жыл бұрын
    • It's called ZoomIt

      @nickchapsas@nickchapsas Жыл бұрын
  • Thanks for the video and all your content, what IDE are you using?

    @guillermomazzari4983@guillermomazzari4983 Жыл бұрын
    • JetBrains Rider

      @nickchapsas@nickchapsas Жыл бұрын
  • 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.

    @TheRoundedTable@TheRoundedTable8 ай бұрын
  • 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.

    @skypravda@skypravda Жыл бұрын
    • 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.

      @nickchapsas@nickchapsas Жыл бұрын
    • 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...

      @jackkendall6420@jackkendall6420 Жыл бұрын
    • @@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!

      @skypravda@skypravda Жыл бұрын
    • @@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.

      @DisturbedNeo@DisturbedNeo Жыл бұрын
    • @@DisturbedNeo SysAdmin? I can't remember the last time I worked with a Sys admin

      @nickchapsas@nickchapsas Жыл бұрын
  • 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

    @pjuliano9000@pjuliano9000 Жыл бұрын
  • Thanks!

    @sanjevsaalaivazan7786@sanjevsaalaivazan7786 Жыл бұрын
  • Hi Nick, thanks for the video. Did you had a video where you created the .ToProblemDetails() extension method? Thanks

    @RichardJohn86@RichardJohn86 Жыл бұрын
  • 4:20 Benchmark API endpoint 6:15 LanguageExt.Core (monad)

    @ivandrofly@ivandrofly Жыл бұрын
  • 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.

    @Chris-pw7xm@Chris-pw7xm Жыл бұрын
    • As long as you don't blindly throw exceptions from any layer to catch them in the API layer, you have a better solution

      @nickchapsas@nickchapsas Жыл бұрын
  • Another great video Nick! Question - is there any performance difference between Result and exceptions when the API is getting hit with a valid request?

    @brianm1864@brianm1864 Жыл бұрын
    • In valid requests you don't see a performance degradation. Result is a struct so in terms of memory it's optimized but it is using Func to match so if you wanna be super technical then there will be an extremely small decrease with it but nothing you will notice and nothing that comes even close to the impact of exceptions both in performance code and code quality

      @nickchapsas@nickchapsas Жыл бұрын
  • 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.

    @zvado@zvado Жыл бұрын
  • 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; }

    @ebrahimmansur9815@ebrahimmansur9815 Жыл бұрын
    • 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.

      @nickchapsas@nickchapsas Жыл бұрын
    • @@nickchapsas totally agree brother , loving what your doing with the channel

      @ebrahimmansur9815@ebrahimmansur9815 Жыл бұрын
    • I have seen people use trupals in the past

      @codeforme8860@codeforme8860 Жыл бұрын
    • May you explain more what is left and right approach?

      @reymarkandog1441@reymarkandog1441 Жыл бұрын
  • 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

    @fswadling@fswadling10 күн бұрын
  • "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

    @Milk-gw1zl@Milk-gw1zl Жыл бұрын
  • 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.

    @nilsberghs3@nilsberghs36 ай бұрын
  • You showed off OneOf some time ago and I was very impressed with that library since it seems to also allow you to literally return different types in the same method using compile time code generation behind the scenes. Would you still prefer this library over that for such a case? 'Cause in that case you could just either return the value or the exception and not worry about wrapping it in a Result-object.

    @buriedstpatrick2294@buriedstpatrick2294 Жыл бұрын
    • So OneOf is more of an investment. It changes the way you pretty much return everything. This video focuses on Result which is specific to exceptions. It's kind of the same concept but we deal with it in different ways. It is easier if you base everything in your app already with exceptions to just return Result and handle that. OneOf is a complete mindset change.

      @nickchapsas@nickchapsas Жыл бұрын
    • @@nickchapsas Thanks for the quick reply! Now I definitely want to try and play around with that. Keep up the amazing work!

      @buriedstpatrick2294@buriedstpatrick2294 Жыл бұрын
    • @@nickchapsas Update: I just tried this out and it really isn't that different, at least from a brief PoC. You could just return a OneOf if you wanted to replicate the same behavior. Of course, to really get the most out of OneOf, you'll probably want to wrap that ValidationException or somehow transform it into a custom domain object like OneOf. Maybe I'm missing something?

      @buriedstpatrick2294@buriedstpatrick2294 Жыл бұрын
  • You don't have to catch your exceptions in that middleware layer, personally I prefer catching them in the Controller, it retains the linear flow of operations while also allowing you to identify what error responses are sent on a per-route basis. The hidden performance cost is the generation of the StackTrace when the exception is thrown, it would be nice to disable StackTrace generation but I've yet to find a way to do that.

    @LinxOnlineGames@LinxOnlineGames Жыл бұрын
  • 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).

    @macgyverswissarmykni@macgyverswissarmykni Жыл бұрын
    • I've been considering introducing it in my code bases - what would you say are the biggest concrete obstacles in team buy-in?

      @thethreeheadedmonkey@thethreeheadedmonkey Жыл бұрын
  • 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.

    @pg_pete@pg_pete Жыл бұрын
  • 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.

    @donparkison4617@donparkison4617 Жыл бұрын
  • Can you make a video showing the benefits of using this extension library ?

    @noctavel@noctavel Жыл бұрын
  • Nick Chapsas , but isn't it actually about using Task instead of Task? And using Validators with some kind of yielding of errors list? I mean, this library may be really cool, but what are benefits against using things we have in the box? I actually don't see any benefits exepts time for writing some code that this library already have or maybe some different code to wire all things together. So am i right or wrong and in what exactly?

    @efimov90@efimov90 Жыл бұрын
    • This isn’t just about validation. It’s just used as the most abused usecase. And on your point, endpoints validators validate api layer concerns. You normally don’t leak your domain validation logic to your api level validation

      @nickchapsas@nickchapsas Жыл бұрын
    • ​@@nickchapsas, yes it's not about validation, but as i see in this video it shown how bad is middleware for collecting exceptions, but what if we make try block for endpoint logick? And use same IActionResult Ok for good precessing and BadRequest for handling errors? For sure you can make some private function or extension method for remove it from endpoint if blocks of code is similar or some other reasons? We will stay with exceptions but with try block we don't need middleware is it sill so slow or in that case we get something similar to that library functionality shown in this video? Excepts of collecting all of the errors? Or i get all of this completely wrong?

      @efimov90@efimov90 Жыл бұрын
  • Many thanks broh

    @titiksasanti2205@titiksasanti2205 Жыл бұрын
  • This technique could be good for a single level request handlers, but it absolutely fails if your call stack contains many-man -many levels of business logic and at the level 41 there is a check that says, that the request was invalid and could not be executed. So you need to return this result through all previous 40 levels and also modify it some way because the top level code knows nothing about low level data structures. As a result you get an additional level of complexity on EACH level just because you should add if(errorResult != null) after each inner call and add error result to each data structure returned.

    @user-lu8vb1pm9p@user-lu8vb1pm9p7 ай бұрын
  • 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.

    @jegtugado3743@jegtugado3743 Жыл бұрын
    • 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… 😥

      @clementdurazzo4774@clementdurazzo4774 Жыл бұрын
    • 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.

      @ftrueck@ftrueck Жыл бұрын
    • @@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.

      @PelFox@PelFox Жыл бұрын
    • @@AbNomal621 until you are affected by it. Then you ask yourself why you did not think about it earlier...

      @ftrueck@ftrueck Жыл бұрын
  • I wonder if it would be possible for Microsoft to optimize exceptions. This could be handled by the compiler by using a Result type implicitly. This way we would have the easy way out "throw" and at the same time something fast. Or maybe using a new syntax, though I already find that writing "async" and "Task" seems redundant.

    @circular17@circular17 Жыл бұрын
  • I'm thinking, so, using this pattern of wrapping the exception inside the result - what's the advantage of using exceptions? For "business errors" like validations, user not found, etc., you can just use an object with error code (enum value), error description, etc.

    @shayvt@shayvt Жыл бұрын
  • Good man!

    @xtinctspecies@xtinctspecies Жыл бұрын
KZhead