Exceptional Processism

Exceptional processism in programming is treating your own process more favorably with regard to exceptions. Errors that happen in your own process throw exceptions while errors that happen in processes that you are running are not mapped into exceptions in your own process. This breaks the uniformity of error handling in the language:

# pseudo code

# exceptions within the language
try {
  my_func()
} catch(...) {
  ...
}

# but status check when running external program
result = run_external_program(...)
if result.exit_code == ... {
  ...
}

Languages with Exceptional Processism

From the top of my head:

  • Python – subprocess.run() has the “check” parameter which is by default false. Setting check to true means non-zero exit codes causes an exception, so it’s half way.
  • JavaScript (Node.js implementation) – child_process.exec() doesn’t distinguish between different exit codes.
  • Java – Process.waitFor() – same
  • Ruby – Process.wait() – same

That’s of course excluding 3rd party libraries or any custom code.

Languages Free of Exceptional Processism

None of languages that have exceptions came into my mind. Do you know one?

  • Shells, such as bash, treat “their own” errors and errors in processes run from the shell in a uniform way.
  • C ignores any errors and you have to check what each of the functions you call (including running external processes) returns, consistent with the rest of the language.
  • Go – treats non-zero exit codes as an error, consistent with the rest of the language. Good job!

The “Standard” Approach

The standard approach is to differentiate between zero and non-zero exit codes. Languages or libraries (ex: Python’s sh) that do have exceptions can map non-zero exit codes to an exception.

Conflating all non-zero exit codes is semantically incorrect though. They are not the same. In many cases non-zero exit codes are signifying an error but … grep and test for example use the following exit codes: 0 – true, 1 – false, 2 and on – error. There are other programs that use non-zero exit codes to signify various conditions which are not errors.

Next Generation Shell

NGS takes unique approach, as far as I know. Did you expect anything else reading NGS blog? 🙂 It does not only avoid Exceptional Processism but goes beyond. NGS strives for the most semantically correct mapping between exit codes and exceptions, compared to all the languages I am aware of, including shells. It knows about different programs and their exit codes and only maps exit codes that signify errors to exceptions.

It is not possible to know about all programs and their exit code of course. That’s why one other project decided not to implement a facility that will get it wrong sometimes. That’s a valid opinion and approach.

For NGS though, it was decided that the benefits of having correct semantic mapping between exit codes and exceptions outweigh the downsides. The possibility of getting the semantics of exit codes wrong sometimes can not be completely eliminated but it is balanced/mitigated in the following ways:

  • For unknown programs, the conservative approach (aka The “Standard” approach) is taken: non-zero exit code is mapped to exception.
  • There is an easy way to override the behavior per command. Specify which exit codes are “good” and should not cause an exception. This one includes syntax, making it concise. If for example grep would be unknown to NGS, it would look like this: $(ok:[0,1] grep EXPR FILE). Notably, google/zx got there recently, not as elegant as in NGS of course because it wouldn’t make sense to have dedicated syntax for this in zx.
  • It is easy to override exception handling per external command, globally. Define finished_ok() method for your command and you are done.

It is effective to write

if $(grep ...)

knowing that syntax error in grep expression will cause an exception while exit codes 0 and 1 will be treated as boolean result and

$(my_prog ...)

will throw exception if anything goes wrong when you don’t need to manually check the exit code, making your code more verbose than necessary.

NGS has still some room for improvement though. More fine grained distinction between different exit codes for example.

Root Cause of the Mess

Here are my guesses about what happened.

The idea to express result of a program run as one integer was flawed. Furthermore, defining only zero and non-zero exit codes without clear further semantics was a mistake too. These decisions made it impossible to uniformly distinguish between errors and other conditions based on exit codes.

I’m fairly certain that it’s very easy to see these mistakes (or classify as such) retrospectively but they looked fine at the time and likely had some solid logic behind them.

Some people would not consider these decisions mistakes even today. I’m a big fan of semantic understanding of the data by programs. The more programs “understand” the more useful they are. Some think awk "{print $8}" is just fine. I think it’s a symptom of lack of semantics and structured data.


What do you think? Leave your comment.

Have a nice day!

Leave a comment