Це відео не доступне.
Перепрошуємо.
Exceptions Are Extremely Expensive… Do This Instead
Вставка
- Опубліковано 17 сер 2024
- Want to master Clean Architecture? Go here: bit.ly/3PupkOJ
Want to unlock Modular Monoliths? Go here: bit.ly/3SXlzSt
Join a community of 1000+ .NET developers: / milanjovanovic
How should you handle errors in your code? This has been a topic of many discussions, and I want to share my opinion. One school of thought suggests using exceptions for flow control. This is not a good approach because it makes the code harder to reason about. The caller must know the implementation details and which exceptions to handle. Exceptions are for exceptional situations. Exceptions are also costly and degrade performance. If you don't believe me, we'll prove it in this video.
GitHub thread: github.com/dot...
Functional Error Handling in .NET With the Result Pattern
www.milanjovan...
Check out my courses:
www.milanjovan...
Read my Blog here:
www.milanjovan...
Join my weekly .NET newsletter:
www.milanjovan...
Chapters
0:00 Are exceptions expensive?
3:14 Exceptions for flow control
7:38 Performance testing exceptions
10:23 Handling errors with Result pattern
14:11 Performance testing Result pattern
15:30 Result pattern drawbacks
Want to master Clean Architecture? Go here: bit.ly/3PupkOJ
Want to unlock Modular Monoliths? Go here: bit.ly/3SXlzSt
As you mentioned in your final remarks if we are doing client-side validation, the number of server-side exceptions should be minimal. With result objects, we have
1. Arguably less readable/maintainable code. Having result checks on each layer is way less fun than having a middleware that handles all exceptions in one place and returns the appropriate result to the user.
2. More memory allocations, since ALL happy paths are now boxed with the Result object. Try testing with a 5% failure rate, and compare memory allocations. In my benchmark, using ErrorOr, resulted in 10x memory allocations.
3. Lost stack trace
1. I don't find it less readable, but that'll be a personal opinion. I agree that layering exacerbates the problem, so the obvious solution is not to have many layers - which is doable.
2. Yes, that's expected.
3. We can add the stack trace to the result at time of creation.
Like everything, exceptions are a trade-off. In one codebase, we went from Exceptions to success/failure wrappers, then back to Exceptions after about 2 years. Exceptions were waaaay simpler, and the code was drastically reduced when we removed the success/failure wrappers. The code was easier to follow and find bugs when we used exception because there was less of/else/switch control flow, and the return types were simpler. The apps were also easier to debug because breakpoints, and stepping through code was simpler when using exceptions as there was less indirection.
To be fair we were also using Dapper which was adding an annoying amount of unnecessary indirection.
Another point is that most of the time, your request should be going through the success state rather than the failure state. Usually the failure state is identified early, where as the success state has the expensive operations after all the failure states have been identified.
"stepping through code was simpler when using exceptions as there was less indirection" - isn't this counterintuitive, since exceptions will essentially throw you to an entirely different part of the code (indirection)?
Whereas with a return statement, you go back to where you called the current method from.
@@MilanJovanovicTech maybe at the start… but then extra abstractions start getting added to make handling different and similar responses consistent, and more and more things eventually get layered on top. Exceptions handlers were easy because there was a single place where exceptions got mapped to error responses and response codes. Again, I think having other libraries like Dapper mixed in exacerbated the problem. Our code never looked like your example. We had thin controller actions.
@@MilanJovanovicTech it just occurred to me that returning errors as part of your return type is the same conceptually as checked exceptions in Java. Different syntax obviously, but the same type of patterns emerge. Any caller must handle your error, return it, or wrap and return it. In that sense it’s also like the “coloured function” problem that the Primagen talked about when using async; the pattern spreads. It’s hard to contain and isolate its usage. It’s almost like an all or nothing. So that’s another reason why I like exceptions over returning error values. There’s just less code.
Great video! In my case, most applications I write are in a controlled environment, so exceptions are really exceptions and a tiny portion of our responses. So, in my case, I find it hard to defend using results in every method on the chain.
That's perfectly acceptable. What do you do for errors (e.g., validation)?
Great video! however one thing to note: All these stats about requests per hour (800,000 vs 15 million) are still not realistic in a real-world scenario, because this means that ALL these requests fail. In a true setting though, a percentage of requests might fail, and some will succeed.
Therefore the gap between the two situations isn't as big as we think in the first place, and the 800,000 r/s might be more toward a few-million r/s (depending on the api consumer behavior of course).
I kind of tried to summarize that in my closing remarks, appreciate your comment though.
@@MilanJovanovicTech I appreciate your videos too! Really good job, Milan!
I use exceptions in most of my methods. If an action needs to be attempted without throwing an exception, then I create a separate "Try..." method. I don't see the point of using the Result type everywhere, as it adds dozens of checks to the code after each operation. But I agree that an exception should not control the flow of execution. When an exception occurs, it should reach the top of the call stack and return the result to the user (if possible).
Makes sense ☝️
This is a functional solution, that you are using in other places: I usually program in JavaScript and Promises is like Result, they can be resolved or rejected, the if/else logic you said is somehow hidden with Promises.
@@adolfomartin5456 In C#, tasks can be used in a similar way to promises in JavaScript. But then there is the problem of large nesting of the code, and a lot of inconvenience. In JavaScript, you can also put await before promises and in that case you will still need a type similar to "Result". If you need to do a dozen asynchronous operations sequentially, then your approach will require a dozen levels of nesting.
and how many applications go behond 1k request a second? Does the applications you built do 1/2k/s? Be pragmatic and default to exception handling first. There are many other ways (infrastructure) to increase request/s very cheaply , and if, somehow, this really gets to be a problem, then c# was probably a bad choice.
I still don't recommend using exceptions for flow control. Exceptions are for exceptional situations.
Use result pattern (or similar) for expected and common errors.
Going with exceptions ends up with dozens or hundreds of custom exception classes. Using generic exceptions ends up with lots of duplication.
With errors as types, you can group them in a single file with much less ceremony than creating a new exception class.
I would partially agree with you both. I would use the result pattern when it's a business case. For example user not found but I would use exception for something unexpected. For example dB connection, network, stream, anything related to communication, etc because exception would give me something more that I can later use may be to retry or change the approach
Exception should only be used when something has actually gone wrong. Like when you try and access memory that isn’t the programs memory. Having a user not it exist doesn’t need to have an exception since the server doesn’t need it anyway it’s only the one requesting it and since it’s slower to do it it’s just worse. Besides the more you use it and the deeper the call stack the worse the penalty. No company will see the exception spam and think this is a qualified programmer because they don’t know simple optimization. The method also have benefits such as being simpler to understand the intent. Using exception will just lead to the server crashing needlessly because someone forgot a catch statement somewhere after a new exception was put in or changed and that’s expensive for a company because it could take hours to fix it all while no money is being made. So only if the server absolutely requires something it can’t get or do what it should do then it should throw an exception otherwise no.
@@sameerband2933agreed. Result for application concerns, exception for infrastructure concerns. If an infrastructure concern becomes an application concern, then mapping to a result makes sense
When using exceptions your Service interface doesn't tell you that UserNotFoundException may be thrown. You could include this in code summary, but it can be quickly become outdated and not all devs write the code summary (and that's bad).
With a result pattern your interface is explicit that you gonna have errors in this method
Im not sure where I saw it, but congrats on being MVP again.
Thank you!
00:02 Exceptions are meant for exceptional situations
02:06 Instead of exceptions, consider using the result pattern for error handling
04:07 Handling validation errors with exceptions
06:10 Handling exceptions using an IExceptionHandler interface in .NET 8.
08:03 Performance test using k6 for API requests
10:02 Implement alternative error handling method without throwing exceptions
12:05 Utilize existing libraries or create custom abstractions for handling exceptions efficiently
14:10 Using result pattern is faster than exceptions for flow control
16:12 Consider using result object instead of exceptions for performance
Thanks!
No, exceptions are for what they are, they bubble up to ensure some one catch and manages the exception. Result pattern doesn't. Exceptions for everything that needs to be handled or the application crahses
Or you'll keep getting a 500 response?
For this example in the video, I totally agree with not using exceptions, but for domain classes, I'm totally against using result, for me it has to be exception.
If used for domain classes, the consumer of the class will have to do ifs all the time, this is hell, and I think it's unnecessary, normally input validations are done in higher layers, like controllers for web api. If you reached the domain class, it is understood that the inputs are ok, and if for some reason, the inputs arrived at the domain class with invalid data, an exception must be thrown, this applies to the class constructor, the methods within and etc...
I can work with that ☝️
Totally agree.
Domain classes are actually the only place where it's justified to throw exceptions because, as you stated, that really IS an exception. A previous validator should have catched an incorrect value and the domain validations are the last line of defense prior to save data in an inconsistent state.
The rest of things, like validating input from an endpoint or checking whether the item you're trying to edit or delete actually exist, then a results-based flow should be followed.
Yes!!
I have an example that may cause problems with returning result from the domain class:
Imagine that you have a method called calculateAmount within the order class, and this method receives, for example, items and a discount coupon to calculate the value, if the values passed by parameter are invalid, and the method returns a result, forcing the consumer of the class method put an if to check, if the if is not done, and then there is a save in the database, this is a silent bug that can cause a lot of problems...
(the example I gave was stupid, but the idea is valid and has happened to me several times)
@@juniorzucareli yeah, in that area, the convoluted work that brings along doesn't really add much value in exchange of all the effort that requires to handle it. At that point it's much more useful to throw as that never should have happened.
"if you reached the domain class, it is understood that the inputs are ok" why do you think so? a genuine question
Great Video !
Almost my code using Exception to bypass all code after error occurs, then handle it by try..catch . We don't consider receiving request speed because the number of users arenot so many. Now the code is live production. Better not touching it anymore as long as it is not broke :D
Agreed 😁
@Rizkiaqa Spoken like a true software developer 😅
@@kyreehenry9202 yeah. :D
I think the gained performance won't matter as much in a real world enterprise solution and I'll explain why. The selected premise is invalid, it sets up an application to fail hundreds of thousands of times per minute in order to be true. I understand that was done to highlight how expensive they are, but an application is not expected to fail like that. We generaly see a couple of exceptions once in a while, just like you said, they are for exceptional cases. If the application is failing like that, it means something is wrong with the caller or the application. Adding so much boilerplate for that is not worth it and will only polute the code.
Balanced argument, makes sense. It was never about performance for me (but I did want to make this video, though) - I much prefer the explicitness of Result pattern. With discriminated unions coming to C#, I think this approach will only become more popular.
@@MilanJovanovicTech fair enough, it was a good video, I didn't know it was so expensive. I personally feel pain having to use wrappers everywhere, makes the code more complex to maintain, specially for junior developers. Java pulled something similar with the Optional return types. I wish they did something like Typescript union types, rather than adding a wrapper for that. If C# adds something similar to Typescript, then I would have to agree that it will only become more popular.
If your service in 99% of time returns bad responses than yes - do not use exceptions. Otherwise there is no point to spend a time for fighting with exception because it is a rare case and performance issues will be in some another places - like slow db requests, caching, pools etc. And better to spend your time there.
Not the only reason to use Result: ua-cam.com/video/WCCkEe_Hy2Y/v-deo.html
@@MilanJovanovicTech validation issue is still issue - in majority cases requests contain a proper json and validation does not fail.
Hi, Milan Jovanović. What is your opinion about exceptions in parameter validation during object initialization? Since there is a trend that says you should not create an object with an invalid state.
By not using the exceptions approach, you would have to use something like FluentValidation. Which, in short, creates an object with a possible invalid state and then checks whether or not it is invalid.
You could also expose a factory method, that returns a failure Result/Error in case some of the input arguments are invalid
Yes. I never create an object in an invalid state. All parameters are checked before creating the object. If any attributerequires a change, a new object is created. There are no setters or getters in the object. Immutable objects are the norm. Saves you a lot of headaches especially in products that have to be maintained for years.
I would love to see a follow up video on this that goes deeper into using exceptions for handling invariants and other validation checks in a DDD Domain Layer. In this case I prefer exceptions because I don't want to dirty my whole domain layer return types with a Result type. DDD states that the domain layer should use domain objects as arguments and return types. So in my eyes, (domain) exceptions are the only way to go.
"DDD states that the domain layer should use domain objects as arguments and return types" - where does it state it? I don't recall reading something like this.
Well done, exposing real pors and cons
Thanks!
What about the Open/Closed Principle in the Result Pattern? If we need to add one more result option, we have to modify a class used by the Result Pattern....
Nah, we'd have a Result and Result in reality. That takes care of both cases.
@@MilanJovanovicTech The class should be closed for modification and open for extension. For example, if a subclass of our class introduces additional result options, and if T is an enum or something similar (which we can't inherit), how does this fit with the Open/Closed Principle?
I would like to know what are the criteria for your decision to use result pattern in certain parts of your software. For example, a method allocates 1MB array. This operation can clearly fail as there might not be enough memory available. Will you use Result pattern for this? What will you do when you clearly cannot perform your intended operation? How will you interrupt work that cannot be finished. Will you place Result return object to basicly every method in call chain up to the entry point of your application? What if you forget to evaluate the result somewhere, system will throw null reference exceptions someplace else.
Wouldn't that throw an OutOfMemoryException anyway?
"What will you do when you clearly cannot perform your intended operation?" - Can I return a meaningful error to the user? If yes, return an error. If not throw an exception.
"Will you place Result return object to basicly every method in call chain up to the entry point of your application?" - Yes, that's what will happen.
"What if you forget to evaluate the result somewhere, system will throw null reference exceptions someplace else." - Write a test so you don't forget.
I'm not sure if that minimal performance gain for very rare cases is worth the effort and extra dependency. Also logging will become complexer with the shown concept.
@@DanielOpitz Some will find the real value in this, good enough for me
The result pattern feels cumbersome to me.
Consider an application using a repository pattern, where the repository is standard and doesn’t involve use cases. In this setup, you have an abstraction layer-let's call it "services." This service layer interacts with repositories, handles validation, and, crucially, manages the conversion between DTOs and entity models. Using the result pattern in this context makes the code more complex and less readable.
I've shifted from the result pattern to using exception handling instead.
The problem here is more about too many layers of indirection rather than the result pattern. But I get your point of view, and trust you've made the right decision. Do you have a global exception handler now?
@@MilanJovanovicTech I like yours better
use a functional language if you want to use the result pattern , c# is not meant for that
I'll stick with C#
Amusing concepts, I saw something like this with Kotlin and Ruby, uses exceptions when it really needs, best regards
Thanks!
The biggest problem of this video is that the author misleads the audience that "you should not use exceptions because they slow down your app". This completely incorrect statement!
Exceptions cost a lot, but the result in k6 depends on your app arch+tech solutions. In the same way I can prove that the SIMD instructions slow down your app by testing app using double and SIMD to sum and mul numbers in the "calculator app"
It is important to tell what solution/technique is better to use in which cases/scenarios
You're right, there are far more important reasons not to use exceptions
A simple alternative to a Result type might be to return a tuple like (T? Value, string? Error) where either of the elements is null. This can be simply checked with a switch expression and pattern matching.
Honestly, just use the exception type as that's what it's designed for. This video is not good advice.
@@tehwabbbit exceptions are for exceptional situations, not for business rules.
That's the standard approach in Golang
@@DanWalshTV never used that, but good to know
How do we maintain consistency with a Tuple, though? In a large team, devs can do weird things.
How you handle flow when your code nested call service layer 3-4 level? And did you try disable log and test again?
Using exceptions there's typically a top-level handler.
With Result pattern, you'd short-circuit the failure from level 3 upward.
Awesome video, Milan. I've surprised by the performance hit exception has. I knew they were slow but not by this much. I'm definately going to have rethink some of our solutions namely one that throws 100.000+ exceptions a day.
I'll do another benchmark with a remote server, to see how much of a difference it makes. Will post an update here.
programing is like food "ow that's not good for you, that's not good for you either, that affects you in that way, you should eat this and that, etc" same is with programing "ow that is a performance hog, ow that's an anti-patern, ow that is that"
What's your takeaway here?
Asking just to polish up my knowledge , imagine we are using a try catch block. Inside the catch block we are not throwing an exception but returning an int value or something Is that bad too. Does that fall under the same category as this
@@Koshala123 Not really. You catch an exception, know how to handle it, and return a meaningful response
I've never seen someone using exceptions as a result code, yes there is more work to be done, but what would say about ppl using a b c for naming their variables... Naming a variable properly is also a lot of work...
Not sure if you've been lucky not to see this in use, or...
It's almost a standard, I'm afraid 😅
I am just thinking , who will result pattern will work with Que and DLQ ? If errors comes it should stop the process.
What does messaging have to do with this? 🤔
Great video. I've always known that exceptions were expensive, but never imagined the difference would be that huge. What are your views on Tuples (containing an error) as simple return types rather than the complexity of Result types? Similar to Go's error handling methodology?
Result standardizes the response from your methods. Tuples are awesome, but I'm afraid of maintaining consistency in a large team.
I don’t think it was a trustable test, since it turns a exceptional situation (UserNotFound) into a situation that happens multiple times sequentially. Internally cache of the machine will change the results here… besides that, it is not usual thing at all.
How would you have structured it?
Need to think about it... Thanks for video.
Check out some of my other videos on the Result pattern
For years I have been doing what I refer to as "Exception driven development", where I model the core architecture via exception flows. This video has been a revelation that I might be doing something wrong.
I also believe in "if it ain't broke, don't fix it" 😁
You are the best, very nice. Thanks for the content, I love that!
@@tiagomabango You bet! 😁
After all the work of cascading the Result result up and down the call stack, you still have to handle exceptions being thrown.
And having used this technique in an enterprise level app, I can't express how much of a PITA it was. Extra code, complexityand risk with zero tangible benefits.
Handle exceptions as low-level as possible, if you know how to handle them. Unhandled exceptions (and caught by a global handler) aren't relevant since you don't know how to handle them.
What did the rest of your team think of this approach? Someone had to introduce it.
@@MilanJovanovicTech When I try to post a reply containing links to dotnetfiddle, my posts are not saved. 😥
When I said "handle exceptions" I was referring to a global exception handler. 👍
As for what the team thought: We rapidly grew to hate it. But the guy that introduced it was not capable of admitting his mistakes.
For example:
Imagine a class used in multiple places in the codebase: dotnetfiddle id: wffxQD
You then add validation: dotnetfiddle id: J5112m This code builds without warnings, but it is actually broken.
To apply this change could require a significant cascading refactor due to ClassUsedByDozensOfOtherClasses now possibly returning a failure.
And the compiler will not identify if you have missed handling a possible Failure. Accidentally missing a failure could have disastrous results.
(I had to only post the dotnetfiddle ids because youtube appears to block posts with links.)
@@MilanJovanovicTech When I said "handle exceptions" I was referring to a global exception handler. 👍
As for what the team thought: We rapidly grew to hate it. But the guy that introduced it was not capable of admitting his mistakes.
For example:
Imagine a class used in multiple places in the codebase: dotnetfiddle id: wffxQD
You then add validation: dotnetfiddle id: J5112m This code builds without warnings, but it is actually broken.
To apply this change could require a significant cascading refactor due to ClassUsedByDozensOfOtherClasses now possibly returning a failure.
And the compiler will not identify if you have missed handling a possible Failure. Accidentally missing a failure could have disastrous results.
(You'll have to create your own dotnetfiddle links based on the ids provided due to youtube seeming to block posts with links.)
@@MilanJovanovicTech When I said "handle exceptions" I was referring to a global exception handler. 👍
As for what the team thought: We rapidly grew to hate it. But the guy that introduced it was not capable of admitting his mistakes.
How it's should work with DDD when we have rich domain with many rules, and method return some value for us?
Errors are a good approach here to describe possible failure scenarios, if that makes sense for your use case.
Realy great video! Clear explanation !
I wnt to ask question.
In this functional error handling; how to manage response checks?
..
..
..
var result = callX();
if(result.Sucess)
{
return result.ErrorMessage;
}
..
..
in this case 5 time call generates 20 line of error check.
is there any good solution for this problem? espacially c# language
What's wrong with this solution?
@@MilanJovanovicTech
- Code sizes growing very fast, many call generates extra 5 line code for error check.
this reduce readability.
- occurs complex variable names like userNameResult & userName
It's becoming popular not throwing exceptions nowadays. Why do you expect that many exceptions to be thrown in the first place? Aren't you gonna handle exceptions that might occur in the front-end? Or are you worried that users might go into the javascript and modify it so that your back-end throws exceptions to decrease performance for other users?
Popular? I'm pretty much the only one advocating for using a Result object or similar.
That doesn't mean not using exceptions (as explained in the video).
@@MilanJovanovicTech UA-cam always recommends me something about exceptions and I see that you didn’t respond to any of the questions I asked.
Edit: forgot to add ErrorOr (@amantinband which also says the same thing about you), OneOf or any other packages does the same thing right? or am I mistaken? If these does not correlate to your video then my apologies.
in modern processors, the context switch to Ring 0 or Kernel mode that happens when an exception is thrown is not as expensive as it used to be. For example, on x64 processors, the SYSCALL op code was added for this transition rather than the software interrupt op code. In many cases the exception doesn't even make a context switch at all. Now that doesn't mean you should be throwing or catching exceptions willy nilly, because that's bad for other reasons
The other reasons are what's more important
With this the "stack trace" would be lost ?
You can always add it when creating the Result using Environment.StackTrace
@@MilanJovanovicTech But may make both solutions run at same time.
how to deal with trace in logs when not using exceptions?
From the "Error" state back to the call state, you always add your call signature to a stack in the Result
You can log details from the Error, and even append a stack trace to the Result
The tests have a weak point - they run locally. If you add real network latency, even just 5ms, the impact of exceptions expensiveness becomes less important.
Returning of result object instead of throwing an exception looks ok when the code is trivial, but it becomes nightmare when the code is complex and deep.
1) Will do the same benchmark with a remote service, and post an update
2) Regarding deep call chains - I agree. Which is why I keep it as shallow as possible. Typically I have an endpoint-handler-entity. Comes out to passing the result 3x at most. The entity could also return void.
I prefer the split rust does way better. In situations the application can't recover. The application simply terminates with a message set at the location the application fails. For everything requiring more graceful handling it uses the result type to let the calling function handle that condition.
This as well provides the fail fast strategy as you can return a failing result in your guard clauses.
With provided syntax to get the successful value with just 1 character of source code it's quite easy to continue with the happy path without any thought about the error.
A pragmatic approach
please sir, regarding DDD and aggregate like Order and OrderItems all methods for adding , updating and removing occurs on Frontend then sent back to backend as a patch to persist in database ,so DDD logic will be in Front-end not Back-end right?
i.e DDD logic will be in JS or TS in case of react or angular.
or C# but in DTOs in case of Blazor. Right ? or i misunderstood ?
Looks like you got just CRUD
@@MilanJovanovicTech
API is CRUD !!!
Post Get Put Delete
I noticed you use “sealed” a lot and I will just want to let you know this is not the best thing to do. According to Microsoft themselves you should only ever use sealed if and only if there’s an inherent security risk to not this might be classes that are unsafe in nature or handle sensitive information. They also directly say that the performance gain you gain from it is not good enough of a reason to do it as someone might need to extend it later.
When they need to extend it, they can remove sealed. Just coding style, I like using it. Not forcing it up any one's throat.
i prefer using manual result pattern, i also use it in flutter so i dont need to use dartz Either package... thank you for the benchmark ❤
Glad it was helpful!
Absolutely true for c++ thou
Haven't used C++ in ages (can't say I miss it)
Awesome same always
@@compman73 Thanks!
This is just a bs. If exceptions are too expensive for you, you should consider another language like rust or c
Watch the video again
For me, exceptions should be the default approach. Only use results when performance becomes an issue
Even MSFT doesn't recommend using exceptions for flow control
Agreed. If it is really needed, go with a result approach. Using the result approach everywhere, it's like avoiding LINQ because it is slower than usual iteration over collections.
Agreed. Throwing exception and catch with exception handler introduced in dotnet 8 just made the code looks cleaner as you can return error response anywhere and stop the execution. To me result pattern is just like props drilling in React where you pass the error from the bottom layer by layer until you reach the top.
That's terrible advice. It even makes the code harder to follow and understand since you have to track what catches it and how it handles it. Not even getting started on how to debug that.
Void is a most bigger lie in a software development. Any function, in a mathematical context, must be return a value. Void is a misconception introduced by POO, an bigger mistake. Also, the truly problem are the layers that only increases the change costs in effort terms. If you need make a change and it's necessary to review many layers, it's a problem, not by errors handling, rather than layers. And a exception, as their name it indicates, represent a truly exceptional behavior, is not for any error. The exception is a very poor control-flow mechanism because encourages break the main flow.
Valeu!
Much appreciated! 😁🍀
Do not do this... It goes against clean code, it adds complexity to a part of the system that os supposed to be clear, and the consuming code has to follow a different pattern. This is dangerous advice imo.
Its a good thought exercise, but thats all
@@tehwabbbit If anything, exceptions go against clean code. They make a method dishonest. The caller doesn't know from the method signature if a method can throw an exception
@@MilanJovanovicTech this is what XmlDoc comments are for in method headers, read by all IDEs automatically and standard practice in enterprise (and in many nuget packages), and if it's an endpoint utilising the data annotations that can feed into an OpenApi spec to state the possible returns.
The only scenario it doesn't fulfil is a JS call to the backend catching the exception with the .fail() but arguably if you catch all exceptions on the server and return an error array in your DTO that's much cleaner for the consumer anyway.
@@MilanJovanovicTech Unless you mention that in the method summary. But then how do you do this for, let's say, a particular implementation of a given abstraction ?
When you will grow as a senior developer, you will learn mostly how to set up the project keeping third party libraries minimal.
Yes, it's best to code everything from scratch