Errors, Exceptions, and DebuggingIntroductionPrograms rarely work perfectly on the first attempt. A program may fail before it starts, stop while running, or produce an answer that is technically valid but incorrect. Professional programming therefore involves not only writing code, but also identifying, explaining, preventing, and correcting faults. Errors are especially important in Java and Python because these languages provide different mechanisms for detecting and handling failures. Java emphasizes compile-time checking and explicitly declared exception categories, while Python performs much checking at runtime and uses dynamic typing. In both languages, programmers must understand what a failure means, where it occurred, why it occurred, and how to respond safely. Debugging is the systematic process of locating and correcting defects. Exception handling is the controlled response to failures that occur during execution. Assertions test assumptions during development, logging records useful evidence, breakpoints pause execution for inspection, and stack traces show the sequence of calls that led to a failure. Used together, these techniques transform debugging from guesswork into an evidence-based process. Learning ObjectivesBy the end of this chapter, you should be able to: - distinguish syntax, compile-time, runtime, and logic errors;
- interpret stack traces and identify the likely source of a failure;
- use Java and Python exception-handling constructs appropriately;
- explain the purpose and limitations of assertions;
- use logging, breakpoints, and controlled experiments in debugging;
- apply a systematic debugging workflow;
- design programs that fail safely and provide useful diagnostic information;
- evaluate the security, reliability, and maintainability implications of error handling.
1. Understanding Program ErrorsA defect is a flaw in a program or its design. A failure is observable incorrect behavior caused by a defect. An error may refer either to the incorrect condition itself or to the event reported by a language or tool. These terms are related but not identical: a defect can exist without producing a failure in every execution. 1.1 Syntax errorsA syntax error violates the grammar of a programming language. The interpreter or compiler cannot determine how the source code should be structured. Python example: if temperature > 30
print("Hot")The colon is missing. Python reports the problem before executing the statement. A Java example is: System.out.println("Ready")If the semicolon is omitted in a context where it is required, the Java compiler reports a syntax or compilation error. Syntax errors are usually local and comparatively easy to correct. However, an error message may point to the location where parsing became impossible rather than the exact location where the mistake began. A missing quotation mark, for example, can make several later lines appear erroneous. 1.2 Compile-time and static errorsA compiled language may accept the general syntax but reject a program during compilation because a rule cannot be verified. Java examples include assigning a string to an integer variable, referring to an undeclared variable, or calling a method with incompatible arguments. int age = "twenty"; // incompatible types Python also performs some checks before execution, but many type and name decisions are deferred until runtime. Tools such as linters, type checkers, and integrated development environments can nevertheless identify many likely problems before a Python program runs. Static checking is valuable because it detects defects early. It does not prove that a program is logically correct. A syntactically valid program can still calculate the wrong result. 1.3 Runtime errors and exceptionsA runtime error occurs while a program is executing. Common causes include division by zero, invalid input, missing files, unavailable network services, invalid indexes, and attempts to use a null or nonexistent value. Python: numbers = [10, 20, 30]
print(numbers[3]) # IndexError Java: int[] numbers = {10, 20, 30};
System.out.println(numbers[3]); // ArrayIndexOutOfBoundsExceptionMany runtime errors are represented by exceptions. An exception is an object or runtime signal describing an abnormal condition. Exception handling allows a program to respond deliberately instead of terminating without explanation. 1.4 Logic errorsA logic error occurs when a program runs but produces an incorrect result. Logic errors are often more difficult than syntax errors because the language may have no reason to reject the code. def average(total, count):
return total * count # logically incorrectThe function is syntactically valid and may execute successfully, but an average normally requires division by the count. Logic errors are found through reasoning, tests, assertions, comparison with requirements, and examination of intermediate values. 1.5 Error classification| Error type | Usually detected by | Typical result | Example |
|---|
| Syntax | Parser or interpreter | Program cannot be parsed | Missing colon | | Compile-time/static | Compiler or analyzer | Program does not build | Wrong Java type | | Runtime | Runtime system | Exception or termination | Invalid index | | Logic | Tests, review, observation | Incorrect output | Wrong formula | | Environmental | Operating system or service | Failure dependent on context | Missing file |
The categories can overlap. A missing file is a runtime failure from the program’s viewpoint but may be caused by an environmental condition. Good diagnosis identifies both the immediate symptom and the underlying cause. 2. Exceptions and Their StructureAn exception has at least four useful dimensions: - Type: what kind of failure occurred;
- message: human-readable information about the condition;
- location: where the failure was detected;
- context: the operations that led to that location.
In Java, exceptions form a class hierarchy rooted in Throwable. Error generally represents serious conditions that applications do not normally recover from, while Exception represents conditions applications may handle. RuntimeException and its subclasses are unchecked exceptions. Other exceptions are checked and must be caught or declared by Java’s compiler. Examples include NumberFormatException, NullPointerException, IOException, and IllegalArgumentException. Python exceptions are classes derived ultimately from BaseException; ordinary application exceptions generally derive from Exception. Examples include ValueError, TypeError, IndexError, KeyError, FileNotFoundError, and ZeroDivisionError. Exception handling should be based on recoverability. A program should catch an exception when it can take a meaningful action, such as requesting valid input, selecting a fallback, retrying a temporary operation, or displaying a safe message. Catching every possible exception and ignoring it is poor practice because it hides defects. 3. Handling Exceptions in PythonPython uses try, except, else, and finally blocks. try:
quantity = int(input("Quantity: "))
price = float(input("Price: "))
total = quantity * price
except ValueError:
print("Enter a whole number for quantity and a number for price.")
else:
print(f"Total: {total:.2f}")
finally:
print("Input operation completed.")The try block contains operations that may fail. An except block handles a matching exception. The else block runs only when no exception occurs. The finally block runs whether an exception occurs or not and is suitable for cleanup. Specific exceptions should be caught before broader ones: try:
with open("sales.txt", encoding="utf-8") as file:
data = file.read()
except FileNotFoundError:
print("The sales file is unavailable.")
except PermissionError:
print("The program is not permitted to read the file.")A programmer may deliberately raise an exception when an input violates a function’s contract: def withdraw(balance, amount):
if amount < 0:
raise ValueError("Withdrawal cannot be negative")
if amount > balance:
raise ValueError("Insufficient balance")
return balance - amountCustom exceptions can make a larger application clearer: class InsufficientFundsError(Exception):
passA useful rule is to add context while preserving the original cause: try:
amount = float(text)
except ValueError as error:
raise ValueError("Amount must be numeric") from errorThe with statement is also an important reliability feature. It ensures that resources such as files are closed even if an exception occurs. 4. Handling Exceptions in JavaJava uses try, catch, finally, and try-with-resources. try {
int quantity = Integer.parseInt(input);
System.out.println(100 / quantity);
} catch (NumberFormatException e) {
System.out.println("Quantity must be a whole number.");
} catch (ArithmeticException e) {
System.out.println("Quantity must not be zero.");
}A catch block handles an exception whose type is compatible with the caught type. More specific catches must precede broader catches. try {
// operation that may fail
} catch (IOException e) {
// specific response
} catch (Exception e) {
// broader response, used sparingly
}The finally block is intended for cleanup, although modern Java commonly uses try-with-resources: try (BufferedReader reader = Files.newBufferedReader(
Path.of("sales.txt"))) {
System.out.println(reader.readLine());
} catch (IOException e) {
System.err.println("Could not read sales file.");
}Resources implementing AutoCloseable are closed automatically. This is safer than relying on every possible execution path to close a resource manually. A method can declare a checked exception with throws: static String readFirstLine(Path path) throws IOException {
try (BufferedReader reader = Files.newBufferedReader(path)) {
return reader.readLine();
}
}A method may also throw an exception explicitly: if (amount < 0) {
throw new IllegalArgumentException("Amount cannot be negative");
}Java’s checked exceptions encourage explicit design decisions, but excessive declarations or indiscriminate wrapping can make code difficult to use. The exception type should communicate a condition that callers can understand and handle. 5. Assertions and Program ContractsAn assertion is a statement that a condition expected by the programmer is true. Assertions are useful for detecting violated assumptions during development. Python: assert balance >= 0, "Internal invariant violated" Java: assert balance >= 0 : "Internal invariant violated"; Assertions differ from ordinary input validation. User input is external and must be checked regardless of whether assertions are enabled. Java assertions can be disabled unless the program is launched with the appropriate assertion option, and Python execution can also disable assertions. Therefore, assertions should not protect security checks, enforce required business rules, or replace exception handling. A useful contract has three parts: - precondition: what must be true before an operation;
- postcondition: what should be true after it completes;
- invariant: what remains true throughout a component’s valid operation.
For example, a queue may have the invariant that its size is never negative and never exceeds its capacity. Assertions can expose a programming defect close to the point where it is introduced. 6. Stack Traces and Failure EvidenceA stack trace records the chain of method or function calls active when an exception occurred. The most recent call is usually shown near the top or bottom depending on the language’s presentation, so programmers must read the complete trace rather than assume the first line is the root cause. A Python trace commonly includes: - the file and line number;
- the call sequence;
- the exception type;
- the exception message.
A Java trace commonly includes entries such as Class.method(File.java:line). The first application-level frame associated with the failure is often a strong starting point, but the real defect may be in an earlier caller that supplied invalid data. A practical interpretation process is: - Read the exception type and message.
- Locate the first relevant line in code you own.
- Inspect the values used on that line.
- Follow the call chain backward to find where those values were produced.
- Reproduce the failure with the smallest useful input.
- Form a hypothesis and test it.
Do not treat a stack trace as an accusation that the named line is always defective. It identifies where the runtime detected a problem, not necessarily where the incorrect state originated. 7. Debugging as a Systematic ProcessDebugging is an investigation. Randomly changing lines until a failure disappears may produce a fragile program and obscure the original cause. Step 1: Reproduce the problemRecord the input, environment, program version, operating system, and precise observed behavior. A reproducible failure is easier to analyze than an intermittent one. Step 2: Characterize the failureDetermine whether the failure is a syntax, runtime, logic, data, or environment problem. Ask whether it occurs for all inputs or only a boundary case. Step 3: Reduce the caseCreate the smallest input and shortest execution path that still demonstrates the problem. Reduction removes irrelevant variables and often reveals the faulty assumption. Step 4: Form a hypothesisExamples include: “The index becomes equal to the list length,” “The file path is relative to a different working directory,” or “The loop updates the wrong variable.” A hypothesis should predict an observable result. Step 5: Gather evidenceUse a debugger, logs, assertions, tests, and carefully selected temporary output. Inspect values before and after important operations. Evidence should distinguish competing explanations. Step 6: Correct the underlying causePrefer a correction that restores the intended design rather than concealing the symptom. For example, increasing an array limit may hide an off-by-one error instead of fixing it. Step 7: Test and prevent regressionRun the original failing case, normal cases, boundary cases, and relevant automated tests. Add a regression test so that the defect is less likely to return. 7.1 Breakpoints and steppingA breakpoint pauses execution at a selected line. In a debugger, programmers can inspect variables, the call stack, and the next statement. Common stepping operations are: - step over: execute the current line without entering a called function;
- step into: enter the called function;
- step out: finish the current function and return to its caller;
- continue: resume execution until another breakpoint or failure.
Conditional breakpoints pause only when an expression is true, which is useful in loops or large data sets. Watch expressions display selected values as execution proceeds. 7.2 LoggingLogging records events with context and severity. Typical levels are DEBUG, INFO, WARNING, ERROR, and CRITICAL in Python ecosystems; Java libraries commonly provide levels such as TRACE, DEBUG, INFO, WARN, and ERROR. Python example: import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger.info("Starting import", extra={"records": 250})
try:
process_records()
except OSError:
logger.exception("Import failed")logger.exception includes the current exception traceback when called inside an exception handler. Logging should explain what operation failed and include safe identifiers, not passwords, tokens, or personal data.
Logging is different from debugging output. It should be purposeful, configurable, and suitable for operations. Excessive logs increase cost and may hide important information; insufficient logs make production diagnosis difficult. 8. Testing and Defensive ProgrammingTesting is a major source of evidence about program behavior. Unit tests examine small components, integration tests examine interactions, and system or acceptance tests examine complete behavior. Boundary values deserve special attention. If a valid quantity is 1 through 100, test 1, 100, 0, 101, empty input, and nonnumeric input. Tests should cover both expected success and expected failure. Defensive programming includes: - validating external input;
- checking assumptions at component boundaries;
- using clear data types and names;
- avoiding duplicated error-prone logic;
- preserving useful exception context;
- failing early when continuation would corrupt results;
- designing operations to be safe to retry when appropriate.
A program should distinguish expected invalid input from unexpected defects. An invalid password supplied by a user may require a normal validation response. A corrupted internal data structure may require an error report, rollback, and developer investigation. 9. Applied Case: An Inventory Import FailureBackgroundThe following is an illustrative case. A small retailer imports a comma-separated inventory file containing product codes, quantities, and prices. The import program occasionally terminates and sometimes accepts incorrect quantities. ProblemThe original Python function is: def load_item(row):
parts = row.split(",")
code = parts[0]
quantity = int(parts[1])
price = float(parts[2])
return code, quantity, priceUsers report three symptoms: blank lines terminate the import, a row with a decimal quantity is rejected, and a missing field produces an unclear IndexError. AnalysisThe function assumes every row has exactly three nonempty fields. That assumption is not documented or checked. It also treats the input format as trustworthy. A stack trace identifies the failing line, but inspection of the input reveals several distinct causes: blank records, malformed structure, and invalid numeric data. Approach and solutionThe improved design validates structure first and reports the row number: class InventoryRowError(ValueError):
pass
def load_item(row, row_number):
parts = [part.strip() for part in row.split(",")]
if len(parts) != 3:
raise InventoryRowError(
f"Row {row_number}: expected 3 fields, got {len(parts)}")
code, quantity_text, price_text = parts
if not code:
raise InventoryRowError(f"Row {row_number}: product code is empty")
try:
quantity = int(quantity_text)
price = float(price_text)
except ValueError as error:
raise InventoryRowError(
f"Row {row_number}: quantity and price must be numeric") from error
if quantity < 0 or price < 0:
raise InventoryRowError(
f"Row {row_number}: quantity and price cannot be negative")
return code, quantity, priceThe import controller can choose whether to reject the whole file or collect invalid rows for review. It should log the number of accepted and rejected records, preserve the original file, and avoid partially updating inventory unless the transaction can be completed safely. OutcomeThe revised program gives users actionable messages, preserves the original cause, validates business rules, and makes testing possible. Tests can now cover empty rows, missing fields, negative values, nonnumeric values, and valid records. Learning pointsThe case demonstrates that exception handling is not merely a way to prevent a crash. Good handling clarifies the contract, identifies the faulty record, protects data integrity, and supports operational recovery. It also shows why validation belongs near the boundary where untrusted data enters the system. 10. Common Mistakes and LimitationsCatching too broadlyCatching Exception in Python or a broad Exception in Java may hide programming defects. Catch the narrowest type that permits a useful response. Empty handlersAn empty except or catch block silently discards evidence. If an exception is intentionally ignored, document why; normally, log it or convert it into a meaningful response. Using exceptions for ordinary control flowExceptions are appropriate for exceptional conditions, not for routine choices that can be represented directly by a return value or normal condition. Leaking internal detailsA user-facing message should not reveal database credentials, file-system structure, stack traces, or confidential data. Detailed diagnostics belong in protected logs. Continuing after data corruptionIf a failure means that program state is unreliable, continuing may produce incorrect output. Stop, roll back, or isolate the affected operation. Assuming a fix is provenA disappearing symptom does not prove the defect is fixed. Reproduce the original case, test boundaries, and add a regression test. Overusing assertionsAssertions are not a substitute for validation, security controls, or required business rules because they may be disabled. 11. Professional, Ethical, and Contemporary ConsiderationsReliable error handling is part of software quality and professional responsibility. A system that silently loses records or reports misleading success can cause financial, safety, or reputational harm. Developers should define who receives technical details, how incidents are recorded, and how sensitive data is protected. Logs may contain personal or confidential information. Apply data minimization, access controls, retention policies, and redaction. Error messages should support accessibility and should be understandable to their intended audience. Modern development environments increasingly combine static analysis, automated tests, integrated debuggers, runtime monitoring, and artificial-intelligence-assisted code suggestions. These tools can propose likely causes or generate tests, but they do not replace human verification. AI-generated fixes may introduce broad exception handling, insecure logging, or incorrect assumptions. Developers remain responsible for testing, reviewing, and governing the resulting software. Best-Practice Checklist- Read the exception type and complete stack trace.
- Reproduce the failure before changing code.
- Catch specific exceptions and take meaningful action.
- Preserve the original cause when adding context.
- Use
finally, context managers, or try-with-resources for cleanup. - Validate external data at system boundaries.
- Use assertions for development-time invariants, not security rules.
- Prefer structured logs with appropriate severity.
- Never log secrets unnecessarily.
- Test normal, invalid, and boundary inputs.
- Add a regression test after correcting a defect.
- Fail safely when continuing could corrupt data.
Chapter SummaryErrors may be syntactic, compile-time, runtime, environmental, or logical. Syntax and many static errors prevent execution; runtime errors occur during execution; logic errors allow execution but produce incorrect results. Exceptions provide structured information about abnormal conditions and can be handled in Python with try–except–else–finally and in Java with try–catch–finally, checked exceptions, and try-with-resources. Assertions test programmer assumptions but must not replace validation or security controls. Stack traces identify the execution path associated with a failure. Breakpoints, stepping, watches, logging, tests, and controlled experiments provide evidence for systematic debugging. Effective debugging reproduces, characterizes, reduces, hypothesizes, tests, corrects, and verifies. Professional error handling protects users, data, confidentiality, and system reliability. Key TermsDefect: A flaw in program code, design, or configuration. Failure: Observable incorrect behavior caused by a defect. Syntax error: A violation of a language’s grammatical rules. Compile-time error: An error detected while source code is compiled. Runtime error: A failure that occurs during execution. Logic error: A defect that produces an incorrect result without necessarily stopping execution. Exception: A structured signal or object representing an abnormal condition. Exception handler: Code that responds to a specified exception. Stack trace: A record of active calls and the location associated with an exception. Assertion: A check that an expected condition is true. Precondition: A condition required before an operation begins. Postcondition: A condition expected after an operation completes. Invariant: A condition that remains true during valid operation. Breakpoint: A debugger instruction that pauses execution at a selected location. Logging: The recording of events and diagnostic information. Regression test: A test that ensures a previously corrected defect does not return. Defensive programming: Designing software to detect and safely handle invalid conditions. Checked exception: A Java exception that must be caught or declared. Boundary value: An input at or near the limit of an accepted range. Review QuestionsShort Answer Questions- What is the difference between a runtime error and a logic error?
- Why should a program usually catch specific exception types rather than broad types?
- What information does a stack trace provide?
- Why should assertions not be used as the only validation of user input?
- State two responsibilities of a useful logging system.
Long Answer Questions- Explain the roles of
try, except, else, and finally in Python, and compare them with Java’s exception-handling constructs. - Describe a systematic debugging process from reproducing a failure through regression testing.
- Explain how try-with-resources in Java and the
with statement in Python improve resource management. - Discuss the differences between user-facing error messages and diagnostic logs.
- Explain how exception handling can protect data integrity in an inventory or financial application.
Analytical / Application Questions- A program accepts an integer from a user and uses it as an array index. Identify the possible failure conditions and design an appropriate handling strategy in either Java or Python.
- A file-import system catches every exception, prints “Import failed,” and continues processing. Analyze the risks and propose a safer design.
- A function produces correct results for most inputs but fails when the input list is empty or contains one item. Describe the tests, assertions, and debugging evidence you would use to locate the defect.
Multiple Choice Questions with AnswersQuestion 1Which type of error allows a program to run but produces an incorrect result? A. Syntax error B. Logic error C. Compilation error D. Parsing error Correct Answer: B. Logic error Question 2Which Python block runs whether or not an exception occurs? A. try B. except C. else D. finally Correct Answer: D. finally Question 3What is the main purpose of a stack trace? A. To format source code B. To record the sequence of calls associated with a failure C. To encrypt program output D. To prevent all runtime errors Correct Answer: B. To record the sequence of calls associated with a failure Question 4Which Java feature automatically closes an AutoCloseable resource? A. A switch statement B. An assertion C. Try-with-resources D. A package declaration Correct Answer: C. Try-with-resources Question 5Why should assertions not replace validation of user input? A. Assertions cannot compare values B. Assertions may be disabled C. Assertions always create logs D. Assertions only work in Python Correct Answer: B. Assertions may be disabled Question 6Which response is generally most appropriate for an invalid numeric input supplied by a user? A. Request valid input or provide a clear validation message B. Ignore the input silently C. Log the user’s password D. Continue using an arbitrary number Correct Answer: A. Request valid input or provide a clear validation message Question 7What does a debugger’s step into operation normally do? A. Deletes the current function B. Enters a called function during execution C. Stops all program execution permanently D. Compiles a different program Correct Answer: B. Enters a called function during execution Question 8Which practice best protects diagnostic logs? A. Record passwords for later analysis B. Expose complete stack traces to every user C. Redact sensitive information and restrict access D. Disable all logging Correct Answer: C. Redact sensitive information and restrict access Question 9What is the principal benefit of a regression test? A. It ensures a corrected defect does not return B. It replaces all unit tests C. It guarantees no future defects exist D. It removes the need for debugging Correct Answer: A. It ensures a corrected defect does not return Question 10In Python, which exception commonly indicates that a list index is outside the valid range? A. KeyError B. IndexError C. ImportError D. PermissionError Correct Answer: B. IndexError
|