Opinions on coding style re: error handling - Page 1
Forum section: electronics / general

author: AndyC_772
author_rank: Super Contributor
timestamp: August 14, 2017, 01:56:02 pm
content: Hi all,Suppose I'm writing a function (in C) to carry out a sequence of steps, any one of which might fail and cause the whole sequence to have to be aborted. The first step might be, say, to determine if a signal is present, the second measures some interesting characteristic about it, and the third determines whether or not that characteristic is within some limits. Subsequent steps can't be performed if an earlier one failed.I'm interested in ideas for a clean, tidy way to handle the error checking. For example, one option might be:Code:init();err = first_stage();if (err)return;err = second_stage();if (err)return;err = third_stage();return err;This way seems quite natural to me. Do as much as you can, but if something fails, then stop and don't go any further. But it only works if it can simply return, rather than having to (say) flush buffers and reset hardware before exiting. If there's anything else to do, then the tidying-up before each return point gets repetitive.Another might be:Code:init();err = first_stage();if (err == 0)err = second_stage();if (err == 0)err = third_stage();return err;This conforms to the 'ideal' that a function has only one exit point, which might be more elegant if the function had to do any tidying-up before exiting. But it suffers from the downside that before every step, it has to check that all the earlier ones completed OK, and that seems ugly and hard to scale.Is there a generally accepted way of handling this type of sequence which scales well and doesn't get repetitive if there are multiple places where errors might occur?

author: ironmonkey
author_rank: Contributor
timestamp: August 14, 2017, 02:08:46 pm
content: Let me know if I am misinterpreting you. How about using a flag and rising it up when something fails, then exiting with breakhttps://stackoverflow.com/questions/9695902/how-to-break-out-of-nested-loops?

author: AndyC_772
author_rank: Super Contributor
timestamp: August 14, 2017, 02:15:22 pm
content: Ah, yes,goto.[evil laugh]Thanks!

author: tombi
author_rank: Regular Contributor
timestamp: August 14, 2017, 02:23:11 pm
content: This gets trickier if you need to unwind stuff in case of errors (for example de-allocating something or reversing a change you made).ret = doSomething()if ( ! success(ret) ){/* Unwind something */return ret;}ret = doSomethingElse()if ( ! success(ret) ){/* Unwind something AND somethingElse() */return ret;}...You get the idea.A relatively common practice is to use a goto to a cleanup section and the bottom of the function and return from there. Yes I know gotos are bad. Lots of coding standards I have seen permit them for this one case as it works very well,An even more evil way of expressing the same thing without gotos is usewhile(1){ret = doSomething();if (! success(ret) ){break;}...break;}A third way to implement what you described is a series of if else blocks but that would be my (as someone reading your code) least preferred. The first option is fine unless the logic gets too nested and it isn't apparent when and where it will exit. Depending on the size of the device this is running on you may want to trace something in the event of the errors so you can figure out what went wrong later.Even if that does happen you can re-factor the function into smaller functions until you end up with understandable chunks.This very much why exceptions were invented.Tom

author: bd139
author_rank: Super Contributor
timestamp: August 14, 2017, 02:31:17 pm
content: I use this pattern. Goto is fine for this (despite complaints from some people), particularly if you need to do something after the error like clean up.Stick a macro around it as well if you fancy it looking a bit cleanerCode:#define STEP(result) { if(!(result)) { /* optionally log */; goto error; }int process() {int result;init();STEP(result = first_step());STEP(result = next_step());STEP(result = final_step());return result;error:cleanup();return -1;}

author: helius
author_rank: Super Contributor
timestamp: August 14, 2017, 02:34:13 pm
content: There is an antipattern called "goto fail" that has led to some serious security bugs.https://nakedsecurity.sophos.com/2014/02/24/anatomy-of-a-goto-fail-apples-ssl-bug-explained-plus-an-unofficial-patch/The problem is, when lines of code are added, moved, or deleted, it's easy for an unconditionalgototo slip in. Your version withoutgotoisn't as risky.

author: bd139
author_rank: Super Contributor
timestamp: August 14, 2017, 02:50:26 pm
content: Goto fail was a hilarious merge fudge. I would have picked that up on a code review.(my macro has a brace mismatch above I just noticed)

