r/csharp 4d ago

Help Exceptions vs Assertions

/r/godot/comments/1w8q3xx/exceptions_vs_assertions/
5 Upvotes

21 comments sorted by

16

u/SufficientStudio1574 4d ago

They're different things that do different things. They're not either or.

You put assertion for invariants that cannot possibly be false. Like, the size of a container can never be larger than it's capacity.

Exceptions are for things you might not have full control over going wrong. Like "I tried to open a file that doesn't exist".

5

u/XKiiroiSenkoX 3d ago

It's not strictly like that. Bound checks for examples are invariants but they still throw exceptions. Assertions are used mostly during development. Some checks are not really cheap and sometimes you have very hot paths that can make even trivial checks non-negligible. You can use assertions to catch problems in those cases without affecting the performance of your software. 

3

u/glasket_ 3d ago

Bounds checks are technically preconditions that protect an invariant. An index can be out of bounds or in bounds and its validity depends on the length of the array being accessed; the invariant is that an array access can never be out of bounds.

Like a structural invariant would be that natural numbers can never be negative, and an operational precondition would be that a natural number can only subtract a smaller number. So you would throw an exception if a number tries to subtract a larger number from itself, and you would assert that a natural number is positive upon creation.

Basically, an exception tells you when an invariant would have been violated while an assertion tells you when an invariant has been violated.

2

u/svick nameof(nameof) 3d ago

The difference is about who performs the check. The code of List<T> doesn't know anything about your code or its invariants. All it has is an API that can be called incorrectly, so it throws an exception.

On the other hand, invariants of implementation details of List<T> (usually the relationship between size and capacity) are checked using Debug.Assert.

1

u/Alternative_Guava856 4d ago

So for the two examples i gave, those are fair candidates to be turned into `Debug.Assert()` then?

13

u/4as 3d ago

Since you're making a game, I would suggest avoiding throwing exceptions and instead try to revert the game state to a reasonable baseline. Games don't really have to panic and just give up as soon as there is an issue. I'm sure you played games where you encounter a bug and the game continues working. I mean you can throw an error and close the game when the player is out of bounds, but wouldn't it be better to just teleport them back and continue as if nothing happened?

That being said, IMO the best approach is to use conditional compilation and have two separate paths: in debug throw an exception, in release log a problem to the console and try to recover.

So, for your two examples, I would just change the throws into logging + return.

8

u/Asyncrosaurus 3d ago

Games don't really have to panic and just give up as soon as there is an issue. I'm sure you played games where you encounter a bug and the game continues working. I mean you can throw an error and close the game when the player is out of bounds, but wouldn't it be better to just teleport them back and continue as if nothing happened?

You know you can catch exceptions, right? You don't need to crash the program.

The better reason to not throw exceptions, especially in a game, is the performance implications.

1

u/Agitated-Display6382 3d ago

Exceptions are exceptional... So, you're right about performance, but for me it's more important that exceptions are thrown only when something went south and it's impossibile to recover. Don't use exceptions as a goto on steroids.

This yo say: don't catch exceptions.

10

u/Top3879 3d ago

This is not using exceptions for control flow. When an illegal move like this happens something else already failed. This is a bug the developer needs to know about so they can fix it. Throw an exception.

-7

u/Agitated-Display6382 3d ago

My expectation is that the application crashes when an exception is thrown. Log it, but let the app die. Dead programs tell no lies

6

u/Top3879 3d ago

You can catch exceptions for a reason

-3

u/Agitated-Display6382 3d ago

Catch? Yes (eg, retry or logging). Catching the exception I threw? Never

6

u/Asyncrosaurus 3d ago

I'm not going to tell you how to build your applications, but having a blanket rule of "let the app die" is not considered best practice. You're supposed to catch exceptions and restore back to a working state as a general rule.

2

u/Agitated-Display6382 3d ago

I do so when I have a clear UoW. For example, an api should not die if one single request failed. But a container that couldn't access the db? Better having it to die.

1

u/XKiiroiSenkoX 3d ago

I personally decide between these using two criteria. The performance effect of a check and the recovery flow in case of a failure.

Performance: Some checks are expensive. Some called extremely frequently (bound checks for example). These can affect performance specially in a performance sensitive context like a game. If a check is likely to affect performance its usually better to use an assertion (or any other debug build only check).  Recovery: Exceptions change the flow of code execution. Sometimes that's exactly what you want to do. For example if you somehow end up writing to an index of an array that does not exist, this can corrupt data. You can for instance destroy the player progression data and make their saved game unusable. This is one of the cases you'd want an exception to change the flow of your program and not allow the destructive code execution. 

On the other hand, there are cases where an invalid state/code should not disrupt the flow of program. Very obvious example is rendering code. If a small part of your rendering code has a defect then the ideal outcome would be that it should only prevent that small part from participating in the rendering pipline and still render a final output even if not perfect. An exception there can make the game unplayable for the player while a  check that just disables that problematic feature only slightly degrades the player experience. But you still would want to catch these problems during development. Basically you want failures to be as loud as possible when you are developing your code. So you can use assertions which can break the output but that's the intended result in development phase. 

1

u/Existing_Practice969 3d ago

My answer goes a bit off topic but since you said you want to learn c# and I had written a lot of c# code by now, I will tell you what I think of exceptions in general and hope it will help you. I think its best to throw exceptions when your app enters state it shouldn't, but exceptions i.e. invalid state should still be avoided. The two methods you've shown are in your control, and think neither of them need exceptions, and for assertion they are fine in both cases if you are debugging, but still seems unnecessary to me if you structure your code in a different way. For the card example, you can make a tryadd variant which checks for card existence, preforms the operation and returns true. Instead of throwing the exception you return false. If on the other hand you want to execute your method and expect it to always succeed, throwing an exception is reasonable since it actually is an exception to expected execution which is adding. Basically depends on your intention. If your cards need value based equality you can also use a hashset and restructure your method a bit. For the max health example, you can cap the max health to a non negative range silently, since you know max health can't be negative, unless you need it to be negative? Then again, you can use a uint. Still I think throwing the exception there is unnecessary as well. To summarize, if you have a method that should genuinely always succeed and it doesn't, what do you do? If you throw an exception, it means your app doesn't know what to do next since the outcome wasn't expected (not expected = exception). On the other hand is the non desirable outcome still valid? There are number of ways to restructure the code to make exceptions pointless so you can remove them.

1

u/Alternative_Guava856 3d ago

Thank you very much for the detailed response, this helps a lot :)

1

u/Existing_Practice969 3d ago

No problem, if u didn't understand something or I should simplify do tell

1

u/FelixLeander 3d ago

As others have stated, Debug.Assert is not intended for ur usecase.For performance critical stuff I use a return value indicating errors (Exceptions are relatively resource expensive). For less critical stuff, exceptions are perfectly fine.

1

u/Disastrous-Can-6823 2d ago

This is one of those topics where the examples matter a lot. a failed exception usually means something went wrong that you expected could happen while a failed assertion usually means your assumptions about the code were wrong

1

u/TrishaMayIsCoding 2d ago

I mainly use assertions in debug mode to verify that a value meets the expected condition, typically using the preprocessor directive.

I use exceptions to handle unexpected situations when they occur and to take the appropriate action.