Developers, don’t be afraid to exit or panic

A 5 minutes story written on Sep 2018 by Adrian B.G.

image

If a function or process should not continue, it is OK to “exit”!

The issue is more prominent to junior level programmers, but I can assure you that it is not exclusive to them.

I have found this “problem” at two main stages: at the function and at the process level. I will try to “treat” both of them in the same article. I will use the exit keyword to signal a function return or a process exit. Error can be an exception, an error or an exit code.

image
bad code, if you cannot open the file return

I think that, we developers, have a psychological issue with the “downstate”. We build “things” that are supposed to run forever, we often only take into consideration the golden path.

I think it is hard for us to write the code that will disable/shutdown our “baby” (process).

image

I. Process level

I identified a few basic examples, in which I think it is OK to close the process.

Cannot fulfill ANY of my functions

I think that this rule applies to the majority of cases: if you cannot achieve your main functionality try harder, degrade your service and then exit with a bang (error).

In case of a partial failure you should throw errors without exiting, hopefully, an alert will be triggered and a developer will respond. Example: you have to respond to a request with data from 2 sources, and only one of them is inaccessible.

image
try your best, throw errors, then fail

Bad Input

It should not be a common problem, but I want to mention it anyway. The most obvious case is a CLI tool. Validate and sanitize your input, and if one of them is invalid you can return with an error. Other example: an invalid connection string to the database. The input refers to all its forms: command parameters, environment variables, config files.

Input Validation Cheat Sheet - OWASP

Missing resources

If your program’s only function is to parse a file or use a resource that is missing or cannot be accessed, there is no reason to continue, exit with an error. Fail fast so the users can notice.

Exit codes

When closing a process in error, usually is enough to exit with a status/code of 1, but if you have multiple failures you can use other positive numbers, just make sure you communicate that to the clients, trough a help command or the documentation.

Exit status — Wikipedia

image

II. Function level

Like I mentioned at the beginning, the “fear” of quick exit from a function when it cannot fulfill its purpose should not exist.

Do not stale or return the wrong result, rather tell your caller that something is wrong.

image

Asserts can be a powerful weapon, but not all developers agree with them. It is an easy and obvious way to check that all the assumptions are true before executing your primary functionality. Two things I want to mention here:

  • return a meaningful error message
  • do not overuse them (they are not a placeholder for tests)

You can write “vanilla” asserts as well if (myCond == false) return error; but a specialized framework will make your code simpler, in my opinion.

Philip Guo - The benefits of programming with assertions (a.k.a. assert statements)

Do not forget to return an error or throw an exception if you exit a function earlier, and you could not “achieved your duty”. As for how the code should look, I like to keep the line of sight as left as possible and the happy path at the end**. More about this: Should I return from a function early or use an if statement?

image

III. Caveats

Exponential backoffs

When accessing a resource (file, API, database, random number generator …) you can use this technique. Meaning if you failed to access the resource, you can try again (if you are not limited by a time deadline) using an exponential pause. Example: you cannot access the database, try again 3 more times and then exit with an error. The first retry could be done after 10ms, the 2nd after 600ms and the 3rd after 2 seconds.

For more details about how to implement and why to do it, see:

Exponential backoff - Wikipedia

Retry

Fail fast

Exponential back offs can introduce “cascading failures” to your services. The requests wait, the queues fill up, the new requests are rejected, and the resources will have a hard time recovering with all those retries coming on them.

The opposite to exponential back off is to avoid a retry and fail as quickly as possible. You can read more about these 2 topics:

Designing a Microservices Architecture for Failure | @RisingStack

Locks

If you own a lock, started a transaction, or blocked a resource do not forget to release it before exiting.

Partial degradation

If you want to have a HA service, and only a part of your functionalities are affected, you may want to keep your process running, and respond with what you have.

A partial response is better than none, most of the times.

Auto restart

If your app instances are controlled by an auto load balancer, or Kubernetes, you should think twice before ending the process. A new identical sibling will be launched and most likely it will have the same problem as the closed process. You do not want to have all of your instances up&down each second, creating havoc in your clusters.

Communicate

If you have a problem, do not forget to tell your clients too. In the case of a web service you should also use the proper HTTP codes:

Which HTTP errors should never trigger an automatic retry?

HTTP Status Codes - httpstatuses.com

Conflict with the “one return” rule

When possible, it is nice to have only one return statement in a function, but do not decrease the quality of the code only to achieve this. I prefer to have many returns than a 5 indentation level function and a few more temporary variables just to stick with this rule. A more comprehensive article about this:

Avoid Else, Return Early

Two Things about Conditionals in JavaScript

Thanks! 🤝

Please share the article, subscribe or send me your feedback so I can improve the following posts!

comments powered by Disqus