22. Software and Errors#

Unfortunately, errors can be found in almost every software system. As mentioned in the second notebook, proving the absence of errors is an undecidable problem in computer science. Regardless, we still have a professional obligation to minimize/avoid such errors and to reduce(mitigate) impacts from those errors.

From an academic standpoint, the programs that you create for your coursework: (Adapted from [1])

  1. Should produce the correct results for all legal inputs

  2. Should provide reasonable error messages and error codes for all illegal inputs

  3. May terminate after finding an error (unless other specified)

  4. Do not need to handle errors arising from issues in the environment (e.g., operating system, hardware, or other systems)

As Bjarne Bjarne Stroustrup stated, the first two items relate to ethics and professsional conduct expected of computing professionals. At Duke University and, more specifically, the FinTech Program, we expect this be be upheld to the maximum extent possible.

Handling issues arising from the fourth point can be difficult to handlle, but absolutely required for certain systems.

22.1. Sources of Errors#

Errors arise from many different sources during the software development process:

22.1.1. Human Factors#

Human error is probably the top source of software defects. Many of the other categories below can be tied or placed into this overarching category. How well we document and communicate software requirements can be challenging. Differences in education, background, culture, and environment affect how people interpret requirements. In the exercises for formatting strings, the reader is asked to create a function to print a receipt. However, those outside the United States may not be familiar with gratuities. Humans also introduce errors through design and coding mistakes. Additionally, developers often perform poorly when testing their own code.

22.1.2. Complexity#

As software systems grow in complexity to meet evolving user needs and technological advancements, so does the potential for errors. Managing intricate interactions between different components increases the likelihood of bugs and glitches.

22.1.3. Time and Resource Constraints#

Just as students often perform poorly when submitting homework and studying at the last possible moment, pressing deadlines and resource limitations often force developers to prioritize speed over thoroughness. Rushed development cycles can lead to insufficient testing and oversight, amplifying the risk of errors slipping through the cracks. Often, developers release incomplete programs as what has been completed provides needed functionality to users.

22.1.4. Changing Requirements#

It’s almost impossible to envision and document a system completely at the start; requirements frequently change or evolve. While certain processes (agile) are designed to both mitigate and embrace the impacts of change. Failure to adapt code accordingly can result in inconsistencies and errors as the software evolves.

22.1.5. Unexpected Inputs#

Typically, programs take inputs from some source, process the input, and then produce output. Programmers frequently make assumptions about such input:

  • what’s the data type?

  • expected number of arguments

  • legal values

22.2. Types of Errors#

Errors in software development manifest in various forms, including:

  • Syntax Errors: Basic errors that violate a programming language’s rules, such as missing semicolons or mismatched parentheses, leading to compilation failures.

  • Logic Errors: Logic errors occur when the code does not produce the intended output due to flaws in the algorithm or reasoning behind the implementation. These errors can be subtle and challenging to detect through testing alone.

  • Runtime Errors: Runtime errors occur during the program’s execution and often result in crashes or abnormal terminations. Common examples include null pointer exceptions, division by zero, and stack overflow.

  • Security Vulnerabilities: Security errors encompass a broad range of issues, including injection attacks, authentication bypasses, and data breaches, which jeopardize the confidentiality, integrity, and availability of the software system.

22.3. Minimizing Errors#

Through several of the upcoming notebooks, we will present various ways to minimize errors in software:

  • Input validation

  • Handling runtime errors through exception handling

  • Testing

We also have the ability to minimize errors through the software we produce. One way to do this is through organization - how we structure our programs into different parts. Within Computer Science, separation of concerns is a design principle that advocates dividing a software system into distinct sections, each addressing a separate aspect or responsibility. By separating code into distinct functions, the codebase can become easier to understand and maintain. Also, such functions are typically easier to test.

We can also organize software into appropriate modules and classes, we can such benefits as -

  1. Encapsulation: Modules encapsulate functionality into cohesive units with well-defined interfaces. By hiding implementation details and exposing only essential functionalities through well-defined interfaces, modules reduce the likelihood of unintended interactions and dependencies between different parts of the codebase. This encapsulation helps contain errors within specific modules, making isolating and debugging issues easier without affecting the entire system.

  2. Abstraction: Modules abstract complex functionalities into manageable units, allowing developers to focus on high-level concepts without getting bogged down in implementation details. By providing clear interfaces and hiding internal complexities, modules facilitate better understanding and maintenance of the codebase, reducing the risk of errors resulting from misunderstandings or misinterpretations of the code.

  3. Modularity: Modular design breaks down software systems into smaller, independent components that can be developed, tested, and maintained separately. This modular architecture enables teams to work on different modules concurrently, reducing development time and minimizing the risk of errors introduced by overlapping changes or conflicting modifications. Additionally, modular systems are easier to scale, extend, and evolve over time, as new functionalities can be added or modified without disrupting existing code.

  4. Reuse: Modules promote code reuse by encapsulating reusable functionalities into standalone components that can be easily integrated into different parts of the software system. By leveraging existing modules instead of reinventing the wheel, developers can reduce the likelihood of errors and ensure consistency and reliability across the codebase. Moreover, modular design encourages the development of libraries, frameworks, and APIs that adhere to standardized interfaces, further enhancing code reuse and interoperability.

