Hi everyone, this isn’t really a Unity question but since I spend a bit of time here discussing the finer points of C#, and have gotten to know some really bright individuals, (and because stack overflow can get a little political), I wanted to pose a general programming style question.
As an open-ended discussion, I’d like to extend the example in the article regarding Int32.Parse. The Microsoft team’s response to the vexing exception was to introduce Int32.TryParse. That helps me to avoid an exception, which is good, but it also hides the reason the operation failed, which is bad.
Here is my question:
How would you extend Int32 to parse strings into integers without raising unnecessary exceptions while still providing meaningful feedback when the parsing fails?
I’m following up with an “invitation” to a number of specific users I’ve “worked” with in the past. If your name isn’t on the list, please don’t take it personally and I hope you will still respond. These are just names of people I know have expressed interest in these kinds of open ended discussions in the past. @lordofduct , @Kiwasi (had to change your name didn’t you …), @Baste , @Suddoha , @JoeStrout , @LaneFox ,
Nice! I never remembered who posted that link a few years ago, when I - fully unaware of what valuable piece of information hid behind that link - clicked on it and kinda felt like the world of exceptions has finally found its perfect shape.
Now I’m pretty sure it was you. Thanks for that.
OnTopic
As I’m doing a lot of C/C++ programming at the moment, the first thing that came to my mind were error codes instead of a boolean. But I’m pretty sure you’re aware of the mess. And to be honest, that doesn’t quite fit into C#.
A similar approach could be an ErrorObject. One could define null as being success, anything else is some sort of error that occured. I’ve never used this one so far.
One trivial approach I’ve used somewhere a while ago was adding an overload with an extra out-param for additional information.
So I want to start first on a slight tangent in relation to that article and how it says to NOT even bother writing try/catch for fatal or boneheaded exceptions because honestly either there’s nothing you can do about it, or in the case of boneheaded ones you should have trapped for that in the first place.
And I fully agree.
It makes me think of a few corporate jobs I’ve had where they make you wrap EVERYTHING in try/catch statements “just in case”. I even worked one job where we had to write a catch for every individual exception type possibility… just in case… I srsly don’t think the lead knew wtf they were talking about.
And in the industry I’ve seen this type of thing brought up by other’s in their own experience. And it still baffles me. Like… I get it from a over zealous perspective, which you find in corporate development a lot. But at the same time… the logical side of me was like “yeah, but really?”
Anyways… moving on from that.
In the case of Int32.Parse and TryParse.
Well there’s really only 3 exceptions that can occur:
In the case of ArgumentNullException, well that’s a boneheaded exception, so you should have trapped for that regardless.
This leaves the FormatException and OverflowException. Aside from if it was a formatting or overflow issue, we don’t really get any more specifics. It’s not like it tells you specifically what was wrong with the format (or does? I don’t know… I never actually dug into the FormatException class, but as far as I know it doesn’t).
And as for OverflowException, well that’s it, it overflowed… there’s no real nuance to that.
Now I’m trying to think about why I might need to know which one happened. And at the end of the day I would consider them both ‘technically’ format issues. In that an overflow is caused by putting in a number that is too large to fit into the memory space. Basically, they wrote too many characters. Which can be seen as a formatting issue… sort of.
What would I do with the information of if it was overflow or format. Maybe display a different message? “You wrote an illegible number” vs “You wrote a larger than supported number”?
The only other thing is that we might want to try to coerce the value into the nearest possible respectible value. Like, if it was a overflow, select MinValue/MaxValue (depending), and if it was a format, try to loosely parse it with a more costly/error tolerant algorithm. In regards to that… well why not just use the more costly/error tolerant algorithm from the get go?
Essentially if I really needed to know this information, I would just write a try/catch. Not the most elegant, but I don’t need it frequently enough to really care.
…
With all this said I technically have my own numeric parsing method in my ‘ConvertUtil’ class:
Now this is really just a port of a port of a port of a port of an old ass conversion class I wrote wayyyy back in Actionscript3 which doesn’t exist anymore. I had it on google code which no longer exists. Someone actually forked the repo from there, but I can’t for the life of me find the conversion util in there… maybe they forked a weird version or I’m just blind:
Anyways, it does its damndest to not fail, and even uses TryParse for part of it. Basically all string parsing boils down to parsing it as a double, and then converting it to the datatype in question. It attempts to do it as a regular number, and if that fails, it checks if it’s a hex value, and if all fails it just returns 0.
It’s like this not out of some preference of clean design (honestly, it’s far from, and its behaviour dates back to 2008, within the first year of my coding career. So it ain’t exactly what you call elegant). Really it’s just that whenever I’ve had to parse numbers, if the value isn’t numeric, 0 is the value I almost always want. So I return that.
A parse fails because the input string was not in a correct format. There could be any number of reasons for this. I don’t think it makes sense for a TryParse to do anything more than just return a value indicating that the parse failed; anything more surely is application-specific and most of the time a caller just wants to know if the parse failed or not.
Having said that, if you wanted to write a kind of uber-TryParse, or report some messages to the user about why it failed, you could analyse the string and look for some common situations, like maybe the user typed a number and accidentally put a space at the end or some garbage characters. Then you could display an appropriate error message. Or maybe you could find the index of the first non-numeric value in the string and move the cursor there and highlight it.
On the topic of exceptions, I prefer using exceptions only for exceptional circumstances, and not for general purpose error detection or error results.
I might be mistaken, but I think the question was focused a little more on how we would neutrally extract the information (no matter what it is and which algorithm we used to detect that) in a way that it’s fully available to the caller, without turning it into a function that can no longer be considered a general purpose / basic utility function.
This implies you do not make any vague assumptions about the environment that it’ll be called in. It has to be very application agnostic.
For that reason, most of the time there will be a seperate on-the-fly analysis or a seperate call to another utility function if parsing failed.
Parsing an integer might be an example that’s a little too obvious though. Like @lordofduct has already pointed out, you can kind of assume what has gone wrong.
So perhaps we could take a look at it in general and apply our ideas to that specific example?
Not specifically about int.parse, but I often miss error checking / return values of c programming. A function returning -1 for instance, and setting errno. Just an old habit, I suppose.
@Suddoha Yes, error codes are the first thing I reached for myself, but as you point out, they come with their own baggage and are discouraged by the designers of C#. My take on error codes is basically this:
The error object was the best idea I came up with, but it does interfere with the normal return of methods which disrupts idiomatic C#. I think it’s about as unusual as the TryXXX technique taken by Microsoft, though the Try methods are much more ubiquitous and well understood.
The out parameter might be better so we can decide when the extra error information is useful. On the other hand, if we wanted to ignore the information, we would be better to just use Exceptions.
@lordofduct Yes, a misconception about how to properly use exceptions seems rampant. Sometimes the problem stems from management who don’t understand the technical details and insist on micro managing the development process. Unfortunately, it seems much more common that it’s developers who don’t understand the technical details. To be honest, developing a good, consistent error strategy is still something I struggle with.
Eric’s categorization was insightful and extremely welcome, but understanding exceptions is just the first step to a good error strategy.
Moving on …
You’re right, my selected example is not particularly nuanced. Parsing integers is fairly menial and I understand that there aren’t too many “recovery options” when you run into a parsing error. However, I hope we can reason through the problem in a more general situation. Every method I write could fail. In some cases, there is only one failure mode but in others, there are dozens of possible reasons.
The real discussion point I’m hoping to hit on is this:
You have a method that could fail for n reasons, where n > 1
You are interested in knowing why said method failed, possibly because you have some recovery options in certain circumstances
Discovering the failures is a part of doing the work, you can’t reasonably identify there will be problems until you actually hit them
The method in question is not a fire-and-forget command, it has some interesting information to return to the caller
@samizzo My comment to lordofduct applies to you as well, I think. I understand parsing an integer is a simple example but it is convenient.
Let’s dig into this a little more. I like your ideas but who would be responsible for displaying the error message or moving the cursor? I don’t think I want my parse method to know about dialog boxes and text editors. If you wanted to relay this kind of diagnostic information back to the caller of the parse method, how would you do it?
@methos5k You’re right, missing error handling is another big issue we have to deal with. This is an interesting read about the advantages of exceptions vs error codes. The Old New Thing
I think the author’s point can be sidestepped by applying better design principles to the framework but it doesn’t invalidate it completely. Sometimes we have to work with old technology. One advantage that exceptions give us though, if we miss catching something important, our code won’t go and do something stupid, it will just die. But the more salient point here is that we want it to be clear, just from our code, that we have good error handling.
Eric also mentioned exogenous exceptions, which vexing exceptions can turn into as it travels up the stack.
It’s all fine and good to try to convert vexing exceptions into something human meaningful, but if that exception causes something else to fail and throw its own exception, the original exception can get lost unless you maintain some form of an exception aggregation pattern (like AggregateException)
And for exception aggregation to be meaningful, you don’t want to ignore or avoid throwing exceptions! You might end up hiding or corrupting the path to the original exception.
These cases are usually situational and dealt with on a case by case basis. Most of the time I (unfortunately) just accept it as a necessary evil as there are bigger problems to solve…
IMO, this is one of the biggest weaknesses of using error codes. Unless you returned an list array of error codes, lol.
I can understand that mindset, though. I used to work at a company that sold industry grade sensors that could be installed in the most brutal environments you could imagine (high temps, sawdust, sparks, etc.), and those environments are cleaned using a pressure washer.
That inspires a zero tolerance policy towards developing in both hardware and software. Once the hardware is built, there’s just no patching out any flaws. Of course, this also meant management wanted to take a similar approach to software: Bugs are just not an option. If any defects happened on an assembly line due to a software bug, you’re looking at a pretty hefty bill for replacements/recalls/lawsuits/etc. Imagine being the engineer responsible for a bug like that!
That code was annoying as hell to read and write, though. And many many many arguments were held over this issue (if the code is harder to read, then that makes it easier for bugs to slip through, no?) but ultimately no one wanted to be the one to make the call to change the policy.
@BlackPete Thanks for weighing in. I’m a little confused what you mean by
Could you expand the idea at all? I think Eric was saying these are two very different things.
Vexing exceptions are already meaningful, they’re just annoying given we are told to avoid exceptions. I’m trying to find out how/if others avoid them without losing their meaning.
Regarding losing one exception to another: I’m familiar with how this can happen in finally blocks because of the two pass exception handling model. After an appropriate exception handler is found, finally blocks are executed from the original exception point. If one of these blocks throws, we might restart the stack unwind. This is why it’s important to code defensively in finally blocks. Is that what you’re getting at when you say …?
I may have misspoke when I suggested the vexing exception turned into an exogenous one – it actually doesn’t.
What I merely mean is that by the time your code receives an exception, you may be looking at a different exception thrown by an API that sits between your code and the original vexing exception. You could argue it’s actually a boneheaded one, but either way, it’s still an exception your code has to deal with
As you’ve already observed, if the original vexing exception was eliminated by using int.TryParse and instead returns false, this gives you less info/context to work with. The calling API may see that it failed and throw a more generic exception.
So this makes me question if it’s really wise to try to work around a vexing exception? Without that exception, the stack trace may end one level higher than where the actual error is, and create a red herring.
int.TryParse is usually trivial enough that it’s a non-issue. However if you have a large function with several int.TryParse calls in it, it’d still be useful to know which one failed just from looking at the call stack.
You could try to have it both ways and do something like this:
int val;
if (!int.TryParse(source, out val))
throw new ArgumentException("Invalid source: " + source);
However, I’m not sure that’s much better than just using int.Parse.
I’m curious why people try to avoid exceptions. They convey so much more information than bools or even error codes. I mean, yes, they shouldn’t be part of your normal logic flow as a replacement for if-else checks. However, if an error pops up, I’d prefer that to be handled the usual way and follow the principle of least surprise.
That’s a great point and something worth discussing a bit more … In my opinion, I think this could happen for two reasons:
Because of an explicit exception handling strategy which includes replacing exceptions for legitimate reasons, such as to protect sensitive security details. This might be important because our overall strategy is to allow deep error messages bubble to the surface of our app so users are informed of what went wrong. For sensitive data, it makes sense to capture that data before it begins bubbling up. In this case, I expect the real error message to be recorded somewhere safe so debuggers are still able to gather the information they need without exposing sensitive details to potentially nefarious users.
Because of a poor understanding of how exceptions work and why they are useful. Unfortunately, I see code like this too often.
try
{
var id = Int32.Parse(str);
var user = UserService.FindById(id);
user.Authenticate(passwrd);
}
catch (Exception e)
{
throw new UnauthorizedException("User doesn't exist");
}
There’s a lot wrong with this code, including that it drops whatever real exception was caught by the catch block.
I don’t want to delve too much into that tangent because I think most of the guys responding to this thread have a good background in Exceptions. I’m more interested in highlighting point #1 because it exposes one of the reasons we might want to avoid exceptions all together. Catching an exception just so it can be replaced is nearly as bad as catching an exception to control flow. If possible, we would rather avoid the underlying exception all together and just throw our own exception if applicable.
The reason we call an exception vexing is because there isn’t really an error. We knew something was likely to fail; we’re just trying to test if something is possible and if not, we have a perfectly good alternative option. We don’t really care about how we’re notified about a failure, just that we get the relevant information.
I think a lot of people at this point will just say “who cares? Just catch the exception and make your recovery”, and that’s exactly what we have to do if there are no alternative methods which provide the information in a different way. But the designers of exceptions tell us is that this isn’t really an exceptional situation and it’s not a good place for exceptions. We are vexed.
I think I explained why we want to avoid exceptions. When there isn’t an unsolvable logic problem, we don’t want superfluous exception handling.
You also nailed one of the things we’re not supposed to do: use exceptions for flow control. The broader question is “why not?” I have two good reasons and I’m sure there are more.
Exceptions are slow.
Exceptions provide a hook for debuggers, so we can get notified and jump to the location every time one is thrown. That’s really useful for finding an exceptional problem in a well designed code base (which doesn’t abuse exceptions). And it is really annoying when exceptions are popping up all over the place under normal operation.
In addition to exceptions being slow, you sometimes also have to recover the program’s state, as there might have occured some initializations or changes before it was thrown. So you’re going to clean up using finally and hope you can undo everything that might need to be undone for the next attempt. However, this might not always be possible. Some languages like C++ do not even use a finally block. You rely alot on the RAII principle and a very controlled application flow.
In such a case, I’d talk to the developers and hope this feedback would be considered. If an exception is replaced by a different one and that one does neither carry a meaningfull message nor the inner exception, it’s a really bad implementation and needs a serious improvement. Happens alot though, you’re right.
Same like above. An exception that turns out to be a red herring is the developers fault.
And with regards to the stack trace, I do not quite understand how this solves any issues. The stack trace does only help the developer, not the handling code. Whenever you catch an exception, you should try to start to prevent this specific exceptional behaviour by improving the defensive strategies so that it does not occur again.
Again, you should only see this exception popping up once and then start to apply defensive strategies. If you know there’s gonna be parsing involved, just try to avoid running into the exception and do not rely on stack traces to tell you what has failed.
Personally, I do neither want to catch three ambigious exceptions, each of them telling me “some integer could not be parsed” nor do I want to catch three more detailed exceptions that I need to handle, if I could just prevent this from happening in the first place.
Somehow missed this thread. Not sure why the tagging didn’t notify me. Anyway, time to weigh in. As a quick disclaimer, this is borrowed knowledge from chemical engineering, I don’t have that much practical experience with code exceptions. Here are some random thoughts.
The first concept for dealing with faults is poka yoke. Loosely translated, that means mistake proofing. If you are dealing with user input, can you make sure that only the correct input can be given. To take your int example, when a user is entering a number, only provide a keypad with numerical values. And so on.
The next idea is input sanitizing. Basically taking the data and checking that it is all valid, before actually trying to parse it. In the int example, one could check that each character is actually a number and that the string length is valid. Its a bit of an overkill for an int, but you get the idea. Invalid inputs can thus be dealt with before we get to an error stage.
Ultimately at some point you have to run the code and see what happens. At this point, errors should be exceptional, so you can catch them and respond appropriately.
In chemical engineering we use the concept of fail safe. That means that whenever the instrument/code/hardware fails, it fails to a state which doesn’t do any harm. For example a high level alarm will default to high level if somebody cuts the wire. And so on. We would delve right down to the level of individual bits in the processor and individual wires in the field.
Unfortunately for this requires the code to know which way to fail is safe. In some cases, like a login system, its obvious. Any error should result in the user not getting access. But in the case of parsing an int, its not clear without external knowledge of the system which way safe is.
This might not solve immediate issues in the code, but “the developer” is an important part of a live system.
In 2014, ThoughtWorks put out a study showing a correlation between “business performance” and mean-time-to-recovery. The point being that, when failures are not “catastrophic”, our ability to detect and fix problems in software is more important than our ability to produce correct software in the first place. To me, this is a strong point for preferring the diagnostic information, including the stack trace, is as helpful as possible to the programmer.
If you’re interested, Martin Fowler gets into the topic a bit in this video about technical excellence. See 5:43 - 6:30 for the specific bit I’m talking about.