7

I'm looking for style guidance. In Python, exceptions are used as "normal" operations:

try:
    z = x/y
except ZeroDivisionError:
    z = 73.0    # set z if y is zero

Rather than checking for y being near zero, we do the division and catch the exception.

This type of approach is illustrated in Ada in "Programming in Ada 2012" by John Barnes:

begin
    Tomorrow := Day'Succ(Today);
exception
    when Constraint_Error =>
        Tomorrow := Day'First;

But then, the book goes on to say "This is a really bad example. Exceptions should be used for rarely occurring cases...".

I am new to Ada, is it good Ada programming style to use exceptions to avoid if statements as we do in Python? Or are exceptions really just used in Ada for nasty things?

Chris Hobbs
  • 345
  • 2
  • 7
  • 1
    I am very far from an expert in python, and seriously don't know anything about Ada, but as far as I know, python is the special one in being able to have easy-to-use and fast `try-catch` structs. In any other programming language that I know, a `try-catch` would **seriously** impact performance, and therefore you should only use it when you can not otherwise handle it with some parsing/checking. Perhaps that is what the book says. – Ander Biguri Jul 22 '20 at 15:33
  • Agree with @AnderBiguri. Extensive use of `try-catch` is part of Python design and is considered a *pythonic* style, which probably makes sense only to Python, but hey: we could be wrong. – VisioN Jul 22 '20 at 15:37
  • 1
    Exception cost in Ada is implementation dependent; more [here](https://stackoverflow.com/q/52138488/230513). – trashgod Jul 22 '20 at 16:34
  • From the **style** and **readability** point of view, your snippet is mighty fine! – Dima Tisnek Aug 13 '20 at 00:38

3 Answers3

6

A major difference from Python is that in Ada, exceptions are not data types. The only data you can associate with an instance of an exception is a String:

raise My_Exception with "some string";

So when catching an exception, we can only ever know which kind of exception got thrown, and get some String which may be useful for logging, but not so much for programmatically analyzing what went wrong in more detail.

This also means that we don't have a way to chain exceptions, like it is often done e.g. in Java, where an exception is often wrapped inside another exception to hide details while still being able to see in the stack trace what exactly went wrong.

The reason for this is that the language has been designed for exceptions to be used scarcely. Unlike Python, Ada has been designed to target bare metal, where you may want to disable runtime checks for performance reasons. If you do that, you won't get the Constraint_Error on division by zero and the program will have undefined behavior. So using exceptions influences code portability negatively.

Some compilers provide additional options, like the No_Exception_Propagation restriction in GNAT, which allows exception only if they are caught within the same subroutine.

Still, exceptions are a useful tool for communicating failure without completely obstructing normal program flow in the code (you will know what I mean if you've ever written Go, or C). But generally, the Ada Style Guide advises that

Exceptions should be used as part of an abstraction to indicate error conditions that the abstraction is unable to prevent or correct. Because the abstraction is unable to correct such an error, it must report the error to the user.

This means that, if you do have the possibility to correct a problem without using an exception, you should do that, because it should be part of the normal, non-exceptional, program flow.

flyx
  • 35,506
  • 7
  • 89
  • 126
4

Ada exception handling syntax does not use a try-catch mechanism. Because of that the exception handler is often separated from the code raising the exception by many lines of source code. Ada exceptions are expected to be used for truly exceptional problems. A more acceptable style for Ada is shown below.

  if Today < Day'Last then
     Tomorrow := Day'Succ(Today);
  else
     Tomorrow := Day'First;
  end if;

This code pattern retains the relationship between the two conditions in close proximity in the source code. Neither condition is an erroneous condition or an exceptional condition.

Historically Ada style favors readability over compactness of writing, however the Ada 2012 standard does allow conditional expressions such as

Tomorrow := (if Today < Day'Last then Day'Succ(Today) else Day'First);

It may be a matter of taste whether or not to use a conditional expression.

Jim Rogers
  • 4,822
  • 1
  • 11
  • 24
  • 4
    I'd say `begin`-`exception`-`end` is pretty close to try-catch, both in syntax and in semantic. – flyx Jul 22 '20 at 17:17
  • 1
    When used in place of a conditional statement or expression it requires you to create a block for every condition. Every block creates a new scope and impacts visibility rules. Unnecessary blocks do more than add visual complexity. They are an invitation for future maintenance errors. – Jim Rogers Jul 22 '20 at 17:21
  • 2
    I didn't argue against the implications. I just don't see how try-catch is really different from begin-exception-end. Without a `declare`, the new scope does not contain any symbols anyway. – flyx Jul 22 '20 at 17:28
1

Exceptions exist to signal exceptional situations, ensuring that they are responded to (unlike status codes, which may be ignored), and separating their processing from unexceptional situations. While errors are exceptional situations, not all exceptional situations are errors. For example, when dealing with user input, invalid input is exceptional but expected from time to time; it is reasonable to use exceptions to handle this. I think it was a mistake for Ada to name all of its predefined exceptions with names ending in _Error.

Sometimes it's necessary to use exceptions even when it would be better to avoid them. For example, if a text file ends will a null line, you want to read that file with Ada.Text_IO, and it's important that you read that null line, you cannot use Ada.Text_IO.End_Of_File. You have to keep reading and handle the resulting End_Error exception.

Where you draw the line between special cases and exceptional situations is a matter of skill, experience, and how much thinking you do before generating code. If you need to cycle through a set of values (such as day names) a lot in a system, a coder will sprinkle if statements or block statements with exception handlers throughout the code, a better coder will create a function to do it for each type involved, and a software engineer will recognize an opportunity for reuse and find or create something like PragmARC.Wrapping, and so simply write

Tomorrow := Wrap_Succ (Today);

without worrying whether Wrap_Succ uses an if or an exception handler.

Jeffrey R. Carter
  • 3,033
  • 9
  • 10