author: Jeroen3
author_rank: Super Contributor
timestamp: August 14, 2017, 03:01:31 pm
content: I was taught not to have more than one return from a function.Code:char fail = 0;if(0 > first_stage()) fail++; elseif(0 > second_stage() ) fail++; elseif(0 > third_stage() ) fail++; else{// Success}if(fail){// fail}

author: magetoo
author_rank: Frequent Contributor
timestamp: August 15, 2017, 01:45:38 pm
content: A friend showed me an elegant way of solving this old problem in F#, illustrated with railway switches. Call all functions in order, passing data along. If there's an error, switch to a pass-through track where functions do nothing.It can't be used in C directly, but I think it might be possible to follow the general idea; it it might look something like this. (Apologies for my rusty C)Code:// This is the thing that gets passed around to all functionstypedef struct {int error;// other data that has to be passed around, file handles / windows / whatever} state;main() {state foo, bar;success = first_stage(foo);}int first_stage(state foo){// useful code here// if error, set foo.error > 0second_state(foo);cleanup_first_stage();return foo.error;}void second_stage(state foo){if (foo.error==0) {// useful code here}third_stage(foo);cleanup_second_stage();}void third_stage(state foo){// similar to second_stage}In practice I think since we're testing for error > 1 in every function you'd want to skip calling the following function too, and we end up with something looking more normal.And it's probably overkill.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: August 15, 2017, 04:20:28 pm
content: I vote forgotoas well, and unironically.Consider thattry catchis just syntactic sugar forgoto fail. If you don't have one, you can use the other.Making sure to note that you use it consistently and sparingly.I'm all for using perversions of the language.As long as it is carefully commented, used very sparingly, and made very obvious.In the above Apple example, note that it would've been solved if they had usedif {statement}, rather thanif ()statement;. If bothgotos were inside theif, only the first one would fire (the second would be dead code, easily detectable on static analysis, and harmless on execution). If only the one were inside, the other would stick out like a sore thumb, and also fail static analysis (therest of the functionis dead code).That said, as written, the second case is true anyway. So, clearly, if that truly was the pattern at fault: they didn't perform a static analysis on that stupid function at all!Tim

author: hendorog
author_rank: Super Contributor
timestamp: August 15, 2017, 09:29:09 pm
content: Quote from: T3sl4co1l on August 15, 2017, 04:20:28 pmAgree, the problem had nothing to do with the use of goto.And always adding the braces is good insurance for future changes - particularly in this type of code. It is so easy to come along later and add another line of code without realising the braces are missing.

author: helius
author_rank: Super Contributor
timestamp: August 16, 2017, 12:29:55 am
content: In a hypothetical universe where Apple had writtenCode:{goto fail;}the danger of duplicate, unguarded lines is unchanged. The basic problem is that merging contextual diffs from different branches of a codebase is easily confused by lines that appear textually the same, but are not interchangeable. It happens that this blows up easily whengotois used, but there are other statements that could also wreak havoc.breakis a perennial issue, for example.Static analysis is of interest to academics, but is basically unknown in the software industry. A major reason is that commercial programs are never finished, so there is no version of the program worth statically analyzing.

author: hendorog
author_rank: Super Contributor
timestamp: August 16, 2017, 02:27:42 am
content: Quote from: helius on August 16, 2017, 12:29:55 amTrue - however it would also depend upon the eyeballing which occurred after the merge. The braces would make the indenting more obvious if they are written in the normal fashion.Not sure why using static analysis or not would have anything to do with something being finished. As features are added it is essentially finished again and again. Probably more due to the low signal to noise ratio of those types of tools.

author: John Coloccia
author_rank: Super Contributor
timestamp: August 16, 2017, 03:05:57 am
content: I use goto, and not only do I use goto but I wrote into our coding standard that we should use goto as opposed to other ridiculous ways of doing this.Even MISRA 2012 now allows gotos after it got it's ass handed to itself in real life performance.

author: Brumby
author_rank: Unknown
timestamp: August 16, 2017, 04:05:04 am
content: As I understand it, the BIG reason for putting the GOTO into the naughty corner is in program structure - for readability and maintainability.I have seen and had to modify programs written (in assembler) using GOTO (Branch) in the haphazard form that earned its dark label. Known as spaghetti code, unstructured jumps forward and backward create a challenge for understanding and a potential headache for maintenance.I, personally, have no qualms about using a GOTO in a scenario as described by the OP - just make sure that itcontributesto readability. To help rein in any tendencies to stray, you can add guidelines on how this is done, such as only jumping forward and only using it for exception situations. I would suggest it gets used in a similar vein as the BREAK statement - but with a specific destination, it avoids the confusion of the scope of a BREAK that can come from complex logic.Absolutecondemnation of the GOTO is, IMHO, a sign of arrogance, ignorance or both. Certainly there are ways to program without using it, but are theyalways better?I'm not suggesting that the GOTO be put on a pedestal. It shouldn't. It should be thelastconsideration - but it is a valid one.

author: IanB
author_rank: Super Contributor
timestamp: August 16, 2017, 04:14:04 am
content: Quote from: helius on August 16, 2017, 12:29:55 amIt is? That's not my experience.QuoteEvery version gets statically analyzed, continually, upon every build. Like running unit tests.

author: radar_macgyver
author_rank: Frequent Contributor
timestamp: August 16, 2017, 05:32:51 am
content: See page 71 ofHP Journal June 1993for a rather different approach. It's what try/catch does under the hood. I personally prefer the goto fail approach, even if my colleagues ridicule it. For the reasons that caused the SSL bug, I always use braces around conditional statements, even one-liners, unless the conditional is on the same line:if (condition) {do_stuff();}if (condition) do_stuff();if (condition)do_stuff();

author: T3sl4co1l
author_rank: Super Contributor
timestamp: August 16, 2017, 05:39:32 am
content: Quote from: helius on August 16, 2017, 12:29:55 amBut who writes braces like that? If it had been,Code:if (expression) {goto fail;}It would stick out like a sore thumb.As for context, dumb merge processes, and checking the results of tools (like static analysis) -- it's possible to miss anything when humans are in the loop. Until we have compilers that understand specifications, code will always be subject to human shortcomings.(Not that there can be an algorithm to convert specifications into code; and even if there were, those damn humans would just bugger up the spec.)Tim

author: dmills
author_rank: Super Contributor
timestamp: August 16, 2017, 10:40:05 am
content: Quote from: T3sl4co1l on August 16, 2017, 05:39:32 amIME They generally do that anyway even without compilers that convert spec into code.Anyone else remember when UML was going to make programming obsolete?Regards, Dan.

author: bd139
author_rank: Super Contributor
timestamp: August 16, 2017, 10:57:15 am
content: Unqualified Muppet Language.The only take home for me from that was sequence diagrams which are a good communication tool for protocol descriptions.

author: stmdude
author_rank: Frequent Contributor
timestamp: August 16, 2017, 11:29:06 am
content: Typically, I write it like this:Code:err = first_stage();if( err != ERROR_OK ) return err;err = second_stage();if( err != ERROR_OK ) return err;err = third_stage();return err;Now,this doesn't allow for unwinding, but that's handled nicely with goto as other have suggested.I have my error-codes setup to be unique per module so that they can be preserved throughout a chain of returns.Let's say you have a stepcounter (pedometer), which uses an accelerometer for it's raw data, which in turn is on a shared I2C bus.Calling "pedometer_process_data()" can easily return "EI2C_BUS_CONTENTION", which is a state that's only returned in the i2c bus driver.(I know OP didn't write code like this), but I hate it when I see code like this:Code:if( function() != 0xAA ) return -1;The return-value from function() is now lost, and it might have a few different ways to fail, but that information is now lost to me, and debugging is more difficult..

