The following are a few points about programming best practice. Following these steps will help you create much better software.
Ada is an incredible too that allows you, with minimal effort, to create software that works. Although your algorithm may suck (in which case you'll have to fix it) the rest will just work or break quickly. In other words, a legal Ada program is likely a fully functional program.
Obviously the best would be to program without errors from scratch. This would be neat. It's not going to happen too often, I'm afraid... The following are the main points that I want to bring forward to save you a lot of time.
Many people have a hard time with strong typing. It can definitively be annoying to have to convert values between all the different types you create in Ada. Yet, that is how you avoid most bugs in software.
Writing in assembly language, and any memory byte can represent absolutely anything: a sound, a pixel, a true/false flag, 8 hardware flags, your age...
Older languages, such as C have typing, but it isn't as strong as Ada typing. In C, you can declare an integer with char, short, int, long. Any variable defined with any one of those types can be copied in another variable of any one of those types. In other words, you may copy a long in a char and lose 24 bits. Silently.1
In Ada, the compiler prevents such direct copy. Instead, you are required to write a conversion (also called a cast.) It is quite unlikely that, if you write a such cast, you would consciously convert a pixel color to a sound unless that was exactly your intend. Yet, both may be represented by an integer.
Complex types, a variable that can only be set to certainly values, can be handled using a private record and functions to handle the content of the record.
It takes longer to write such in the first place. But in exchange it takes 0 seconds to debug the resulting program.
Most programmer refer to Ada as a very annoying language because it has the bad tendency to spit out errors by the dozens. It is correct. But think about it this way: compilation errors can be fixed by programmer on the spot.
The worst kind of error you get is a run-time error on a customer's machine. Generally you won't be able to get good feedback (unless you, the programmer, are right there when it happens.) A compiler error is wonderful. You fix it once and the customer doesn't even know it occurred.
Note: Some Ada rules say that the compiler has a choice in generating errors at compile time or run-time. This is generally converted in a warning by gnat and other compilers. Watch out for those because it is a sure thing that the code will break when the program reaches that line of code on execution. Just fix it.
Rarely will linkage error be of much help in preventing bugs although it happens. Again, these are preferable to run-time errors.
Assertions are superfluous tests that check for errors and raise an exception when an invalid parameter is caught. By definition an assertion is a read-only test function that is not otherwise required by the software.
Examples of assertions are:
The Ada language automatically adds many assertions to your program.
Note that assertions are quite different from errors. A function encountering an I/O failure while reading a file often returns zero (no bytes read,) although this may be exceptional, it is not considered an assertion because testing for such errors has to always occur2. Assertions, on the other hand, can all be removed from your software and your software will continue to behave in the same way (assuming that none of the assertions were found to be true in all the types of operation supported by your software.)
To be honest, Ada does not yet give us means to include user assertions in our code. This may appear at some point though. The idea of an assertion in a language such as C is that in a release build, it disappear. However, if that assertion had any side effect (which the C compiler won't be capable to tell you about...) then your debug and your release versions aren't the same software. Part of the Ada resilience resides in that aspect: you are writing exactly ONE program.3
A quick note about the level of assertions: one aspect of software are public or private functions. The assertions in public functions should always stay while developing a software. Private function assertions can be removed (assuming the the public functions have been debugged and the private function assertions did not raise an exception.)
A strong compiler that can determine whether an assertion is necessary or not can greatly optimize the generated code.
AAda intends to offer some assertion capabilities taking Assertion Level into consideration. Programmers should also be capable of offering some hints on the matter.
Note that processors also generate exceptions. Two basic and well known exceptions are (1) an access to an invalid pointer (especially a NULL pointer,) and (2) the famous division by zero.
These exception are generally viewed as assertions. (Although an access pointer exception may be handled by your operating system because that specific memory buffer was swapped out to disk, for example.)
In most cases, it is possible to catch these exceptions. Just like any software raised exception. Make sure to do so only as a last resort. The best is to write good code and never ever rely on being able to recover from a processor exception.
Whenever you write a procedure or a function, something is supposed to happen which you know exactly. Make sure to translate that in your code.
For example, your function inputs may be 3 positive numbers and the sum of those 3 numbers may not be more than 100. In that case, add a test to ensure that this is the case.
If necessary, provide documentation of why you do such and such test (i.e. satisfy requirement A34.7)
Note that the small waste in processing time to verify such input (and output) is worth it. Once we have Ada assertion, these can be done with assertions and thus a final release build can remove those extraneous tests.
When calling a function, the input parameters are generally expected to remain unchanged unless the function succeeds. In other words, in a scenario where you change value of variable A, check something, then change the value of B before returning successfully, if an exception occurs while the check runs, the value of A was anyway changed...
Ada provides a solution to this problem: it creates a copy of all the function parameters, whether they are in, in out or out. While running the function, you are modifying the copy. On an exception, the copy is discarded and the source remains unchanged.
This is true with most Ada types, including records, although some complex types such as a record with a discriminant, may not automatically be transactional.
Again, an Ada compiler automatically solves several known basic issues with multi-processing programming. Especially the basic processing of event handling between processes (often called threads in other realms.)
Yet, sometimes, some processes may end up hanging forever. To avoid such problems, it is possible to create a watchdog process. Such process sits around and wakes up once in a while. If its companion process does not respond for too long, the watchdog process can then act upon (i.e. raise an exception, attempt to restart the companion, etc.)