0% found this document useful (0 votes)
28 views

exception.handling.fundamentals.and.programming

Uploaded by

yan.liang.sh
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
28 views

exception.handling.fundamentals.and.programming

Uploaded by

yan.liang.sh
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 121

SpringerBriefs in Computer Science

Pedro Mejia Alvarez · Raul E. Gonzalez Torres ·


Susana Ortega Cisneros

Exception Handling
Fundamentals and
Programming
SpringerBriefs in Computer Science
SpringerBriefs present concise summaries of cutting-edge research and practical
applications across a wide spectrum of fields. Featuring compact volumes of 50 to
125 pages, the series covers a range of content from professional to academic.
Typical topics might include:

• A timely report of state-of-the art analytical techniques


• A bridge between new research results, as published in journal articles, and a
contextual literature review
• A snapshot of a hot or emerging topic
• An in-depth case study or clinical example
• A presentation of core concepts that students must understand in order to
make independent contributions

Briefs allow authors to present their ideas and readers to absorb them with
minimal time investment. Briefs will be published as part of Springer’s eBook
collection, with millions of users worldwide. In addition, Briefs will be available
for individual print and electronic purchase. Briefs are characterized by fast, global
electronic dissemination, standard publishing contracts, easy-to-use manuscript
preparation and formatting guidelines, and expedited production schedules. We
aim for publication 8–12 weeks after acceptance. Both solicited and unsolicited
manuscripts are considered for publication in this series.
**Indexing: This series is indexed in Scopus, Ei-Compendex, and zbMATH **
Pedro Mejia Alvarez • Raul E. Gonzalez Torres
Susana Ortega Cisneros

Exception Handling
Fundamentals and Programming
Pedro Mejia Alvarez Raul E. Gonzalez Torres
CINVESTAV-Guadalajara CINVESTAV-Guadalajara
Zapopan, Jalisco, Mexico Zapopan, Jalisco, Mexico

Susana Ortega Cisneros


CINVESTAV-Guadalajara
Zapopan, Jalisco, Mexico

ISSN 2191-5768 ISSN 2191-5776 (electronic)


SpringerBriefs in Computer Science
ISBN 978-3-031-50680-2 ISBN 978-3-031-50681-9 (eBook)
https://doi.org/10.1007/978-3-031-50681-9

© The Editor(s) (if applicable) and The Author(s), under exclusive license to Springer Nature Switzerland
AG 2024
This work is subject to copyright. All rights are solely and exclusively licensed by the Publisher,
whether the whole or part of the material is concerned, specifically the rights of translation, reprinting,
reuse of illustrations, recitation, broadcasting, reproduction on microfilms or in any other physical way,
and transmission or information storage and retrieval, electronic adaptation, computer software, or by
similar or dissimilar methodology now known or hereafter developed.
The use of general descriptive names, registered names, trademarks, service marks, etc. in this publication
does not imply, even in the absence of a specific statement, that such names are exempt from the relevant
protective laws and regulations and therefore free for general use.
The publisher, the authors, and the editors are safe to assume that the advice and information in this
book are believed to be true and accurate at the date of publication. Neither the publisher nor the
authors or the editors give a warranty, expressed or implied, with respect to the material contained herein
or for any errors or omissions that may have been made. The publisher remains neutral with regard to
jurisdictional claims in published maps and institutional affiliations.

This Springer imprint is published by the registered company Springer Nature Switzerland AG
The registered company address is: Gewerbestrasse 11, 6330 Cham, Switzerland

Paper in this product is recyclable.


Preface

Whether you’re building a simple app or an intricate software system, things can go
wrong. These unexpected events, or "exceptions", can range from trivial matters like
a missing file to severe errors that can crash an entire system. If these exceptions
are not handled, they can lead to unreliable and unpredictable software behavior.
Exception handling is the process of responding to the occurrence of exceptions
– abnormal or exceptional conditions requiring special processing – during the
software’s execution. Exception handling is crucial in producing robust software
that can cope with unexpected situations, ensuring the software is more resilient,
maintainable, and user-friendly.
In this book, we will delve deep into the world of exception handling with examples
written in C++ and Python. Starting with its history and evolution, we will explore
the many facets of exception handling, such as its syntax, semantics, challenges, best
practices, and more. You’ll understand the nuances between syntax and semantic
errors, learn how to employ try-catch blocks effectively, grasp the importance of
logging exceptions, and even delve into advanced exception-handling techniques.
The following chapters will provide you with a comprehensive understanding of
this crucial software development concept:
Chapter 1 provides an introduction, covering the history, various definitions,
and challenges of exception handling. Chapter 2 delves into the basics, offering
insights into the foundational concepts and techniques. Chapter 3 touches upon the
best practices for exception handling, including the differences between errors and
exceptions, the use of assertions, and how to provide meaningful error messages.
Chapter 4 takes a deep dive into advanced exception-handling techniques. Here, we
explore patterns, guard clauses, hierarchical exception handling, and much more.
Finally, chapter 5 focuses on the complexities of exception handling in real-time and
embedded systems.
Whether you are a seasoned developer looking to refine your understanding of
exception handling or a newcomer eager to grasp its essentials, this book offers a clear,
thorough, and practical guide to mastering this crucial area of software development.
As you progress through the chapters, you will acquire the knowledge and skills
needed to handle exceptions effectively, ensuring that your software applications

v
vi Preface

are more robust, reliable, and resilient to unexpected disruptions. Welcome to the
enlightening journey of mastering exception handling!

CINVESTAV-Guadalajara, Mexico Pedro Mejia-Alvarez


CINVESTAV-Guadalajara, Mexico Raul. E. Gonzalez-Torres
CINVESTAV-Guadalajara, Mexico Susana Ortega-Cisneros
Contents

1 Introduction to Exception Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1


1.1 History and Evolution of Exception Handling . . . . . . . . . . . . . . . . . . . 1
1.2 Definition of Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2.1 Examples of Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2.2 Exception Objects and Structures . . . . . . . . . . . . . . . . . . . . . . . 5
1.2.3 Checked and Unchecked Exceptions . . . . . . . . . . . . . . . . . . . . 7
1.2.4 Internal and External Exception Handling . . . . . . . . . . . . . . . . 8
1.2.5 Attachment of Handlers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.3 Challenges of Exception Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.3.1 Preventing Crashes and Unexpected Behavior . . . . . . . . . . . . 12
1.3.2 Improving Robustness and Maintainability . . . . . . . . . . . . . . . 13
1.3.3 Enhancing Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.4 Syntax and Semantic Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.4.1 Syntax Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.4.2 Semantic Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.5 Design by Contract: A Precursor to Exception Handling . . . . . . . . . . 15
1.5.1 Principles of Design by Contract . . . . . . . . . . . . . . . . . . . . . . . 15
1.5.2 Relationship with Exceptional Situations . . . . . . . . . . . . . . . . . 15

2 Basics of Exception Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17


2.1 Try-Catch Blocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.2 The Throw Keyword in Exception Handling . . . . . . . . . . . . . . . . . . . . 19
2.3 Custom Exceptions Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.4 Exception Propagation in Exception Handling . . . . . . . . . . . . . . . . . . . 21
2.5 Nested Try-Catch Blocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.5.1 Benefits of Nested try-catch blocks . . . . . . . . . . . . . . . . . . . . . 23
2.5.2 Challenges of Nested Try-Catch Blocks . . . . . . . . . . . . . . . . . . 24
2.5.3 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.5.4 Best Practices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.6 Conditional Exception Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.6.1 Event-driven Exception Handling . . . . . . . . . . . . . . . . . . . . . . . 26

vii
viii Contents

3 Exception Handling Best Practices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29


3.1 Differences between Errors and Exceptions . . . . . . . . . . . . . . . . . . . . . 30
3.2 Errors as Undesired Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.2.1 Critical Aspects to Undesired Events . . . . . . . . . . . . . . . . . . . . 31
3.3 Error Codes and Return Values in Exception Handling . . . . . . . . . . . 33
3.3.1 Error Codes in C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
3.3.2 Return Values as Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.4 Assertions and Exception Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.4.1 Assertions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.4.2 Assertions vs. Exception Handling . . . . . . . . . . . . . . . . . . . . . . 35
3.5 Using Specific Exception Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
3.5.1 Examples of Specific Exception Types . . . . . . . . . . . . . . . . . . . 38
3.6 Providing Meaningful Error Messages . . . . . . . . . . . . . . . . . . . . . . . . . 39
3.6.1 Examples of Meaningful Error Messages . . . . . . . . . . . . . . . . 41
3.7 Logging Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
3.7.1 Objectives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
3.7.2 Challenges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
3.7.3 Examples of Logging Exceptions . . . . . . . . . . . . . . . . . . . . . . . 42
3.8 Handling Exceptions at the Appropriate Level . . . . . . . . . . . . . . . . . . . 44
3.9 Implementing Graceful Degradation and Recovery Strategies . . . . . . 46
3.10 Employing Separation of Concerns in Exception Handling . . . . . . . . 46
3.11 Avoiding Empty Catch Blocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

4 Advanced Exception Handling Techniques . . . . . . . . . . . . . . . . . . . . . . . . 49


4.1 Exception Handling Patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
4.2 Guard Clauses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
4.2.1 Exception Translation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
4.2.2 Exception Aggregation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
4.2.3 Exception Shielding Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
4.3 Retry and Backoff Strategies for Exception Handling . . . . . . . . . . . . . 58
4.3.1 Fixed Interval Retry . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
4.3.2 Randomized Interval Retry . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
4.3.3 Decorrelated Jitter Backoff . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
4.4 Exception Handling in Multi-Threaded Environments . . . . . . . . . . . . 60
4.4.1 Techniques for Exception Handling in Multi-Threaded
Environments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
4.4.2 Challenges and Limitations . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
4.5 Hierarchical Exception Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
4.5.1 Default Exception Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
4.5.2 Customizing the Hierarchy . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
4.5.3 Testing and Maintaining the Hierarchy . . . . . . . . . . . . . . . . . . 66
4.5.4 Exception Propagation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
4.5.5 Hierarchical Exception Handling in Different Programming
Paradigms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
4.5.6 Benefits and drawbacks of Hierarchical Exception Handling 68
Contents ix

4.6 Clean-Up Actions in Exception Handling . . . . . . . . . . . . . . . . . . . . . . . 70


4.6.1 The Necessity of Clean-Up Actions . . . . . . . . . . . . . . . . . . . . . 70
4.6.2 Clean-Up Techniques in Different Programming Languages . 71
4.6.3 Best Practices for Clean-Up Actions . . . . . . . . . . . . . . . . . . . . 72

5 Exception Handling in Real-Time and Embedded Systems . . . . . . . . . . 73


5.1 Example of Exceptions in Real-Time Systems . . . . . . . . . . . . . . . . . . . 74
5.2 Constraints in Real-Time Systems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
5.2.1 Timing Constraints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
5.2.2 Resource Constraints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
5.2.3 Predictability and Determinism . . . . . . . . . . . . . . . . . . . . . . . . 76
5.2.4 Reliability and Fault Tolerance . . . . . . . . . . . . . . . . . . . . . . . . . 77
5.3 Types of Timing Exceptions in Real-time Systems . . . . . . . . . . . . . . . 77
5.3.1 Hardware Timing Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . 78
5.3.2 Software Timing Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
5.4 Priorities and Deadlines in Exceptions Handling in Real-time
Systems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
5.4.1 Prioritizing Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
5.4.2 Deadline Management . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
5.5 Interrupts and Exception Handling in C++ . . . . . . . . . . . . . . . . . . . . . . 83
5.5.1 Linking Interrupts and Exceptions . . . . . . . . . . . . . . . . . . . . . . 84
5.5.2 Similarities and diferences of Interrupts and Exceptions . . . . 85
5.5.3 Combining Interrupts and Exceptions . . . . . . . . . . . . . . . . . . . 86
5.6 Methodologies for Exception Handling in Real-Time Systems . . . . . 87
5.6.1 Recovery Blocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
5.6.2 Checkpoints and Rollbacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
5.6.3 Proactive Monitoring of Exception Handling in Real-Time
Systems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
5.7 Design Patterns for Exception Handling in Real-Time Systems . . . . . 91
5.7.1 The Error Handler Pattern in Real-Time Systems . . . . . . . . . . 92
5.7.2 State Machine Pattern in Real-Time Systems . . . . . . . . . . . . . 94
5.7.3 Supervisory Control Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
5.8 Exception Handling in Ada for Real-Time Systems . . . . . . . . . . . . . . . 98
5.8.1 Defined Exception Model in Ada . . . . . . . . . . . . . . . . . . . . . . . 99
5.8.2 Deterministic Exception Propagation in Ada . . . . . . . . . . . . . . 100
5.8.3 Explicit Exception Declarations in Ada . . . . . . . . . . . . . . . . . . 102
5.8.4 Hierarchical and Scoped Handling in Ada . . . . . . . . . . . . . . . . 103
5.8.5 Tasking and Exception Handling in Ada . . . . . . . . . . . . . . . . . 105
5.8.6 Real-time Considerations in Handler Actions . . . . . . . . . . . . . 107

References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
Chapter 1
Introduction to Exception Handling

Exception handling is an essential aspect of software development that enables


programmers to manage unexpected events and errors during program execution.
The chapter begins by providing a general overview of the history and evolution
of exception handling. Then we describe key concepts and principles of exception
handling, including the types of exceptions, how they are defined and raised, and the
different approaches to handling them. This section also delineates the definition of
exceptions, that navigate through the architecture of exception objects, distinctions
between checked and unchecked exceptions, and strategies for internal and external
handling. Furthermore, the process of attaching handlers to specific exceptions is
spotlighted, emphasizing its pivotal role in effective exception management. Excep-
tion handling is not just a theoretical construct; its implications are profound and
far-reaching. In the next section, the chapter underscores the paramount importance
of adept exception handling. By averting crashes and unexpected behavior, fostering
robustness and maintainability, and bolstering security, exception handling elevates
software quality, ensuring users experience reliability and trust in their interactions
with systems. Finally, we also delve into the different types of exceptions, including
syntax and semantic errors.
By the end of the chapter, readers will have a solid understanding of the key
concepts and principles of exception handling and the tools and frameworks avail-
able for implementing effective exception handling in their software projects. The
chapter sets the foundation for the rest of the book, which details specific topics and
techniques related to exception handling in software development.

1.1 History and Evolution of Exception Handling


The history of exception handling [67] can be traced back to the early days of
programming, when languages like Assembly and Fortran were the primary means
of coding. As programming languages and paradigms advanced, the need for better
error and exception-handling mechanisms became apparent. This section provides an
extensive overview of the evolution of exception handling throughout the years. In the

© The Author(s), under exclusive license to Springer Nature Switzerland AG 2024 1


P. Mejia Alvarez et al., Exception Handling, SpringerBriefs in Computer Science,
https://doi.org/10.1007/978-3-031-50681-9_1
2 1 Introduction to Exception Handling

1950s and 1960s, assembly language and Fortran were the dominant programming
languages. They relied on fundamental error-checking mechanisms, using simple
branching instructions to handle errors. As a result, programmers have to write
extensive code to handle different types of errors and exceptions, often leading to
less maintainable and more error-prone code.
One of the first programming languages to introduce structured exception han-
dling was PL/I in the 1960s. The language featured condition handling, where specific
conditions could be associated with particular handlers. However, the system still
required manual management of error propagation, making it less flexible and more
error-prone than modern exception-handling systems. The advent of object-oriented
programming languages like C++ and Python played a crucial role in popularizing
the try-catch-finally construct. This construct allowed developers to write more struc-
tured, maintainable code when dealing with exceptions. In addition, the exception-
handling mechanism provided a clear separation of concerns, with specific blocks
of code designated for error handling and recovery.
As programming languages continue to evolve, newer languages have adopted
more advanced and flexible exception-handling constructs. For example, languages
like Python, C#, and Ruby support exception-handling constructs similar to those
found in Java, focusing on making the code more readable and maintainable. Script-
ing languages, such as JavaScript and PHP, also feature their exception-handling
mechanisms. For example, while JavaScript relies on the try-catch-finally
construct, similar to Java and C++, PHP uses try-catch with optional finally blocks.
In conclusion, exception handling has come a long way since the early days of
programming, with each programming paradigm introducing new approaches and
constructs to handle errors and exceptions effectively. Therefore, studying the history
and evolution of exception-handling mechanisms provides valuable insights into the
progress of programming languages and the software development process.

1.2 Definition of Exceptions


An exception can be defined as an unexpected event or condition that occurs during
the execution of a program, leading to abnormal or undesired behavior [31]. These
events or conditions deviate from the expected or regular flow of the program. They
may result in various consequences, such as incorrect outputs, system crashes, or data
corruption. Exception handling is the process of managing and resolving exceptions
to allow the program to continue executing or terminate gracefully without causing
further harm.
Exceptions can arise from various sources, such as:
• Errors in the code or logic of the program
• Invalid user inputs or actions
• Resource limitations or constraints, such as running out of memory or disk space
• Hardware failures or malfunctions
• External factors, such as network interruptions or loss of communication with
remote services
1.2 Definition of Exceptions 3

1.2.1 Examples of Exceptions

In this section, we provide examples of exceptions with their corresponding imple-


mentation in C++, from the sources discussed before:
• Division by zero: This scenario occurs when attempting to divide a number by
zero, which is an undefined mathematical operation. It often results in a runtime
error or exception.

1 # include <iostream >


2 int main () {
3 int numerator = 5;
4 int denominator = 0;
5
6 try {
7 if ( denominator == 0)
8 throw std :: runtime_error (" Division by zero!");
9 int result = numerator / denominator ;
10 } catch (const std :: runtime_error & e) {
11 std :: cerr << " Error : " << e.what () << std :: endl;
12 }
13
14 return 0;
15 }

• Null pointer dereference: This situation arises when trying to access or deref-
erence a null pointer, which points to no valid memory location. It can lead to
crashes, segmentation faults, or other memory-related errors.

1 # include <iostream >


