Welcome to the Modern Embedded Systems Programming course. My name is Miro Samek, and in this lesson, you'll learn about software assertions and, more formally, the Design by Contract methodology in the context of embedded programming. This is part-1 of the two-part series on this subject. The technique you're going to learn today is the single most valuable and effective strategy for delivering high-quality code. In my programming career, assertions and Design by Contract helped me more than any other programming technique, even more than my favorite state machines. Unfortunately, assertions seem to be the most important non-practice in embedded software. An alarming number of embedded developers haven't heard about assertions at all, know about them but still don't use them, or use them incorrectly. So, today in part-1, my goal is to explain what assertions and Design by Contract are and, even more important, what they are not, and the proper use of assertions. In part-2, you'll see how to apply assertions and DBC in embedded C or C++ and other practical aspects of assertions in embedded systems. What Are Software Assertions? Assertions are Boolean expressions that allow a program to check itself as it runs. When an assertion evaluates to true, a program is operating as expected. Conversely, an assertion evaluating to false indicates an error, and it makes no sense for the program to continue. The C Standard provides a simple `assert` facility. To use these standard assertions, you need to include the standard header file. You then specify a Boolean assertion expression as the argument of the assert() macro. For example, here you see a function int_div() that performs an integer division of x by y. But the division is not defined for zero divisors, so you assert that y is not zero. You then print the x and y parameters and then return x divided by y. Of course, you can have more than one such assertion in a given file, so here, you have another function to calculate integer log-base-two. You've encountered such a function in the lessons about the RTOS, where it was used in the scheduler to quickly find the highest-priority task ready to run. But anyway, a logarithm is not defined for a zero argument, so you assert that x is not zero. Moreover, the function here is implemented with a lookup table, so you additionally assert that you don't index beyond the end of that table. So now, in main, you just call the div and log functions with various parameters and print the results. The last two calls intentionally violate the assertions so that you can see what happens. Now, I will demonstrate how to build and run that simple program on a desktop computer from the command prompt using the GCC compiler. The code will be provided in the lesson-47_desk directory. If you work on Windows, as I do here, you might not have the GCC compiler, but one way of getting it is to download the QP-bundle for Windows, which, among others, contains the MinGW GCC toolset. You build the assert program with GCC as follows. When you run the assert executable, you can see that the program continues until an assertion fails, whereas the printout tells you the assertion expression and the line of code where the failure occurred. In a regular run, the output produced by the program and the error message from the failing assertion are combined. But when you redirect the output into the stdout and stderr streams, you can see that a failing assertion sends the error message to the stderr stream, whereas the normal output from the program goes to the stdout stream. Finally, you can see that a failing assertion apparently aborts the program because the final "end" output is not produced. This is consistent with the premise that continuing after a failing assertion makes no sense. The last feature of the standard assertions I'd like to demonstrate is the ability to disable assertions by defining the NDEBUG macro, for example, in a command-line option to the compiler. This causes the assertion macros to expand into nothing, so assertions generate no code and no overhead. When you run the executable this time, you indeed no longer see assertion messages. Interestingly, now the program prints a logarithm of zero (as minus 1), so it survives the first error previously causing an assertion failure. But the program does not survive division by zero and silently aborts. You guess the program was aborted only because the final printout "end" is still missing. So, this is all there is to the standard assert() facility. It is remarkably simple but, unfortunately, not directly applicable to embedded systems because they typically don't have the stderr stream for assertion messages and cannot abort the same way as desktop systems. Later in this lesson, you will see an implementation that is much more appropriate for embedded systems. But before that, I'd like to discuss the proper use of assertions. Errors Versus Exceptional Conditions To use assertions properly and effectively, you must clearly distinguish between programing errors and exceptional conditions. Errors (known otherwise as "bugs") are persistent defects due to design or implementation mistakes (e.g., dividing by zero, overrunning an array index, de-referencing a NULL-pointer, or using a peripheral before initializing it). When your software has a bug, typically, you cannot reasonably "handle" the situation. Instead, you should focus on detecting, reporting, and ultimately fixing the bug. Also, you typically cannot continue execution. Instead, you must carefully devise a damage control strategy. This is where the assertions come in. In contrast to errors, exceptional conditions are specific circumstances that can legitimately arise during the system's lifetime but are relatively rare and lie off the main execution path of your software. Examples include incorrect user input, transmission errors on inherently unreliable connections (such as wireless), abnormal or degraded modes of operation, etc. In those cases, you should NOT use assertions; rather, you must carefully design and implement strategies that handle such exceptional conditions using regular code. Defensive Programming Attempting to "handle" an error as an exceptional condition is just as bad as the other way around. This programming style is known as "defensive programming." It aims to make the software more "robust" to errors by accepting a wider range of inputs or allowing an order of operations inconsistent with the program's state. For example, a defensively programmed int_div() function would not assert, but instead, it would "handle" the zero-devisor by returning some bogus value, such as 0xFFFF. Similarly, a defensively programmed int_log2() would "handle" the below the range x by returning, say -1, the above-the-range value, by another made-up value, such as 999, and only otherwise the real value from the lookup table. Another example would be the Board Support Package functions for turning LEDs on and off in your TivaC LaunchPad board. Normally, these functions simply write to the GPIO registers, assuming that the GPIO pins have been initialized. But coded defensively, the functions would check wheter the GPIO was initialized and would silently perform the initialization if needed. Defensive programming is often advertised as a better coding style, but unfortunately, it hides bugs and frequently introduces new bugs due to the additional complexity. Please also note that behaviors, like reacting to incorrect user inputs or limping mode in a vehicle, are always a result of intentional design and dedicated code. Specifically, it is rather naive to expect that such behaviors could ever miraculously emerge from "defensive programming." Instead, it is far more likely that defensive programming will produce bogus, incorrect results and undesirable behaviors. Design by Contract But going back to assertions, a powerful methodology that takes assertions to the next level is Design by Contract (DBC), pioneered by Bertrand Meyer in the mid 1980's. (A link to Bertrand Meyer's article "Applying Design by Contract" is in the video description.) DBC views assertions as specifications of mutual obligations between software components -- analogous to contracts between people. The central idea of this method is to inherently embed those contracts in the code (as assertions) and validate them automatically at run time. Doing so consistently has two significant benefits: 1) It automatically helps detect bugs (as opposed to "handling" them), and 2) It is one of the best ways to document the mutual obligations of software components and the internal assumptions necessary for the code to work correctly. What Assertions are NOT Design by Contract perspective helps you to truly understand software assertions. And that is, assertions are NOT an error-handling mechanism. They neither handle nor prevent errors, just like contracts among people don't prevent fraud. For example, asserting that the divisor is not zero does not really prevent calling the int_div() function with zero divisors. Similarly, asserting that an array index is in range might give you a warm and fuzzy feeling that you have "handled" or prevented a bug, when actually, you haven't. What really happened, however, is that you did establish a contract, in which you spelled out that the parameter to your function int_log2() must be in a certain range. As long as assertions are enabled, the contract will be checked automatically, and sure enough, the program will brutally abort if the contract fails. At first, you might think that this must be backward. Contracts not only do nothing to handle (let alone fix) bugs, but they actually make things worse by turning every asserted condition, however benign, into a fatal error! However, please recall from the previous discussion that the first priority when dealing with errors is to detect them, not to hide them. To this end, a bug that causes a loud crash (and identifies precisely which contract was violated) is much easier to find and fix than a subtle one that manifests itself intermittently with millions of machine instructions downstream from the spot where you could have easily detected it. Also, as you'll see in a minute, assertions can provide the last line of defense and an opportunity to perform corrective actions and damage control. In contrast, defensive programming surrenders to the bugs and does not offer such an opportunity. Assertions as Fuses Besides assertions as contracts, another insightful analogy is to think of assertions in software as corresponding to fuses in electrical circuits. Electrical engineers insert fuses in various places of their circuits to instill controlled damage (burning a fuse) in case the circuit fails or is mishandled. It is unimaginable to have any non-trivial circuit, such as a home wiring or electrical system of a car, without many differently rated fuses. Please note that a fuse can neither prevent nor fix a problem, so replacing a burned fuse doesn't help until the root cause of the problem is removed. In fact, the terms "bug" and "debugging" originate from an actual bug found in the wiring. The problem was fixed only after removing the bug. This concludes Part-1 of the two-part series about assertions and Design by Contract. In Part-2, you'll see how to apply assertions and DBC in embedded C or C++. If you like this channel, please give this video a like and subscribe to stay tuned. You can also visit state-machine.com/video-course for the class notes and project file downloads. Finally, all the projects are also available on GitHub in the Quantum Leaps repository "modern embedded programming course." Thanks for watching!