author: Kjelt
author_rank: Super Contributor
timestamp: August 16, 2017, 11:57:12 am
content: Quote from: Jeroen3 on August 14, 2017, 03:01:31 pmYeah me too and Lint produces warnings if you don't use a single return per function.I would like to make a distinction between: 1) an error that you are going to handle and have to preserve and 2) an error that is fatal.The first one I declare enum and have to preserve the return code (=errorcode) of the function so it is trivial that after an error has occurred no more other processing can overwrite the errorcode.Instead of returning right away which is the first that comes to mind you could test each processing step if an error has occurred. So in practice this means if the NO_ERROR is 0 and all other errors are enums you can just use "if (Returncode != NO_ERROR) (.......)" this is more readable IMO than nesting all functionlity till the bottom and then unwinding.The nice quirck is that you can call as many functions from different modules you like as long as you keep the errorcode handling consistent, if an error occurs all other functions will not be called or return without modifying the errorcode. Than when you get back to the main calling function you can still determine what and exactly where went wrong. Using a lot of errorcodes (preferably each unique) can help dramatically in debugging.For the last one I prefer to call an abort function, logging all relevant items you need to reproduce the error and then run a while(1) {} do nothing till the watchdog kicks in and resets the product.

author: nctnico
author_rank: Super Contributor
timestamp: August 16, 2017, 11:59:50 am
content: Quote from: AndyC_772 on August 14, 2017, 01:56:02 pmI use a goto but try to avoid it.

