Search Unity

The Poor Man's Error Handling

Discussion in 'General Discussion' started by mydogbrandie, Sep 27, 2021.

  1. mydogbrandie

    mydogbrandie

    Joined:
    Apr 21, 2019
    Posts:
    3
    I've been using Unity for months and have a good .Net background. It seems like the philosophies behind error trapping and unit testing for a game do not apply well to reality. I'd love to hear what experienced gamedevs think.

    You can't run a game with any bugs, so the debugging process handles those. We all know that's not a complete solution, however. From a lot of research, it seems like the advice is to use error trapping (Try/Catch blocks) when you can handle the error, otherwise if you can't handle it, the error trapping leaves the software mostly frozen or unstable if it runs. Error trapping doesn't seem to give me a direct link to the error like I get in the Console, though I do get a message, often more cryptic. Plus I'm unsure if a global exception handler exists in Unity, unlike other .Net applications. So error trapping for debugging seems not as good as the console. It seem like writing your own error logger by testing everything you can and having the Console window report it is the way to go. Gracefully giving the user a dialog with advice (such as a directory missing) and an error reporting dialog like we get with Unity or Windows is helpful too, but that seems like the limit.

    The same goes for unit tests. From my .Net days, they're a lot of work and it's easier to simply write statements that test something, such as if(myObject == Null){ Debug.Log("myObject is null");}. Sure, unit tests can give you a larger range of errors than you can think of, but we fall back to what we do to handle them - if we can't come up with a strategy, what good are they?

    I've read a lot about error logging. That seems to fall in the same camp where if you can't solve the user's problem in the code, then you may be stuck or destabilized.

    Is there a compelling argument that I'm missing whereby the "poor man's" system above should be replaced with error trapping and/or robust unit tests?

    Do you release games with full or partial error trapping?

    This seems to be the best advice I've come across.
    https://gamedev.stackexchange.com/questions/174579/error-handling-in-unity

    Thank you for your time and advice.
     
  2. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    I'd recommend to avoid gamedev stackexchange mostly because those guys gave me AWFUL advice in the past. Completely misinformed level.

    Regarding the example in that post, however.

    You're trying to load json. An exception is thrown.
    In practice that means you'll have an exception handler around that area that may fail, and then you indicate failure with an error code.
    Code (csharp):
    1.  
    2. public bool loadLevel(...){
    3.     try{
    4.          loadJson()
    5.     }
    6.     catch(System.Exception e){
    7.         Debug.LogWarningFormat($"Exception: {e}");
    8.         return false;
    9.     }
    10.     ...
    11.     return true;
    12. }
    13.  
    If level loading has failed, the game will report to the user, and that's it.

    In this scenario, error state caused by json loader does not propagade outside of level loader function. The game outside of the loader is still stable and working as usual. An error has occured. So what? It's not the end of the world.

    So, basically you'd isolate the exception at the level where error no longer matters, and then continue operating as usual.

    The reason why you isolate is because uncaught exception is highly likely going to kill your program. It won't kill it on PC, but it will kill it on Android instantly. And the user definitely doesn't want the game to silently crash to desktop while they were trying to save their progress. They want the game to continue functioning so there's another chance to save again.

    Beyond that point you log everything.
    -------

    In practice, if the json loader was written by a 3rd party idiot, the error state can theoretically propagate into the main program and introduce instability, but in order for that the underlying loader must be sharing some state with the main program via a global variable. And in order for that to happen, like I said, the program has to be written poorly. Because the normal usual idea is to write a loader self contained.

    Questions?
     
    Last edited: Sep 27, 2021
    Joe-Censored likes this.
  3. Meltdown

    Meltdown

    Joined:
    Oct 13, 2010
    Posts:
    5,822
    I use Raygun for error reporting across all my tiers, frontend, backend, multiplayer servers etc.
    They have a Unity SDK, and for my Azure Functions (Playfab backend server code), they have a .NET Core SDK.

    These SDK's give important information in a nice dashboard that helps me figure out problems.

    Their pricing is also affordable, charging per 10000 events.

    What I found really useful, was being able to tag events, sometimes it's not apparent what the user was doing in game, or in which game mode they were in, so I tag everything, such as raceId, raceMode, race direction, player currency at the time, etc etc. Pretty much the more context you can give for an error, the more an error can make sense of it.

    Since I've been using Raygun i've fixed some important issues I otherwise wouldn't have known about.
     
    MadeFromPolygons and neginfinity like this.
  4. mydogbrandie

    mydogbrandie

    Joined:
    Apr 21, 2019
    Posts:
    3
    Both of these replies were very useful. I'm clear on it now. Thank you both:)
     
  5. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    It's a "use the right tool for the job" kind of thing. I use different approaches in different parts of my work. Exception handling is generally rare, but it's still really useful in areas such as file handling or communicating with external services. For "internal" errors I'm better able to figure out the edge cases and either control them or explicitly check and handle them.

    One thing to note is that even though we're using C# what we're writing is "scripts" rather than a whole application. When an application hits an unrecoverable error it crashes back to the OS. When a script hits an unrecoverable error it's just that script which crashes back to the controlling application. So an uncaught exception in your script doesn't crash Unity, it just crashes your script.

    Extending the above, one thing to note here is that in practice Unity exposes a huge amount of effectively global state. Anything in a Scene can be looked up and modified by anything else. This makes perfect sense in the context of what Unity is used for and what our scripts usually do within that, but it does mean we need to look after ourselves "by convention" in some cases rather than by the nature of our tools.

    For example, if I'm working in standard .NET and I have an object which creates a collection of other objects, I know that nothing else can mess with those unless I deliberately give them access. In Unity, if those objects are in a Scene then that is not the case. So a programmer experienced with environments such as Unity will stick to a bunch of by-convention rules, such as "I will not directly modify the Transform of an object which I do not 'own'" (because that could really easily mess with the state of other stuff in unexpected ways).
     
  6. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    One other thing is to think of when we try to spot certain errors. If something can be considered a "configuration error" (eg: missing or invalid data in the Inspector for an object) then that's something that you can check in the Editor and flag before it gets to runtime.
     
  7. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    One thing that is worth keeping in mind is that traditional desktop environment is different from unity's.
    A desktop program is going to be somewhat large, and somewhat interconnected, which means errors can propagate.

    Unity, when you use component approach/monobehaviors, is a physics sandbox with bunch of objects placed at 3d coordinates and those objects have small scripts attached to them.
    If you're doing that properly, the scripts are tiny, are not interconnected, and communication is minimal.

    Which means, that if one component breaks, there's a good chance that that component is the only thing that is going to break.

    You actually don't know that. C# does not implement const correctness so anything that ever receives a reference to your object can screw up your object, unless you rigorously implement immutability everywhere, in which case they may still screw up your objects by accessing its internals via reflection just because they can.

    However this kind of thing requires a great deal of malice and progressively increasing amount of insanity from the enemy programmer.

    C++ has const correctness, meaning that a "hypothetical creative malevolent 3rd party idiot" would need to use const_cast to make your life interesting, but at least you can bulletproof your own code from all kind of side effects (which can't be done in C# without immutability, and you don't want immutability in unity because GC). Although if you wanna make your life interesting still, there's "mutable" keyboard which can be a source of fun in perfectly const correct C++.

    If you want a guarantee you'd need hardcore functional programming language that forbids variable reassignment. Then you'll have a guarantee.

    However, state is natural part of implementing games so trying to implement a game in a stateless fashion is going to be a lot of fun. It can still be done (someone wrote a demo of a simple 3d maze game in haskell, once), it is just there's not much point in doing that.
     
    Last edited: Sep 28, 2021