Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Discussion How do you handing exceptions and errors?

Discussion in 'Scripting' started by ununion, Sep 13, 2023.

  1. ununion

    ununion

    Joined:
    Dec 2, 2018
    Posts:
    268
    I have carefully studied the opinions of experts on exceptions and errors/bugs, and the general consensus is that exceptions (whether handled with try-catch or error codes) are commonly used to handle known, solvable problems that are not caused by internal code issues. On the other hand, bugs in the code, such as runtime bugs caused by carelessness, should be located and resolved during development using assert.

    However, theory doesn't always align with reality, and it's impossible to completely eliminate bugs before releasing software. In such cases, following the aforementioned theory may lead app crash due to bugs only.

    For example if you are developing a game, where the requirement for correctness is not high but it's very important to not crash, using try-catch to prevent unknown bugs and handling them without crashing,is this the correct way to use exception?
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    That is known as a Pokemon Exception Handling (aka, "Gotta catch 'em all!") and is absolutely the WORST possible way to work.

    If you persist in this then you may reasonably expect to forego 100% of the benefit of receiving crashes / exceptions.

    Remember: crashes / exceptions are FOR YOU, not for anyone else. If you hide them like this, your code will go out with bugs you can never know about, but the software will now malfunction.

    You have been advised.

    That's a nice world if you live in it. But you will NEVER be able to assert all the ways my code can crash.

    Let me tell you, my code can crash in a LOT of ways you never even remotely dreamed about. I've been coding for over 40 years and I am STILL amazed at how a subtle change can cause disaster.

    Repeat after me: Exceptions are your friend. Every exception is a chance to fix your code and learn something.
     
    Last edited: Sep 13, 2023
  3. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    3,899
    No!

    An exception caused by user code will not crash the game. Well, not in Unity.
    You need to know exactly why and how you're handling an exception.

    If the inventory code does raise an exception here, either way the inventory will be broken in some way. So handling any exception isn't helping, in fact if anything the exception handling here will make it harder to debug the issue. In this case you went from logging an exception as Debug.LogError to just logging it as an info statement, which means it'll be easily overlooked and "pause on error" will not trigger.

    You should only handle exceptions when you are not 100% sure that an exception may be raised due to circumstances outside your control, such as the user's disk being full or not having write permission or no Internet connection and things like that.
     
    SisusCo likes this.
  4. Nad_B

    Nad_B

    Joined:
    Aug 1, 2021
    Posts:
    303
    Unity apps do not crash when an unhandled exception is thrown. It has a global exception handler that just logs the exception and ignores it (think of it as a giant try/catch block around your whole game). It's good and bad at the same time.

    Good:
    A crash is the worst thing that could happen for a game. Players *might* forgive anything, except crashes.

    Bad:
    Ignoring exceptions could lead to an unstable state of your game and totally breaks the gameplay. That's why you should always catch possible exceptions and handle them correctly before they go up the call stack and hit Unity's global exception handler, especially when dealing with external resources, like loading data from a web API, saving data to disk... those tasks can (and will) fail from time to time, because they're out of your control. For gameplay logic, there's no need to worry about exceptions, on the contrary, it's good to let them be thrown so you can detect them during dev/testing and fix them.
     
    SisusCo likes this.
  5. ununion

    ununion

    Joined:
    Dec 2, 2018
    Posts:
    268
    but in fact ,we cannot fix all bugs before release.
    for giving player a better experience, why catching all bugs is not good?
     
  6. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,105
    Littering all your methods through the whole call stack with a lot of error handling code adds a lot of noise, which hurts readability. Try-catch statements also complicate the task of understanding the flow of the code, again hurting readability.

    The more difficult your code is to read, the more additional bugs will be introduced.

    What could have been a really easy-to-read, linear two-line method, became +250% lines longer, with branching, variables, and some very weird potential side effects (very unexpected that calling a method named "Pick" can result in an object being destroyed from existence!).


    It is a very good thing that you are thinking carefully about edge cases, and about everything that could go wrong in the code. However, just adding try-catch everywhere isn't the best next step.

    For one, it's almost always best to try and write code that can never throw exceptions in the first place.

    The try-do pattern for example is extremely useful for this.
    Code (CSharp):
    1. bool Pick(object item)
    2. {
    3.     if(inventory.TryAdd(obj))
    4.     {
    5.         obj.OnPicked();
    6.         return true;
    7.     }
    8.  
    9.     return false;
    10. }
    Another option is the fallback result pattern:
    Code (CSharp):
    1. float GetScreenAspectRatio(float fallbackValue)
    2. {
    3.     if(Screen.height == 0f)
    4.     {
    5.         return fallbackValue;
    6.     }
    7.  
    8.     return Screen.width / Screen.height;
    9. }
    If you have one method, that is used in 10 places in your codebase, adding a couple of lines of additional code in that method to make exceptions in it impossible, would be much preferable to having potentially hundreds of lines of try-catch code all around your code base.

    You can also use attributes like [CanBeNull] and [NotNull] on your method parameters and return values, as well as well-written xml documentation comments, to underline the contracts that your methods have, so that the way that they should be used is made very clear and exceptions are less likely to occur.

    Code (CSharp):
    1. /// <exception cref="NullReferenceException"> Thrown if item is null. </exception>
    2. /// <exception cref="InvalidOperationException"> Thrown if inventory has not been setup yet. </exception>
    3. void Pick([NotNull] object item)
    4. {
    5.     obj.OnPicked();
    6.     inventory.Add(obj);
    7. }
     
    Last edited: Sep 14, 2023
    Kurt-Dekker, Yoreki and spiney199 like this.
  7. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,326
    Try/Catch will not fix bugs. Your game will still be broken and not function properly. You won't see any errors in the console, but you'll just be pretending you don't have problems.
     
    Yoreki likes this.
  8. Yoreki

    Yoreki

    Joined:
    Apr 10, 2019
    Posts:
    2,588
    You cant "catch bugs". That is not even what exceptions are for in the first place. Exceptions are not bugs. We catch them to handle expected yet unavoidable behavior that would otherwise lead to bugs. Let's say you want to read a file. Best case it's your own file, so you can make sure it exists. But the user may have deleted it between sessions. Or you are trying to read arbitrary files the user selects. Generally speaking, you cant know if the file you attempt to read even exists at that location, or if you have permission to read it. So in order for your programm not to crash, you handle thexe expected cases. Whether the reading is successfull is out of your control. You expect it to work, but you need to handle the case that is does not because it's out of your control.

    Bugs are the difference between specification and implementation. Meaning you implemented something you did not intend to, or forgot to implement something you should have. You cannot catch bugs. If you arbitrarily surround all your code with try-catch blocks, all this does it make it slower and, as others said, unreadable. The whole point of handling an exception is that you know how to handle that specific exception, because you know it can happen. If the file could not be read, tell the user and ask them to select another one. But how on earth would you handle just "any bug"? You cant. If you dont know what you are handling, you cant correctly fix it at runtime either. So all you are doing is acting like there was no problem, which doesnt help. In fact this simply causes more problems down the line, which you will keep on ignoring, until your entire game crumbles down into an unusable mess that further deteriorates by the minute.

    I dont intend to sound rude here, but the idea to surround your entire code in try-catch blocks is something ive only seen in first semester students who had no previous experience. So if you feel that need, you probably just lack experience and confidence in your code. With experience you will write less bugs and get a feel for what code is prone to bugs, and which is not. Of course we all will always continue to write bugs, just less obvious ones :p
     
    Last edited: Sep 14, 2023
  9. ununion

    ununion

    Joined:
    Dec 2, 2018
    Posts:
    268
    what I mean is not hiding the bugs,I just want to make sure player feel there is no crash during the game.but actually the log system will save and upload these error info and i will fix them in next update.
     
  10. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    280
    This is just my opinion, but I think the main cases for throwing exceptions are when the area of code you are in is at a low enough or modular enough level that it does not have access to a method of log output, and/or when the state of the application is so severe that it should crash if that bit of code is reached. Your application should not ship with exceptions. It also should not ship with error level logs. Warnings, maybe... You can catch all exceptions somewhere at the root of your application, just as Unity does, but it doesn't solve the fact that there is a generally unhandled exception thrown up through the application. That exceptional case should always be found in QA and solved before release. Nothing that exceptional should ever happen on the customer end after release. Of course, accidents happen, but whether the application crashes or not it's more about a minor (potential) convenience in the development/testing cycle when those exceptions accidentally occur. You would definitely need to solve the unhandled exception so it never occurs in a release build either way. Sometimes there are legitimate cases to try to execute a method, and catch specific types of exceptions that the method has the potential to throw. Those are the cases where it doesn't really make sense that that method would have access to another way of logging or reporting back a specific reason for total failure to execute properly. However, you should usually just create conditional code that would prevent those exceptional states in the first place, but there are times when it's valid to catch an expected exception type from a certain method. Just adding catch all exception handling and trying to move on through the code though is not a solution for the fact that an unexpected exception is occurring.
     
    Last edited: Sep 14, 2023
  11. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    280
    This is the part where I disagree. I might concede something like, "It seems common for many developers to release buggy software without taking the time to resolve every critical issue. They create code that causes unhandled, exceptional states whose operation is undefined." However, I do not consider it impossible to release, or at least set a reasonable expectation for, bug free code.
     
  12. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    10,468
    It's a fact of the Universe that you don't know what you don't know. You cannot ever be sure something is bug free. All you can do is perform as much QA as your standards, time and money allow. :)

    You're correct in that you can reduce the chance of bugs to a "reasonable" level with QA and defensive coding practices/techniques.
     
    ununion and Yoreki like this.
  13. Yoreki

    Yoreki

    Joined:
    Apr 10, 2019
    Posts:
    2,588
    You seem to be under the impression that people will just be able to continue playing after you "caught" a bug that would have lead to them crashing otherwise. For most situations, that's not the case. I tried to explain that already and so did others. Imagine you attempt to set a reference to something, but this fails. You catch an arbitrary exception (without handling it properly, because how could you) and the player continues playing for a moment longer. But now the reference is null. Each time you access or work with this reference, it's gonna cause more issues, which slowly but surely corrupt more of the game state.

    Worst case the player plays a game where nothing works anymore, and eventually saves corrupted data, which your loading mechanism cant make sense of anymore, thus ruining their entire savegame. I bet they appreciated being able to "play" a broken game for a couple minutes longer tho.

    This is about what you can expect, all the while reducing readability, slowing down development and making the game run worse. Just invest that time into QA and finding bugs in the first place. If the game crashes, yes that's bad. It can happen tho, and if you deal with it quickly, players are usually forgiving enough in the long run. Worst case is they lose some playtime, but unless you do really weird stuff, or the crash happens while saving, or you do the above and save broken data in the first place, it at least should not corrupt their savegames.
     
  14. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    280
    That's true, but in any case, I would still never consider a crash on the user's end to be worse than an unexpected exception being thrown in a release build. The fact that an unpredicted exception is still floating around waiting to occur is the root problem. The crashing is just a symptom, and sometimes symptoms are good in that they reveal the underlying issue. (Not to mention that Unity already has a catch all at the root of the game loop.)
     
  15. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,105
    In my opinion there can be value in this approach. Small mistakes can slip through the QA process into live builds, and it definitely is a better end-user experience if the game can recover from an unintended yet inconsequential situation, rather than the game getting stuck during the loading screen for all users on a certain platform for example.

    But not all error handling is equal, and adding error handling code doesn't come without a cost - so I just think you should be smart about where you add it and where not.

    Sometimes low level methods really just don't have enough information about how they should best try to recover from an exceptional situation. If you catch an exception at very low level methods, and convert it into an error/warning and terminate execution of the function, then it becomes impossible for higher level methods with more contextual information to do the same. This is why I think the try-do pattern is so powerful, because then the higher level methods can better decide how to respond to a situation where the operation fails, with the most fitting strategy for each particular situation (retry again next frame? return a default value? display an error on screen?).

    Also, something like checking all arguments passed to all methods against being null everywhere in the codebase does introduce a substantial amount of additional noise. And this logging code itself could even cause errors (e.g. trying to read the name of an Object that is null). So all things considered, it might potentially be best for the end product, if you just check references for null at the boundaries where the references are injected into the system (e.g. in a component with a serialized field that is assigned via the Inspector), and after this you simply trust that the reference won't be null in the ten other methods that it is passed to. But this, I would say is more of a matter of opinion - some coders do prefer to add a lot of assertions everywhere, to ensure issues are caught as early as possible, even if some boundary layer object fails to validate everything properly.
     
    Last edited: Sep 14, 2023
  16. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    280
    Yeah, I agree with pretty much everything you said. Unity already has a catch all try catch. When I made my own replacement for .NET events, I added a catch-all that continues invoking events for all observers and then properly rethrows any captured exception or aggregate exception with multiple nested inner exceptions. So that is one of those places that acts as a catch all in my own code base. Therefore my update manager, which Unity has recommended implementing (although it's less necessary than it used to be), will update every observer without crashing the entire frame. There are reasonable places to do it. Also, much of my lower level core library throws argument null exceptions and such, just like .NET libraries. But the mindset should still attempt with reasonable confidence to simply not have an exceptionally erroneous runtime on stable release versions.
     
    SisusCo likes this.
  17. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    ^ ^ ^ This.

    No, what you mean (try / catch pokemon style) actually IS HIDING ALL THE BUGS.

    It's not "like hiding" the bugs. It actually IS hiding the bugs...

    It's like your kitchen catches fire... but you're busy with dinner so you just grab your plate and run out, shutting the kitchen door behind you because it is so important to finish your dinner without being interrupted by a fire. That's not going to end well!!
     
    Yoreki likes this.
  18. ijmmai

    ijmmai

    Joined:
    Jun 9, 2023
    Posts:
    188
    I understand, all you want to provide is a safety net. What I try to do, but this may not be possible in all situations, provide a default value, as mentioned above in examples.

    No one likes their game to crash, but turning all your code into a try/catch system is just overkill. It is like taking survival gear while going grocery shopping.
     
    SisusCo likes this.
  19. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,105
    That's a great example! A bit of exception handling code in a central place on a high level, lowering the chances of the whole system grinding to a halt if one piece has a little hiccup - and a lot of bang for the buck when it comes readability cost versus error recovery benefit :)

    This is also my main argument for why one should really think twice before just slapping a try-catch-log-an-error-and-return in a random method and calling it a day; it's a code smell, because there are usually better alternatives to handling the situation.

    But as a last resort fail-safe on the higher levels of abstraction, I think it can be a very sensible and practical thing to add. And if you just make sure to log an error, you're not doing any error hiding - on the contrary, you have the opportunity to enhance the error message with additional information and a context Object. Hell, you could even open a dialog popup if something really scary happens, to ensure the error definitely won't go unnoticed during a hectic playtesting session.

    But yeah, if one converts the exception into a normal log message, or even a warning, then it does have the potential to subtly start leading the project towards a path where a couple of warnings here and there aren't seen as anything that critical.
     
  20. ununion

    ununion

    Joined:
    Dec 2, 2018
    Posts:
    268
    that's right,even unity has known issues in every version.human cannot create a program without bugs
     
  21. ununion

    ununion

    Joined:
    Dec 2, 2018
    Posts:
    268
    I think you guys ignore the code i posted.
    the code is describing a scene that once something wrong in picking action.then the catch just destroy the item instead of crash.
    i think this is acceptable for players,but crash is unacceptable for players.
    and i still think people cannot make sure there is no bugs in their code.

    btw ,i m talking about a small offline pc game, but if this is a mmorpg destroy item is unacceptable also.
     
    SisusCo likes this.
  22. ununion

    ununion

    Joined:
    Dec 2, 2018
    Posts:
    268
    and i have to mention that.
    the try catch is not at very lower layer for example in update manager
    void Update(){
    try
    foreach update()
    }
    i agree that this is a big mistake for using try catch.


    i m use try catch for each action forexample everytime player hit F button to pick item to inventory,in this function a try catch is exist. and for example player hit mouse button to attack, in the Attack() action a try catch exist,once someting wrong in attack,just do noting with attacking.this will not break the player saving data,in most case reopen the game or just attack again will repair the bugs.
     
    SisusCo likes this.
  23. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,326
    Ok, but what if there is in exception because obj is null? Then you're "catch" will attempt to delete null and remove null from the inventory so your "catch" introduces an uncaught exception, making the whole thing pointless.

    Even if that code works perfectly, you could still have a situation where the player attempts to use an item in their inventory and then the item just magically dissappears. So your game will still be buggy.
     
  24. ununion

    ununion

    Joined:
    Dec 2, 2018
    Posts:
    268
    i have to mention that.in my workspace the obj.Destroy() is an extension method,so it doesn't care whether obj is null,and also the inventory.tryRemove can pass in an null obj also.
    but this is isn't important,this is only an example to show you what is my mean.
    and my mind is using some magic to cover crashing for player. so for this
    if i don't use try and catch, the game will crash when have this situation,and i think maybe crash is worse...
    sorry for my english.
     
  25. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    In case you didn't read the second post on this thread, it won't. Exceptions thrown in your code will generally just log to the player.log file while Unity continues on. The only errors that will primarily hard crash are ones with the engine itself.

    Just write your code to be readable, and test for any potential bugs.
     
  26. ununion

    ununion

    Joined:
    Dec 2, 2018
    Posts:
    268
    yes i read that but unity exception will break current update,this means if you are using updatemanager to controll all updating,one exception will break all of your update,that is same as crash,even more danger than crash.
     
  27. Nad_B

    Nad_B

    Joined:
    Aug 1, 2021
    Posts:
    303
    Then instead of letting exceptions being thrown and catching them... just write code that eliminates all causes of exceptions... That's what programming is all about. As people here said, you just need to catch exceptions that can be caused by things OUTSIDE your game/control (disk operations, web calls...etc). Your own gameplay code SHOULD NOT throw exceptions, so there's no need to add any try/catch blocks in your gameplay code.

    How do you ensure your gameplay code does not throw exceptions?
    Testing, testing, and retesting everything and every possibility, either manually, and/or Unit Tests (highly recommended)
     
  28. Nad_B

    Nad_B

    Joined:
    Aug 1, 2021
    Posts:
    303
    Also Exceptions != Bugs. Exceptions can cause bugs, but all bugs are not exceptions.

    A bug is an abstract concept, it means that "your application is not behaving like it should". This can be caused by exceptions (easy to fix, since Unity catches this and logs it), or by your game logic being broken/untested for edge cases (harder to fix, since there are no exceptions being thrown)

    For example you have a weapon in your game, with a cooldown timer (you can fire one bullet each 2 seconds). You can add a timer to your weapon after firing one bullet that starts at 2 seconds and decreases over time, when it reaches 0 you can fire again. Simple and very common. Now let's see the code:

    Code (CSharp):
    1. public class Weapon : MonoBehavior
    2. {
    3.     private float _cooldownTimer;
    4.     private bool _canFire = true;
    5.  
    6.     private float Update()
    7.     {
    8.         if (Input.MouseDown(0) && _canFire)
    9.             FireWeapon();
    10.  
    11.         CheckWeaponStatus();
    12.     }
    13.  
    14.     private void FireWeapon()
    15.     {
    16.         // Instantiate bullet, particles, play sound...
    17.  
    18.         // We disable firing and reset cooldownTimer to 2 seconds
    19.         _canFire = false;
    20.         _cooldownTimer = 2;
    21.     }
    22.  
    23.     private void CheckWeaponStatus()
    24.     {
    25.         if (_canFire)
    26.              return;
    27.  
    28.         // Decrease cooldownTimer...
    29.         _cooldownTimer -= Time.deltaTime;
    30.  
    31.         if (_coolDownTimer == 0) // Timer at 0, we can fire again!
    32.         {
    33.             _canFire = true;
    34.         }
    35.  
    36.     }
    37. }
    OK you start playing, you fire your weapon, it works for the 1st bullet, then you wait 2 seconds and try to fire again... it doesn't work! it fires only once! and there is no exception being thrown, nothing.

    It's because... there is no exception to be thrown. Your code is technically 100% valid C# code.

    But... You have a bug! since the weapon is not working as intended.

    This is the type of bugs you should deal with in your game. No amount of try catch will help you here, since there's no exception... the bug is a logical one, not a runtime exception.

    This is why "Exceptions" are called like this (and not called "Errors" or "Bugs"). They are exceptional, they should not happen, only in very rare occasions! So you should always write code that DO NOT throw exceptions, not code that throws and handle them.
     
    Last edited: Sep 17, 2023