author: bd139
author_rank: Super Contributor
timestamp: August 16, 2017, 12:12:23 pm
content: Not sure why people avoid using more than one return. Sounds like an anti pattern not using that. Rationale being that most short circuit functionality up front such as input validation and "fail fast" functionality needs to return early and you don't want to have to carry the resulting conditional through the entire function body or execute any code as that increases complexity, execution cost and indent levels which are an anti pattern.The least number of conditional tests, allocations (heap or stack) and lowest final instruction count has the least number of failure conditions.

author: Kjelt
author_rank: Super Contributor
timestamp: August 16, 2017, 12:29:58 pm
content: Quote from: bd139 on August 16, 2017, 12:12:23 pmThat is a can of worms you are opening and the possible answers can differ on application.For me personally:- readability, a maze is easier to solve when there is only one exit.- if you need multiple returns than it can be a signal that your function is overly complex, eg can't you better break it up in multiple functions ?- if you need to clean things up before exiting, for instance you allocated memory you have to free, you have to write that cleanup code for each and every return.I am definitely not saying it is the best way to do it or always better, definitely not. There are cases I am inclined to use multiple returns myself (for my own code) , for my employer it is a no go.see alsohttps://en.wikipedia.org/wiki/Structured_programmingat "early exit"Quote

author: tggzzz
author_rank: Super Contributor
timestamp: August 16, 2017, 12:34:33 pm
content: Quote from: bd139 on August 16, 2017, 10:57:15 amThe FSM diagrams are OK, but they are a ripoff of Harel's StateCharts.Revealingly, when designing the Boeing 777 systems in the early 80s, it was realised there was a need for two parallel hierarchies:one for specification, and it could be executedone for implementationThey are independent, since one part of the specification can be implemented in hardware and/or software and/or mechanics.The only part they automated was a database containing the cross references between the design and implementation hierarchies, so they could seeall specs had been implemented and wherefor any implementation artifact, which parts of the specification it was implementing

author: AndyC_772
author_rank: Super Contributor
timestamp: August 16, 2017, 01:03:49 pm
content: Thanks for the suggestions so far, everyone! Some good ideas here.Quote from: Kjelt on August 16, 2017, 12:29:58 pmI'm sure I've been taught the 'only one return' "rule", the justification being that if there's only one way to exit from a function, then it's easy to see that the function is always going to carry out whatever clean-up operations are needed prior to that exit.I'm not convinced writing the code that way is necessarily a good thing, though. In this particular case, for example, the steps to be performed are basically:- Trigger an external device to generate some raw data, which should result in that data ending up in a queue in RAM- If, and only if, that data is received OK, then check it's valid- If, and only if, that data is valid, then calculate a useful result from itThe exit conditions aren't necessarily quite the same in each case, so generic clean-up code might still have to act differently depending on what the failure was. For example, if the external device doesn't generate the expected data, we probably don't want to advance the queue, but we do in every other case.Maybe another option is to set flags before each potential exit point, which describe the things that need doing, eg.Code:uint32_t exit_actions = 0;if (fetch_data() != RESULT_FETCH_OK){exit_actions |= FLAG_REBOOT_DATA_SOURCE;exit_code = FAIL_NO_DATA;goto exit_point;}if (data_valid() != RESULT_VALID){exit_actions |= FLAG_ADVANCE_QUEUE | FLAG_RECALIBRATE;exit_code = FAIL_DATA_INVALID;goto exit_point;}if (process_data() != RESULT_WITHIN_LIMITS){exit_actions |= FLAG_ADVANCE_QUEUE;exit_code = FAIL_DATA_OUTSIDE_LIMITS;goto exit_point;}..exit_point:if (exit_actions & FLAG_REBOOT_DATA_SOURCE){reboot_data_source();flush_everything();log_major_error();}if (exit_actions & FLAG_ADVANCE_QUEUE){advance_queue();}..return exit_code;

author: Kjelt
author_rank: Super Contributor
timestamp: August 16, 2017, 01:15:01 pm
content: Quote from: AndyC_772 on August 16, 2017, 01:03:49 pmNeither am I and the programmers community is very devided on this topic AFAIK.Most young players I encounter only have knowledge of program languages that have automatic garbage collection so that is a totally different story.QuoteI would split this up in seperate functions.Func 1: trigger device and get dataFunc 2: check received data for completenessFunc 3: Process dataI would probably put it in a statemachine, that way the transition conditions are even more clear.But each programmer has its own style and preferences, in that sense we are artists