While we haven’t yet introduced modules and classes, as you read those sections, think back to this section and how those concepts help create less error-prone software.

  ______________________________________
/ Everything should be made as simple as \\
\\ possible, but no simpler               /
  --------------------------------------
         \   ^__^ 
          \  (oo)\_______
             (__)\       )\/\\
                 ||----w |
                 ||     ||

While the above quote is often attributed to Albert Einstein, the actual source is unknown. What’s important, though, is how this relates to software development. We must avoid unnecessary complexity and functions; needlessly complicating software often leads to more errors.

22.4. Suggested LLM Prompts#

  • Is proving the absence of software defects an unsolvable(intractable) problem in computer science? How does this relate to the halting problem?

  • Why is a developer the worst person to test his or her own code?

  • Provide a Python example using financial technology as the domain to show how separation of concerns can reduce software errors.

  • Explain the importance of effective error reporting and logging in software development. Discuss best practices for logging errors, such as using appropriate log levels, providing contextual information, and separating log files for different components or environments. Introduce techniques for aggregating and analyzing log data to identify and resolve errors more effectively.

The following prompts explore different types of errors that may arise in programs. Many of these prompts introduce techniques that will be covered in future notebooks:

  • Syntax Errors: Explain what syntax errors are and provide examples of common syntax errors in programming languages like Python. Discuss how to read and interpret syntax error messages, and how to fix them by correcting the code structure or syntax.

  • Runtime Errors and Exceptions: Introduce the concept of runtime errors and exceptions. Explain the difference between syntax errors and runtime errors. Provide examples of common runtime errors, such as TypeError, ValueError, IndexError, and ZeroDivisionError. Discuss how to handle exceptions using try-except blocks and how to raise custom exceptions.

  • Logical Errors: Explain what logical errors are and how they differ from syntax and runtime errors. Provide examples of logical errors, such as infinite loops, incorrect conditions, or incorrect algorithm implementations. Discuss techniques for debugging and identifying logical errors, such as adding print statements, using a debugger, or writing unit tests.

  • Resource Handling Errors: Introduce errors related to resource handling, such as file I/O errors, network errors, or database errors. Explain common error scenarios, such as attempting to read from a non-existent file, losing network connectivity, or accessing a locked database record. Discuss error handling strategies, such as checking for file existence, retrying network operations, or implementing locking mechanisms.

  • Numerical Errors: Discuss numerical errors, such as overflow, underflow, and precision loss. Provide examples of how these errors can occur when working with floating-point numbers or large integers. Explain techniques for mitigating numerical errors, such as using appropriate data types, checking for boundary conditions, and implementing error handling.

  • Memory Management Errors: Explain memory management errors, such as null pointer dereferences, dangling pointers, and memory leaks. Provide examples of these errors in languages like C or C++, and discuss how they can lead to crashes or unstable behavior. Introduce strategies for avoiding memory management errors, such as using smart pointers, automatic memory management (e.g., garbage collection), or manual memory allocation and deallocation.

  • Security Errors: Introduce security-related errors, such as injection attacks (e.g., SQL injection, command injection), cross-site scripting (XSS), and cross-site request forgery (CSRF). Explain how these errors can occur and their potential consequences. Discuss best practices for preventing security errors, such as input validation, sanitization, and following secure coding principles.

  • Performance and Scalability Errors: Introduce errors related to performance and scalability, such as inefficient algorithms, excessive memory usage, or poor resource utilization. Provide examples of how these errors can impact application performance and scalability. Discuss techniques for identifying and addressing performance issues, such as profiling, optimizing algorithms, and implementing caching or load balancing strategies.

22.5. Review Questions#

  1. Early in this notebook, it was stated that your programs “Do not need to handle errors arising from issues in the environment (e.g., operating system, hardware, or other systems)”. Why is this the case for classwork?

  2. From an industrial perspective, discuss why we should handle such errors. Provide an example of what could occur if such errors were not appropriately handled. It may help to look at cyber-physical systems.

  3. Revisit some of your recent programs. How can the program be restructured into more functions? Does this make your program easier to understand?

answers

22.6. References#

  1. Bjarne Stroustrup. 2014. Programming: Principles and Practice Using C++, Second Edition. Addison-Wesley, USA. O’Reilly