2 int main () {
3 int* ptr = nullptr ;
4

5 try {
6 if (! ptr)
7 throw std :: runtime_error ("Null pointer dereference
!");
8 *ptr = 10;
9 } catch (const std :: runtime_error & e) {
10 std :: cerr << " Error : " << e.what () << std :: endl;
11 }
12

13 return 0;
14 }

• Out-of-bounds array access: This case occurs when accessing an array element
using an index that exceeds the array’s size or falls outside its defined range. It
can cause unpredictable behavior, memory corruption, or program crashes.
4 1 Introduction to Exception Handling

1 # include <iostream >


2 int main () {
3 int arr [5] = {1, 2, 3, 4, 5};
4
5 try {
6 int index = 10; // out -of - bounds
7 if ( index < 0 || index >= sizeof (arr) / sizeof (arr [0])
)
8 throw std :: out_of_range ("Out -of - bounds array
access !");
9 int val = arr[ index ];
10 } catch (const std :: out_of_range & e) {
11 std :: cerr << " Error : " << e.what () << std :: endl;
12 }
13
14 return 0;
15 }

• Memory allocation failure: In scenarios where dynamic memory allocation is


used (e.g., using new in C++), memory allocation can fail due to various reasons
such as insufficient memory or fragmentation. It results in a std::bad_alloc
exception.

1 # include <iostream >


2 # include <new >
3 int main () {
4 try {
5 int* largeArray = new int[std :: numeric_limits <std ::
size_t >:: max () ];
6 delete [] largeArray ;
7 } catch (const std :: bad_alloc & e) {
8 std :: cerr << " Error: " << e.what () << std :: endl;
9 }
10

11 return 0;
12 }

• File not found or inaccessible: This situation occurs when attempting to access
a file that does not exist or is not accessible due to permissions, network issues,
or other reasons. It results in a runtime error or exception.
• Invalid data type conversion or casting: This scenario arises when performing
an invalid data type conversion or casting operation, such as trying to reinterpret a
value of one type as another incompatible type. It can result in undefined behavior,
data corruption, or type-related errors.
These examples illustrate common scenarios where exceptions can occur in pro-
gramming. Exception-handling mechanisms allow developers to catch and handle
these exceptions gracefully, enabling better control over the program’s behavior and
providing error-recovery strategies.
1.2 Definition of Exceptions 5

1 // File not found or inaccessible in C+


2 # include <iostream >
3 # include <fstream >
4 int main () {
5 std :: ifstream file(" non_existent_file .txt");
6

7 try {
8 if (! file. is_open ())
9 throw std :: runtime_error ("File not found or
inaccessible !");
10 } catch ( const std :: runtime_error & e) {
11 std :: cerr << " Error: " << e.what () << std :: endl;
12 }
13

14 return 0;
15 }

1 // Invalid data type conversion or casting in C++


2 # include <iostream >
3 int main () {
4 int num = 5;
5 void* voidPtr = &num;
6

7 try {
8 char* charPtr = reinterpret_cast <char *>( voidPtr );
9 if (! charPtr )
10 throw std :: runtime_error (" Invalid data type
conversion or casting !");
11 } catch ( const std :: runtime_error & e) {
12 std :: cerr << " Error: " << e.what () << std :: endl;
13 }
14
15 return 0;
16 }

1.2.2 Exception Objects and Structures

In most programming languages, exceptions are represented as objects or structures


that carry information about the nature of the exception, the location in the code
where it occurred, and any additional data that may be relevant to diagnosing or
resolving the issue [31]. Exception handlers can use this information to determine
the appropriate course of action, such as displaying a meaningful error message
to the user, logging the error for debugging purposes, or attempting to recover
from the error and resume normal execution. Exception handling mechanisms in
programming languages like C++ and Python provide the ability to throw and catch
exception objects. Exception objects encapsulate information about an exceptional
condition that occurred during the program’s execution. In this section, we will
explore how exception objects and structures are used in C++ and Python.
6 1 Introduction to Exception Handling

1. C++ programming language:


In C++, exceptions are represented by objects that are derived from the base class
std::exception. Developers can create custom exception classes by inheriting
from std::exception or its derived classes. These exception classes can have
additional member variables and methods to provide more information about the
exception. When an exceptional condition occurs in C++, the throw statement is
used to raise an exception object. The exception object is then caught and handled
by try-catch blocks. The catch block specifies the type of exception to catch,
allowing specific handling based on the type of exception.
Here’s an example of using exception objects in C++:

1 # include <iostream >


2 # include <exception >
3 class MyException : public std :: exception {
4 public :
5 const char* what () const throw () {
6 return "My custom exception ";
7 }
8 };
9 int main () {
10 try {
11 throw MyException ();
12 } catch (const std :: exception & ex) {
13 std :: cerr << " Exception caught : " << ex.what () << std
:: endl;
14 }
15

16 return 0;
17 }

2. Python programming language: In Python, exceptions are represented by ob-


jects that are instances of classes derived from the base class Exception. Python
provides a built-in hierarchy of exception classes that cover various exceptional
conditions. Developers can also create custom exception classes by deriving from
Exception or its derived classes. The raise statement is used to raise an excep-
tion in Python, followed by an instance of an exception class. Exception handling
in Python is done using try-except blocks. The except block specifies the type
of exception to catch, allowing for specific handling based on the exception type.
Here’s an example of using exception objects in Python.

1 class MyException ( Exception ):


2 def __str__ (self):
3 return "My custom exception "
4
5 try:
6 raise MyException ()
7 except Exception as ex:
8 print (f" Exception caught : {ex}")

Listing 1.1: Exception Handling in Python


1.2 Definition of Exceptions 7

Exception objects and structures provide a way to encapsulate and convey in-
formation about exceptional conditions in a program. By using exception objects,
developers can give detailed error messages, stack traces, and other relevant infor-
mation that can aid in debugging and handling exceptional situations. The specific
implementation details and syntax may vary across programming languages, but the
concept of using exception objects remains consistent. Understanding how excep-
tions are represented and handled in different languages allows developers to write
robust, error-tolerant code.

1 // Checked exceptions in C++


2 # include <iostream >
3

4 void readFile () {
5 throw std :: runtime_error ("File not found");
6 }
7 int main () {
8 try {
9 readFile ();
10 } catch ( const std :: exception & e) {
11 // Handle the exception
12 }
13
14 return 0;
15 }

Listing 1.2: Checked Exception in C++

1.2.3 Checked and Unchecked Exceptions

Exception handling in programming languages often categorizes exceptions into


two types: checked and unchecked exceptions. In this section, we will explore the
concepts of checked and unchecked exceptions and how they are handled in different
programming languages [75]. Checked exceptions are exceptions that must be
declared or caught at compile-time. Unchecked exceptions, also known as runtime
exceptions, are exceptions that do not need to be declared or caught at compile-time.
In languages like C++ and Python, exceptions are unchecked by default. Developers
have the flexibility to catch and handle exceptions using try-catch or try-except
blocks, but it is not mandatory.
Next, we provide examples of checked exceptions in C++ and Python languages.
1. C++ programming language: In C++, exceptions are generally unchecked, al-
lowing for a more lightweight and flexible error-handling approach. Developers
can choose to catch and handle exceptions as needed, without being burdened
by explicit exception declarations. In C++, you can throw a built-in or custom
exception and catch it using try-catch, as described in the example above.
8 1 Introduction to Exception Handling

2. Python programming language: In Python, all exceptions are unchecked, pro-


viding developers with the freedom to choose how and when to handle exceptions.
The use of try-except blocks allows for targeted exception handling, making it
easier to write concise and readable code. In Python, you can raise a built-in or
custom exception and catch it using try-except, as in the following listing.

1 def divide (a, b):


2 if b == 0:
3 raise ZeroDivisionError (" Division by zero")
4 # Code that performs division
5 try:
6 divide (10 , 0)
7 except ZeroDivisionError as e:
8 # Handle the exception
9 pass

Listing 1.3: Division by cero in Python

1.2.4 Internal and External Exception Handling

The complexity and unpredictability of software systems often lead to unforeseen


situations or errors, known as exceptions. Based on their origin and nature, these
exceptions can be classified as either internal or external [56]. Understanding these
classifications can be crucial in designing robust exception-handling strategies.
• Internal Exception Handling
Internal exceptions are typical errors that occur within the boundary of a system
or a program. These are often a result of logical errors or incorrect data within
the system. Examples of internal exceptions include divide-by-zero errors, null
pointer dereferencing, out-of-bounds array indexing, and overflow errors. Since
these exceptions originate within the system, they can usually be caught and
handled by the system’s own exception-handling mechanisms. This often involves
error logging, displaying an error message, or, in some cases, terminating the
program if the error cannot be recovered.
1. C++ programming language: In C++, the try and catch blocks are used
for internal exception handling. You can throw exceptions using the throw
keyword, and these can be of any type (integers, characters, objects, etc.).

1 try {
2 // Code that might throw an exception
3 } catch ( ExceptionType1 &e1) {
4 // Handle exception of type ExceptionType1
5 } catch ( ExceptionType2 &e2) {
6 // Handle exception of type ExceptionType2
7 }

Listing 1.4: Internal Exceptions in C++


1.2 Definition of Exceptions 9

2. Phyton programming language: As described before, Python uses a similar


try-except construct. Python also provides a plethora of built-in exceptions
like IOError, ValueError, and you can also define custom exceptions by
creating a new class derived from the base Exception class.

1 try:
2 # Code that might raise an exception
3 except ExceptionType1 as e1:
4 # Handle exception of type ExceptionType1
5 except ExceptionType2 as e2:
6 # Handle exception of type ExceptionType2
7 finally :
8 # This block is always executed

Listing 1.5: Internal Exceptions in Python

• External Exception Handling


External exceptions, in contrast, are caused by conditions outside the system.
These include events such as hardware faults, network connectivity issues, miss-
ing files, or incorrect user inputs. External exceptions are often unpredictable
and can occur even if the system’s internal logic is flawless. Handling external
exceptions requires more sophisticated strategies as they often involve interacting
with external systems or users. For instance, a program may need to retry a failed
network operation, prompt the user for valid input, or log hardware faults for
system administrators to handle. External exception handling relates to the mech-
anisms that exist outside of the primary application logic, usually at the system
or framework level, to catch and manage unforeseen errors.
1. Java programming language: Java’s external exception handling is often
managed by the runtime environment or the container in which the application
is running. For instance, in a web application running on a server like Tomcat,
unhandled exceptions can be caught by the container and a proper HTTP re-
sponse can be returned. Java also allows the definition of uncaught exception
handlers globally using Thread.setDefaultUncaughtExceptionHandler
method [60, 61, 80].
2. C++ programming language: C++ doesn’t have an "external" exception han-
dling mechanism per se. However, if an exception isn’t caught, it will propagate
up to previous levels of the call stack. If it reaches main() and still isn’t caught,
the program will terminate. Some operating systems might provide diagnostics
information or logs. To handle such uncaught exceptions, one can utilize the
std::set_terminate function to set a new termination handler [75].
10 1 Introduction to Exception Handling

1.2.5 Attachment of Handlers

Exception handlers can be associated with different parts of a software system,


including: (i) a single statement, (ii) a block of statements, (iii) a function or method,
(iv) an instance of a class, or (v) a class definition [56].
1. Statement Handlers: Statement handlers can be attached to a single statement.
In the following Python code, the ZeroDivisionError is handled right at the
division operation:

1 try:
2 x = 1 / 0
3 except ZeroDivisionError :
4 print ("Error: Division by zero.")

In this example, the handler is attached to the division statement, making it a


statement handler [62].
2. Block Handlers: Block handlers are associated with a block of statements. In
Java, exceptions within a block can be handled using a try/catch block. In this
Java code, the handler is attached to a block of statements, making it a block
handler [32].

1 try {
2 int result1 = 10 / 0;
3 int result2 = result1 + 2;
4 } catch ( ArithmeticException e) {
5 System .out. println (" Error : Division by zero.");
6 }

3. Method Handlers: Method handlers are attached to a specific method. In the


following Python code, the ZeroDivisionError exception is handled within
the divide_numbers method. In this Python code, the handler is attached to the
divide_numbers method, making it a method handler [62].

1 def divide_numbers (a, b):


2 try:
3 result = a / b
4 except ZeroDivisionError :
5 print("Error: Division by zero.")
6 result = None
7 return result
8

9 divide_numbers (1, 0) # This will raise and handle a


ZeroDivisionError .
1.2 Definition of Exceptions 11

4. Object Handlers and Class Handlers


Object handlers are specific to instances of a class. While most popular languages
like Python or Java do not directly support attaching handlers to objects, they
provide ways to define methods that can handle exceptions at the instance level.

1 class MyClass :
2
3 def __init__ (self , value):
4 self. value = value
5 def divide_by (self , divisor ):
6 try:
7 return self. value / divisor
8 except ZeroDivisionError :
9 print("Error in MyClass : Division by zero.")
10 return None
11

12 # Object - level handler


13 obj1 = MyClass (10)
14 result1 = obj1. divide_by (0) # This will raise and handle a
ZeroDivisionError .
15
16 # Class - level handler
17 result2 = MyClass . divide_by (obj1 , 0) # Equivalent to the
previous line.

Similarly, class handlers are attached to a class definition. Mainstream languages


like Python and Java do not directly support attaching handlers to classes. How-
ever, they provide ways to define methods that can handle exceptions at the class
level. In Python, exception handling can be designed at the object and class lev-
els by defining methods that handle exceptions. Consider the example above. In
the above Python code, the divide_by method acts as both an object-level and
class-level handler. If an exception is raised during the division operation, the
method will handle it, and the handling behavior will be the same for all instances
of MyClass. This demonstrates how exception handling can be designed at the
class and object levels in Python, even though the language doesn’t have explicit
support for attaching handlers to classes or objects [62].
In Java, methods within an object or class can be designed to handle exceptions.
Consider the following example below. In this Java code, the divideBy method
acts as an object-level handler. If an ArithmeticException is raised during the
division operation, the method will handle it. The handling behavior will be the
same for all instances of MyClass. This demonstrates how exception handling
can be designed at the object level in Java, even though the language doesn’t
explicitly support attaching handlers to objects [32].
12 1 Introduction to Exception Handling

1 public class MyClass {


2 private int value;
3

4 public MyClass (int value) {


5 this. value = value ;
6 }
7
8 public int divideBy (int divisor ) {
9 try {
10 return value / divisor ;
11 } catch ( ArithmeticException e) {
12 System .out. println ("Error in MyClass : Division by
zero.");
13 return 0;
14 }
15 }
16 }
17
18 // Object -level handler
19 MyClass obj1 = new MyClass (10);
20 int result1 = obj1. divideBy (0); // This will raise and handle
an ArithmeticException .
21

22 // Class - level handler is not applicable in Java as non - static


methods require an object instance .

1.3 Challenges of Exception Handling

Exception handling is a critical aspect of software development that enables pro-


grammers to manage unexpected events and errors that can occur during program
execution [31]. Without proper exception handling, errors and exceptional condi-
tions can cause software applications to crash or behave unpredictably, leading to
lost data, frustrated users, and potential security vulnerabilities. Exception handling
has two main objectives: (1) to detect and report errors that occur during the execu-
tion of a program, and (2) to facilitate the recovery from these errors and continue
the program’s execution [31]. It is crucial to note that not all errors are exceptions,
as exceptions represent a subset of unexpected, abnormal, or otherwise exceptional
errors. The primary challenge of exception handling is to provide a robust system that
is easy to understand, implement, and maintain while minimizing the performance
overhead associated with error detection and recovery.

1.3.1 Preventing Crashes and Unexpected Behavior


One of the primary benefits of exception handling is that it can prevent crashes and
unexpected behavior in software applications [33]. Errors and exceptional conditions
1.3 Challenges of Exception Handling 13

can arise from a wide range of sources, including user input, hardware failures,
network issues, and software bugs. By implementing proper exception handling,
programs can detect and handle these errors in a controlled and graceful manner,
preventing the program from crashing or terminating prematurely. Consider, for
instance, a program that reads data from a file and performs some calculations on the
data. If the file is not found or cannot be read, the program will encounter an error and
fail to execute. However, with proper exception handling, the program can detect the
error and gracefully exit, providing feedback to the user and avoiding any unexpected
behavior. Another example is a program that performs network communication. If
a network connection is lost or interrupted, the program may encounter an error
and terminate. However, with proper exception handling, the program can detect
the error and attempt to reconnect, ensuring that the communication can continue
without any significant impact on the user.

1.3.2 Improving Robustness and Maintainability


Another benefit of proper exception handling is that it can improve the overall
robustness and maintainability of software applications [71]. By implementing a
systematic approach to handling errors and exceptional conditions, programmers
can ensure that their programs are resilient and can withstand a wide range of
scenarios and inputs. For instance, a program that performs network communication
may encounter errors due to network latency, packet loss, or other issues. With
proper exception handling, the program can detect and handle these errors, ensuring
that the communication can continue without any significant impact on the user.
Furthermore, proper exception handling can also enhance the maintainability of
software applications by enabling programmers to isolate and debug errors more
efficiently. By providing detailed error messages and stack traces, exception handling
can help programmers identify the root cause of errors and implement effective fixes.

1.3.3 Enhancing Security


Proper exception handling can also enhance the security of software applications by
preventing malicious inputs or attacks from causing unexpected behavior or exposing
vulnerabilities [33]. By detecting and handling errors and exceptional conditions in
a secure and controlled manner, programs can mitigate the risk of security breaches
and protect sensitive data. For example, a program that performs database queries
may encounter errors due to invalid input or malicious SQL injection attacks. With
proper exception handling, the program can detect and handle these errors, preventing
the injection of malicious code and protecting the integrity and confidentiality of the
database.
14 1 Introduction to Exception Handling

1.4 Syntax and Semantic Errors

In software development, errors and exceptions are common occurrences that can
lead to unexpected program behavior, crashes, and incorrect results. Therefore,
understanding the different types of errors and exceptions is essential for developing
robust and reliable software applications that can handle unexpected events and
gracefully recover from errors.

1.4.1 Syntax Errors

Syntax errors occur when the program’s syntax is incorrect, leading to a compile-
time error. These errors are easy to detect and fix, as they cause the program to fail
to compile. Here’s an example of a syntax error in C programming:

1 # include <stdio .h>


2 int main () {
3 printf ("Hello , World !\n")
4 return 0;
5 }

In this example, the program attempts to print a message to the console but needs
to include a semicolon at the end of the line. This results in a syntax error, which
causes the program to fail to compile.

1.4.2 Semantic Errors

Semantic errors occur when the program’s logic is incorrect [50], leading to unex-
pected or incorrect results. Various factors, such as incorrect calculations, algorithms,
or data types, can cause these errors. Semantic errors can be challenging to detect
and fix, as they may not cause the program to crash or generate an error message.
Here’s an example of a semantic error in Python programming:

1 def divide (a, b):


2 return a / b
3
4 result = divide (10 , 0)
5 print( result )

In this example, the program attempts to divide a number by zero, which is


undefined. The program does not generate an error message but returns an incorrect
result, causing unexpected behavior.
1.5 Design by Contract: A Precursor to Exception Handling 15

1.5 Design by Contract: A Precursor to Exception Handling


Design by Contract (DbC) [54] is a powerful software correctness methodology
introduced by Bertrand Meyer in the 1980s, associated primarily with the Eiffel
programming language. At its core, it emphasizes the mutual obligations and benefits
between two parties — a routine and its caller. This approach can be seen as a
precursor to the concept of exception handling, setting the groundwork for how
software deals with exceptional situations.

1.5.1 Principles of Design by Contract


The main principles of DbC revolve around three primary annotations:
• Preconditions: What must be true before a routine can be executed. It is the
caller’s responsibility to satisfy these conditions.
• Postconditions: What will be true after the routine has been executed, assuming
the preconditions were met. This is the responsibility of the routine itself.
• Invariants: Conditions that must always hold true, irrespective of the routine’s
execution. Typically, invariants relate to the state of an object.
These contract elements ensure that as long as the caller keeps their side of the
"contract" by fulfilling the preconditions, the routine guarantees that it will achieve
the postconditions and maintain the invariants.

1.5.2 Relationship with Exceptional Situations


DbC’s principles lay the foundation for understanding exceptional situations in a
number of ways:
• Violation of Contracts: If a precondition, postcondition, or invariant is violated,
it’s indicative of an exceptional situation. For instance, if a routine expects a
positive integer as an input (precondition) but receives a negative one, it’s a
breach of contract.
• Explicit Expectations: DbC makes the expectations explicit. This clarity often
reduces the number of exceptional situations because potential issues are caught
early in the development phase.
• Failure as a Breach of Contract: Instead of thinking of failures as errors, they’re
seen as contract breaches. This perspective shift changes the way developers
approach problem-solving. If a postcondition isn’t met, it’s not merely a "bug";
it’s a failure of the software to uphold its end of the contract.
• Exception Handling Mechanism: Modern exception handling can be viewed as
an evolution of the DbC principle. When an exception is thrown, it’s essentially
signaling that some part of the "contract" (often an implicit one) has been vio-
lated. Catching and handling that exception allows developers to deal with these
breaches in a controlled manner.
16 1 Introduction to Exception Handling

• Predictable Software Behavior: Both DbC and exception handling aim to make
software behavior more predictable. While DbC does this through clear con-
tracts, exception handling provides mechanisms to deal with unpredicted scenar-
ios gracefully.

Bertrand Meyer’s Design by Contract emphasized the clear delineation of respon-


sibilities in software components. This clarity and explicitness in defining obligations
and benefits set the stage for more advanced exception-handling mechanisms. By
understanding the contractual obligations between different software components,
developers can better anticipate, handle, and recover from exceptional situations,
leading to more robust and resilient software.
Chapter 2
Basics of Exception Handling

As programmers, we embark on the journey of software creation with the aim of


achieving flawless execution. Yet, software, being a construct of human endeavor, is
susceptible to unforeseen challenges. These challenges, if unmitigated, can morph
into frustrating experiences for the end-users. Exception handling, therefore, stands
as a beacon, guiding us to not only anticipate these challenges but also respond to
them with grace and efficacy.
In this chapter, we will uncover the fundamental pillars of exception handling.
We begin with Try-catch blocks, the first line of defense against unpredicted hic-
cups during program execution. Moving on, we’ll delve into the realm of the throw
keyword, an essential tool enabling programmers to raise custom alerts. The next
section is dedicated to the art of crafting Custom Exceptions, allowing for a more
granular level of error classification and response. As we progress in the chapter,
the concept of Exception Propagation comes to the fore, illuminating how excep-
tions ripple through the layers of our code. A deeper dive into the architecture of
exception handling awaits with Nested try-catch blocks. Here, we’ll examine the
manifold benefits and potential pitfalls of stacking our defense mechanisms. A series
of examples, best practices, and a comparative study across various programming
languages will further enrich our understanding. Lastly, we will explore the nuanced
world of Conditional Exception Handling, introducing the reader to a more dynamic
and responsive approach to error management, including the intriguing paradigm of
Event-driven Exception Handling.

2.1 Try-Catch Blocks

Try-catch blocks are a programming construct that enables the handling of exceptions
or errors during the execution of a program. When an error occurs within a try block,
the program flow is directed to the corresponding catch block, where the error can
be managed gracefully, without crashing the entire program. Many programming
languages, such as Java, C++, 𝐶#, Python, and JavaScript, offer built-in support for

© The Author(s), under exclusive license to Springer Nature Switzerland AG 2024 17


P. Mejia Alvarez et al., Exception Handling, SpringerBriefs in Computer Science,
https://doi.org/10.1007/978-3-031-50681-9_2
18 2 Basics of Exception Handling

try-catch blocks. In this section, we will explore the concept of try-catch blocks in
various programming languages.
1. Try-Catch blocks in C++: In C++, try-catch blocks are used to manage ex-
ceptions that may occur during the program execution. The following example
demonstrates a simple try-catch block in C++:

1 # include <iostream >


2 # include <stdexcept >
3

4 int main () {
5 int a = 5, b = 0;
6 int result ;
7
8 try {
9 if (b == 0) {
10 throw std :: runtime_error (" Division by zero.");
11 }
12 result = a / b;
13 std :: cout << " Result : " << result << std :: endl;
14 } catch (const std :: runtime_error & e) {
15 std :: cout << " Error : " << e.what () << std :: endl;
16 }
17

18 return 0;
19 }

In this example, we try to perform a division operation that could potentially


lead to a division-by-zero error. When the exception occurs, the catch block is
executed, and a descriptive error message is printed to the console.
2. Try-Except blocks in python: In Python, try-catch blocks are called try-except
blocks. The following example demonstrates a simple try-except block in Python:

1 numbers = [1, 2, 3]
2

3 try:
4 print ( numbers [3])
5 except IndexError :
6 print ("Error: List index out of range.")

In this example, we again try to access an array element with an invalid index.
When the exception occurs, the except block is executed, and a descriptive error
message is printed to the console.
2.3 Custom Exceptions Handling 19

2.2 The Throw Keyword in Exception Handling

The throw keyword is an essential component of exception handling in many pro-


gramming languages. It allows developers to explicitly raise an exception in the code,
which can then be caught and managed by a suitable exception-handling mechanism.
This section will discuss the purpose and usage of the throw keyword in various pro-
gramming languages.
1. C++: In C++, the throw keyword is used to raise an exception explicitly. Custom
exception classes can be created by inheriting from one of the standard exception
classes in the stdexcept library or from a custom-made base exception class.
The following example shows throwing a built-in exception in C++.

1 # include <stdexcept >


2

3 void checkAge (int age) {


4 if (age < 18) {
5 throw std :: invalid_argument ("Age must be at least 18."
);
6 }
7 }

2. Python: In Python, the raise keyword is used instead of throw to raise an exception
explicitly. Custom exception classes can be created by inheriting from the built-in
Exception class or one of its subclasses.
The following shows raising a built-in exception.

1 def check_age (age):


2 if age < 18:
3 raise ValueError ("Age must be at least 18.")

2.3 Custom Exceptions Handling

In exception handling, custom exceptions can be created to handle specific error


conditions or unusual scenarios that are not covered by the built-in exceptions pro-
vided by the language. Custom exceptions can make error reporting and debugging
more informative and easier to understand. This section will discuss the creation and
usage of custom exceptions in various programming languages.
1. C++: In C++, custom exceptions can be created by inheriting from one of the
standard exception classes in the stdexcept library or by creating a custom
base exception class. A custom exception class can have additional methods or
properties to provide more context or information about the exception.
20 2 Basics of Exception Handling

Creating a custom exception class with additional properties in C++ is shown in


the following example.

1 // Custom Exceptions in C++


2 # include <iostream >
3 # include <stdexcept >
4 # include <string >
5 class CustomException : public std :: runtime_error {
6 public :
7 explicit CustomException ( const std :: string & message , int
errorCode )
8 : std :: runtime_error ( message ), errorCode ( errorCode ) {}
9 int getErrorCode () const {
10 return errorCode ;
11 }
12 private :
13 int errorCode ;
14 };
15 int main () {
16 try {
17 throw CustomException ("This is a custom exception .",
42);
18 } catch (const CustomException & e) {
19 std :: cout << " Caught custom exception : " << e.what ()
<< ", error code: " << e. getErrorCode () << std :: endl;
20 }
21 }

2. Python: In Python, custom exceptions can be created by inheriting from the


built-in Exception class or one of its subclasses. A custom exception class
can have additional methods or properties to provide more context or information
about the exception.
Creating a custom exception class with additional properties in Python is shown
in the example below.

1 # Custom Exceptions in Python


2 class CustomException ( Exception ):
3 def __init__ (self , message , error_code ):
4 super (). __init__ ( message )
5 self. error_code = error_code
6

7 try:
8 raise CustomException ("This is a custom exception .", 42)
9 except CustomException as e:
10 print (f" Caught custom exception : {e}, error code: {e.
error_code }")
2.4 Exception Propagation in Exception Handling 21

2.4 Exception Propagation in Exception Handling

One key component of exception handling is exception propagation, the process


by which unhandled exceptions are passed up the call stack, allowing higher-level
functions or methods to manage them. Exception propagation enables more cen-
tralized, efficient, and maintainable error handling in software development. This
section aims to provide a comprehensive understanding of exception propagation
in various programming languages such as Java, C++, 𝐶#, Python, and JavaScript,
and to discuss its role in creating robust software applications. We will explore the
importance of exception propagation, its challenges, and its practical applications in
software development.

1 // Exception propagation in C++


2 # include <iostream >
3 # include <exception >
4
5 class CustomException : public std :: exception {
6 public :
7 const char* what () const noexcept override {
8 return "An exception occurred ";
9 }
10 };
11

12 void functionC () {
13 throw CustomException ();
14 }
15
16 void functionB () {
17 functionC ();
18 }
19

20 void functionA () {
21 try {
22 functionB ();
23 } catch ( const CustomException & e) {
24 std :: cout << " Exception caught in functionA : " << e.what () << std
:: endl;
25 }
26 }
27

28 int main () {
29 functionA ();
30 return 0;
31 }

Exception propagation plays a crucial role in error handling as it enables cen-


tralized management of errors at higher call stack levels. By propagating unhandled
exceptions up the call stack, developers can implement coherent and maintainable
error-handling strategies, avoiding the need for redundant or complex error-handling
code in every function or method. As a result, exception propagation contributes to
22 2 Basics of Exception Handling

more efficient and robust software development. In the following subsections, we will
examine the concept of exception propagation in several programming languages.
Each subsection will present a scenario where an exception occurs, demonstrate how
the exception propagates through the call stack, and provide examples illustrating
how to handle the exception at different levels.
1. C++: In C++, exceptions propagate up the call stack similarly to Java. For example,
if an exception is thrown and not caught within a function, it propagates up the call
stack to the calling function. This process continues until the exception is caught
or reaches the main function, resulting in program termination if not handled.
The example above demonstrates how exception propagation works in C++:
• A custom exception class, CustomException, is defined by inheriting from
the std::exception class and overriding the what method.
• The main function calls functionA without any exception handling.
• functionA calls functionB and catches any CustomException that occurs,
printing "Exception caught in functionA."
• functionB calls functionC, but does not handle any exceptions, allowing
them to propagate up the call stack.
• functionC throws a new instance of CustomException, which propagates
up to functionA.

1 # Exception propagation in Python


2 class CustomException ( Exception ):
3 pass
4

5 def function_c ():


6 raise CustomException ("An exception occurred ")
7

8 def function_b ():


9 function_c ()
10

11 def function_a ():


12 try:
13 function_b ()
14 except CustomException as e:
15 print (" Exception caught in function_a :", e)
16

17 def main ():


18 function_a ()
19
20 if name == "main":
21 main ()

2. Python: In Python, exception propagation works similarly to Java and C++. When
an exception is raised and not caught within a function, it propagates up the call
stack to the calling function. This process continues until the exception is caught
or reaches the top level of the script, where it will result in program termination if
2.5 Nested Try-Catch Blocks 23

not handled. The example above demonstrates how exception propagation works
in Python.
• A custom exception class, CustomException, is defined by inheriting from
the Exception class.
• The main function calls function_a without any exception handling.
• function_a calls function_b and catches any CustomException that oc-
curs, printing Exception caught in function_a.
• function_b calls function_c but does not handle any exceptions, allowing
them to propagate up the call stack.
• function_c raises a new instance of CustomException, which propagates
up to function_a.

2.5 Nested Try-Catch Blocks

Exception handling is an essential aspect of software development, allowing devel-


opers to handle errors that occur during program execution gracefully. When an
exception is thrown, it can be caught and handled by a try-catch block. However, it
is also possible to have nested try-catch blocks, where one try-catch block is nested
within another. The objective of this section is to discuss the use of nested try-catch
blocks in exception handling, including their benefits, challenges, and best practices.
We will also provide examples of nested try-catch blocks in several programming
languages and compare the techniques used in each language.

2.5.1 Benefits of Nested try-catch blocks

There are several benefits to using nested try-catch blocks in exception handling:
• Granularity of error handling: By using nested try-catch blocks, you can handle
errors at a more granular level. This can make it easier to identify and fix problems
in your code.
• Improved program stability: By catching and handling exceptions at a more
granular level, you can improve the stability of your program. This can help
prevent crashes and other errors that could lead to data loss or other problems.
• Simpler code: In some cases, using nested try-catch blocks can simplify your
code. By breaking down error handling into smaller, more manageable blocks,
you can make your code easier to read and understand.
• Better error reporting: By catching and handling exceptions at a more granular
level, you can provide better error reporting to your users. This can help them
understand what went wrong and how to fix the problem.
24 2 Basics of Exception Handling

2.5.2 Challenges of Nested Try-Catch Blocks

While there are several benefits to using nested try-catch blocks, there are also some
challenges to be aware of:
• Increased code complexity: Using nested try-catch blocks can make your code
more complex and harder to understand. This can make it harder to maintain and
debug your code over time.
• Performance overhead: Using nested try-catch blocks can result in performance
overhead, as the exception handling code needs to be executed for each try-catch
block. This can impact the performance of your program, especially if you have
many nested try-catch blocks.
• Risk of error masking: If you are not careful, using nested try-catch blocks can
mask errors and make it harder to identify and fix problems in your code. This
is especially true if you catch exceptions too broadly or fail to provide adequate
error reporting.

1 // C++ Example
2 try {
3 // Some code here
4 try {
5 // Some more code here
6 } catch ( exception & e) {
7 // Handle exception from inner try block
8 }
9 } catch ( exception & e) {
10 // Handle exception from outer try block
11 }

2.5.3 Examples

Here are some examples of nested try-catch blocks in several programming lan-
guages.
1. C++ In C++, nested try-catch blocks can also be used to handle exceptions more
granularly. In the example above, an outer try block catches exceptions from the
code within it. An inner try block also catches exceptions from the code within it.
If an exception occurs in the inner try block, it is caught and handled by the catch
block within that block. Likewise, if an exception occurs in the outer try block, it
is caught and handled by the catch block within that block.
2. Python: In Python, nested try-catch blocks are also commonly used. In the
example below, an outer try block catches exceptions from the code within it.
There is also an inner try block that catches exceptions from the code within it. If
an exception occurs in the inner try block, it is caught and handled by the catch
block within that block. Likewise, if an exception occurs in the outer try block, it
is caught and handled by the catch block within that block.
2.6 Conditional Exception Handling 25

1 # Python example
2 try:
3 # Some code here
4 try:
5 # Some more code here
6 except Exception as e:
7 # Handle exception from inner try block
8 except Exception as e:
9 # Handle exception from outer try block

2.5.4 Best Practices

When using nested try-catch blocks, there are some best practices to keep in mind:

• Keep blocks small: It is generally a good idea to keep try-catch blocks as small
as possible. This can make it easier to identify and fix problems in your code.
• Avoid nesting too deeply: Try to avoid nesting too many try-catch blocks. This
can make your code harder to read and understand.
• Use specific catch blocks: Whenever possible, use catch blocks that are specific
to the type of exception you are handling. This can help avoid masking errors and
make it easier to identify and fix problems in your code.
• Provide good error reporting: When catching and handling exceptions, provide
good error reporting to your users. This can help them understand what went
wrong and how to fix the problem.

2.6 Conditional Exception Handling

Conditional exception handling is a critical concept in computer programming lan-


guages that refers to the techniques used to handle the unexpected events or errors
that occur during the execution of a program [64]. Exception handling can signifi-
cantly improve the stability and maintainability of a software system, as it provides a
systematic way to detect, report, and recover from errors. Conditional exception han-
dling refers to managing exceptions based on certain conditions, rather than merely
catching them based on their type. Instead of unconditionally handling an exception
once it’s caught, specific conditions are evaluated to determine the next steps [64].

1. C++: In C++, exception handling is accomplished using try, catch, and throw
constructs. Conditional handling in C++ mirrors the approach in Java, where the
condition is checked within the catch block (see the example below).
In C++, you can rethrow the caught exception using a simple throw; statement
inside the catch block.
26 2 Basics of Exception Handling

2. Python: Python uses the try, except, and raise keywords for exception handling.
Conditional exception handling in Python involves inserting a conditional state-
ment inside the except block.
Python provides the flexibility of catching multiple exceptions, either by specify-
ing them in a tuple or using separate except blocks. The conditional logic inside
each block can then determine the specific handling for each exception type.

1 // Example in C++
2 try {
3 // Code that might throw an exception
4 } catch (std :: exception &e) {
5 if ( someCondition ) {
6 // Handle the exception in one way
7 } else {
8 // Handle it differently or rethrow
9 throw;
10 }
11 }

1 # Example in Python
2 try:
3 # Code that might raise an exception
4 except Exception as e:
5 if some_condition :
6 # Handle the exception in a specific way
7 else:
8 # Handle differently or re - raise
9 raise

Conditional exception handling provides a finer level of control over how excep-
tions are dealt with. By evaluating certain conditions after catching an exception,
developers can craft more nuanced and appropriate responses to exceptional situa-
tions. However, it’s essential to ensure that conditional exception handling doesn’t
complicate the codebase unnecessarily. The primary goal should always be clar-
ity and ensuring that the software behaves predictably and robustly in the face of
unexpected events.

2.6.1 Event-driven Exception Handling

Event-driven exception handling is a paradigm wherein exceptions are handled based


on events, rather than just the type or the nature of the exception itself. In traditional
exception handling, when an exception occurs, it’s caught and dealt with in a catch or
equivalent block. In an event-driven approach, exceptions trigger events that can be
caught and handled by one or more dedicated event handlers. This approach provides
flexibility and modularizes exception handling, making it easy to adapt and extend.
2.6 Conditional Exception Handling 27

1. C++: Event-driven programming in C++ is often achieved with the help of li-
braries. For this example, we’ll implement a rudimentary event-driven exception
handling system without any external libraries

1 # include <iostream >


2 # include <functional >
3
4 class ExceptionEvent {};
5
6 class MyClass {
7 public :
8 std :: function <void( ExceptionEvent )> onException ;
9

10 void doSomething () {
11 try {
12 // Some operation that can throw an exception
13 throw "An error occurred ";
14 } catch ( const char* e) {
15 if ( onException ) {
16 onException ( ExceptionEvent ());
17 }
18 }
19 }
20 };
21

22 int main () {
23 MyClass obj;
24 obj. onException = []( ExceptionEvent e) {
25 std :: cout << " Exception handled using an event - driven
approach !" << std :: endl;
26 };
27
28 obj. doSomething ();
29 return 0;
30 }

In C++, event-driven programming can be achieved using a variety of methods.


Here, we’ll utilize the C++’s std::function for a simplistic approach.
The example above in C++ is explained as follows.

• Event class definition: An empty ExceptionEvent class serves as our event.


• Class throwing exceptions: The class MyClass contains a public mem-
ber onException, an instance of std::function. This allows us to as-
sign a callable (like a lambda or a function) to handle the event. Inside
doSomething, if an exception is caught, it checks for the presence of a handler
in onException and calls it if available.
• Driver code: The main function demonstrates how to bind a lambda function
as an event handler to handle the exception.
28 2 Basics of Exception Handling

2. Python: Python, with its dynamic and object-oriented nature, can effortlessly
implement an event-driven exception handling mechanism.

1 class ExceptionEvent :
2 pass
3

4 class MyClass :
5 def __init__ (self):
6 self. _exception_event_handlers = []
7

8 def add_exception_event_handler (self , handler ):


9 self. _exception_event_handlers . append ( handler )
10

11 def do_something (self):


12 try:
13 # Some operation that can raise an exception
14 raise Exception ("An error occurred ")
15 except Exception as e:
16 for handler in self. _exception_event_handlers :
17 handler ( ExceptionEvent ())
18

19 def handle_exception_event (event ):


20 print (" Exception handled using an event - driven approach !")
21

22 obj = MyClass ()
23 obj. add_exception_event_handler ( handle_exception_event )
24 obj. do_something ()

An explanation of the code above follows.

• Event class Definition: We have an ExceptionEvent class that will represent


our exception event.
• Class throwing exceptions: MyClass has a method do_something that
raises an exception. If an exception occurs, all registered event handlers
(_exception_event_handlers) are called.
• Driver code: An instance of MyClass is created. A handler function is regis-
tered to the object (handle_exception_event). When the exception event
occurs, this handler prints a notification.
Event-driven exception handling provides a higher-level abstraction for handling
exceptions, making the code more modular and flexible. However, it’s vital to ensure
clarity and maintainability, as this approach can complicate the code if not managed
correctly.
Chapter 3
Exception Handling Best Practices

Exception handling is a fundamental aspect of building reliable and robust software


systems. Its importance cannot be overstated, as it allows developers to tackle unfore-
seen issues that might arise during a program’s execution, ensuring smooth operation
and a superior user experience. Therefore, employing the appropriate exception-
handling techniques is vital, as it not only helps to identify and resolve errors but
also significantly impacts the overall stability and maintainability of the software.
This chapter delves into various best practices for exception handling, exploring their
importance, benefits, and practical applications in software development.
The primary objectives of this chapter are to provide developers with a com-
prehensive understanding of exception-handling best practices and to demonstrate
the practical application of these practices across different programming languages
and scenarios. In addition, through an extensive discussion of each best practice,
the chapter seeks to establish a strong foundation that developers can build upon to
create resilient, maintainable, and efficient software systems.
In this chapter we first will provide some practical concepts necessary to under-
stand the best practices:
• Differences between exceptions and errors.
• Errors as Undesired Events.
• Error Codes and Return Values in Exception Handling
• Assertions and Exception Handling.
In the following sections, we will examine a range of best practices related to
exception handling, including:
• Using specific exception types to facilitate accurate identification and handling
of different error scenarios
• Providing meaningful error messages to streamline the debugging process and
assist in identifying the root cause of exceptions
• Logging exceptions to create a historical record of errors and enhance overall
system observability

© The Author(s), under exclusive license to Springer Nature Switzerland AG 2024 29


P. Mejia Alvarez et al., Exception Handling, SpringerBriefs in Computer Science,
https://doi.org/10.1007/978-3-031-50681-9_3
30 3 Exception Handling Best Practices

• Handling exceptions at the appropriate level within the application’s architecture


to minimize redundancy and maintain a clear separation of concerns
• Implementing graceful degradation and recovery strategies to ensure continued
operation in the face of unexpected errors
• Employing separation of concerns in exception handling to enhance code read-
ability and maintainability
• Avoiding empty catch blocks to ensure that exceptions are addressed adequately,
and potential issues are not masked by insufficient error handling
By adhering to these best practices, developers can create more maintainable and
resilient software systems, improving overall system quality and stability. Addition-
ally, understanding and implementing these best practices can contribute to a more
collaborative and efficient development environment, as developers can more readily
comprehend, debug, and modify exception handling code.

3.1 Differences between Errors and Exceptions

Errors and exceptions are two types of events that can occur in a program. They differ
in their causes, handling mechanisms, and consequences. This section will explore
the differences between errors and exceptions in more detail.
1. Causes: Both internal and external factors can cause errors. Internal factors
include bugs in the code or the program’s architecture. External factors include
environmental issues such as power outages or network failures. In contrast,
exceptions are caused by specific program logic or behavior errors. Some common
examples of exceptions include invalid input, arithmetic errors, or out-of-memory
errors [43, 79].
2. Handling mechanisms: Errors and exceptions are handled differently in a pro-
gram. When an error occurs, the program terminates, and the error message is
displayed. In contrast, when an exception occurs, the program may continue run-
ning, and the code may handle the exception. Error handling usually requires the
programmer to check for errors manually by inspecting error codes or return val-
ues. Exception handling, on the other hand, is done using try-catch blocks, which
allow the program to handle exceptions in a centralized and structured manner
[68, 75].
3. Consequences: Errors can have severe consequences for the program and its users.
Errors can result in data loss, system downtime, or other negative outcomes. In
contrast, exceptions can be handled without significantly affecting the program’s
overall operation or stability. By handling exceptions gracefully, programs can
provide a better user experience and minimize the impact of errors on the system
[68, 79].
3.2 Errors as Undesired Events 31

3.2 Errors as Undesired Events

An exception can be considered as an undesired event under the following conditions:

• Unexpected outcomes: The occurrence of the exception was not foreseen in the
normal flow of program execution and leads to outcomes that deviate from the
expected behavior.
Disruption in execution: The exception disrupts the standard operational proce-
dure, resulting in the interruption of processes, tasks, or the application itself.
• Potential damage: If left unhandled, the exception could cause damage, like data
corruption, loss of unsaved data, or potentially, in some systems, even physical
harm (e.g., exceptions in embedded systems controlling machinery).
• User experience impact: The exception negatively impacts the user experience,
causing confusion, delays, or forcing the user to restart or rerun operations.
• System resources: The exception may lead to resource leaks, such as memory
not being freed, files left open, or network connections lingering, leading to
inefficiencies or even crashes over time.
• Safety concerns: Especially relevant in mission-critical applications or embedded
systems where an exception can result in safety hazards.
• Requires intervention: An exception that requires manual intervention, either to
fix the underlying cause or to restart services, is generally considered undesired.
• Reproducibility: If the exception is not just a one-time fluke and can be consis-
tently reproduced under certain conditions, it confirms its nature as an undesired
event, requiring attention.
• Inconsistencies in data: Exceptions that lead to data inconsistency, such as
database transaction failures or interrupted data transfers, are considered unde-
sired.
• Impacts business processes: For enterprise applications, any exception that dis-
rupts a business process, delays a workflow, or impedes decision-making can be
seen as an undesired event.
It’s essential to note that almost all exceptions are inherently "undesired" as
they represent disruptions in the expected flow. However, their criticality and the
urgency to address them can vary based on the application, the domain (e.g., finance,
healthcare, entertainment), and the specific circumstances of the exception.

3.2.1 Critical Aspects to Undesired Events

An undesired event or an exception is considered "handled" when a program identifies


an unwanted occurrence and, in response, initiates an alternative and explicitly
defined path that allows the program to continue executing successfully.
32 3 Exception Handling Best Practices

There are three critical aspects to undesired events handling:


1. Detection: This refers to the process by which the system realizes that something
unwanted has occurred.
2. Recovery: Prior to addressing an undesired event, it is essential to recover from
its occurrence. Recovery involves taking all necessary actions to minimize the
damage caused by the event, such as undoing or neutralizing any side effects and
freeing up temporary resources. Recovery implies the activation of an alternative
logic to return the program to a predictable state where the issue can be addressed.
Broadly speaking, recovery can be achieved in two ways:
• Forward error recovery: Rectifying the compromised state.
• Backward error recovery: Reverting the system to a previously known, stable
state.
3. Treatment: Merely recovering from an undesired event isn’t sufficient. Programs
should also attempt to address the root cause. Addressing or treating the undesired
event is more challenging than recovering from it. For recovery, it’s enough
to know that something went wrong, but for treatment, you need to ascertain
precisely what went awry and devise the appropriate workaround. This becomes
particularly challenging when considering rare events, such as file corruption.
System design plays a significant role in the ease or difficulty of addressing
undesired events. For instance, if data corruption occurs due to a flawed file format
design, recovery becomes almost impossible if all data is stored in a single file with
variable-length records and no separators.
Addressing undesired events involves activating an alternative execution path.
This path should:
• Recognize the cause of the undesired event.
• If possible, take necessary steps to prevent its recurrence.
• Ideally, continue the client-requested action, working around the error condition,
perhaps with some acceptable level of degradation, so the overall system can
continue operations.
Generally, there are three ways to address an undesired event:
1. Ignore: In principle, this isn’t a real option. If one could ignore an operation’s
outcome, invoking it in the first place would make little sense. This option is
mentioned for the sake of completeness.
2. Resolve: This aims to address the identified issue, which can be further divided
into two strategies:
• Retry: Useful in some situations, but must be handled judiciously to avoid
cascading failures.
• Invoke an alternative: There might be options to execute a different flow.
Maybe a different, slower, but safer algorithm can be tried. For instance, if
reading an essential file fails, a local backup file could be used, or the system
might revert to the last known good version or proceed using default values.
3.3 Error Codes and Return Values in Exception Handling 33

3. Abort: This means signaling a definitive failure. It’s the last resort when no other
attempts seem fruitful or possible. In such scenarios, logging the error for future
reference might be beneficial.
Since ignoring isn’t a viable alternative, once an undesired event is addressed,
there are only two potential outcomes: success if the issue was resolved or a definitive
failure if the resolution attempts were unsuccessful or if the choice was made to abort
directly.

3.3 Error Codes and Return Values in Exception Handling

Error codes and return values are traditional mechanisms for handling errors in
software development. Even before the widespread use of exceptions in modern
programming languages, developers relied on these methodologies to signal the
occurrence of errors during the execution of a program.
In the context of exception handling, understanding the role of error codes and
return values is crucial. While they are considered less sophisticated compared to
structured exception handling, they are still prominent, especially in systems where
performance is a critical factor or where full-fledged exception handling might
introduce overhead.
The advantages of using error codes and return values are the following.
• Performance: Exception handling can introduce a performance overhead, es-
pecially if exceptions are thrown frequently. Error codes, being a more direct
mechanism, generally have a predictable and often lower overhead.
• Explicit control flow: With error codes, the control flow is explicit. A function
returns an error, and the caller explicitly checks this return value.
The disadvantages of using error codes and return values are the following.
• Verbose: You have to check after every call if an error occurs. This can lead to a
lot of repetitive code, making the source code longer and harder to read.
• Mistakes: It’s easy to forget to check an error code. If this happens, the program
will continue running as if nothing went wrong, possibly leading to more issues
down the line.

3.3.1 Error Codes in C++

C++ doesn’t have a standard way of returning error codes, but a conventional approach
is to return a status (often an integer or an enum) and use output parameters for
actual function results.
Below is an example of using integer error codes in C++.
34 3 Exception Handling Best Practices

1 # include <iostream >


2

3 int divide (int dividend , int divisor , int & result ) {


4 if ( divisor == 0) {
5 return -1; // Error code for division by zero
6 }
7 result = dividend / divisor ;
8 return 0; // Success
9 }
10
11 int main () {
12 int result ;
13 int status = divide (8, 2, result );
14 if ( status == 0) {
15 std :: cout << " Result : " << result << std :: endl;
16 } else if ( status == -1) {
17 std :: cerr << " Error: Division by zero." << std :: endl;
18 }
19 return 0;
20 }

3.3.2 Return Values as Errors

Another approach is to use specific return values to indicate errors. This works best
when the range of valid return values is limited, so certain values can be set aside to
represent errors.
Below is an example in C++ of using return values as errors.

1 # include <iostream >


2 # include <optional >
3

4 std :: optional <int > divide (int dividend , int divisor ) {


5 if ( divisor == 0) {
6 return {}; // std :: nullopt , indicating an error
7 }
8 return dividend / divisor ;
9 }
10

11 int main () {
12 auto result = divide (8, 0);
13 if ( result ) {
14 std :: cout << " Result : " << * result << std :: endl;
15 } else {
16 std :: cerr << " Error: Division by zero." << std :: endl;
17 }
18 return 0;
19 }
3.4 Assertions and Exception Handling 35

In the above example, we’ve used std::optional, a C++17 feature, to indicate


the potential absence of a value.

3.4 Assertions and Exception Handling

In software development, both assertions and exception handling are essential tools
for managing unexpected conditions. However, their primary roles and use cases
are quite distinct. This section delves into the nature of assertions and exception
handling, outlining their purposes, differences, and best practices. Since we already
know what is exception handling we will only describe the concept of assertion.

3.4.1 Assertions

An assertion is a statement in a program that tests a condition and triggers an


error if the condition is false. The primary purpose of an assertion is to catch
programming errors; it’s not designed to handle runtime errors that are expected
to occur. Assertions are typically used during the debugging phase and are often
removed or disabled in a release version of the software.
Below is an example of an exception in C++.

1 # include <cassert >


2
3 int main () {
4 int x = 10;
5 x = x - 10;
6

7 assert (x != 0); // This will trigger an error because x is 0


8
9 return 0;
10 }

3.4.2 Assertions vs. Exception Handling

The following are general criteria to decide when to use exceptions vs. exception
handling.
• Debugging vs. runtime errors: Use assertions for conditions that should never
be false unless there’s a bug in the program. Use exception handling for errors
that can legitimately occur during the program’s operation, even if the code is
correct.
36 3 Exception Handling Best Practices

• Severity: Assertions usually indicate severe errors that can’t be recovered from,
leading to immediate termination. Exceptions can sometimes be caught and han-
dled, allowing the program to continue running or fail gracefully.
• Control: Exception handling offers more control than assertions. With exceptions,
you can catch specific types of errors and decide how to handle each one. With
assertions, the program simply crashes.
• Performance: Since assertions are generally disabled in release builds, they don’t
impact the performance of the shipped product. Exceptions, being a runtime
mechanism, do introduce some overhead, but modern compilers and runtimes
have optimized this to a large extent.
In general, the best practices recommended for the use of assertions vs. the use
of exception handling are the following.
• Don’t overuse assertions: While they are useful, peppering code with too many
assertions can make the code hard to read. Use them judiciously to check condi-
tions that would indicate a definite bug in the program.
• Use Meaningful exception messages: When throwing exceptions, provide mes-
sages that help diagnose the problem. This aids debugging and can provide
end-users with useful information about what went wrong.
• Don’t catch every exception: It’s tempting to catch all exceptions to prevent a
program from ever crashing. However, not every exception can be handled mean-
ingfully. Sometimes, especially if the program’s state might be compromised, it’s
better to let it terminate.
• Consider performance: In performance-critical code, the overhead of exception
handling might be a concern. However, it’s essential to balance this with the need
for robust error handling.

Both assertions and exception handling play critical roles in building robust
software. By understanding their distinct purposes and applying them judiciously,
developers can ensure that their software is both bug-free during development and
resilient in the face of unexpected runtime errors.

3.5 Using Specific Exception Types

Using specific exception types is a best practice in exception handling, as it enables


developers to identify and handle different types of exceptions accurately. This
approach allows for targeted exception handling, simplifying the debugging process
and improving overall code readability and maintainability. However, it also presents
challenges, such as the need to define custom exception types and ensure that catch
blocks are adequately ordered to prevent overly broad exception handling. In this
section, we will explore the benefits and challenges associated with using specific
exception types, along with examples and comparisons across various programming
languages.
3.5 Using Specific Exception Types 37

1. Benefits: By using specific exception types, developers can differentiate between


various error scenarios and handle them accordingly. This makes it easier to
identify the root cause of an issue, leading to a faster resolution. Specific exception
types also help ensure that only the relevant catch block is executed, preventing
unintended side effects and reducing the likelihood of masking other errors [2].
2. Challenges of using specific exception types: Although using specific exception
types offers many advantages, it also comes with some challenges. For example,
defining custom exception types can be time-consuming and may require addi-
tional maintenance as the software evolves. Furthermore, when handling multiple
exception types, it is crucial to order catch blocks correctly, ensuring that more
specific exceptions are caught before more generic ones.

1 // Specific Exception Types in C++


2 # include <iostream >
3 # include <fstream >
4 # include <stdexcept >
5
6 class FileReadException : public std :: exception {
7 public :
8 FileReadException (const std :: string & fileName )
9 : fileName ( fileName ) {}
10

11 const char* what () const noexcept override {


12 return (" Failed to read file: " + fileName ). c_str ();
13 }
14
15 private :
16 std :: string fileName ;
17 };
18

19 int main () {
20 try {
21 std :: ifstream file("data.txt");
22 if (! file. is_open ()) {
23 throw FileReadException ("data.txt");
24 }
25
26 // Code to read data from the file
27 // ...
28 } catch ( const FileReadException & ex) {
29 std :: cerr << "File read exception : " << ex.what () << std
:: endl;
30 } catch ( const std :: exception & ex) {
31 std :: cerr << " Exception caught : " << ex.what () << std ::
endl;
32 }
33

34 return 0;
35 }
38 3 Exception Handling Best Practices

3.5.1 Examples of Specific Exception Types

1. Specific exception types in C++: Above there is an example of a specific ex-


ception type in C++ for a file reading operation. In the example above, we
define a custom exception class called FileReadException, derived from
std::exception. The FileReadException class takes the file name as a pa-
rameter in its constructor and overrides the what() function to provide a
custom exception message. Within the main() function, we attempt to open a
file called data.txt using std::ifstream. If the file fails to open, we throw
a FileReadException, passing the file name as an argument. The exception is
then caught in the respective catch block, where we print the custom exception
message using ex.what().

1 # specific exception types in Python


2 def read_file ( filename ):
3

4 """ Read a chapter from a book."""


5 try:
6 with open(filename , ’r’, encoding =’utf -8 ’) as file:
7 chapter_content = file.read ()
8 return chapter_content
9

10 except FileNotFoundError :
11 print(f"Error : The file ’{ filename }’ was not found.")
12 except PermissionError :
13 print(f"Error : You don ’t have permission to read the
file ’{ filename }’.")
14 except UnicodeDecodeError :
15 print(f"Error : The file ’{ filename }’ contains
characters that could not be decoded .")
16 except Exception as e:
17 # Catching any other exceptions not specifically
handled above
18 print(f"An unexpected error occurred : {e}")
19
20 return None
21
22 if __name__ == " __main__ ":
23

24 filename = input("Enter the filename of the chapter you


want to read: ")
25 content = file( filename )
26 if content :
27 print( content )
28 else:
29 print(" Unable to read the chapter .")
3.6 Providing Meaningful Error Messages 39

2. Specific exception types in Python: Let’s consider a file reading operation in


Python. This program will handle specific exception types associated with file
operations:
• FileNotFoundError: This will be raised if the file does not exist.
• PermissionError: This will be raised if the user does not have the appropriate
permissions to read the file.
• UnicodeDecodeError: This can occur if the file has characters that cannot
be decoded with the encoding specified.
In the example above, we use a try-except block to handle exceptions that may
occur during the file reading operation. We’ve defined a function read_file that
attempts to read a chapter from a file using the filename provided. Within the
function, a series of specific exceptions are caught and handled. If the program
encounters an exception type that isn’t explicitly caught, the generic Exception
type will catch it. The main execution part of the script prompts the user for a
filename and then attempts to read and display the chapter. If there’s an error, the
program will inform the user with an appropriate message based on the exception
raised. In this program, by using specific exception types, we ensure that the user
is provided with meaningful feedback, helping them understand and potentially
rectify the issue they’re facing.

3.6 Providing Meaningful Error Messages


Providing meaningful error messages is a crucial aspect of exception handling. When
an exception occurs, a well-crafted error message can significantly help developers
and users understand the cause of the issue, enabling them to take appropriate
action and resolve it more quickly. In this section, we will discuss the objectives and
challenges associated with providing meaningful error messages and explore best
practices for creating them, with examples from various programming languages.
The primary objectives of providing meaningful error messages are to:
• Help developers quickly identify the root cause of an exception, thus accelerating
the debugging process.
• Inform users about the issue clearly and promptly, reducing confusion and frus-
tration.
• Improve the overall user experience, as errors are handled more gracefully and
communicated more effectively.
The main challenges of providing meaningful error messages include:
• Striking the right balance between technical details and user-friendly information,
depending on the target audience.
• Ensuring that error messages are accurate, informative, and up-to-date as the
software evolves.
• Crafting error messages that are concise and easy to understand without oversim-
plifying or omitting essential information.
40 3 Exception Handling Best Practices

To create meaningful error messages, consider the following best practices:


1. Be specific: Clearly describe the error condition and its consequences.
2. Be actionable: Provide guidance on how to resolve the issue or recover from the
error, if possible.
3. Be concise: Keep the message short and focused, avoiding unnecessary jargon
and verbosity.
4. Be user-friendly: Use clear, natural language that is appropriate for the target
audience, whether it’s developers, end users, or both.
5. Be consistent: Follow a consistent style and format for error messages throughout
the application, making them easier to recognize and understand.

1 // meaningful error messages in C++


2 # include <iostream >
3 # include <stdexcept >
4 // function divide throws an exception (std :: runtime_error ) when
trying to divide by zero.
5 double divide ( double num1 , double num2) {
6 if (num2 == 0.0) {
7 throw std :: runtime_error (" Division by zero error");
8 }
9 return num1 / num2;
10 }
11 // main function , we first ensure the user provides valid input
numbers . If the user enters invalid inputs (like characters ),
the program prompts them to input the numbers again .
12 int main () {
13 double num1 , num2;
14 std :: cout << " Enter two numbers ( numerator and denominator ):
";
15 // We use std :: cin to get the inputs
16 while (!( std :: cin >> num1 >> num2)) {
17 std :: cin. clear (); // Clear the error flag
18 std :: cin. ignore (std :: numeric_limits <std :: streamsize >:: max
() , ’\n’); // Discard invalid input
19 std :: cout << " Invalid input . Please enter two numbers : ";
20 }
21 try {
22 double result = divide (num1 , num2);
23 std :: cout << " Result : " << result << std :: endl;
24 }
25 catch (std :: runtime_error &e) {
26 std :: cerr << " Error: " << e.what () << std :: endl;
27 }
28

29 return 0;
30 }
3.7 Logging Exceptions 41

3.6.1 Examples of Meaningful Error Messages


1. Meaningful error messages in C++: Let’s design a program that divides two
numbers. This program will provide meaningful error messages for different
exception scenarios, such as division by zero or invalid input. In this program,
we then use a try block to attempt the division and catch any thrown exceptions.
If an exception occurs, we output a meaningful error message using e.what()
which fetches the error message associated with the exception. The example
above demonstrates how to provide meaningful error feedback to the user both
for unexpected scenarios (like division by zero) and for common input errors.
2. Meaningful error messages in Python: Below is an example of providing mean-
ingful error messages in Python: In this example, the error messages are specific,
actionable, concise, user-friendly, and consistent, following the best practices
outlined earlier.
1 # Meaningful error messages in Python
2 try:
3 with open("file.txt", "r") as file:
4 content = file.read ()
5 except FileNotFoundError :
6 print ("Error: File ’file.txt ’ not found . Please ensure the
file exists and is accessible .")
7 except PermissionError :
8 print ("Error: Permission denied while attempting to read ’
file.txt ’. Check your file permissions .")

3.7 Logging Exceptions


Exception logging is a critical aspect of any software development project. Logging
exceptions allows developers and system administrators to identify and diagnose
issues that might arise during the execution of a program. It also enables tracking
down bugs and understanding the program’s behavior under different conditions [17].
This section will provide an extensive overview of logging exceptions, its objectives,
challenges, and a comparison of techniques used in several programming languages.

3.7.1 Objectives
The primary objectives of logging exceptions are:
• Record unexpected events and errors that occur during program execution.
• Provide meaningful and detailed information to diagnose issues.
• Track the sequence of events leading to an exception, facilitating debugging and
problem resolution.
• Monitor application health and performance, allowing proactive actions to prevent
issues.
42 3 Exception Handling Best Practices

3.7.2 Challenges

Exception logging may pose challenges such as:

• Deciding the appropriate level of granularity for log messages.


• Ensuring log messages are clear, concise, and useful for debugging.
• Configuring logging frameworks to provide the desired level of detail.
• Managing log storage and rotation, preventing excessive disk usage, and ensuring
logs are available for analysis.
• Ensuring log data is secure and private, especially when it contains sensitive
information.

3.7.3 Examples of Logging Exceptions

Different programming languages provide various libraries and frameworks for log-
ging exceptions. This section will present examples of logging exceptions in C++
and Python along with an explanation of the techniques used in each language.

1 // Using a Logging Library in C++


2 # include <spdlog / spdlog .h>
3 # include <spdlog / sinks / basic_file_sink .h>
4
5 void divide (int numerator , int denominator ) {
6

7 try {
8 if ( denominator == 0) {
9 throw std :: runtime_error (" Division by zero");
10 }
11 double result = static_cast <double >( numerator ) / denominator ;
12 SPDLOG_INFO (" Result of division : {}", result );
13 } catch ( const std :: exception & ex) {
14 SPDLOG_ERROR (" Exception caught : {}", ex.what ());
15 }
16 }
17

18 int main () {
19

20 try {
21 divide (10 , 0); // Potential division by zero
22 } catch ( const std :: exception & ex) {
23 SPDLOG_ERROR (" Unhandled exception : {}", ex.what ());
24 }
25

26 return 0;
27 }
3.7 Logging Exceptions 43

1 // Custom Logging Function in C++


2 # include <iostream >
3 # include <fstream >
4 # include <chrono >
5

6 void logException (const std :: exception & ex) {


7 std :: ofstream logFile ("error .log", std :: ios :: app);
8

9 if ( logFile . is_open ()) {


10 auto now = std :: chrono :: system_clock :: now ();
11 auto time = std :: chrono :: system_clock :: to_time_t (now);
12 logFile << " Exception occurred at: " << std :: ctime (& time);
13 logFile << " Exception details : " << ex.what () << std :: endl;
14 logFile . close ();
15 } else {
16 std :: cerr << " Failed to open log file" << std :: endl;
17 }
18 }
19

20 void divide (int numerator , int denominator ) {


21 try {
22 if ( denominator == 0) {
23 throw std :: runtime_error (" Division by zero");
24 }
25 double result = static_cast <double >( numerator ) / denominator ;
26 std :: cout << " Result of division : " << result << std :: endl;
27 } catch ( const std :: exception & ex) {
28 logException (ex);
29 }}
30 int main () {
31 try {
32 divide (10 , 0); // Potential division by zero
33 } catch ( const std :: exception & ex) {
34 logException (ex);
35 }
36 return 0;
37 }

1. Logging exceptions in C++: When handling exceptions in C++, it’s often helpful
to log information about the exception for debugging and error-reporting purposes.
Logging exceptions allows you to capture relevant details about the exception,
such as the error message, stack trace, timestamp, and any additional contextual
information. In this section, we’ll explore how to log exceptions in C++.

• Using a logging library: To log exceptions in C++, you can utilize a log-
ging library that provides facilities for logging messages with various levels
of severity. Popular logging libraries in C++ include Boost.Log, spdlog,
and log4cpp. These libraries offer features such as logging to files, console
output, and remote servers, as well as customizable log message formatting
and filtering [7, 18, 26].
44 3 Exception Handling Best Practices

We show above an example using the spdlog library to log exceptions. In this
example, we include the spdlog library and its basic file sink for logging into
a file. We define a function divide that performs a division operation and logs
the result using SPDLOG_INFO. If a std::exception is thrown, it is caught
in the catch block, and the exception message is logged using SPDLOG_ERROR.
The main function calls divide and catches any unhandled exceptions, logging
them with SPDLOG_ERROR. Ensure that you have the spdlog library installed
and properly configured for your project.
• Custom logging function: If you prefer a more lightweight approach or don’t
want to rely on external libraries, you can create your own custom logging
function to log exceptions. An example in C++ is shown above. In this example,
we define a custom function logException that takes a std::exception object
as input. Inside the function, we open an output file stream in append mode and
log the exception details, including the timestamp, using the ctime function
and the what method of the exception object. The log file is closed after writing
the exception details. If the log file fails to open, an error message is printed
to std::cerr. The divide function performs a division operation and catches
any thrown exceptions. When an exception occurs, it calls the logException
function to log the exception details.
2. Logging exceptions in Python: Python provides a built-in logging library, al-
lowing developers to log exceptions easily. The following example demonstrates
how to log an exception using Python’s built-in logging library [25].

1 # Logging Exceptions in Python }:


2 import logging
3 try:
4 with open("file.txt", "r") as file:
5 content = file.read ()
6 except FileNotFoundError as e:
7 logging . exception ("File not found: %s", e)

In this example, the logging.exception function is used to log the exception.


The log message includes the exception message and a stack trace, providing
context for the error.

3.8 Handling Exceptions at the Appropriate Level

Modern applications often follow multi-tier architectures, such as the commonly


adopted three-tier structure consisting of presentation, logic, and data layers. It’s vital
to realize that not all exceptions that arise in a lower tier (e.g., data) should be handled
there. Propagating exceptions to a higher level (e.g., logic or presentation) can
sometimes offer more contextual awareness, allowing for more informed handling
decisions. Doing so can prevent redundancy, as a higher layer might already have
mechanisms in place for similar types of exceptions.
3.8 Handling Exceptions at the Appropriate Level 45

The following is an example of this concept in C++.

1 # include <iostream >


2 # include <stdexcept >
3 void databaseFunction () {
4 // Simulating a database operation that might fail
5 throw std :: runtime_error (" Database connection error .");
6 }
7 void businessLogicFunction () {
8 try {
9 databaseFunction ();
10 } catch ( const std :: runtime_error & e) {
11 std :: cerr << " Error caught in business logic layer : " <<
e.what () << std :: endl;
12 // Here we might decide to retry the operation , queue it
for later , etc.
13 }
14 }
15 int main () {
16 businessLogicFunction ();
17 return 0;
18 }

In the example, databaseFunction represents a low-level operation. Instead of


catching the database error immediately within this function, we allow it to propagate
to businessLogicFunction. This higher level has the context to decide what the
next steps should be (e.g., retrying the operation).

1 // Implementing graceful degradation and recovery


2 # include <iostream >
3 # include <stdexcept >
4 bool advancedFeature () {
5 throw std :: runtime_error (" Advanced feature malfunction .");
6 return false ;
7 }
8 int main () {
9 try {
10 if (! advancedFeature ()) {
11 std :: cout << " Switching to basic features ." << std ::
endl;
12 }
13 } catch ( const std :: runtime_error & e) {
14 std :: cerr << " Error: " << e.what () << ". Engaging
recovery procedures ." << std :: endl;
15 // Implement recovery mechanisms here: for instance ,
reloading resources , reconnecting , etc.
16 }
17 return 0;
18 }
46 3 Exception Handling Best Practices

3.9 Implementing Graceful Degradation and Recovery Strategies

When a critical error occurs, an application should avoid complete failure. Instead,
it should transition into a "degraded" mode where essential functionalities still
operate. This approach minimizes user disruption. Moreover, recovery strategies are
mechanisms that the application can use to restore full functionality after an error.
The following is an example of this concept in C++. In the example above, when
the advancedFeature function encounters an error, the application doesn’t crash.
Instead, it degrades to basic functionalities and tries to engage potential recovery
procedures.

1 // Separation of concerns in C++


2 # include <iostream >
3 # include <stdexcept >
4

5 void handleDatabaseError (const std :: exception & e) {


6 std :: cerr << " Database Error: " << e.what () << std :: endl;
7 // Here , we might log the error , alert administrators , etc.
8 }
9
10 void databaseOperation () {
11 throw std :: runtime_error (" Failed to fetch data.");
12 }
13

14 int main () {
15 try {
16 databaseOperation ();
17 } catch ( const std :: runtime_error & e) {
18 handleDatabaseError (e);
19 }
20 return 0;
21 }

3.10 Employing Separation of Concerns in Exception Handling

Separation of Concerns (SoC) is a design principle dictating that an application


should be segmented into distinct sections, each handling a specific concern. When
it comes to exception handling, SoC implies that error-handling code should not be
mixed with core business logic, ensuring better readability and maintainability.
The example above describes this concept in C++. In this code, rather than
intertwining error-handling logic with the primary function databaseOperation,
we segregate the error-handling to a dedicated function handleDatabaseError.
This makes the code cleaner and allows for more centralized and consistent error
handling.
3.11 Avoiding Empty Catch Blocks 47

3.11 Avoiding Empty Catch Blocks

While it might be tempting to use empty catch blocks to "silence" errors temporarily,
this can lead to overlooked issues and hidden bugs. An empty catch block captures
the exception but does nothing about it, essentially ignoring the error. This can be
problematic as it masks potential issues, making debugging more challenging in the
future.
The following is an example of this concept in C++.

2 # include <iostream >


3 # include <stdexcept >
4

5 void riskyOperation () {
6 throw std :: runtime_error ("A risky operation failed .");
7 }
8
9 int main () {
10 try {
11 riskyOperation ();
12 } catch ( const std :: runtime_error & e) {
13 // We avoid leaving this catch block empty
14 std :: cerr << " Error during risky operation : " << e.what ()
<< std :: endl;
15 // Further error handling can be performed here
16 }
17 return 0;
18 }

In the example provided, we ensure that every exception caught is handled or at


least logged. By capturing the exception and providing feedback (in this case, an error
message), developers and users can become aware of potential issues, facilitating
debugging and future maintenance.
Chapter 4
Advanced Exception Handling Techniques

The foundational principles of exception handling are well-known and widely prac-
ticed. They form the bedrock upon which we build reliable and resilient software.
However, as systems evolve in complexity and as the demands for high availability
and fault tolerance increase, developers often need more nuanced and sophisticated
techniques to manage exceptions effectively. In this chapter, we delve deeper into
the realm of exception handling, exploring advanced patterns and techniques that
seasoned developers employ to enhance the robustness of their applications. From
understanding common patterns like Guard Clauses, Circuit Breakers, and Retry
and Backoff programming’s impact on exception handling, this chapter aims to offer
insights that transcend the basics. We will also consider the challenges and solutions
related to handling exceptions in multi-threaded environments and Hierarchical Ex-
ception Handling, crucial topics in today’s era of concurrent computing. Lastly, we
will not only address the act of exception handling itself but also the crucial clean-up
actions that often accompany it. Ensuring that resources are released and system
states are restored is an art in itself, deserving its spotlight.

4.1 Exception Handling Patterns

Exception handling patterns are methods to deal with different types of errors and
exceptions that arise during the execution of a program. Implementing these patterns
helps to maintain the robustness and reliability of software. In this section, we will
discuss some of the most common exception handling patterns and provide examples
for different programming languages. We will also compare the techniques used in
several languages using a table.

© The Author(s), under exclusive license to Springer Nature Switzerland AG 2024 49


P. Mejia Alvarez et al., Exception Handling, SpringerBriefs in Computer Science,
https://doi.org/10.1007/978-3-031-50681-9_4
50 4 Advanced Exception Handling Techniques

4.2 Guard Clauses

The guard clauses pattern, also known as early returns or preconditions, is an ex-
ception handling technique that focuses on handling exceptional scenarios at the
beginning of a function or method. Instead of nesting the main logic inside a try-
catch block, guard clauses enable you to perform upfront checks and validations to
detect exceptional conditions and handle them appropriately. By using guard clauses,
you can improve the code’s readability and maintainability by reducing the level of
nesting and separating error-handling logic from the main flow of the function. This
pattern promotes the principle of "fail fast" by checking for errors early on and
returning from the function if an exceptional condition is encountered.
Let’s explore how guard clauses can be implemented in C++ and Python with
some examples.
1. C++: In this C++ example, the processFile function takes a filename as
input. The first guard clause checks if the filename is empty and throws
an std::invalid_argument exception if it is. The second guard clause
attempts to open the file using an std::ifstream object and throws an
std::runtime_error exception if the file fails to open. This approach allows
for early detection and handling of exceptional scenarios.

1 // Guard clauses in C++


2 # include <iostream >
3 # include <fstream >
4 # include <stdexcept >
5
6 void processFile ( const std :: string & filename ) {
7 if ( filename .empty ()) {
8 throw std :: invalid_argument (" Invalid filename ");
9 }
10 std :: ifstream file( filename );
11 if (! file. is_open ()) {
12 throw std :: runtime_error (" Failed to open file");
13 }
14 // File processing logic
15 std :: cout << " Processing file: " << filename << std :: endl;
16 }

1 # Guard clauses in Python


2 def process_file ( filename ):
3 if not filename :
4 raise ValueError (" Invalid filename ")
5 try:
6 with open(filename , "r") as file:
7 # File processing logic
8 print(" Processing file:", filename )
9 except FileNotFoundError :
10 raise IOError ("File not found ")
4.2 Guard Clauses 51

2. Python: In the Python example above, the process_file function takes a file-
name as input. The guard clause checks if the filename is empty and raises a
ValueError exception if it is. The main file processing logic is then encapsu-
lated within a try block. If a FileNotFoundError is raised, it is caught and
transformed into an IOError for consistency or additional processing.
By employing guard clauses, you can enhance the readability and maintainability
of your code by handling exceptional conditions early on. This approach helps
separate error handling logic from the main flow, resulting in more concise and
robust code. Please note that the provided examples are for illustrative purposes and
may require additional error handling and customization based on specific use cases
and requirements.
1 public class ExceptionTranslationExample {
2 public static void main( String [] args) {
3 try {
4 performTask ();
5 } catch ( HighLevelException e) {
6 System .out. println (" Error: " + e. getMessage ());
7 }}
8 public static void performTask () throws HighLevelException {
9 try {
10 lowerLevelOperation ();
11 } catch ( LowLevelException e) {
12 throw new HighLevelException ("A high - level error
occurred .", e);
13 }
14 public static void lowerLevelOperation () throws
LowLevelException {
15 // Code that may throw a LowLevelException
16 }}
17 class LowLevelException extends Exception {
18 public LowLevelException ( String message ) {
19 super ( message );
20 }}
21 class HighLevelException extends Exception {
22 public HighLevelException ( String message , Throwable cause ) {
23 super (message , cause);
24 }}

4.2.1 Exception Translation

Exception translation is a pattern in which an exception from a lower-level component


is caught and re-thrown as a new exception type that is more appropriate for the
higher-level component’s abstraction [57]. This pattern allows a more meaningful
exception to be presented to the caller and helps to maintain the separation of
concerns. The example above demonstrates exception translation in Java. Here, we
use a guard clause to check if the divisor is zero before proceeding with the division.
If the divisor is zero, a ValueError is raised with a meaningful error message. The
caller of the function can then handle the exception as needed.
52 4 Advanced Exception Handling Techniques

4.2.2 Exception Aggregation

Exception aggregation is a technique used in software development to combine


multiple exceptions into a single exception object [69, 77, 81]. It provides a way
to gather and encapsulate multiple errors or exceptional conditions into a unified
representation, handling and managing them easier.
Here are some key aspects of exception aggregation:
• Simplified error handling: Exception aggregation simplifies error handling by
consolidating multiple exceptions into a single entity. Instead of dealing with
each exception individually, developers can focus on handling the aggregated
exception, reducing the complexity of error-handling logic.
• Comprehensive error reporting: By aggregating exceptions, it becomes possi-
ble to provide more comprehensive error reports. The aggregated exception can
contain information from each individual exception, such as error messages, stack
traces, timestamps, or additional contextual data. This consolidated information
can be invaluable for debugging and troubleshooting purposes.
• Higher-Level abstraction: Exception aggregation helps to abstract away the
low-level details of individual exceptions and provide a higher-level view of
the exceptional conditions. It allows developers to deal with exceptions more
abstractly, making the code more readable, maintainable, and reusable.
• Error prioritization: Exception aggregation allows for prioritizing errors based
on their severity or importance. By collecting multiple exceptions and examining
their characteristics, developers can assign priorities to individual errors within
the aggregated exception. This can be helpful in determining the appropriate
course of action or escalation for handling exceptional conditions.
• Custom aggregation logic: Depending on the requirements of the application,
developers can define custom aggregation logic to determine how exceptions
should be combined. This can involve merging exception messages, selecting the
most severe exception, or applying specific rules for aggregating exception data.
Custom aggregation logic enables developers to tailor the exception-handling
process to their specific needs.
• Framework and library support: Many programming languages and frame-
works provide built-in support or libraries for exception aggregation. These tools
offer standardized ways to create and handle aggregated exceptions, reducing the
amount of manual work required.
When implementing exception aggregation, it is important to strike a balance be-
tween providing sufficient information about the exceptional conditions and avoiding
excessive verbosity. The aggregated exception should offer enough details to facili-
tate error diagnosis and resolution without overwhelming the user or developer with
unnecessary information. By adopting exception aggregation, developers can stream-
line error handling, improve code readability, and enhance the overall resilience and
robustness of their software systems. In this section, we provide examples of excep-
tion aggregation in C++ and Python languages.
4.2 Guard Clauses 53

1 // Exception aggregation in C++


2 # include <exception >
3 # include <vector >
4 # include <stdexcept >
5 class AggregateException : public std :: exception {
6 private :
7 std :: vector <std :: exception_ptr > exceptions ;
8 public :
9 AggregateException (const std :: vector <std :: exception_ptr >&
exceptions )
10 : exceptions ( exceptions ) {}
11 const char* what () const noexcept override {
12 return " AggregateException : Multiple exceptions occurred .
";
13 }
14 const std :: vector <std :: exception_ptr >& getExceptions () const
{
15 return exceptions ;
16 }};
17 void processFile () {
18 try {
19 // Code for processing a file
20 }
21 catch (const std :: exception & ex) {
22 std :: vector <std :: exception_ptr > exceptions ; exceptions .
push_back (std :: current_exception ());
23 // Additional code for handling exceptions
24 throw AggregateException ( exceptions );
25 }}
26 int main () {
27 try {
28 processFile ();
29 }
30 catch (const AggregateException & ex) {
31 for ( const auto& exception : ex. getExceptions ()) {
32 try {
33 std :: rethrow_exception ( exception );
34 }
35 catch ( const std :: exception & innerEx ) {
36 // Handle each individual exception
37 }]]
38 return 0;
39 }

1. Exception aggregation in C++: In C++, exception aggregation can be imple-


mented using custom exception classes. These classes can have the ability to
aggregate or contain multiple exceptions as data members. By aggregating ex-
ceptions, developers can provide a higher-level exception that encapsulates the
underlying issues and simplifies the error-handling process. The aggregation logic
can be customized based on the specific requirements of the application or system.
This may involve creating new exception objects with modified error messages,
54 4 Advanced Exception Handling Techniques

additional context information, or wrapping existing exceptions into a single ag-


gregated exception. Exception aggregation in C++ can be beneficial when dealing
with complex scenarios where multiple exceptions may occur but need to be re-
ported or handled as a single error. It helps consolidate error information, simplify
error reporting, and provide a more meaningful representation of the exceptional
conditions encountered during program execution.
The example above shows the exception aggregation in C++. In this example, the
AggregateException class is derived from std::exception and stores mul-
tiple exceptions in a vector. The processFile() function catches any exception
that occurs during file processing, creates an AggregateException object, and
throws it. In the main() function, the AggregateException is caught, and each
individual exception is extracted and handled separately.
2. Exception aggregation Python: In Python, exception aggregation can be
achieved by using a custom exception class that aggregates multiple exceptions.
An example is shown below. In this example, the AggregateException class
inherits from the base Exception class and stores multiple exceptions in a list.
The process_file() function catches any exception that occurs during file
processing, creates an AggregateException object, and raises it. In the outer
try-except block, the AggregateException is caught, and each individual ex-
ception is extracted and handled separately.

1 # Exception aggregation in Python


2 class AggregateException ( Exception ):
3 def __init__ (self , exceptions ):
4 self. exceptions = exceptions
5 super (). __init__ (" AggregateException : Multiple
exceptions occurred .")
6 def process_file ():
7 try:
8 # Code for processing a file
9 pass
10 except Exception as ex:
11 exceptions = [ex]
12 # Additional code for handling exceptions
13 raise AggregateException ( exceptions )
14 try:
15 process_file ()
16 except AggregateException as ex:
17 for exception in ex. exceptions :
18 # Handle each individual exception
19 pass
4.2 Guard Clauses 55

4.2.3 Exception Shielding Pattern

Exception shielding is a design pattern aimed at protecting an application from ex-


posing sensitive information or causing unwanted side effects through unhandled
exceptions. For example, when applications encounter errors, they often throw ex-
ceptions containing detailed information about the problem. These exceptions can
reveal sensitive data or allow attackers to deduce the application’s internal structure,
leading to security risks. The exception shielding pattern addresses this issue by
catching all exceptions at the application’s entry points and replacing them with
sanitized, user-friendly error messages before propagating to the client or other ex-
ternal components. Developing robust applications that handle errors gracefully and
maintain security is challenging. Developers must anticipate various types of ex-
ceptions and decide how to manage them without exposing sensitive information or
compromising application stability. Several approaches have been proposed in the
literature to address these challenges, each with its advantages and disadvantages. In
the following sections, we’ll talk about some prominent techniques for implementing
the exception shielding pattern, along with their respective programming examples,
and provide an in-depth comparison of these techniques.
In the following section, we will discuss different approaches to the exception
shielding pattern.

4.2.3.1 Wrapper Exceptions

Wrapper exceptions involve catching the original exception and wrapping it in a cus-
tom exception that hides sensitive information before re-throwing it. This approach
allows developers to maintain a consistent interface for handling exceptions while
keeping the original exception available for further processing if needed [74].
An example in Python using a wrapper exception is shown below.

1 class ShieldedException ( Exception ):


2 def init(self , original_exception ):
3 self. original_exception = original_exception
4 super ().init("A sanitized error message ")
5

6 def vulnerable_function ():


7 raise ValueError (" Sensitive information ")
8 def safe_function ():
9 try:
10 vulnerable_function ()
11 except ValueError as e:
12 raise ShieldedException (e)
13 try:
14 safe_function ()
15 except ShieldedException as e:
16 print(e)
56 4 Advanced Exception Handling Techniques

Wrapper exceptions offer a flexible way to manage exceptions by encapsulating


sensitive information and providing a clean interface for handling them. However,
this approach may require additional effort from developers to create custom ex-
ception classes and to implement exception wrapping in each function that needs it.
Additionally, maintaining and updating these custom exception classes can become
more complex and error-prone as the application grows.

4.2.3.2 Exception Filter


The exception filter technique involves using an application-wide filter that intercepts
all unhandled exceptions and replaces them with sanitized error messages. This ap-
proach ensures consistent handling of exceptions across the entire application and
can be easier to implement than wrapper exceptions, as it does not require creating
custom exception classes [42]. An example of Python using the exception filter
approach is shown below. Python provides more flexibility in exception handling,
allowing us to utilize exception filters quite conveniently with the help of if state-
ments inside except blocks. Here’s a detailed example using Python to demonstrate
exception filtering:

1 class CustomException ( Exception ):


2 def __init__ (self , code , message ):
3 self.code = code
4 self. message = message
5 super (). __init__ (self. message )
6 def __str__ (self):
7 return f"[{ self.code }] {self. message }"
8 def process_with_filter (task):
9 try:
10 task ()
11 except CustomException as e:
12 if e.code == 404: # Filter : Only handle exception if the
error code is 404
13 print(f" Caught a ’not found ’ exception : {e}")
14 else:
15 raise # Re - raise exception if it doesn ’t match our
filter
16 def main ():
17 def task_1 (): # This exception will be caught and
handled by our filter
18 raise CustomException (404 , " Resource not found ")
19 def task_2 (): # This exception will be caught but re -
raised because it doesn ’t match the filter
20 raise CustomException (500 , " Internal server error ")
21 try:
22 process_with_filter ( task_1 )
23 process_with_filter ( task_2 )
24 except CustomException as e:
25 print (f" Caught exception in main: {e}")
26 if __name__ == " __main__ ":
27 main ()
4.2 Guard Clauses 57

We defined a CustomException class that holds an error code and a message.


The process_with_filter function accepts a task (which is a function) that
might raise a CustomException. Within the process_with_filter, we catch
the exception, and based on its error code (in this example, we’re checking for code
404), we decide whether to handle it or re-raise it. This pattern allows us to centralize
exception-filtering logic in Python.

4.2.3.3 Centralized Exception Handling

Centralized exception handling involves managing all exceptions in a central location,


such as a dedicated error handler or middleware. This approach promotes code
reusability and ensures consistent behavior across the application [45].
An example of Java using centralized exception handling is shown below.

1 @RestControllerAdvice
2 public class GlobalExceptionHandler {
3 @ExceptionHandler (value = { Exception .class })
4 public ResponseEntity <Object > handleAnyException ( Exception ex ,
WebRequest request ) {
5 return new ResponseEntity <>("A sanitized error message ",
HttpStatus . INTERNAL_SERVER_ERROR );
6 }
7 }

Centralized exception handling provides a unified approach to managing excep-


tions, which can greatly simplify implementation and maintenance. By handling all
exceptions in a single location, developers can easily update the logic for sanitizing
error messages or adding new exception types without needing to modify multi-
ple parts of the codebase. This approach is particularly well-suited for large-scale
applications with complex exception handling requirements. However, centralized
exception handling may not be as flexible as wrapper exceptions or exception filters,
as it relies on a single error handler to manage all types of exceptions. In some
cases, this may not provide enough granularity for handling different exceptions in
a customized manner.
In conclusion, choosing an exception shielding technique depends on the applica-
tion’s specific requirements and the development team’s priorities. Each technique
has its own strengths and weaknesses, and it is essential to consider factors such as
ease of implementation, maintainability, granularity, and scalability when deciding
on the best approach. By understanding the trade-offs associated with each technique,
developers can make informed decisions to help them create more secure and robust
applications.
58 4 Advanced Exception Handling Techniques

4.3 Retry and Backoff Strategies for Exception Handling

Modern software systems are often composed of interconnected components that


rely on external services such as databases, web services, or other components.
Communication between these components can be prone to failures due to network
latency, temporary unavailability, or transient errors. To improve the reliability and
resilience of such systems, developers implement retry and backoff strategies that
allow components to recover from transient failures by automatically retrying failed
operations after waiting for a certain amount of time [3, 20].
Retry and backoff strategies come in various forms, each with its own advantages
and trade-offs. In this section, we provide a comprehensive discussion of the key
concepts and techniques related to retry and backoff strategies for exception handling.
We also present programming examples for each technique, discuss the associated
equations, tables, and figures where necessary, and compare these techniques in the
last subsection.

4.3.1 Fixed Interval Retry

Fixed interval retry is a simple retry strategy where a failed operation is retried
after waiting for a fixed amount of time, regardless of the number of attempts. This
approach is easy to implement and understand. However, it may not be the most
efficient solution, as it does not adapt to the varying nature of transient errors [3,38].
Here is an example of fixed interval retry in Python.

1 import time
2

3 def fixed_interval_retry (func , retries , interval ):


4 for i in range ( retries ):
5 try:
6 return func ()
7 except Exception :
8 if i < retries - 1:
9 time. sleep ( interval )
10 else:
11 raise

In this example, the fixed_interval_retry function accepts a callable func,


the number of retries, and a fixed interval between retries. If func raises an
exception, the function waits for the specified interval before retrying.
4.3 Retry and Backoff Strategies for Exception Handling 59

4.3.2 Randomized Interval Retry

Randomized interval retry is a variation of the fixed interval retry strategy that
introduces a degree of randomness to the waiting time between retries. This strategy
can help reduce contention when multiple clients experience failures simultaneously
and attempt to retry simultaneously [38].
Below is an example of a randomized interval retry in Python. In this example,
the randomized_interval_retry function calculates the waiting time between
retries by choosing a random value between the minimum and maximum intervals.

1 import time
2 import random
3
4 def randomized_interval_retry (func , retries , min_interval ,
max_interval ):
5 for i in range ( retries ):
6 try:
7 return func ()
8 except Exception :
9 if i < retries - 1:
10 interval = random . uniform ( min_interval , max_interval )
11 time.sleep ( interval )
12 else:
13 raise

1 # example of exponential backoff in Python


2 import time
3 import random
4 def exponential_backoff_retry (func , retries , initial_interval ,
max_interval ):
5 for i in range ( retries ):
6 try:
7 return func ()
8 except Exception :
9 if i < retries - 1:
10 interval = min( initial_interval * (2 ** i), max_interval )
11 time.sleep ( interval + random . uniform (0, 0.1 * interval ))
12 else:
13 raise

4.3.2.1 Exponential Backoff

Exponential backoff is a more sophisticated retry strategy that exponentially


increases the waiting time between retries, allowing the system to adapt to the
transient nature of errors. This strategy reduces the likelihood of overwhelming the
target service by backing off more aggressively as the number of retries increases
[20].
60 4 Advanced Exception Handling Techniques

Above is an example of exponential backoff in Python. In the example, the


exponential_backoff_retry function calculates the waiting time between re-
tries by multiplying the initial interval by 2 to the power of the current retry count.
The function also introduces jitter (randomness) to the waiting time to prevent syn-
chronization issues that may occur when multiple clients retry simultaneously.

1 # Decorrelated jitter backoff in Python


2 import time
3 import random
4
5 def decorrelated_jitter_backoff_retry (func , retries ,
initial_interval , max_interval ):
6 sleep_time = initial_interval
7 for i in range ( retries ):
8 try:
9 return func ()
10 except Exception :
11 if i < retries - 1:
12 time.sleep ( sleep_time )
13 sleep_time = min( max_interval , random . uniform ( initial_interval ,
sleep_time * 3))
14 else:
15 raise

4.3.3 Decorrelated Jitter Backoff

Decorrelated jitter backoff is a variation of exponential backoff that further random-


izes the waiting time between retries to reduce contention between multiple clients.
This strategy aims to balance the benefits of exponential backoff and jitter, while
avoiding their potential drawbacks [6, 10].
An example of decorrelated jitter backoff in Python is shown above. In this exam-
ple, the decorrelated_jitter_backoff_retry function calculates the waiting
time by choosing a random value between the initial interval and the current sleep
time multiplied by a factor (3 in this case). The sleep time is then updated by selecting
the minimum value between the calculated waiting time and the maximum interval.

4.4 Exception Handling in Multi-Threaded Environments

Exception handling in multi-threaded environments is crucial to writing robust and


reliable concurrent software. However, the concurrent nature of program execution in
multi-threaded applications, where multiple threads of execution run simultaneously
and share resources such as memory, file handles, and other system resources, can
lead to complex interactions between threads [46]. These interactions can make
4.4 Exception Handling in Multi-Threaded Environments 61

exception handling more challenging and error-prone, as exceptions can be thrown


concurrently, and handling them correctly may involve coordination among multiple
threads. In this section, we will extensively discuss various techniques for handling
exceptions in multi-threaded environments, their advantages, and the challenges they
present. We will also include programming examples for each technique, along with
tables and figures to illustrate important concepts.

4.4.1 Techniques for Exception Handling in Multi-Threaded


Environments

In this subsection, we will discuss several techniques for handling exceptions in


multi-threaded environments, along with examples in C++ and Java.

1 // Thread -Local Exception Handling in C++


2 # include <iostream >
3 # include <stdexcept >
4 # include <thread >
5 void threadFunction () {
6 try {
7 // Perform operations that may throw an exception
8 throw std :: runtime_error ("An error occurred ");
9 } catch (const std :: exception & e) {
10 std :: cerr << " Error in thread : " << e.what () << std :: endl;
11 }
12 }
13 int main () {
14 std :: thread t( threadFunction );
15 t.join ();
16 return 0;
17 }

4.4.1.1 Thread-Local Exception Handling

Thread-local exception handling is an approach where each thread is responsible


for handling its exceptions independently, ensuring that exceptions do not propagate
across threads. This can simplify exception handling and prevent potential issues due
to inter-thread communication, as the exception handling code is localized within
the thread that generates the exception [40].
An example of thread-local exception handling in C++ using the standard excep-
tion mechanism is shown above. In this example, the threadFunction catches and
handles exceptions within the same thread. This approach keeps exception handling
localized, simplifying the code and reducing the potential for unexpected interac-
tions between threads. However, this approach may not be suitable for handling
exceptions that require coordination or communication between multiple threads or
when a more centralized approach is desired.
62 4 Advanced Exception Handling Techniques

1 // Thread -Group Exception Handling in Java


2 public class ThreadGroupExceptionHandler implements Thread .
UncaughtExceptionHandler {
3
4 @Override
5 public void uncaughtException ( Thread t, Throwable e) {
6 System .err. println ("Error in thread " + t. getName () + ": " + e.
getMessage ());
7 }
8
9 public static void main( String [] args) {
10 ThreadGroup group = new ThreadGroup (" ExceptionHandlingGroup ");
11 Thread . UncaughtExceptionHandler handler = new
ThreadGroupExceptionHandler ();
12 Runnable runnable = () -> {
13 // Perform operations that may throw an exception
14 throw new RuntimeException ("An error occurred ");
15 };
16

17 Thread thread = new Thread (group , runnable , " Thread1 ");


18 thread . setUncaughtExceptionHandler ( handler );
19 thread .start ();
20

21 try {
22 thread .join ();
23 } catch (
24 InterruptedException e) {
25 e. printStackTrace ();
26 }
27 }
28 }

4.4.1.2 Thread-Group Exception Handling

Thread-group exception handling is an approach where threads are grouped together,


and exceptions are handled at the group level, allowing for centralized exception
handling and improved code organization. This technique enables the propagation
of exceptions among threads within the same group, which can be useful when
handling exceptions requires coordination among multiple threads [76]. An example
of thread-group exception handling in Java using the ThreadGroup class is shown
above.
In this example, we create a custom Thread.UncaughtExceptionHandler
implementation that handles unhandled exceptions for a specific thread group. This
approach allows for centralized exception handling within the group, making it easier
to coordinate exception handling among related threads. However, this approach may
not be suitable when exceptions need to be propagated to other thread groups or when
a more global approach is desired.
4.4 Exception Handling in Multi-Threaded Environments 63

4.4.1.3 Global Exception Handling

Global exception handling is an approach where exceptions are handled at the ap-
plication level, allowing for a centralized point of exception handling across all
threads in the application. This technique can be useful for capturing and logging
unhandled exceptions or performing application-wide cleanup tasks in response
to an exception [30]. An example of global exception handling in Java using the
setDefaultUncaughtExceptionHandler method is shown below.

1 public class GlobalExceptionHandler implements Thread .


UncaughtExceptionHandler {
2 @Override
3 public void uncaughtException ( Thread t, Throwable e) {
4 System .err. println (" Unhandled error in thread " + t. getName () + "
: " + e. getMessage ());}
5 public static void main( String [] args) {
6 Thread . setDefaultUncaughtExceptionHandler (new
GlobalExceptionHandler ());
7 Runnable runnable = () -> {
8 // Perform operations that may throw an exception
9 throw new RuntimeException ("An error occurred ");
10 };
11 Thread thread = new Thread (runnable , " Thread1 ");
12 thread .start ();
13 try {
14 thread .join ();
15 } catch ( InterruptedException e) {
16 e. printStackTrace ();
17 }
18 }
19 }

In this example, we create a custom Thread.UncaughtExceptionHandler


implementation that handles unhandled exceptions for all threads in the application.
This approach provides a single point of exception handling, which can be useful for
centralized logging or other application-wide tasks.

4.4.2 Challenges and Limitations

Exception handling in multi-threaded environments presents several challenges and


limitations:
1. Concurrency issues: Exception handling in multi-threaded environments can be
prone to race conditions, deadlocks, and other concurrency-related issues, making
it more complex and error-prone [46].
64 4 Advanced Exception Handling Techniques

2. Error propagation: Propagating exceptions across threads can be difficult, as


threads may not share the same call stack or execution context. This can complicate
exception handling and lead to unexpected behaviors [76].
3. Resource management: Exception handling in multi-threaded environments re-
quires careful management of shared resources, such as memory, file handles,
and other system resources. Improper resource management can lead to resource
leaks, data corruption, or other issues [30].
4. Complexity: Handling exceptions in multi-threaded environments can introduce
additional complexity to the code, making it more difficult to understand, main-
tain, and debug [40].
The techniques discussed in this section provide different approaches to exception
handling in multi-threaded environments, each with its advantages and challenges.
By understanding and implementing these techniques, developers can create more ro-
bust and maintainable multi-threaded applications, effectively addressing the unique
challenges posed by concurrent exception handling.

4.5 Hierarchical Exception Handling


Hierarchical exception handling is a structured approach to managing errors and
exceptions in programming [19,36,39,55,58,59]. This strategy organizes exceptions
based on a class hierarchy that extends from a generic base exception class to more
specific exception classes. The hierarchy usually begins with a base exception class,
and more specific exceptions are subclassed from this base class or its children,
forming a tree-like structure.
At the top of the hierarchy is the most generic exception class. In Python, this
is the base Exception class, and in Java, it’s the java.lang.Exception class.
These classes represent the most general types of exceptions that can occur in a
program. As we traverse down the exception hierarchy, we encounter exception
classes that describe more specific types of exceptions. These specific exceptions
are subclasses of more generic exceptions and provide more detailed information
about the exceptional event that occurred.
The principle of hierarchical exception handling is to catch and handle the most
specific exception possible. Suppose a catch block for a particular type of exception
is not present or cannot appropriately resolve the issue. In that case, the exception is
passed up the hierarchy to a catch block for a more general exception. This layered
exception-handling mechanism provides a way to handle errors at the most relevant
level while maintaining a safety net for unhandled or unexpected exceptions.
Different techniques have been designed for hierarchical exception handling:
1. Local Exception Handling: Local exception handlers are designed to handle
specific types of exceptions that can occur in a particular part of a system.
These handlers usually have the most context about what operations were being
performed when the exception occurred; thus, they are often in the best position
to handle the exception.
4.5 Hierarchical Exception Handling 65

For example, suppose a piece of code is reading a file. In that case, a local
exception handler might be designed to handle exceptions related to file access,
such as the file not being found or being inaccessible. These handlers would have
specific knowledge about the file and the operations being performed, allowing
them to handle the exception appropriately - perhaps by trying a different file path
or by prompting the user to select a different file.
2. Higher-Level Exception Handling: If a local exception handler cannot resolve an
exception, the exception is passed up to a higher level of the system. This higher-
level handler will have less context about the specific operation that caused the
exception, but it will have a broader understanding of the overall system state.
Using the previous example, if the local handler for a file access exception cannot
resolve the issue (maybe because the file is missing and the whole directory is
gone), it might pass the exception up to a higher-level handler. This handler might
be designed to handle broader I/O exceptions, such as issues with the disk drive or
network. For example, it could handle the exception by logging the error, alerting
the user, or even redirecting operations to a backup drive.
3. Global Exception Handling
If no other handler can deal with an exception, it is passed to a global handler.
This handler is often a last line of defense, designed to catch any exceptions that
were not caught by any other handlers. It has a minimal understanding of the
specific operations that caused the exception but understood the system’s state as
a whole.
Global handlers are often used to prevent a program from crashing entirely when
an unhandled exception occurs. For example, they might log the exception, alert
the user, and attempt to safely close or restart the program.

4.5.1 Default Exception Handling

Sometimes, an exception may reach the top of the hierarchy without being handled.
For example, this could happen if the system doesn’t have a handler for a particular
type of exception or if all the handlers that could catch the exception could not resolve
it. In these cases, the exception is typically handled by a default exception handler.
The default exception handler is a kind of "catch-all" mechanism that is designed
to handle any exceptions that haven’t been handled elsewhere. This handler may have
very little context about the operations that led to the exception, but it can still take
some actions to prevent the entire system from crashing.
For example, the default handler might log the exception, along with any available
information about the system state when the exception occurred. It might also display
a generic error message to the user, and it could attempt to safely shut down or restart
the system to prevent further errors.
66 4 Advanced Exception Handling Techniques

4.5.2 Customizing the Hierarchy

The specific structure of the exception-handling hierarchy can be customized to suit


the needs of a particular system. For instance, a system might have several levels
of handlers, each designed to handle exceptions related to a particular module or
feature of the system. Alternatively, a system might have a very flat hierarchy, with
most exceptions being handled at the global level.
The hierarchy structure should be designed to balance the need for specific,
contextual handling of exceptions against the need to keep the system robust and
easy to maintain. For example, having too many levels in the hierarchy could make
the system complex and difficult to understand. On the other hand, having too few
levels could lead to exceptions being handled inappropriately or not at all.

4.5.3 Testing and Maintaining the Hierarchy

Like any part of a system, the exception-handling hierarchy should be thoroughly


tested to ensure that it works as expected. This might involve generating test ex-
ceptions at various points in the system and verifying that they are handled at the
appropriate level of the hierarchy.
Maintaining the hierarchy involves adding new handlers as new types of excep-
tions are introduced and updating existing handlers as the system changes. It’s also
important to review the hierarchy periodically to ensure that it’s still effectively serv-
ing the needs of the system. For example, if many exceptions are being handled at the
global level, it might be a sign that the lower levels of the hierarchy aren’t catching
as many exceptions as they should be.

4.5.4 Exception Propagation

An important aspect of Hierarchical Exception Handling is the concept of exception


propagation, or the process by which an exception is passed up the hierarchy until
it’s handled.
In a well-designed hierarchy, if a lower-level handler cannot adequately resolve
an exception, it should be propagated upwards until it finds a suitable handler. This
allows the exception to be handled at the most appropriate level of the system,
considering the severity and context of the error. If an exception isn’t caught and
handled by any specific handler, it ultimately reaches the top of the hierarchy. As
mentioned earlier, a default or global handler at this level typically logs the error and
may attempt to salvage the situation to prevent a total system crash.
4.5 Hierarchical Exception Handling 67

4.5.5 Hierarchical Exception Handling in Different Programming


Paradigms

Hierarchical Exception Handling isn’t limited to any particular programming


paradigm, and can be used in procedural, object-oriented, or functional programming
languages. The specific implementation details might vary between languages and
paradigms, but the underlying concept remains the same [11, 22, 66]. For instance,
in object-oriented programming languages, exceptions are typically represented as
objects of classes that inherit from a common base exception class. This allows
the structure of the exception classes to mirror the hierarchy of exception handlers,
making the system easier to understand and manage.
In this section, we’ll provide more detailed examples using Python and Java.

1 # Hierarchical Exception Handling in Python


2

3 try:
4 # Attempt to open a non - existent file
5 file = open(’non_existent_file .txt ’, ’r’)
6 # Attempt to read from the file
7 content = file.read ()
8 # Attempt to parse the content as an integer
9 number = int( content )
10 except FileNotFoundError :
11 # Handle the case where the file does not exist
12 print ("The specified file does not exist.")
13 except IOError :
14 # Handle other I/O errors , such as issues with file read/
write permissions
15 print ("An I/O error occurred .")
16 except ValueError :
17 # Handle the case where the file content could not be parsed
as an integer
18 print ("The file content could not be parsed as an integer .")
19 except Exception :
20 # Handle all other types of exceptions
21 print ("An unexpected error occurred .")

1. Python Example: Python provides a built-in hierarchy of exceptions, all deriving


from the base class Exception. This allows you to catch exceptions at various
levels of specificity. Above is an expanded example. In the Python example
above, we try to open a non-existent file, read from it, and parse the content as an
integer. We have separate handlers for FileNotFoundError, IOError, ValueError,
and a generic Exception. These handlers catch and handle the exceptions in a
hierarchical manner, from more specific to more general.
2. Java Example: Java also provides a built-in hierarchy of exceptions, all deriving
from the base class Exception. Similar to Python, you can catch exceptions at
various levels of specificity. Here’s an expanded example:
68 4 Advanced Exception Handling Techniques

1 / Hierarchical Exception Handling in Java


2 try {
3 // Attempt to open a non - existent file
4 FileReader fileReader = new FileReader (" non_existent_file .
txt");
5 BufferedReader bufferedReader = new BufferedReader (
fileReader );
6 // Attempt to read from the file
7 String line = bufferedReader . readLine ();
8 // Attempt to parse the content as an integer
9 int number = Integer . parseInt (line);
10 } catch ( FileNotFoundException e) {
11 // Handle the case where the file does not exist
12 System .out. println ("The specified file does not exist.");
13 } catch ( IOException e) {
14 // Handle other I/O errors , such as issues with file read/
write permissions
15 System .out. println ("An I/O error occurred .");
16 } catch ( NumberFormatException e) {
17 // Handle the case where the file content could not be
parsed as an integer
18 System .out. println ("The file content could not be parsed
as an integer .");
19 } catch ( Exception e) {
20 // Handle all other types of exceptions
21 System .out. println ("An unexpected error occurred .");
22 }

In this Java example, we try to open a non-existent file, read from it, and parse the
content as an integer. We have separate handlers for FileNotFoundException,
IOException, NumberFormatException, and a generic Exception. These han-
dlers catch and handle the exceptions in a hierarchical manner, from more specific
to more general.
In both these examples, handling the exceptions hierarchically allows us to provide
more specific error messages and recover from the error gracefully. If a particular
handler cannot handle the exception, it is passed up the hierarchy to a more general
handler. This is the essence of Hierarchical Exception Handling.

4.5.6 Benefits and drawbacks of Hierarchical Exception Handling

Hierarchical exception handling is a common practice in many programming lan-


guages due to its numerous benefits. However, like any technique, it also has its
drawbacks. Understanding these advantages and disadvantages can help developers
use hierarchical exception handling effectively.
4.5 Hierarchical Exception Handling 69

Benefits of Hierarchical Exception Handling:


1. It promotes separation of concerns, as each level of the system handles the
exceptions it is most equipped to deal with.
2. It makes the system more robust, as exceptions can be passed up the hierarchy if
they can’t be handled at a lower level.
3. It can improve debugging, as it’s clear where each exception type should be
handled.
4. It helps maintain the system, as adding new exception types or handlers often
involves making changes at one level of the hierarchy without impacting others.
5. Many modern programming languages, such as Java, C++, and Python, support
hierarchical exception handling through the use of try/catch blocks and exception
propagation. When an exception is detected (often by ’throwing’ an exception),
the system attempts to ’catch’ that exception using the most local handler. If that
handler can’t resolve the exception, it is re-thrown and caught by the next level
up, and so on.
6. In a well-designed system, each level of the hierarchy should have handlers for
the exceptions that it’s capable of handling, and all other exceptions should be
propagated up to a level that can handle them.
7. Increased code clarity and maintainability: By grouping related exceptions to-
gether in a hierarchical structure, it’s easier to understand the types of errors that
can occur in a system. The exception-handling code also becomes more structured
and easier to maintain.
8. Granular control over error handling: Hierarchical exception handling provides
a high degree of control over error handling. Developers can choose to handle
specific types of exceptions in detail while providing more general handlers for
broader exception classes.
9. Promotes code reusability: The hierarchical organization of exception classes
promotes code reusability. Common error-handling code can be written for more
general exceptions, which more specific exception classes can then inherit.
10. Improved error diagnosis: Hierarchical exception handling allows more specific
exceptions to provide more detailed information about errors. This can help in
diagnosing and fixing errors.

On the other hand, the drawbacks of Hierarchical Exception Handling may be the
following:
1. Can lead to complex code: If not managed properly, the exception hierarchy can
become overly complex, making the code harder to understand and maintain.
2. Performance overhead: Each try-catch block introduces some performance over-
head. If the program is full of try-catch blocks, it can slow down the execution
speed.
3. Risk of catching too many exceptions: Catching too many exceptions at a
high level in the hierarchy can sometimes hide errors and make debugging more
difficult. It’s important only to catch exceptions that you can meaningfully handle.
70 4 Advanced Exception Handling Techniques

4. Can lead to code duplication: If similar exceptions are caught and handled in
the same way in multiple places, it can lead to code duplication. Developers need
to be mindful of this and strive to handle exceptions at the appropriate level to
avoid unnecessary code duplication.

Overall, hierarchical exception handling is a powerful tool for managing and


responding to errors in a structured way. However, like any tool, it should be used
thoughtfully and carefully to avoid potential drawbacks.

4.6 Clean-Up Actions in Exception Handling

Exception handling is a critical aspect of robust and reliable software development.


A significant part of this process is the clean-up of resources, which helps maintain
the system stability and prevents resource leakage when exceptions occur [31].

4.6.1 The Necessity of Clean-Up Actions

Clean-up actions are crucial for managing resources such as memory, file handles,
and network connections that a program might allocate during its execution. If these
resources are not adequately released, particularly in the event of an exception,
the system can become unstable, leading to degraded performance or crashes [56].
Clean-up actions typically refer to those actions that release resources or restore the
system to a stable state when an exception occurs. Resources here can mean any
assets that the program uses, such as memory, file handles, database connections,
network sockets, or even hardware resources. Failing to clean up these resources
properly can lead to various issues, such as memory leaks, system instability, or data
corruption.
A clean-up action usually includes, but is not limited to:
• Releasing memory that has been dynamically allocated
• Closing file handles that have been opened
• Closing database or network connections that have been established
• Releasing locks to prevent deadlocks
• Restoring the state of the user interface
It is worth noting that the requirement for clean-up actions arises from the very
nature of exceptions - they alter the normal control flow of a program. When an
exception is raised, the current operation may need to be completed, leaving resources
in an inconsistent or unusable state.
4.6 Clean-Up Actions in Exception Handling 71

4.6.2 Clean-Up Techniques in Different Programming Languages

Several programming languages offer different mechanisms for handling clean-up


actions during exception handling.

1. Using the ‘finally‘ Block in Python: Python provides the try-except-finally


construct for handling exceptions and performing clean-up actions. The ‘finally‘
block executes regardless of whether an exception was raised in the ‘try‘ block
[82]. Consider a scenario where we are working with a database connection.
If an exception occurs during data manipulation, we must close the connection
properly to prevent resource leakage.

1 import sqlite3
2 try:
3 conn = sqlite3 . connect (’test.db’)
4 cursor = conn. cursor ()
5 cursor . execute (’SELECT * FROM my_table ’)
6 except sqlite3 . DatabaseError :
7 print (" Database error occurred ")
8 finally :
9 if conn:
10 conn.close ()

2. The try-with-resources statement in Java: Java 7 introduced try-with-resources


statement, an auto-close feature for more efficient handling of resource clean-
up. Any object that implements the java.lang.AutoCloseable or java.io.Closeable
interfaces can be used as a resource [34]. Let’s look at a more complex example
where we use a BufferedReader to read data from a file.

1 import java.io. BufferedReader ;


2 import java.io. FileReader ;
3 import java.io. IOException ;
4 public class Main {
5 public static void main( String [] args) {
6 try ( BufferedReader br = new BufferedReader (new
FileReader (" example .txt"))) {
7 String line;
8 while (( line = br. readLine ()) != null) {
9 System .out. println (line);
10 }
11 } catch ( IOException e) {
12 System .out. println ("Error reading file: " + e.
getMessage ());
13 }}}

In this Java example, the BufferedReader instance is automatically closed


after exiting the try block. This is useful in ensuring that the file resource is
appropriately cleaned up after usage.
72 4 Advanced Exception Handling Techniques

3. Resource acquisition is initialization (RAII) in C++: In C++, the RAII idiom


is often used for exception-safe resource management. The resource is acquired
during the initialization of an object and released when the object is destroyed [75].
Consider a scenario where we are working with dynamic memory allocation.
Using RAII, we can ensure that the allocated memory is freed, even if an exception
occurs.

1 # include <iostream >


2 # include <memory >
3

4 int main () {
5 try {
6 std :: unique_ptr <int > ptr(new int (10));
7 // Do something that might throw an exception
8 throw std :: runtime_error ("An exception occurred ");
9 } catch (const std :: runtime_error & e) {
10 std :: cout << e.what () << ’\n’;
11 // The memory allocated by ptr will be automatically
freed here
12 }
13 return 0;
14 }

In this C++ example, the dynamically allocated memory is automatically freed


when the std::unique_ptr object is destroyed, even if an exception is thrown
within the try block. This is a key advantage of the RAII idiom in C++.

4.6.3 Best Practices for Clean-Up Actions


Effective clean-up actions are crucial for robust exception handling. Here are some
recommended practices [78]:
• Minimize the scope of resources. By limiting the scope of a resource, you re-
duce the chance of resource leaks and make your code easier to understand and
maintain.
• Use language features designed for resource management. Many programming
languages provide features specifically designed to help with resource manage-
ment (e.g., finally in Python, try-with-resources in Java, RAII in C++).
• Maintain consistency in handling clean-up actions. Consistent use of resource
management techniques makes your code easier to read and less error-prone.
• Ensure your code is exception-safe. Write your code with the understanding that
exceptions can occur at any time. Use exception-handling mechanisms to ensure
that your program behaves correctly in the face of unexpected conditions.
Effective clean-up actions during exception handling are vital for maintaining
system stability and preventing resource leakage. By understanding and adequately
implementing these actions, developers can enhance the robustness and reliability
of their applications.
Chapter 5
Exception Handling in Real-Time and
Embedded Systems

Modern computing not only demands precise execution but also timely and respon-
sive actions. Nowhere is this truer than in the realm of real-time and embedded
systems, where both the logic and the timing of computation play critical roles.
As systems grow more complex and intertwined, handling anomalies or unexpected
events – commonly known as exceptions – in a graceful manner becomes paramount.
The real-time context adds another layer of intricacy as we not only need to address
the exception but also to ensure that the resolution adheres to strict time constraints.
The pivotal role of exception handling in ensuring system stability and pre-
dictability is undeniable. In this chapter, we will embark on an in-depth exploration
of exception handling as it specifically applies to real-time and embedded systems.
We begin by illustrating some typical examples of exceptions that may arise in real-
time contexts. This sets the stage for a detailed examination of the specific constraints
that real-time systems operate under, from the rigidity of timing to the scarcity of
resources.
Diving deeper, we’ll discuss various types of timing exceptions, shedding light on
both hardware and software perspectives. But in a real-time world, is it enough to just
’handle’ an exception? Prioritization becomes essential. Some exceptions might be
more critical than others, demanding immediate attention, while others can be dealt
with in a deferred manner. This leads us to the fascinating interplay of priorities,
deadlines, and their significance in exception handling.
Another intriguing facet is the synergy (and distinctions) between interrupts and
exceptions, especially in the context of a widely used language like C++. How do
we effectively link them? What are the best practices? And more importantly, can
we leverage them together for optimal real-time performance?
Methodologies play a pivotal role in system design. Therefore, we’ll delve into
established methodologies like recovery blocks and proactive monitoring, each de-
signed to address and resolve exceptions while ensuring system uptime and reliability.
The latter sections will introduce us to design patterns tailored for real-time scenar-
ios, each offering unique strategies and solutions for robust exception handling.
Lastly, as Ada remains a popular choice for real-time system development, we’ll
explore its rich features and constructs designed explicitly for exception handling in

© The Author(s), under exclusive license to Springer Nature Switzerland AG 2024 73


P. Mejia Alvarez et al., Exception Handling, SpringerBriefs in Computer Science,
https://doi.org/10.1007/978-3-031-50681-9_5
74 5 Exception Handling in Real-Time and Embedded Systems

such contexts. From defining exception models to the intricate details of tasking and
exception interactions, this section provides comprehensive insights for developers
and architects working with Ada in real-time environments.

5.1 Example of Exceptions in Real-Time Systems


Below is an example of structured exception handling in C++ and Python applied
to a real-time system. The C++ program below demonstrates real-time exception
handling by monitoring the time taken to process some data. The code attempts to
identify situations where the data processing exceeds a given deadline, and if so,
raises an exception.
The function process_data simulates a delay of 200 milliseconds (using
std::this_thread::sleep_for) to represent some kind of data processing. After
this delay, it throws a std::runtime_error exception with the message "An error
occurred."
The main function does the following:
• Captures the start time.
• Calls the process_data function.
• Captures the end time.
• Calculates the elapsed time. If the elapsed time exceeds 100 milliseconds, it
throws another exception with the message "Deadline missed." If any exception
(from the process_data function or the "Deadline missed" check) is thrown,
the catch block catches it and displays the message using e.what().
• In execution, when you run this program, you’ll see the output: Caught exception:
An error occurred. This is because the process_data function itself throws an
exception even before the timing check can be done.

1 # include <iostream >


2 # include <stdexcept >
3 # include <chrono >
4 # include <thread >
5 void process_data () {
6 $std :: this_thread :: sleep\ _for$ (std :: chrono :: milliseconds (200));
// Simulate processing delay
7 throw $std :: runtime \ _error$ ("An error occurred .");
8 }
9 int main () {
10 try {
11 auto start = $std :: chrono :: steady \ _clock :: now$ ();
12 process_data ();
13 auto end = $std :: chrono :: steady \ _clock :: now$ ();
14 auto elapsed = $std :: chrono :: duration \ _caststd :: chrono ::
milliseconds$ (end - start);
15 if ( elapsed . count () > 100) {
16 throw $std :: runtime \ _error$ (" Deadline missed .");
17 }} catch ( const std :: runtime_error & e) {
18 $std :: cerr <<$ " Caught exception : " $<< e.what () << std :: endl$;
19 } return 0;}
5.2 Constraints in Real-Time Systems 75

1 import time
2 from datetime import datetime , timedelta
3

4 def process_data ():


5 time. sleep (0.2) # Simulate processing delay
6 raise Exception ("An error occurred .")
7
8 try:
9 start = datetime .now ()
10 process_data ()
11 end = datetime .now ()
12 elapsed = end - start
13 if elapsed > timedelta ( milliseconds =100):
14 raise Exception (" Deadline missed .")
15 except Exception as e:
16 print(f" Caught exception : {e}")

Listing 5.1: Structured exception handling example in Python for real-time systems

5.2 Constraints in Real-Time Systems

The unique characteristics of real-time and embedded systems pose distinct chal-
lenges for exception handling compared to traditional application development. For
example, in real-time systems, timing constraints and predictability are crucial, as
these systems are required to meet deadlines and respond to external events within
a specified time frame. On the other hand, embedded systems often operate under
resource constraints, such as limited processing power, memory, or power consump-
tion, which impact the choice and implementation of exception-handling techniques.

5.2.1 Timing Constraints

One of the main challenges in real-time systems is meeting strict timing constraints.
These systems must respond to events or complete tasks within a specified time
frame, often called deadlines. Failure to meet these deadlines can result in degraded
system performance or even catastrophic failure in safety-critical applications [14].
There are two main types of real-time systems:
• Hard real-time systems: Missing a deadline is considered a system failure. Exam-
ples include avionics and automotive control systems.
• Soft real-time systems: Occasional deadline misses are tolerable but may result in
reduced quality of service. Examples include multimedia and telecommunication
systems.
76 5 Exception Handling in Real-Time and Embedded Systems

Designing real-time systems to meet timing constraints often involves careful


scheduling of tasks, prioritization, and resource management. Several scheduling al-
gorithms have been proposed for real-time systems, such as Rate Monotonic Schedul-
ing (RMS) and Earliest Deadline First (EDF) [4,48]. These algorithms provide theo-
retical guarantees for meeting deadlines under certain conditions, such as when tasks
are independent and have fixed execution times [44]. However, in practice, many real-
time systems must handle tasks with varying execution times, shared resources, and
complex dependencies. To address these challenges, researchers have proposed ex-
tensions to traditional scheduling algorithms, such as hierarchical scheduling [72],
adaptive scheduling [51], and feedback control scheduling [15].

5.2.2 Resource Constraints

Embedded systems typically operate under resource constraints, including limited


processing power, memory, and power consumption. These constraints can impact
the choice and implementation of exception-handling techniques, as some methods
may be too resource-intensive for specific embedded environments.
Developers must strike a balance between robust exception handling and re-
source usage. Techniques such as static analysis and lightweight runtime checks can
help minimize the overhead of error detection and exception handling in embedded
systems [8, 23]. Furthermore, optimizing memory usage is essential for embedded
systems, as memory is often critical. Techniques such as memory pooling, cus-
tom memory allocators, and data compression can help reduce memory usage and
improve performance in embedded systems [29, 84].
Power consumption is another critical resource constraint in many embedded
systems, particularly those that rely on battery power or energy harvesting. Tech-
niques for managing power consumption in embedded systems include dynamic
voltage and frequency scaling (DVFS) [24], power gating [52], and energy-aware
task scheduling [5].

5.2.3 Predictability and Determinism

Predictability and determinism are crucial for real-time systems, as they ensure
consistent system behavior under varying conditions. This is particularly important
for safety-critical applications, where unpredictable behavior can lead to severe
consequences.
Sources of non-determinism in real-time and embedded systems include:
• Hardware-related factors, such as manufacturing variability, aging, and external
disturbances.
• Software-related factors include dynamic memory allocation, dynamic task cre-
ation, and non-deterministic scheduling policies.
5.3 Types of Timing Exceptions in Real-time Systems 77

To address these challenges, developers can use techniques such as fixed-priority


scheduling, static memory allocation, and worst-case execution time (WCET) anal-
ysis [12]. Fixed-priority scheduling, such as RMS, can help ensure predictability by
assigning tasks with static priorities based on their periods or deadlines. Static mem-
ory allocation can be used to prevent issues caused by dynamic memory allocation,
such as fragmentation and unpredictable memory allocation times.
WCET analysis is a crucial technique for ensuring predictability in real-time
systems. It involves determining the maximum amount of time that a task can take
to execute on a specific hardware platform. WCET analysis can be performed using
static analysis methods, measurement-based techniques, or hybrid approaches [83].
Accurate WCET estimates are essential for guaranteeing that tasks will meet their
deadlines and for ensuring the overall predictability of the system.

5.2.4 Reliability and Fault Tolerance

Real-time and embedded systems often operate in harsh environments and must
be able to handle various types of faults, including hardware failures, software
bugs, and transient faults caused by external factors (e.g., radiation, temperature,
or electromagnetic interference). Ensuring system reliability and fault tolerance is
essential for maintaining system functionality and avoiding catastrophic failures.
Techniques for improving reliability and fault tolerance in real-time and embedded
systems include redundancy, fault isolation, error detection, and error recovery [63].
Redundancy can be achieved through hardware replication (e.g., triple modular
redundancy) or software techniques such as N-version programming or recovery
blocks. Fault isolation techniques aim to prevent the propagation of faults through-
out the system, using methods such as watchdog timers, sandboxing, and memory
protection. Error detection methods include parity checks, error-correcting codes,
and runtime assertions. Finally, error recovery techniques aim to restore system
functionality after a fault has been detected, using methods such as checkpointing,
rollback, and restart.

5.3 Types of Timing Exceptions in Real-time Systems

Real-time systems, by their very nature, rely heavily on the precise timing of op-
erations. A missed deadline or an unexpected delay can have cascading effects,
sometimes even catastrophic. These timing-related exceptions can be categorized
into hardware timing exceptions and software timing exceptions. Let’s explore each
in detail with examples.
78 5 Exception Handling in Real-Time and Embedded Systems

5.3.1 Hardware Timing Exceptions

Hardware timing exceptions originate from the physical components of a system.


They may be due to unexpected delays in hardware operations, interference, or
synchronization issues among various hardware components.
In the following example, we will write in C++ an interface with a slow hardware
component. Imagine a real-time system interfacing with an I/O device. If the device
doesn’t respond within a set time, it might lead to a hardware timing exception.

1 # include <iostream >


2 # include <chrono >
3 # include <thread >
4 # include <stdexcept >
5
6 bool read_from_device () {
7 std :: this_thread :: sleep_for (std :: chrono :: milliseconds (500));
// Simulating delay
8 throw std :: runtime_error (" Hardware read timed out!");
9 }
10
11 int main () {
12 try {
13 read_from_device ();
14 } catch ( const std :: runtime_error & e) {
15 std :: cerr << e.what () << std :: endl;
16 }
17 return 0;
18 }

The following example in Python is about waiting for a sensor reading. Let’s say
a sensor is expected to give a reading every second. A delay beyond this could be a
hardware timing exception.

1 import time
2

3 def get_sensor_reading ():


4 time.sleep (1.5) # Simulate a delay in sensor reading
5 raise TimeoutError (" Sensor reading timed out!")
6
7 try:
8 get_sensor_reading ()
9 except TimeoutError as e:
10 print (e)
5.3 Types of Timing Exceptions in Real-time Systems 79

5.3.2 Software Timing Exceptions

Software timing exceptions occur due to software processes taking longer than
expected, mismanagement of resources, or the inherent complexity of certain algo-
rithms that cause them to run longer than the allocated time.
In the following example in C++, a function takes longer than expected to execute.
Let’s consider a scenario where data processing takes longer than its allocated time,
causing a software timing exception.

1 # include <iostream >


2 # include <chrono >
3 # include <thread >
4 # include <stdexcept >
5
6 void lengthy_data_processing () {
7 std :: this_thread :: sleep_for (std :: chrono :: seconds (3)); //
Simulate a lengthy processing
8 throw std :: runtime_error (" Processing timed out!");
9 }
10
11 int main () {
12 try {
13 lengthy_data_processing ();
14 } catch ( const std :: runtime_error & e) {
15 std :: cerr << e.what () << std :: endl;
16 }
17 return 0;
18 }

The following example in Python is similar to the above but in Python language.
Algorithms, especially those with high computational complexity, can sometimes
take longer than expected.

1 import time
2

3 def complex_algorithm (data):


4 time.sleep (2.5) # Simulating a complex algorithm ’s runtime
5 raise TimeoutError (" Algorithm execution timed out!")
6
7 try:
8 complex_algorithm ([])
9 except TimeoutError as e:
10 print (e)

By wrapping potential problem areas in try blocks and catching specific excep-
tions, we can create more resilient systems that can respond more gracefully to
unexpected events. The catch (C++) or except (Python) clauses allow for handling
these exceptions, giving the program an opportunity to recover or at least terminate
gracefully.
80 5 Exception Handling in Real-Time and Embedded Systems

When developing real-time systems, it’s crucial to account for these timing ex-
ceptions. Both hardware and software perspectives need thorough testing and redun-
dancies to ensure that when a timing exception does occur, the system can either
recover gracefully or fail safely.

5.4 Priorities and Deadlines in Exceptions Handling in Real-time


Systems

Real-time systems have stringent requirements related to response times, making it


crucial to handle exceptions without causing unacceptable delays. In this chapter, we
will discuss how exceptions in real-time systems can be prioritized and managed to
meet specific deadlines, illustrated with examples in both C++ and Python.

1 / Prioritizing Exceptions in C++


2 # include <iostream >
3 # include <stdexcept >
4

5 enum class ExceptionPriority { CRITICAL , MINOR };


6

7 class CustomException : public std :: exception {


8 ExceptionPriority priority ;
9 public :
10 CustomException (const char* message , ExceptionPriority p) :
std :: exception ( message ), priority (p) {}
11

12 ExceptionPriority getPriority () const { return priority ; }


13 };
14

15 int main () {
16 try {
17 throw CustomException ("A specific issue occurred !",
ExceptionPriority :: CRITICAL );
18 } catch ( const CustomException & e) {
19 if (e. getPriority () == ExceptionPriority :: CRITICAL ) {
20 std :: cerr << " Critical Error: " << e.what () << std ::
endl;
21 // Handle critical error , maybe reset the system or
halt operations
22 } else if (e. getPriority () == ExceptionPriority :: MINOR) {
23 std :: cerr << "Minor Error : " << e.what () << std :: endl
;
24 // Handle minor error , possibly log it and continue
operations
25 }
26 }
27 return 0;
28 }
5.4 Priorities and Deadlines in Exceptions Handling in Real-time Systems 81

5.4.1 Prioritizing Exceptions

In real-time systems, not all exceptions are of equal urgency. Some exceptions, if not
addressed immediately, can lead to more severe consequences than others. Thus, it
becomes paramount to prioritize exceptions and handle them accordingly.
The example above in C++ demonstrates the usage of an enumeration to priori-
tize exceptions. The CustomException class has a priority attribute, which allows
specific error handling based on its urgency.
In the following example in Python, exception classes are used to differentiate
between a critical error and a minor one. By using separate exception classes, the
catch blocks can distinctly handle each error type based on its criticality.

2 class BaseException ( Exception ):


3 pass
4
5 class CriticalError ( BaseException ):
6 pass
7
8 class MinorError ( BaseException ):
9 pass
10
11 try:
12 raise CriticalError ("A critical error occurred !")
13 except CriticalError :
14 print (" Critical Error : Handling with utmost urgency !")
15 # Handle critical error , maybe reset the system or halt
operations
16 except MinorError :
17 print ("Minor Error : Handling , but can proceed with other
tasks!")
18 # Handle minor error , possibly log it and continue operations

5.4.2 Deadline Management

Meeting deadlines is the hallmark of any real-time system. When exceptions occur,
they can potentially disrupt the system’s ability to meet these deadlines. It’s essential
to ensure that the time spent handling exceptions doesn’t breach any time constraints.
In the following example in C++, the program simulates a time-sensitive task. If
an exception occurs during this task and the time spent in handling the exception
(along with the task’s execution time) exceeds the defined deadline (100 milliseconds
in this example), an error message is displayed.
82 5 Exception Handling in Real-Time and Embedded Systems

1 # include <iostream >


2 # include <stdexcept >
3 # include <chrono >
4 # include <thread >
5

6 void time_sensitive_task () { std :: this_thread :: sleep_for (std ::


chrono :: milliseconds (70)); // Simulating some task
7 throw std :: runtime_error ("An error occurred during the task."
);
8 }
9 int main () {
10 auto start = std :: chrono :: steady_clock :: now ();
11 try {
12 time_sensitive_task ();
13 } catch ( const std :: runtime_error & e) {
14 std :: cerr << " Exception : " << e.what () << std :: endl;
15 }
16 auto end = std :: chrono :: steady_clock :: now ();
17 auto elapsed = std :: chrono :: duration_cast <std :: chrono ::
milliseconds >( end - start);
18 if ( elapsed .count () > 100) {
19 std :: cerr << " Error: Missed the deadline !" << std :: endl;
20 } else {
21 std :: cout << "Task completed within deadline ." << std ::
endl;
22 }
23 return 0;
24 }

The following Python example similarly showcases a time-sensitive task. If the


combined time of task execution and exception handling exceeds the defined deadline
(0.1 seconds in this case), an error message gets printed.

1 import time
2 def time_sensitive_task ():
3 time.sleep (0.07) # Simulating some task
4 raise Exception ("An error occurred during the task.")
5 start = time.time ()
6 try:
7 time_sensitive_task ()
8 except Exception as e:
9 print(f" Exception : {e}")
10 end = time.time ()
11 elapsed = end - start
12 if elapsed > 0.1:
13 print ("Error : Missed the deadline !")
14 else:
15 print ("Task completed within deadline .")
5.5 Interrupts and Exception Handling in C++ 83

5.5 Interrupts and Exception Handling in C++

In computer systems, an interrupt is an event that stops the regular flow of a program
and diverts the control of the CPU to handle a specific task or situation. Interrupts
can originate from various sources, including hardware (like a keyboard input) or
software (a specific instruction in a program). Exception handling in C++, on the
other hand, deals with unexpected or exceptional situations that arise during program
execution. This section will explore the relationship between interrupts and exception
handling in C++ and demonstrate their applications with examples.

1. Hardware Interrupts: Hardware interrupts are generated by hardware devices,


like IO peripherals. They’re essential for real-time systems where certain actions
have to be taken immediately upon receiving specific input or under particular
conditions.
As an example, consider a scenario where a button press should immediately
pause a playing media file. The button press can generate a hardware interrupt
that informs the system to pause the media playback. In C++, however, direct
handling of hardware interrupts isn’t typical since it’s platform-dependent. It’s
usually managed by lower-level languages or assembly. In applications, libraries or
OS functions might abstract away this hardware interaction, but it’s still important
to understand the concept.
2. Software Interrupts: Software interrupts are initiated by software, like a system
call. For instance, when a program wants to read a file, it can issue a system call
which might be implemented as a software interrupt.
In C++, you can simulate software interrupts using function calls. It’s a simplified
view, but it aids in understanding.

In the following example in C++ a timer-based event where after a certain period,
a message is printed.

1 # include <iostream >


2 # include <thread >
3

4 void timerInterrupt () {
5 std :: this_thread :: sleep_for (std :: chrono :: seconds (5));
6 std :: cout << " Timer interrupt called !" << std :: endl;
7 }
8

9 int main () {
10 std :: cout << " Starting main program ..." << std :: endl;
11 timerInterrupt (); // Simulating a software interrupt after 5
seconds
12 std :: cout << "End of main program ." << std :: endl;
13 return 0;
14 }
84 5 Exception Handling in Real-Time and Embedded Systems

5.5.1 Linking Interrupts and Exceptions

In some systems, especially real-time systems or embedded systems, interrupts might


require immediate attention, and not handling them could lead to failures. Such
interrupts can be linked to exceptions in C++ to ensure that they’re adequately
addressed. Linking interrupts and exceptions directly in C++ is a tricky task because it
relies heavily on platform-specific and low-level constructs. However, for educational
purposes, we can create a simplified simulation using standard C++ constructs to
demonstrate the concept.
Let’s take the example of a temperature sensor in a hypothetical scenario. Suppose
we have a temperature sensor that generates a "signal" (in our simulation, this would
be a function call) when the temperature exceeds a certain threshold. This signal can
be considered analogous to a hardware interrupt in a real-world scenario. When this
"interrupt" is triggered, we throw an exception in C++ to handle the situation, such
as shutting down a machine. Here’s the simulated example in C++.

1 # include <iostream >


2 # include <stdexcept >
3 // Simulated temperature sensor
4 class TemperatureSensor {
5 public :
6 // This function simulates reading the temperature
7 int readTemperature () {
8 // In a real -world scenario , this would get the
temperature from hardware
9 // Here we’re hardcoding a value for simplicity
10 return 105; // temperature in Fahrenheit
11 }
12 };
13 void checkAndHandleTemperature ( TemperatureSensor & sensor ) {
14 int temp = sensor . readTemperature ();
15 // If temperature exceeds 100F, we consider it as an "
interrupt " or " signal "
16 if (temp > 100) {
17 throw std :: runtime_error (" Temperature exceeded safe
limits !");
18 }
19 }
20 int main () {
21 TemperatureSensor sensor ;
22 try {
23 checkAndHandleTemperature ( sensor );
24 } catch ( const std :: runtime_error & e) {
25 std :: cout << " Error: " << e.what () << "\n";
26 std :: cout << " Shutting down machinery ...\n";
27 }
28
29 return 0;
30 }
5.5 Interrupts and Exception Handling in C++ 85

In the above code, TemperatureSensor class simulates our hardware sensor.


checkAndHandleTemperature function reads the temperature and throws an ex-
ception if it’s above 100F (considered our danger threshold for this example). The
main function demonstrates how this exception would be caught and handled, simu-
lating the action of shutting down a machine. This code doesn’t represent how you’d
handle interrupts in real-world C++ applications, especially since handling hardware
interrupts directly in C++ isn’t common. Instead, this is a conceptual demonstration
of how an "interrupt" could be linked to an exception in a higher-level language like
C++.

5.5.2 Similarities and diferences of Interrupts and Exceptions

Both interrupts and exceptions are mechanisms used by computer systems to deal
with unexpected or prioritized events. However, they serve distinct purposes, orig-
inate from different sources, and are handled differently in the realm of system
design.
The similarities in both mechanisms are the following.
• Event Handling: Both interrupts and exceptions are tools to handle events. An
interrupt typically responds to hardware signals, whereas an exception handles
unusual or unexpected software conditions.
• Priority: Both have a notion of priority. For interrupts, certain types might be
given precedence over others. In software exceptions, some exceptions, because
of their critical nature, might be handled before others.
• Preemption: Both can result in the current execution being halted or preempted
to deal with the event.

The differences in both mechanisms are the following.


• Origin: Interrupts are typically generated by hardware components (like IO de-
vices) and are serviced by Interrupt Service Routines (ISR). Exceptions, on the
other hand, arise due to software issues such as division by zero or attempting to
access a null pointer.
• Handling Mechanism: Interrupts, being hardware-generated, are often managed
by dedicated hardware mechanisms, whereas exceptions are usually managed by
the OS or the software application itself using constructs like try-catch blocks.
• Predictability: While interrupts can be asynchronous and might occur at any time,
exceptions are typically synchronous and occur as a direct result of a particular
instruction.
86 5 Exception Handling in Real-Time and Embedded Systems

5.5.3 Combining Interrupts and Exceptions

There are scenarios where it’s useful to link interrupts with exceptions:

• Hardware Abstraction: In systems where hardware faults or conditions need to


be conveyed to a higher-level software layer, a hardware interrupt can be used to
detect the condition, and an exception can then be thrown to notify the software
layer. This provides a level of abstraction and allows software developers to handle
hardware conditions using software constructs they are familiar with.
• Unified Error Handling: By translating interrupts to exceptions, systems can
have a unified error-handling mechanism. This means there’s a consistent strategy
for logging, recovery, and debugging.
Also, the are scenarios when is more convenient to use those separately.
• Performance: Handling interrupts can be time-sensitive. Converting every inter-
rupt to an exception might introduce latency which can be detrimental in real-time
systems.
• Complexity: If the system needs to only signal the event and doesn’t require a
comprehensive recovery mechanism, directly using interrupts without exceptions
might be more straightforward.
• Resource Constraints: Exceptions, especially in languages like C++, can have
overhead due to stack unwinding. On resource-constrained systems, it might
be more efficient to handle errors directly after the interrupt without throwing
exceptions.
Also, it is important to consider when to use an Exception as an Interrupt (or vice
versa).

• Urgency: If an exception represents a critical fault (e.g., a critical resource be-


came unavailable), it might be treated similarly to an interrupt, with the system
prioritizing its handling.
• Interrupt Overload: In some scenarios, there might be too many interrupts
occurring simultaneously. In such cases, handling every interrupt might not be
feasible, and they might be batched together and handled as an exception in the
software layer.
In conclusion, while interrupts and exceptions serve different purposes, under-
standing when and how to use them, either separately or together, is crucial for robust
system design. Being able to abstract hardware signals into software exceptions can
simplify error handling, but care must be taken to ensure that the system remains re-
sponsive and efficient. Properly utilized, these mechanisms can significantly enhance
the reliability and maintainability of systems.
5.6 Methodologies for Exception Handling in Real-Time Systems 87

5.6 Methodologies for Exception Handling in Real-Time Systems


Handling exceptions in Real-Time Systems (RTS) presents unique challenges. It’s
not just about catching errors but ensuring that the system continues to function
within its timing constraints. Overcoming the aforementioned challenges requires a
blend of traditional and innovative methodologies [13, 41, 47, 53, 73].
• Static Analysis: Tools that can analyze codebase statically, without executing it,
can identify potential sources of exceptions. Such tools can be beneficial during
the development phase.
• Recovery Blocks: This approach involves having backup routines in place. If the
primary routine fails (throws an exception), a secondary routine takes over, and
so forth.
• Checkpoints and Rollbacks: Systems periodically save their state. In case of an
exception, they can roll back to the last saved state, ensuring minimal data loss or
disruption.
• Proactive Monitoring: Instead of waiting for exceptions to occur, systems can
employ sensors and monitoring tools to predict potential faults or overloads and
act before an exception is raised.
To sum up, exception handling in real-time systems is not just about addressing
errors but ensuring that the system remains functional, predictable, safe, and secure
even in the face of unforeseen events. As we delve deeper into this section, we’ll
explore the intricacies of managing these exceptions effectively. The last 3 techniques
will be described in detail next with programming examples in C++ and Python.
1 # include <iostream >
2 # include <stdexcept >
3 bool primaryRoutine () { // ... some logic
4 throw std :: runtime_error (" Primary routine encountered an
error.");
5 return false ;
6 }
7 bool secondaryRoutine () { // ... alternative logic
8 return true;
9 }
10 int main () {
11 try {
12 if (! primaryRoutine ()) {
13 throw std :: runtime_error (" Primary routine failure .");
14 }
15 } catch (...) {
16 if ( secondaryRoutine ()) {
17 std :: cout << " Secondary routine succeeded ." << std ::
endl;
18 } else {
19 std :: cerr << "Both routines failed ." << std :: endl;
20 }
21 }
22 return 0;
23 }
88 5 Exception Handling in Real-Time and Embedded Systems

5.6.1 Recovery Blocks


Recovery blocks essentially mean having multiple routines for a single task. If the
primary routine fails, the subsequent one tries to accomplish the task, and so on.
In the C++ example above, we have two routines: primaryRoutine() and
secondaryRoutine(). If primaryRoutine() encounters an error and throws an
exception, the catch block will invoke secondaryRoutine(). This ensures that if
the primary function fails, we still have a backup to try and accomplish the task.
Similarly, in the example below in Python, we use a try-except block. The
primary_routine() is called within the try block. If it raises an exception, the
code within the except block runs the secondary_routine().

1 def primary_routine ():


2 # ... some logic
3 raise Exception (" Primary routine encountered an error .")
4 return False
5 def secondary_routine ():
6 # ... alternative logic
7 return True
8 try: if not primary_routine ():
9 raise Exception (" Primary routine failure .")
10 except : if secondary_routine ():
11 print (" Secondary routine succeeded .")
12 else: print ("Both routines failed .")

1 # include <iostream >


2 # include <stdexcept >
3 # include <vector >
4 std :: vector <int > data;
5 std :: vector <int > checkpoint ;
6 void saveCheckpoint () {
7 checkpoint = data; // Save current state
8 }
9 void rollback () {
10 data = checkpoint ; // Revert to saved state
11 }
12 void modifyData () {
13 // ... some modification logic
14 throw std :: runtime_error ("Error during data modification .");}
15 int main () {
16 saveCheckpoint ();
17 try { modifyData ();
18 } catch ( const std :: runtime_error & e) {
19 std :: cerr << " Exception : " << e.what () << std :: endl;
rollback ();
20 std :: cout << "Data rolled back to the last checkpoint ."
<< std :: endl;
21 } return 0; }
5.6 Methodologies for Exception Handling in Real-Time Systems 89

5.6.2 Checkpoints and Rollbacks


With checkpoints, the system periodically saves its state. If an exception arises, the
system can return to the most recent checkpoint, thereby minimizing data loss or
disruptions. In the example above in C++, we maintain a data list and a checkpoint.
Before any significant modification to data, we save the current state as a checkpoint.
If an error occurs during data modification, we revert the data to the last saved
checkpoint.
In the example below in Python, we follow a similar strategy using lists. Before
modifying the data, we save a copy in the checkpoint. If an exception is raised during
modification, we reset the data using the rollback() function.

1 data = []
2 checkpoint = []
3
4 def save_checkpoint ():
5 global checkpoint
6 checkpoint = data.copy () # Save current state
7

8 def rollback ():


9 global data
10 data = checkpoint .copy () # Revert to saved state
11

12 def modify_data ():


13 # ... some modification logic
14 raise Exception ("Error during data modification .")
15

16 try:
17 save_checkpoint ()
18 modify_data ()
19 except Exception as e:
20 print (f" Exception : {e}")
21 rollback ()
22 print ("Data rolled back to the last checkpoint .")

Both the Recovery Blocks and Checkpoints and Rollbacks methodologies allow
RTS to deal with exceptions in a way that minimizes disruptions and ensures timely
responses.

5.6.3 Proactive Monitoring of Exception Handling in Real-Time


Systems

In real-time systems, proactive monitoring refers to actively and continuously check-


ing the system’s state and processes to catch potential errors before they evolve into
full-blown exceptions. It’s like having a security guard who constantly patrols a
facility to catch any suspicious activities before they result in actual threats.
90 5 Exception Handling in Real-Time and Embedded Systems

Here, we’ll dive into how you can implement proactive monitoring for exception
handling in C++ and Python.
In the following C++ example, let’s consider a real-time system that processes
data packets. Proactive monitoring would involve checking the integrity of these
packets before they are processed.

1 # include <iostream >


2 # include <stdexcept >
3

4 class DataPacket {
5 public :
6 bool isValid = true; // A simple flag to denote data
integrity
7 };
8
9 class RealTimeSystem {
10 private :
11 DataPacket data;
12

13 public :
14 void receiveData (const DataPacket & packet ) {
15 data = packet ;
16 // Proactive Monitoring
17 if (! data. isValid ) {
18 throw std :: runtime_error (" Invalid data packet
received !");
19 }
20 process ();
21 }
22

23 void process () {
24 std :: cout << " Processing valid data ..." << std :: endl;
25 // ... Further data processing logic
26 }
27 };
28

29 int main () {
30 RealTimeSystem rts;
31 DataPacket packet ;
32 packet . isValid = false; // Simulating invalid data
33

34 try {
35 rts. receiveData ( packet );
36 } catch ( const std :: runtime_error & e) {
37 std :: cerr << " Exception : " << e.what () << std :: endl;
38 // Here , we can decide what steps to take upon catching
an exception
39 }
40
41 return 0;
42 }
5.7 Design Patterns for Exception Handling in Real-Time Systems 91

In the following Python example, let’s use a similar system that processes data
packets.

1 class DataPacket :
2 def __init__ (self):
3 self. is_valid = True # A simple flag to denote data
integrity
4
5 class RealTimeSystem :
6 def __init__ (self):
7 self.data = DataPacket ()
8

9 def receive_data (self , packet ):


10 self.data = packet
11 # Proactive Monitoring
12 if not self.data. is_valid :
13 raise Exception (" Invalid data packet received !")
14 self. process ()
15
16 def process (self):
17 print (" Processing valid data ...")
18 # ... Further data processing logic
19

20 if __name__ == " __main__ ":


21 rts = RealTimeSystem ()
22 packet = DataPacket ()
23 packet . is_valid = False # Simulating invalid data
24

25 try:
26 rts. receive_data ( packet )
27 except Exception as e:
28 print (f" Exception : {e}")
29 # Here , we can decide what steps to take upon catching an
exception

In both the above examples, we proactively checked the integrity of data packets
as they were received. If a packet was deemed invalid, an exception was thrown
immediately, before any processing logic could operate on the potentially corrupted
data. Such proactive monitoring can be further enriched with logging, alarms, or
integrations with incident management systems to ensure that potential issues are
not just detected but also acted upon swiftly and effectively.

5.7 Design Patterns for Exception Handling in Real-Time


Systems

Exception handling in real-time systems is crucial for ensuring system reliability,


safety, and availability. However, designing and implementing an effective exception-
92 5 Exception Handling in Real-Time and Embedded Systems

handling mechanism in such systems is a challenging task. One approach to tackle


this challenge is to use design patterns that have been proven to be effective in
managing exceptions in real-time systems.
Design patterns are a set of proven and reusable solutions to commonly occur-
ring design problems in software engineering. They provide a structured approach to
solving design problems by offering a generic solution that can be customized to spe-
cific contexts. Design patterns have been widely adopted in various areas of software
engineering, including real-time systems [28]. Several design patterns have been
proposed and widely used in the context of exception handling in real-time systems.
These patterns provide a set of reusable solutions to manage exceptions and faults
and ensure system safety and reliability. Furthermore, using these patterns can signif-
icantly simplify the design and implementation of exception-handling mechanisms
in real-time systems while ensuring their correctness and efficiency.
This section aims to introduce and discuss some of the commonly used design
patterns for exception handling in real-time systems. We will describe each pattern
in detail, provide relevant examples, and discuss their advantages and limitations.
Specifically, we will discuss the Error Handler Pattern, the State Machine Pattern,
the Supervisory Control Pattern, and the Hierarchical Exception Handling pattern.
By using these design patterns, real-time system developers can significantly im-
prove their systems’ robustness, safety, and reliability while reducing the complexity
of their exception-handling mechanisms. In the following subsections, we will de-
scribe each pattern in detail and provide programming examples to illustrate their
application in real-world scenarios.

5.7.1 The Error Handler Pattern in Real-Time Systems

The Error Handler Pattern is a commonly used design pattern in software engineering
for handling exceptions in real-time systems. The primary objective of this pattern is
to handle exceptions effectively and to ensure that the system can recover from errors
as quickly as possible. The Error Handler Pattern can be implemented in various
ways, including the use of callbacks, event handlers, and exception [65].
One example of implementing the Error Handler Pattern is by using callbacks. In
this approach, a callback function is registered with the error handler. Whenever an
exception occurs, the error handler calls the registered callback function and passes
the exception as a parameter. The callback function can then take appropriate action,
such as logging the error or displaying an error message to the user. Next listing
shows an example of implementing the Error Handler Pattern using callbacks in
Python.
5.7 Design Patterns for Exception Handling in Real-Time Systems 93

1 class ErrorHandler :
2 def init(self):
3 self. callbacks = []
4 def register_callback (self , callback ):
5 self. callbacks . append ( callback )
6

7 def handle_error (self , exception ):


8 for callback in self. callbacks :
9 callback ( exception )
10 def log_error ( exception ):
11 print(" Error occurred :", exception )
12
13 def display_error ( exception ):
14 print("An error occurred . Please contact the administrator .")
15
16 error_handler = ErrorHandler ()
17

18 error_handler . register_callback ( log_error )


19

20 error_handler . register_callback ( display_error )


21
22 try:
23 # some code that may raise an exception
24 except Exception as e:
25 error_handler . handle_error (e)

In this example, an instance of the ErrorHandler class is created, and two


callback functions, log_error and display_error, are registered with the error
handler using the register_callback method. When an exception occurs in the
try block, the error handler’s handle_error method is called, which, in turn, calls
each registered callback function and passes the exception as a parameter.

5.7.1.1 Advantages and Disadvantages

The Error Handler Pattern provides several advantages for handling exceptions in
real-time systems [35]. First, it enables more granular control over exception han-
dling, allowing developers to define different handlers for different types of ex-
ceptions. This approach can help improve the system’s reliability and reduce the
likelihood of catastrophic failures. Additionally, the Error Handler Pattern provides
a centralized location for handling exceptions, making it easier to maintain and debug
the system.
However, the Error Handler Pattern can also have some disadvantages. One po-
tential issue is that the use of callbacks or event handlers can introduce additional
overhead, which may impact the system’s performance. Additionally, the Error Han-
dler Pattern can increase the complexity of the system, making it more difficult to
understand and maintain.
94 5 Exception Handling in Real-Time and Embedded Systems

1 class State ; // Forward declaration


2 class TrafficLight {
3 State* current_state ;
4 public :
5 TrafficLight ();
6 void set_state (State * state );
7 void request () ;};
8 class State {
9 public :
10 virtual void handle ( TrafficLight * light ) = 0;};
11 class Red : public State {
12 public :
13 void handle ( TrafficLight * light);};
14 class Green : public State {
15 public :
16 void handle ( TrafficLight * light);
17 };
18 // Implementation
19 TrafficLight :: TrafficLight () {
20 current_state = new Red ();
21 }
22 void TrafficLight :: set_state (State* state ) {
23 current_state = state ;
24 }
25 void TrafficLight :: request () {
26 current_state -> handle (this);
27 }
28 void Red :: handle ( TrafficLight * light) {
29 std :: cout << " Turning green ...\n";
30 light -> set_state (new Green ());
31 }
32 void Green :: handle ( TrafficLight * light ) {
33 std :: cout << " Turning red ...\n";
34 light -> set_state (new Red ());
35 }

5.7.2 State Machine Pattern in Real-Time Systems

The primary challenges in designing and implementing real-time systems stem from
their need to simultaneously manage complex interactions with their environment,
handle concurrency, satisfy precise timing constraints, and maintain high reliability
[49].
5.7 Design Patterns for Exception Handling in Real-Time Systems 95

The State Machine pattern is a behavioral design pattern that allows an object to
change its behavior when its internal state changes. It appears as if the object has
changed its class [27]. This pattern can be particularly beneficial in the context of
RTS. It provides a clear and predictable control flow, which is crucial for meeting
the timing constraints of RTS. It also facilitates easy state transition management
and promotes modularity and maintainability [70]. However, the State Machine
pattern has its drawbacks. For instance, the risk of state explosion, which refers
to the exponential growth of states and transitions with increasing complexity, can
make the state machine difficult to manage and maintain. Additionally, as the state
machine becomes more complex, handling shared resources across different states
can become challenging.
In a State Machine, an application’s behavior is determined by its state, and
it can switch from one state to another if certain conditions are met. Each state
can be considered an individual entity that knows how to handle the events it’s
interested in and how to transition to another state. Consider the example above
where we implement a basic traffic light system as a state machine in C++. This
example, while simple, illustrates the basic principles of the State Machine pattern.
The system changes its state in response to an event or condition (a request in this
case), and different behavior is exhibited depending on the system’s current state.

5.7.2.1 Advantages and Disadvantages

The State Machine pattern has several advantages in the context of RTS:
1. Predictability: The behavior of the system is predictable as it depends on its
current state and the defined state transitions. This predictability is crucial in RTS
where unexpected behavior can have serious consequences [70].
2. Modularity and Maintainability: The system behavior corresponding to a par-
ticular state is encapsulated within that state. This separation of concerns leads to
highly modular code, which is easier to maintain and debug [37].
3. Extensibility: New states can be added to the system without disturbing the exist-
ing states, making the system highly extensible. It helps in adapting to changing
requirements or adding new features [9].
While the State Machine pattern is highly beneficial, it also comes with its own
set of challenges:
1. State Explosion: As the number of states and transitions in the system increases,
managing and maintaining them can become challenging. This problem, known as
state explosion, can complicate the design and implementation of the system [16].
2. Complexity: Although the pattern can make the system more modular and main-
tainable, it can also increase the complexity of the system, particularly when the
number of states and transitions is large. This can lead to errors and increase the
time required for testing and verification [16].
96 5 Exception Handling in Real-Time and Embedded Systems

3. Resource Management: Managing shared resources across different states can


be difficult. Proper synchronization mechanisms need to be put in place to pre-
vent potential concurrency-related issues. This often involves intricate locking
mechanisms and can lead to issues like deadlocks if not handled correctly.
Despite these potential disadvantages, the State Machine pattern remains a power-
ful tool for modeling and implementing RTS. It provides a clear and structured way to
handle the system behavior, making the system more predictable and maintainable.

5.7.3 Supervisory Control Pattern

In the field of Real-Time Systems (RTS), the Supervisory Control pattern emerges
as an essential tool to orchestrate and manage complex operations. This pattern is
applied in a variety of systems, including manufacturing, automation, power grids,
and software systems, where a supervisory controller oversees the operation of
lower-level controllers or subsystems [85].
The supervisory controller ensures that the system operates within the specified
constraints and takes corrective actions whenever necessary. It also makes high-level
decisions based on the overall state of the system, while lower-level controllers or
subsystems focus on their specific tasks. This hierarchical structure helps manage
complexity and improve system modularity. However, implementing the Supervisory
Control pattern in RTS is not a trivial task. The supervisory controller must be
designed to handle multiple concurrent tasks, manage shared resources, and make
decisions under stringent timing constraints, which adds to the complexity of the
system [49]. Another challenge is to ensure that the supervisory controller can
respond in real-time to changes in the system and maintain the overall stability and
performance of the system.

5.7.3.1 Supervisory Control Pattern in Practice

The supervisory control pattern is typically implemented in a multi-threaded or


multi-process environment, where each subsystem is a separate thread or process, and
the supervisory controller is another thread or process that oversees their operation.
Below is a more detailed example of a supervisory control system implemented
in C++. Here, the supervisory controller manages two subsystems, each modeled as
a separate thread: In this example, each subsystem is executed in a separate thread.
The supervisory controller waits for all subsystems to complete their execution. This
example illustrates the basic principles of the supervisory control pattern. Still, in
a real-world application, the supervisory controller would likely interact with the
subsystems during their execution, not just wait for them to complete. It would
gather sensor data, make decisions based on the system’s current state, and adjust
the behavior of the subsystems accordingly.
5.7 Design Patterns for Exception Handling in Real-Time Systems 97

1 # include <thread >


2 # include <vector >
3

4 class Subsystem {
5 public :
6 virtual void execute () = 0;
7 };
8

9 class Subsystem1 : public Subsystem {


10 public :
11 void execute () override {
12 // Implementation for Subsystem1
13 }
14 };
15
16 class Subsystem2 : public Subsystem {
17 public :
18 void execute () override {
19 // Implementation for Subsystem2
20 }
21 };
22

23 class SupervisoryController {
24 std :: vector <std :: thread > subsystems ;
25 public :
26 SupervisoryController () {
27 subsystems . push_back (std :: thread ([]( Subsystem * s){ s->
execute (); }, new Subsystem1 ()));
28 subsystems . push_back (std :: thread ([]( Subsystem * s){ s->
execute (); }, new Subsystem2 ()));
29 }
30 void control () {
31 for (std :: thread & subsystem : subsystems ) {
32 subsystem .join ();
33 }
34 }
35 };

The supervisory controller could also include safety mechanisms to handle sub-
system failures or to shut down the system in a controlled way if necessary. For
instance, it might monitor the execution time of the subsystems and take corrective
actions if a subsystem takes too long to complete its task, which could indicate a
problem.

5.7.3.2 Advantages and Disadvantages

The Supervisory Control pattern provides several advantages in the context of RTS.
1. Centralized Control: The pattern allows for centralized decision-making, which
can simplify the control logic and make the system more predictable.
98 5 Exception Handling in Real-Time and Embedded Systems

2. Modularity: The pattern promotes the separation of concerns, as each subsystem


can focus on its specific task while the supervisory controller manages the overall
system behavior. This modularity can enhance the maintainability and scalability
of the system.
3. Fault Tolerance: The supervisory controller can monitor the status of each sub-
system and handle failures in a controlled way, which enhances the system’s fault
tolerance [85].
4. Flexibility: The supervisory controller can dynamically adjust the subsystems’
behavior based on the system’s current state. This adaptability is particularly
beneficial in RTS, where the system must respond to external events in a timely
manner [85].
Despite its advantages, the Supervisory Control pattern also has some potential
drawbacks.
1. Complexity: The supervisory controller can become complex, especially in large
systems with many subsystems and intricate dependencies between them. Design-
ing a supervisory controller that can handle such complexity can be challenging.
2. Performance: The system’s performance depends on the supervisory controller.
If the supervisory controller fails or slows down, the entire system could be
affected. In RTS, where stringent timing constraints are involved, this could lead
to severe consequences [14].
3. Concurrency and Resource Management: The supervisory controller needs to
manage concurrent tasks and shared resources, which can be challenging. This
often involves complex synchronization and communication mechanisms and can
lead to issues like race conditions and deadlocks if not handled correctly.
4. Single Point of Failure: In some designs, the supervisory controller can become
a single point of failure. If it fails, the entire system can be paralyzed. Therefore,
it is crucial to design the supervisory controller with high reliability and fault
tolerance.
Despite these potential disadvantages, the supervisory control pattern remains
a powerful tool for managing complex RTS. Providing a centralized control point
simplifies the control flow and makes the system more predictable and easier to
manage.

5.8 Exception Handling in Ada for Real-Time Systems

Ada’s approach to exception handling in real-time contexts Ada, having been de-
signed with high-reliability, safety-critical, and real-time systems in mind, offers a
robust framework for exception handling that addresses the unique challenges of
real-time environments. This deep-rooted commitment to safety and determinism in
the face of exceptions is manifested in several facets of the language, which will be
described in the following sections [1, 13, 21].
5.8 Exception Handling in Ada for Real-Time Systems 99

1. Defined Exception Model.


2. Deterministic Exception Propagation.
3. Explicit Exception Declarations.
4. Hierarchical and Scoped Handling.
5. Tasking and Exception Handling.
6. Real-time Considerations in Handler Actions.
In essence, Ada’s exception-handling mechanisms, tailored for real-time contexts,
prioritize both the system’s functional correctness and its timing guarantees. This
balance is essential to building resilient real-time systems capable of gracefully
managing unexpected events while adhering to strict timing constraints.

5.8.1 Defined Exception Model in Ada

Ada’s exception handling is constructed on a rigorous and well-defined model. This


model plays a pivotal role in ensuring that real-time systems behave predictably
even when faced with unexpected conditions. Understanding the anatomy of Ada’s
exception model is vital to grasping its strengths and how it fits seamlessly into
real-time contexts.
Ada’s exception model stands on three primary pillars:

• Exception Declaration: This is the process by which an exception is defined.


• Exception Raising: This pertains to the act of signaling that an exception has
occurred.
• Exception Handling: This relates to the procedures or mechanisms put in place
to address a raised exception.

5.8.1.1 Exception Declaration

In Ada, before an exception can be raised or handled, it must first be declared. The
declaration gives a name to the exception and allows it to be identified in subsequent
parts of the program.
An example of an exception declaration follows.

1 Buffer_Overflow : exception ;

Here, Buffer_Overflow is declared as an exception that can be raised when a


buffer in the system is filled beyond its capacity.
100 5 Exception Handling in Real-Time and Embedded Systems

5.8.1.2 Exception Raising

Once an exception is declared, it can be raised using the raise statement followed by
the name of the exception. Optionally, a string can be added to provide a detailed
explanation about the exception.
An example of an exception raising follows.

1 if Buffer_Size > Buffer_Capacity then


2 raise Buffer_Overflow with " Buffer exceeded its allowable
capacity .";
3 end if;

In this example, if the size of a buffer exceeds its predefined capacity, the
Buffer_Overflow exception is raised with a descriptive message.

5.8.1.3 Exception Handling

To respond to an exception, Ada provides exception handlers. These handlers are


defined using the when keyword within an exception block. Every sequence of
statements (like a block or subprogram) can have an associated exception handler to
manage any exceptions that might be raised within those statements.
An example of exception handling follows.

1 begin
2 -- some code that might raise Buffer_Overflow
3 exception
4 when Buffer_Overflow =>
5 -- corrective actions to handle the buffer overflow
6 Reset_Buffer ;
7 Put_Line (" Buffer was reset due to an overflow .");
8 end;

In this example, if the Buffer_Overflow exception is raised in the preceding


code, the program will execute the corrective actions specified within the when
block, which in this case resets the buffer and prints a message.

5.8.2 Deterministic Exception Propagation in Ada

Deterministic behavior in real-time systems is paramount. The predictability of how


exceptions are propagated through a system can have a significant impact on its
real-time performance. Ada offers a highly structured and deterministic model for
exception propagation that suits the demands of real-time environments.
5.8 Exception Handling in Ada for Real-Time Systems 101

Exception propagation refers to the process by which an exception moves upward


through the call stack, looking for a suitable handler. Deterministic propagation
means that, for any given exception raised in a program, the path it will follow and
the handler it will reach (if any) are predictable every time.
Ada’s design ensures deterministic exception propagation in several ways:
• Static Scoping: Exceptions are propagated through a static scope, not dynami-
cally. This means that an exception is always propagated to the lexically enclosing
block or subprogram, regardless of how the control got there.
• Explicit Handlers: The handlers must be explicitly defined for exceptions. If a
block doesn’t have a handler for a particular exception, the exception continues to
propagate to the next enclosing block.
• Finalization Guarantees: Even when exceptions occur, the finalization of objects
is guaranteed. This ensures that resources are always released, preventing potential
resource leaks even during exceptions.

5.8.2.1 Example of Deterministic Propagation

Let’s consider a nested procedure call scenario:

1 procedure Outer_Procedure is
2 Buffer_Underflow : exception ;
3

4 procedure Inner_Procedure is
5 begin
6 -- some operations
7 raise Buffer_Underflow ;
8 exception
9 when others =>
10 -- generic handler
11 Put_Line (" Handled in Inner_Procedure .");
12 end Inner_Procedure ;
13
14 begin
15 Inner_Procedure ;
16 exception
17 when Buffer_Underflow =>
18 -- specific handler
19 Put_Line (" Buffer underflow detected in Outer_Procedure .");
20 end Outer_Procedure ;

In the code above, if Buffer_Underflow is raised in Inner_Procedure, it will


bypass the generic others handler within Inner_Procedure and will be caught by
the specific Buffer_Underflow handler in Outer_Procedure.
This shows that even if there are handlers in a nested block, the exception will
be propagated deterministically to the lexically appropriate handler, ensuring pre-
dictable behavior.
102 5 Exception Handling in Real-Time and Embedded Systems

5.8.3 Explicit Exception Declarations in Ada

Ada’s approach to exception handling emphasizes safety and predictability, which are
paramount in real-time systems. One of the key features that support this approach is
Ada’s requirement for explicit exception declarations. This ensures that exceptions
are used intentionally and that their propagation paths are always clear. In many
languages, an exception can be raised (or thrown) without being explicitly declared.
While this offers flexibility, it can also lead to unpredictability, especially in complex
systems. Ada, however, opts for safety over flexibility by necessitating exception
declarations.
Explicit declarations offer a number of advantages:
• Readability and Maintainability: By merely looking at a package or a subpro-
gram specification, a developer can immediately understand the exceptions that
can be raised. This clear contract simplifies maintenance and debugging.
• Safety: Ada’s compiler checks to ensure that only declared exceptions are raised.
This prevents inadvertent errors and enforces disciplined coding practices.
• Documentation: Exception declarations serve as an implicit form of documen-
tation, making the code’s intent clear and aiding in system analysis.

1 Exception_Name : exception ;
2 procedure File_Processor is
3 File_Not_Found : exception ;
4 procedure Read_File is
5 begin
6 -- hypothetical situation where the file doesn ’t exist
7 raise File_Not_Found ;
8 end Read_File ;
9 begin
10 Read_File ;
11 exception
12 when File_Not_Found =>
13 Put_Line (" Error: File not found.");
14 end File_Processor ;

5.8.3.1 Ada Syntax for Exception Declaration and Raising

Exceptions in Ada are first-class types, and they must be declared before they can be
raised or handled. Let’s look at the syntax and an illustrative example.
An example of the syntax for declaration in ADA is above. In this example, the ex-
ception File_Not_Found is declared and then raised within the nested Read_File
procedure. The outer File_Processor procedure handles this exception.
5.8 Exception Handling in Ada for Real-Time Systems 103

5.8.3.2 Associating Information with Exceptions

Ada provides the capability to associate additional information with an exception


when it’s raised. This information, often referred to as the exception message or the
exception occurrence, can be useful for diagnostics.
The syntax for raising an exception with a message is the following.

1 raise Exception_Name with " Descriptive message ";


2

3 procedure Data_Processor is
4 Data_Error : exception ;
5

6 procedure Process is
7 begin
8 -- hypothetical situation where data processing fails
9 raise Data_Error with "Data out of range ";
10 end Process ;
11

12 begin
13 Process ;
14 exception
15 when Data_Error =>
16 Put_Line (" Error encountered : " & Exception_Message );
17 end Data_Processor ;

In this example, if Data_Error is raised, the associated message "Data out of


range" can be retrieved using the predefined function Exception_Message.

5.8.4 Hierarchical and Scoped Handling in Ada

Ada’s exception-handling mechanisms are versatile and provide developers with


powerful tools to manage and handle exceptions in structured ways. The capability
to handle exceptions hierarchically and within specific scopes is especially significant
for real-time systems, where predictable response times and control over exception
propagation paths are crucial.
Ada’s support for hierarchical exception handling means that exceptions can be
caught and handled at different levels of the program’s call stack. If an exception
isn’t caught at its point of origin, it propagates up the call hierarchy until it’s either
caught or escapes the main program, leading to termination. This hierarchical model
ensures that if a lower-level procedure or function doesn’t have a specific strategy to
recover from an exception, the higher levels can implement a more general recovery
or reporting strategy.
An example of the hierarchical model is the following.
104 5 Exception Handling in Real-Time and Embedded Systems

1 procedure Process_Data is
2 Invalid_Data : exception ;
3 procedure Validate is
4 begin
5 -- Let ’s say validation fails
6 raise Invalid_Data ;
7 end Validate ;
8 procedure Compute is
9 begin
10 Validate ;
11 exception
12 when Invalid_Data =>
13 Put_Line (" Compute : Data validation failed .");
14 end Compute ;
15 begin
16 Compute ;
17 exception
18 when Invalid_Data =>
19 Put_Line (" Process_Data : Data invalid . Cannot proceed .");
20 end Process_Data ;

In this example, the Validate procedure raises an Invalid_Data exception. The


immediate caller, Compute, catches and handles it. If Compute didn’t handle the
exception, it would propagate to Process_Data.

1 procedure Transaction_Processor is
2 Transaction_Error : exception ;
3

4 procedure Execute_Transaction is
5 begin
6 -- Simulate a transaction that fails
7 raise Transaction_Error ;
8 end Execute_Transaction ;
9

10 begin
11 begin
12 Execute_Transaction ;
13 exception
14 when Transaction_Error =>
15 Put_Line ("Inner Block: Transaction failed .");
16 -- Maybe try to redo the transaction here
17 end;
18
19 -- Other unrelated code can go here without being affected by
the above exception handling
20
21 exception
22 when Transaction_Error =>
23 Put_Line (" Outer Block: Critical failure in transaction
processing .");
24 end Transaction_Processor ;
5.8 Exception Handling in Ada for Real-Time Systems 105

5.8.4.1 Scoped Exception Handling

Scoped handling refers to handling exceptions within a specific portion of the code.
This allows for fine-grained control over which exceptions are handled and where
ensuring specific responses in well-defined parts of the code. Ada achieves this
through the use of blocks, where each block can have its exception-handling section.
An example of the scoped exception handling is above. In the example above, the
inner block tries to handle Transaction_Error, and if that fails, the outer block
can catch it.

5.8.5 Tasking and Exception Handling in Ada

The concurrency mechanism in Ada, known as tasking, plays a crucial role in real-
time systems development. Since real-time systems often run multiple operations
concurrently to meet stringent timing requirements, understanding how tasking in-
teracts with exception handling is of paramount importance. Ada has integrated
mechanisms that handle exceptions raised in tasks, ensuring that exceptions in par-
allel activities do not jeopardize the entire system.

5.8.5.1 Exception Propagation in Tasks

In Ada, when a task raises an exception and doesn’t handle it within its own scope,
the exception propagates to the master of the task (which can be the main program or
another task). The exception will then be handled in the master’s exception handlers,
if present.
An example of exception propagation is the following.

1 task body Task_Example is


2 begin
3 -- Some processing
4 raise Program_Error ;
5 exception
6 when others =>
7 Put_Line (" Handled within Task_Example ");
8 end Task_Example ;
9
10 begin
11 -- Execution of the main program
12 exception
13 when Program_Error =>
14 Put_Line (" Exception caught in the main program ");
15 end;
106 5 Exception Handling in Real-Time and Embedded Systems

If the exception raised in Task_Example isn’t handled within the task body, it
would be propagated and caught by the main program’s exception handler.

5.8.5.2 Task Termination and Exceptions

When an exception is raised within a task and isn’t caught, the task is terminated.
However, the other tasks can continue their execution unless they are dependent on
the terminated task or are affected by the propagated exception. This ensures that a
fault in one task doesn’t necessarily lead to a complete system breakdown.
An example of task termination is the following.

2 task T1;
3 task T2;
4 task body T1 is
5
6 begin
7 -- Some processing
8 raise Program_Error ;
9 end T1;
10
11 task body T2 is
12 begin
13 -- Some other processing
14 end T2;
15

16 begin
17 -- Main program execution
18

19 exception
20 when Program_Error =>
21 Put_Line (" Exception from T1 caught in main program ");
22 end;

In this scenario, if T1 raises an exception and doesn’t handle it, T1 will be


terminated. However, T2 will continue its execution.

5.8.5.3 Asynchronous Transfer of Control (ATC) and Exceptions

Ada supports Asynchronous Transfer of Control (ATC), where one task can asyn-
chronously interrupt another. This is especially relevant for real-time systems where
one might need to preempt a long-running operation if a more critical task arrives.
Exceptions play a key role in the ATC mechanism.
5.8 Exception Handling in Ada for Real-Time Systems 107

An example of ATC and Exceptions is the following. In this case, T4 cancels the
operation in T3 using the ATC mechanism, which raises the Cancellation exception
in T3.

1 task T3;
2 task T4;
3

4 task body T3 is
5 Cancellation : exception ;
6 begin
7 delay 5.0; -- Simulate some long - running operation
8 raise Cancellation ;
9 end T3;
10
11 task body T4 is
12 begin
13 delay 1.0; -- Wait for a short while
14 T3. Asynchronous_Transfer_of_Control ;
15 end T4;
16
17 begin
18 -- Main program
19 exception
20 when Cancellation =>
21 Put_Line ("T3 was cancelled by T4");
22 end;

5.8.6 Real-time Considerations in Handler Actions

Real-time systems are characterized by strict timing requirements. The actions taken
within exception handlers must thus be timely and predictable. This is particularly
significant in Ada given its widespread use in safety-critical domains like aerospace
and defense. Below we delve into the implications of real-time considerations in the
context of handler actions in Ada.

5.8.6.1 Predictable Execution Time

In a real-time context, not only must exceptions be caught, but the handling of these
exceptions must also be completed within a predictable and guaranteed timeframe.
For instance, consider a scenario where a sensor’s data needs to be processed within
a given deadline. If the sensor returns a malfunction error, the system should handle
this exception and possibly switch to a backup sensor within a defined time.
108 5 Exception Handling in Real-Time and Embedded Systems

1
2 procedure Process_Sensor_Data is
3 begin
4 -- Assume Get_Sensor_Data can raise a Sensor_Error
5 Data := Get_Sensor_Data ;
6 exception
7 when Sensor_Error =>
8 -- Handler action
9 Data := Get_Backup_Sensor_Data ;
10 if Clock < Deadline then
11 -- Process backup data within time
12 Process (Data);
13 else
14 -- Log or take alternative action
15 Log_Error (" Sensor processing missed deadline ");
16 end if;
17 end Process_Sensor_Data ;

5.8.6.2 Minimal Overhead

Exception handling must introduce minimal overhead to ensure that the system
remains responsive. This means the code within the exception handler should be as
concise as possible, especially in high-frequency exception scenarios.
An example of the minimal overhead is the following.

1 procedure Communicate is
2 begin
3 -- Assume Send can raise a Communication_Error
4 Send(Data);
5 exception
6 when Communication_Error =>
7 -- Minimal handler action
8 Increment_Error_Counter ;
9 Retry(Send , Data); -- Retry method with bounded attempts
10 end Communicate ;

Here, instead of complex logging or recovery logic directly inside the handler, a
concise action is taken to ensure minimal overhead.

5.8.6.3 Bounded Recovery Actions

Real-time systems often have bounded recovery actions to ensure they don’t get stuck
in indefinite recovery loops. This is vital for maintaining real-time guarantees.
An example of the bounded recovery actions is the following.
5.8 Exception Handling in Ada for Real-Time Systems 109

1
2 procedure Update_System is
3 Attempts : Integer := 0;
4 begin
5 -- Assume Update can raise an Update_Failed error
6 Update ;
7 exception
8 when Update_Failed and then Attempts < Max_Attempts =>
9 -- Increment and retry
10 Attempts := Attempts + 1;
11 Retry ( Update );
12 when others =>
13 Log_Error ("Max update attempts reached ");
14 end Update_System ;

5.8.6.4 Preemptive and Non-blocking Actions

Exception handlers in real-time contexts should preferably be non-blocking to ensure


that they don’t unduly delay other critical tasks. Handlers should be designed to allow
preemption if a higher-priority task becomes ready.
An example of preemptive and non-blocking actions are the following.

1
2 task type Critical_Task is
3 ...
4 end Critical_Task ;
5

6 procedure Handle_Exception is
7 begin
8 -- Assume Handle can raise a Critical_Error
9 Handle (Data);
10 exception
11 when Critical_Error =>
12 -- Non - blocking handler action
13 Signal_Alarm ;
14 -- Allow preemption for critical tasks
15 Yield_To ( Critical_Task );
16 end Handle_Exception ;
References

1. ISO/IEC JTC 1/SC 22/WG 9. Ada 2012 Reference Manual. Language and Standard Libraries:
International Standard ISO/IEC 8652/2012 (E), 2012.
2. J. Albahari and B. Albahari. C# 8.0 in a Nutshell: The Definitive Reference. O’Reilly Media,
Inc., 2020.
3. John Allspaw. The Art of Capacity Planning: Scaling Web Resources in the Cloud. O’Reilly
Media, 2010.
4. N.C. Audsley, A. Burns, M.F. Richardson, and A.J. Wellings. Hard real-time scheduling: The
deadline-monotonic approach. Proceedings of the IEEE Workshop on Real-Time Operating
Systems and Software, pages 133–137, 1991.
5. H. Aydin, Q. Yang, and R. Melhem. Energy-aware partitioning for multiprocessor real-
time systems. Proceedings of the 17th International Parallel and Distributed Processing
Symposium, pages 1135–1142, 2001.
6. Microsoft Azure. Azure architecture center: Transient fault handling, 2021.
7. Bastiaan et-al. Bakker. Log for c++ project, 2021.
8. T. Ball and S.K. Rajamani. Automatically validating temporal safety properties of interfaces.
SPIN, pages 103–122, 2001.
9. Grady Booch, James Rumbaugh, and Ivar Jacobson. Unified modeling language user guide,
The. Addison-Wesley Reading, 1999.
10. Rory Bradford, Mark Erlank, and Andries Van der Berg. Using decorrelated jitter in backoff
algorithms to improve resilience to distributed denial-of-service attacks. In Proc. of the 2018
Annual Conference of the South African Institute of Computer Scientists and Information
Technologists (SAICSIT), pages 27:1–27:7, 2018.
11. Eckel Bruce. Thinking in Java. Prentice Hall, 2006.
12. A. Burns and A. Wellings. Predicting the behaviour of ada programs. Software Engineering
Journal, 8(4):173–181, 1993.
13. Alan Burns and Andy Wellings. Real-Time Systems and Programming Languages: Ada,
Real-Time Java and C/Real-Time POSIX. Addison Wesley, 2012.
14. Giorgio Buttazzo. Hard Real-Time Computing Systems: Predictable Scheduling Algorithms
And Applications. Springer, 2011.
15. A. Cervin, D. Henriksson, B. Lincoln, J. Eker, and K. Årzén. How does control timing affect
performance? analysis and simulation of timing using jitterbug and truetime. IEEE Control
Systems Magazine, 23(3):16–30, 2002.
16. Edmund M Clarke, Orna Grumberg, and Doron A Peled. Model Checking. MIT Press, 1999.
17. N. Collins. Effective logging: Best practices for logging in applications. DZone, 2019.
18. The Boost C++ Community. Chapter 1. boost.log v2, 2021.
19. Cplusplus. Exceptions. http://www.cplusplus.com/doc/tutorial/exceptions/.
20. Jeffrey Dean. Tail at scale. Communications of the ACM, 56(2):74–80, 2013.
21. Truc Dinh et al. Exception handling patterns in real-time systems. In International Conference
on Real-Time Systems, 2015.
22. Allen Downey. Think Python: How to Think Like a Computer Scientist. Green Tea Press, 2008.
23. J. Engblom. Reducing the complexity of complex real-time systems. PhD thesis, 2000.
24. K. Flautner and T. Mudge. Vertigo: Automatic performance-setting for linux. Proceedings of
the 5th Symposium on Operating Systems Design and Implementation, pages 105–116, 2001.
25. Python Software Foundation. Python logging - basic logging tutorial. https://docs.
python.org/3/howto/logging.html.
26. Andrew Gabriel and Guilbert Pollo. Fast c++ logging library, 2021.
27. Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Design patterns: elements
of reusable object-oriented software. Addison-wesley, 1994.
28. Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Design patterns: elements
of reusable object-oriented software. Addison-Wesley Professional, 1995.
29. D. Gay and P. Levis. Bridging the gap: Programming sensor networks with application-specific
virtual machines. ACM Transactions on Sensor Networks, 5(4):1–31, 2009.

© The Author(s), under exclusive license to Springer Nature Switzerland AG 2024 110
P. Mejia Alvarez et al., Exception Handling, SpringerBriefs in Computer Science,
https://doi.org/10.1007/978-3-031-50681-9
References 111

30. B. Goetz, T. Peierls, J. Bloch, J. Bowbeer, D. Holmes, and D. Lea. Java Concurrency in
Practice. Addison-Wesley, 2006.
31. John B Goodenough. Exception handling: issues and a proposed notation. Communications
of the ACM, 18, Issue 12:683–696, 1975.
32. James Gosling, Bill Joy, Guy Steele, and Gilad Bracha. The Java Language Specification.
Addison-Wesley Professional, 2005.
33. James Gosling, Bill Joy, Guy Steele, Gilad Bracha, and Alex Buckley. The Java Language
Specification. Addison-Wesley, 3 edition, 2000.
34. James Gosling, Bill Joy, Guy L. Steele Jr, Gilad Bracha, and Alex Buckley. The Java Language
Specification, Java SE 8 Edition. Addison-Wesley, 2014.
35. Bahram Hamidzadeh, Janos Sztipanovits, and Changjun Jiang. Runtime support for the error
handler pattern in distributed real-time systems. In Proceedings of the 18th IEEE International
Symposium on Object-Oriented Real-Time Distributed Computing (ISORC’03), pages 332–
341. IEEE, 2003.
36. S. Hanenberg and R. Unland. Exception handling in object-oriented programming languages:
A survey. In Proceedings. 14th International Workshop on Database and Expert Systems
Applications, pages 294–298. IEEE, 2003.
37. David Harel. Statecharts: A visual formalism for complex systems. Science of computer
programming, 8(3):231–274, 1987.
38. Joseph L. Hellerstein, Yixin Diao, Sujay Parekh, and Rodney Griffith. Feedback Control of
Computing Systems. John Wiley and Sons, 2002.
39. Apple Inc. Error handling. https://docs.swift.org/swift-
book/LanguageGuide/ErrorHandling.html.
40. Lee Jaewook. C++ multithreading: Thread exception handling. Dr. Dobb’s Journal, 2006.
41. Mathew Johnson and Patrick Thomas. A survey on the role of static analysis in software
verification. Journal of Computer Science and Technology, 25(6):1203–1216, 2010.
42. Robert Johnson and Michael Brown. Improving application security with exception filters. In
International Conference on Secure Software Engineering, 2018.
43. Brian W Kernighan and Dennis Ritchie. C Programming Language. Pearson Education, 2015.
44. M. Klein, T. Ralya, B. Pollak, R. Obenza, and M. Gonzales. A practitioner’s handbook for
real-time analysis: Guide to rate monotonic analysis for real-time systems. Kluwer Academic
Publishers, 1993.
45. Tom Lee and Kevin Park. A centralized approach to exception handling for robust applications.
Journal of Computer Science and Technology, 33(2):250–265, 2020.
46. B. Lewis and D.J. Berg. Multithreaded Programming with PThreads. Prentice Hall, 2004.
47. Ming Li and Jong-Deok Choi. Checkpointing and rollback-recovery for distributed real-time
systems. Journal of Systems Architecture, 51(4):213–226, 2005.
48. C.L. Liu and J.W. Layland. Scheduling algorithms for multiprogramming in a hard-real-time
environment. Journal of the ACM, 20(1):46–61, 1973.
49. Jane W. S. Liu. Real-time systems. Prentice Hall Press, 2000.
50. Qiang Liu, Jun He, and Xing Liu. A study on semantic errors in exception handling. Journal
of Computer and Communications, 4(12):103–109, 2016.
51. C. Lu, J.A. Stankovic, G. Tao, and S.H. Son. Design and evaluation of a feedback control edf
scheduling algorithm. IEEE Transactions on Computers, 54(6):706–720, 2005.
52. H. Mamaghanian, N. Khaled, D. Atienza, and P. Vandergheynst. Compressed sensing for
real-time energy-efficient ecg compression on wireless body sensor nodes. IEEE Transactions
on Biomedical Engineering, 58(9):2456–2466, 2011.
53. Ramiro Martinez and Javier Lopez. Proactive monitoring for fault-tolerant real-time systems.
In International Conference on Real-Time Computing, pages 85–94. IEEE, 2012.
54. Bertrand Meyer. Object-Oriented Software Construction. Prentice Hall, Upper Saddle River,
NJ, USA, 2nd edition, 1997.
55. Microsoft. Handling and throwing exceptions. https://docs.microsoft.com/en-
us/dotnet/standard/exceptions/.
56. Robert Miller and Anand Tripathi. Issues with exception handling in object-oriented systems.
In ECOOP’97—Object-Oriented Programming, pages 85–103. Springer, 1997.
112 References

57. Gustavo Niemeyer and Python Software Foundation. Pep 344 – exception chaining and
embedded tracebacks. https://www.python.org/dev/peps/pep-0344/, 2006.
58. R. Oliva-Garcia, C.M. Fischer-Rubira, and A. Romanovsky. A survey of exception handling
in the c++ standard. Journal of Systems and Software, 80(7):1054–1077, 2007.
59. Oracle. Lesson: Exceptions. https://docs.oracle.com/javase/tutorial/essential/exceptions/.
60. Oracle. Thread.setdefaultuncaughtexceptionhandler. https://docs.oracle.com/javase/8/docs/
api/java/lang/Thread.htmlsetDefaultUncaught ExceptionHandlerjava.lang.Thread.
UncaughtExceptionHandler-.
61. Oracle. The java™ tutorials: Handling errors using exceptions.
https://docs.oracle.com/javase/tutorial/essential/exceptions/, n.d.
62. Python Org. Python documentation. https://docs.python.org/3/tutorial/errors.
html, 2021.
63. D.J. Pradhan. Fault-Tolerant Computer System Design. Prentice Hall, 1996.
64. David Pritchard. Exceptions in C++: The Definitive Guide. Packt Publishing, 2020.
65. L. Pullum, J. Gonzalez, and R. DeLong. Error handling and java exception design in real-time
systems. In Proceedings 10th IEEE International Workshop on Object-oriented Real-time
Dependable Systems, pages 27–34, 2001.
66. Python Software Foundation. Errors and exceptions. https://docs.python.org/3/
tutorial/errors.html.
67. Arnold Robbins. A history of the development of exception handling in programming lan-
guages. ACM SIGPLAN Notices, 38(10):16–25, 2003.
68. McConnell S. Code Complete: A Practical Handbook of Software Construction. Microsoft
Press, 2004.
69. Romany Saad. Exception aggregation – a new validation pattern. https://medium.com/nerd-for
tech/exception-aggregation-a-new-validation-pattern-71d26f2ffee8, 2021.
70. Miro Samek. Practical statecharts in C/C++. CMP Books, 2002.
71. Herbert Schildt. C++: The Complete Reference, Fourth Edition. McGraw-Hill, 2002.
72. L. Sha, T. Abdelzaher, K. Årzén, A. Cervin, T. Baker, A. Burns, G. Buttazzo, M. Caccamo,
J. Lehoczky, and A. Mok. Real time scheduling theory: A historical perspective. Real-Time
Systems, 28(2-3):101–155, 2004.
73. Jason Smith and James Anderson. Recovery blocks in real-time systems. In Proceedings of
the Real-Time Systems Symposium, pages 194–203. IEEE, 1998.
74. John Smith and Jane Doe. Effective error handling with wrapper exceptions. Journal of
Software Engineering, 25(4):123–137, 2019.
75. Bjarne Stroustrup. The C++ Programming Language. Addison-Wesley, 2013.
76. H. Sutter. Exceptional C++ Style. Addison-Wesley, 2005.
77. Herb Sutter. Exceptional C++. Addison-Wesley Professional, Boston, MA, 1st edition, 2000.
78. Herb Sutter. Exceptional C++: 47 Engineering Puzzles, Programming Problems, and Solu-
tions. Addison-Wesley, 2000.
79. Budd T. Computer Science: An Overview. Pearson, Boston, MA, 12th ed edition, 2015.
80. Apache Tomcat. Error handler. https://tomcat.apache.org/.
81. Stephen Toub. Aggregating exceptions. MSDN Magazine, 24(08), August 2009.
82. Guido Van Rossum and Fred L. Drake Jr. Python Essential Reference. Addison-Wesley, 2007.
83. R. Wilhelm, J. Engblom, A. Ermedahl, N. Holsti, S. Thesing, D. Whalley, G. Bernat, C. Ferdi-
nand, R. Heckmann, T. Mitra, F. Mueller, I. Puaut, P. Puschner, J. Staschulat, and P. Stenström.
The worst-case execution-time problem—overview of methods and survey of tools. ACM
Transactions on Embedded Computing Systems, 7(3):1–53, 2008.
84. P.R. Wilson, M.S. Johnstone, M. Neely, and D. Boles. Dynamic storage allocation: A survey
and critical review. Proceedings of International Workshop on Memory Management, pages
1–116, 1997.
85. W. M. Wonham. Supervisory Control of Discrete Event Systems. Springer, 2018.

You might also like