author: bd139
author_rank: Super Contributor
timestamp: August 16, 2017, 01:42:15 pm
content: Unfortunately it's my job to stop the artists painting with their own excrement

author: IanB
author_rank: Super Contributor
timestamp: August 16, 2017, 06:09:12 pm
content: Quote from: AndyC_772 on August 16, 2017, 01:03:49 pmIn that case I would do the clean up locally after each operation, then check whether it is OK to continue, then "goto exit" if not. In this case "goto exit" does nothing except jump to the single return at the end of the method.I do like the "goto exit" pattern rather than having extra returns interspersed within a method. This helps enormously with logging and debugging (for instance you can reliably put a trace log or a break point in one place before the method returns and be sure it always gets encountered).

author: IanMacdonald
author_rank: Frequent Contributor
timestamp: August 16, 2017, 07:37:16 pm
content: I would prefer using multiple exit points rather then having a situation where the process runs on though multiple error tests, any one of which might fail to trap the problem and allow something bad to happen. Basically, an abort should be an abort.You can always call a cleanup function before each return.As for GOTO, I'm currently writing code using javascript callbacks, and that is just oh-so like vintage BASIC. Only worse, since at least after a GOTO you can GOTO back where you came from. Perfectly manageable if you are careful about keeping track of where your branches are.With callback functions you can't even go back where you came from, because the callback can only be to a function. So whatever process was executing when the need for the callback arose has to be started over afresh. Needless to say there are plenty of opportunities for creating endless loops with that scenario. Which is typically what's involved when a website displays the 'script has stopped responding' message. It likely hasn't stopped running as such, but is stuck in an endless series of callbacks.It's been said that if you wait long enough, all bad ideas will be resurrected.

author: tggzzz
author_rank: Super Contributor
timestamp: August 16, 2017, 09:03:08 pm
content: Quote from: IanMacdonald on August 16, 2017, 07:37:16 pmIndeed, I've seen that happen too many times.Classic examples arebecoming sick of unnecessary errors discovered at runtime in dynamically typed languages, so moving to errors caught by the compiler in statically typed languages. Then the next generation feeling constrained by statically typed languages (and disliking typing control-space in IDEs) and moving to dynamically typed languagesasynchronous protocols layered on top of synchronous protocols layered on top of synchronous protocols layered.... it is turtles all the way downgraphical programming languages that are supposed to make it less necessary to use the underlying textual language (today's example is visduino)

author: Howardlong
author_rank: Super Contributor
timestamp: August 16, 2017, 11:21:56 pm
content: I haven't used a goto in a high level language since BASIC with line numbers was a thing. I also tend to avoid returning from a function prematurely. I even make efforts to try to avoid 'continue' and 'break' inside loops. Those last points are more of a value judgement, if it's going to make things more convoluted and less readable by avoiding them, then I go for the most maintainable solution. It is rare though to see a return other than at the end of a function, or a break within a loop, and even more rare a continue in my code (I can't remember the last time I used continue).While I'm not particularly dictatorial about it to others, my own C code follows a few different methods for dealing with exceptions, depending on the complexity and scenario, but I always use braces for single line blocks, and always consistently follow the same white space/indent methodology. At this stage in my career, and being a one man band mostly working on my own projects, I almost always get to decide how to write code. I realise not everyone has that luxury.I have little trouble reading others' code in whatever format they choose, as long as it's well laid out and consistent. Frankly IMHO if you get upset reading someone else's reasonably formatted and structured code, then you need to practice more!One of the nice things about lower level headless embedded stuff (about 70% of what I do programming-wise) is that a good deal of errors are fatal, and generally you will avoid memory and resource allocation errors as everything tends to be statically allocated. Catching all errors is good practice in the debugging cycle, even if it simply generates a CPU exception where you can figure out where it was generated. In a finished retail product, a good deal of the time you should never get there.Dealing with the OP, within the scope of a medium to complex function, I include an initialised local "rc" variable (Return Code, often set up as an a global enumerated typedef) and test rc at each functional block in the function. Also at function scope I'm likely to have a selection of handles and pointers, that are initialised to null, but are subsequently set up as the function progresses; alternatively they may be within a structure passed by reference to the function.At the end of the function, just prior to returning, if rc contains an error, I deallocate/dispose/terminate/reset any allocated resources by testing each of the handle and pointer values that were set in the reverse order they were allocated. I also reset to null/invalid each handle and pointer as they're deallocated/terminated etc. The rc is then returned to the caller, and that can choose to do its own error handling based on that.