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

  Рет қаралды 262,442

Nick Chapsas

Nick Chapsas

Күн бұрын

Пікірлер: 636
@Tal__Shachar
@Tal__Shachar 2 жыл бұрын
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 жыл бұрын
You sure know how to make them feel exceptional
@LostDotInUniverse
@LostDotInUniverse 2 жыл бұрын
Lol
@diligencehumility6971
@diligencehumility6971 2 жыл бұрын
If you don't throw exceptions, your boss think you are better, that's why I keep them for myself
@Donder1337
@Donder1337 2 жыл бұрын
@@nickchapsas im crying here 🤣
@quickben3672
@quickben3672 Жыл бұрын
Lame, I just catch all exceptions and do nothing with them, silent fail is the way to go :p
@randomcodenz
@randomcodenz 2 жыл бұрын
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 жыл бұрын
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 жыл бұрын
> 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 жыл бұрын
@@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 жыл бұрын
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 жыл бұрын
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 жыл бұрын
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 жыл бұрын
That would be intresting
@dcuccia
@dcuccia 2 жыл бұрын
As long as it ends with "...and that's it" I'll feel good about it.
@orterves
@orterves 2 жыл бұрын
A monad is just a monoid in the category of endofunctors, what's the problem?
@dcuccia
@dcuccia 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
@jammycakes
@jammycakes Жыл бұрын
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 Жыл бұрын
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 Жыл бұрын
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 Жыл бұрын
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 Жыл бұрын
@@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 Жыл бұрын
@@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.
@heinzk023
@heinzk023 Жыл бұрын
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 Жыл бұрын
No. You can still throw exceptions for truly exceptional situations, but avoid doing this for control flow.
@acasualviewer5861
@acasualviewer5861 Жыл бұрын
@@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#)
@BrendonParker
@BrendonParker 2 жыл бұрын
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 жыл бұрын
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 жыл бұрын
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 жыл бұрын
Also it easier for monitoring. Wihtout exception your app looks like everything Ok but it is not.
@martinjuranek3095
@martinjuranek3095 2 жыл бұрын
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 жыл бұрын
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.
2 жыл бұрын
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 жыл бұрын
Great comment!
@thethreeheadedmonkey
@thethreeheadedmonkey 2 жыл бұрын
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 жыл бұрын
@@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).
@Rick-mf3gh
@Rick-mf3gh Жыл бұрын
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.
@sleeper789
@sleeper789 2 жыл бұрын
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 жыл бұрын
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 жыл бұрын
@@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 жыл бұрын
@@sleeper789 On any layer? In any method? In any delegate and function? Sounds a bit of a red flag to me.
@sleeper789
@sleeper789 2 жыл бұрын
​@@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 жыл бұрын
@@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.
@whatisgosu
@whatisgosu 2 жыл бұрын
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 Жыл бұрын
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 Жыл бұрын
@@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 Жыл бұрын
@@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.
@ronsijm
@ronsijm 2 жыл бұрын
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 жыл бұрын
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 жыл бұрын
@@nickchapsas well what I mean is, what you're doing at kzbin.info/www/bejne/l2LclmybfLl1b5o - 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 жыл бұрын
@@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 жыл бұрын
@@nickchapsas That would be a denial of service attack, for which there are many vectors, and perhaps is outside the scope of this discussion.
@Ailurophopia
@Ailurophopia Жыл бұрын
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.
@EduardQualls
@EduardQualls 2 жыл бұрын
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.
@notpingu
@notpingu 2 жыл бұрын
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 жыл бұрын
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 жыл бұрын
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 жыл бұрын
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 жыл бұрын
@@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 жыл бұрын
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.
@evilpigeon4976
@evilpigeon4976 2 жыл бұрын
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 жыл бұрын
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.
@CoderCoronet
@CoderCoronet 2 жыл бұрын
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 Жыл бұрын
It's interesting that neither Nick or the other commenters discuss this. Validation errors are validation errors and not exceptions!
@Funestelame
@Funestelame 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 жыл бұрын
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 жыл бұрын
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 жыл бұрын
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 жыл бұрын
@@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.
@skypravda
@skypravda 2 жыл бұрын
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 жыл бұрын
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 жыл бұрын
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 жыл бұрын
@@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 жыл бұрын
@@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 жыл бұрын
@@DisturbedNeo SysAdmin? I can't remember the last time I worked with a Sys admin
@donparkison4617
@donparkison4617 2 жыл бұрын
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.
@anderslinden4701
@anderslinden4701 2 жыл бұрын
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
@cartsp
@cartsp 2 жыл бұрын
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 жыл бұрын
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 жыл бұрын
​@@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.
@TiagoCastilhos
@TiagoCastilhos 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 жыл бұрын
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.
@oggatog3698
@oggatog3698 2 жыл бұрын
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 жыл бұрын
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 Жыл бұрын
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.
@jimread2354
@jimread2354 2 жыл бұрын
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.
@anatolia23
@anatolia23 2 жыл бұрын
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!
@josephwilliamson7350
@josephwilliamson7350 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.
@jegtugado3743
@jegtugado3743 2 жыл бұрын
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 жыл бұрын
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 жыл бұрын
@@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...
@ScottBlomquist
@ScottBlomquist 2 жыл бұрын
At 8:17, you could get away with the implicitly typed `return new(validationException)` instead of `return new Result(validationException)`.
@RealCheesyBread
@RealCheesyBread Жыл бұрын
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.
@ablues15
@ablues15 8 күн бұрын
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 Жыл бұрын
This video is so exceptional, it really changed the way I work with exceptions
@DavidJohnsonFromSeattle
@DavidJohnsonFromSeattle 2 жыл бұрын
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
@oldwhitowl
@oldwhitowl 2 жыл бұрын
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!
@efrenb5
@efrenb5 2 жыл бұрын
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.
@cesarcmc
@cesarcmc 2 жыл бұрын
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.
@zvado
@zvado 2 жыл бұрын
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.
@Nochdarus
@Nochdarus Жыл бұрын
Totally liked it as a primarily C person, who just always returns error codes. I guess technology does evolve in a spiral.
@macgyverswissarmykni
@macgyverswissarmykni 2 жыл бұрын
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 жыл бұрын
I've been considering introducing it in my code bases - what would you say are the biggest concrete obstacles in team buy-in?
@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.
@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 жыл бұрын
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.
@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.
@pg_pete
@pg_pete 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.
@willemschipper7736
@willemschipper7736 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
@nickchapsas
@nickchapsas 2 жыл бұрын
Totally agree
@Corrup7ioN
@Corrup7ioN 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 жыл бұрын
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#.
@FarukLuki111
@FarukLuki111 2 жыл бұрын
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 жыл бұрын
@@clementdurazzo4774 It may be the case when you optimize an exception path by the cost of a happy path.
@clementdurazzo4774
@clementdurazzo4774 2 жыл бұрын
@@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 жыл бұрын
@@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.
@clementdurazzo4774
@clementdurazzo4774 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.
@pjuliano9000
@pjuliano9000 2 жыл бұрын
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
@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
@IAmFeO2x
@IAmFeO2x 2 жыл бұрын
Very good video. Show us how much performance is lost by throwing and catching exceptions.
@enricobuonanno
@enricobuonanno 2 жыл бұрын
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 жыл бұрын
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.
@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.
@AlFasGD
@AlFasGD 2 жыл бұрын
12:50 19483 / 34396 = 0.566... > 1/2 Not as worse as < 1/3, but still bad and the difference shows
@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!!
@DummyFace123
@DummyFace123 Жыл бұрын
Shoot, the primary benefit of doing this is actually just simplifying flow. The performance is is too. Awesome!
@tomheijtink8688
@tomheijtink8688 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 Жыл бұрын
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.
@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!
@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
@WillApplebyUK
@WillApplebyUK 2 жыл бұрын
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.
@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
@drgusman
@drgusman 2 жыл бұрын
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.
@nicholash8021
@nicholash8021 8 күн бұрын
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.
@creativemanix
@creativemanix 2 жыл бұрын
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....
@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.
@KibbleWhite
@KibbleWhite 2 жыл бұрын
This is the way forwards for sure. Exceptions should be reserved for, well exactly that, exceptions.
@Dxpress_
@Dxpress_ 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 жыл бұрын
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 жыл бұрын
@@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.
@mohammedelsuissey1745
@mohammedelsuissey1745 2 жыл бұрын
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
@chudchadanstud
@chudchadanstud 5 ай бұрын
It's important to remember that exceptions and errors are two completely different things. People, including Microsoft, don't seem to know this and they just throw exceptions. Exception are there to tell you your code is wrong and you need to write more code to handle that event. They act as Estops for software. These are things like forgetting to check for divide by zero when programming. They **SHOULD** crash your process.
@enquiresandeep
@enquiresandeep 2 жыл бұрын
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 .
@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
@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.
@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.
@LuigiTrabacchin
@LuigiTrabacchin 2 жыл бұрын
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 жыл бұрын
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.
@ivandrofly
@ivandrofly 2 жыл бұрын
4:20 Benchmark API endpoint 6:15 LanguageExt.Core (monad)
@XXnickles
@XXnickles 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
@kishortiwari1348
@kishortiwari1348 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.
@ronaldabellano5643
@ronaldabellano5643 2 жыл бұрын
I love it! Suggestions with performance test is the best!
@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.
@prettywiththelightsout
@prettywiththelightsout 2 жыл бұрын
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!
@mohitkumar-vm6zl
@mohitkumar-vm6zl Жыл бұрын
Them : How complex code you want. Him: YES
@petedavis7970
@petedavis7970 2 жыл бұрын
There are things I like about exceptions and things I don't like about them. Yes, kinda like GOTO, but on the other hand, if you think about it, it's also a good way to say: "Go to a place to LOG this error and quit." And I like that. I disagree that they're less readable, just because I think they're so used, everyone knows what an exception does and knows how to look at a call stack and know what's going to happen. Exceptions have their place, but I think a lot of code overuses exceptions. Providing an incorrectly formatted phone number, for example, is not an exception, to me. That's not an event I care about. It's simply a user mistake and I'll let them know. But I don't need to log that or really care about it. If, however, I can't connect to my database, then there's no point in trying anything else. I'm done. Throw an exception, log the error and quit, because you're done. Those are sort of the two extremes and then there's the in-betweens and that's where it starts to gets to be a little more of an art. As for optimizations, I'm old school: "We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up on our opportunities in that critical 3%" - Donald Knuth My first goal is to write obvious code. My second goal is to make it performant. If that's not enough, then I'll optimize, but only after I've seen the evidence that it's not enough. I wrote a graphics app for company back in the early 90s and there were a few routines that were really slow. Step 1: Optimize the algorithm. I jumped through a lot of hoops to optimize the algorithm in C, but at the end of the day: Step 2: Code it in assembly. To be clear, assembly isn't necessarily faster. Bad assembly code can be slower than the worst compilers. There are assumptions a programmer can make that a compiler can't that can lead to optimizations. Keeping a commonly accessed variable available in a register instead of swapping it back and forth to memory, for example, or taking advantage of special instructions. Compilers can do this to some degree, and they can surely do it better today than 30 years ago, but you can still code better than a compiler, if you know what you're doing.
@georgekerwood9100
@georgekerwood9100 2 жыл бұрын
Great video, thank you. I enjoyed the space and presentation, made for an enjoyable watch.
@ryanhaney
@ryanhaney 2 жыл бұрын
"Conversely, do not throw an exception if user input is invalid, because you can expect users to occasionally enter invalid data." - Microsoft documentation on exceptions.
@ОлександрОлександрович-е4о
@ОлександрОлександрович-е4о 2 жыл бұрын
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 Жыл бұрын
It is only the illusion of functional programming and functional style. Bad analysis and bad advise.
@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
@swedishprogrammer
@swedishprogrammer 2 жыл бұрын
Awesome content, thanks for that! 😍
@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 :)
@Yorgarazgreece
@Yorgarazgreece 2 жыл бұрын
Don't do this if you value code reliability and stability. An thrown exception will always halt the operation. A forgotten if statement will not. Especially in enterprise software this is highly discouraged.
@yasin.i
@yasin.i 2 жыл бұрын
you want to kill us 😅😅😂 Thanks Nick , It's very important
@ebrahimmansur9815
@ebrahimmansur9815 2 жыл бұрын
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 жыл бұрын
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?
@edelwater
@edelwater 2 жыл бұрын
Actually ... I HAD this in our business layer for the past 2 years and recently moved everything back to regular exceptions.... I read the comments here and I think ... i came to the same conclusion after some time believing holy in the approach. It gives some mental overload and more code especially in large codebases and in the end... its seldom of any use. There are some case where multiple error states can be returned where it is sometime handy to distinguish between e.g. no records is error and no records is business problem or no records is no problem at all depending if incoming parameter X=23 or something alike but in 99 percent it was extra work to change the return values of each method, etc
@edvin3956
@edvin3956 2 жыл бұрын
Great video Nick! I am curious if there was any delay on the happy-path. See you in Oslo!
@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.
@doom-child
@doom-child 2 жыл бұрын
Monads really aren't as complicated as everybody seems to think. If you want to get into all of the math, you can definitely find some complex stuff, but that's true of everything in programming (when did you last have to think about voltages across traces or latch times for memory access?). Monads are just a data structure. They're a box that holds some kind of data and has a specific contract. Every other data structure you use is the same way. A list, stack, or queue abstracts away the specifics of storage and access in favor of a contract with specified rules. Monads just abstract how functions are composed together. It's not a concept we usually think about (we tend to take function composition for granted), but it's also not rocket surgery.
@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. 👍
@efimov90
@efimov90 2 жыл бұрын
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?
@nickchapsas
@nickchapsas 2 жыл бұрын
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
@efimov90
@efimov90 2 жыл бұрын
​@@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?
@adambickford8720
@adambickford8720 2 жыл бұрын
The problem is the 'intellectual weight' is kind of all-or-nothing. Once you get it it's easier because everything is so self-reinforcing, but until then all this feels crazy and completely academic. Give me an intern and they can explain what: `bool epic` means. I've seen experienced devs struggle with nested 'containers' like `Task epic`. You can only crutch on the type inference for so long, you eventually WILL need to understand what each execution context means. I'm not convinced exceptions and all that are actually easier in the long run but it IS a whole lot easier to tunnel vision on that one `bool` while completely ignoring blocking, errors, etc. to get off the ground... and your boss is short-sighted ;)
@ВладиславДараган-ш3ф
@ВладиславДараган-ш3ф 2 жыл бұрын
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 жыл бұрын
@@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
Settling the Biggest Await Async Debate in .NET
14:47
Nick Chapsas
Рет қаралды 147 М.
What's New in .NET 9 with Examples
25:02
Nick Chapsas
Рет қаралды 39 М.
快乐总是短暂的!😂 #搞笑夫妻 #爱美食爱生活 #搞笑达人
00:14
朱大帅and依美姐
Рет қаралды 11 МЛН
How Much Tape To Stop A Lamborghini?
00:15
MrBeast
Рет қаралды 202 МЛН
HELP!!!
00:46
Natan por Aí
Рет қаралды 75 МЛН
Exceptions Are Extremely Expensive… Do This Instead
17:15
Milan Jovanović
Рет қаралды 47 М.
The weirdest way to loop in C# is also the fastest
12:55
Nick Chapsas
Рет қаралды 253 М.
Fixing the performance problem of enums in C#
11:19
Nick Chapsas
Рет қаралды 50 М.
"Stop Using Async Await in .NET to Save Threads" | Code Cop #018
14:05
The Logging Everyone Should Be Using in .NET
15:34
Nick Chapsas
Рет қаралды 83 М.
What is Span in C# and why you should be using it
15:15
Nick Chapsas
Рет қаралды 260 М.
Dependency Injection Is Now Complete In .NET 8!
9:49
Nick Chapsas
Рет қаралды 95 М.
The Right Way To Return API Errors in .NET
10:40
Nick Chapsas
Рет қаралды 56 М.
How IEnumerable can kill your performance in C#
11:02
Nick Chapsas
Рет қаралды 119 М.
8 await async mistakes that you SHOULD avoid in .NET
21:13
Nick Chapsas
Рет қаралды 315 М.