Splitting assertions from unit testing/TDD
authorGreg Wilson <gvwilson@third-bit.com>
Fri, 13 Sep 2013 18:13:35 +0000 (14:13 -0400)
committerW. Trevor King <wking@tremily.us>
Sat, 9 Nov 2013 15:44:05 +0000 (07:44 -0800)
W. Trevor King: I dropped everything from the original c632882 except
for the lessons/swc-python/ modifications.

Conflicts:
lessons/swc-python/tutorial.html

lessons/swc-python/python-5-errors.ipynb [new file with mode: 0644]
lessons/swc-python/python-6-testing.ipynb [moved from lessons/swc-python/python-5-testing.ipynb with 51% similarity]

diff --git a/lessons/swc-python/python-5-errors.ipynb b/lessons/swc-python/python-5-errors.ipynb
new file mode 100644 (file)
index 0000000..d01b818
--- /dev/null
@@ -0,0 +1,599 @@
+{
+ "metadata": {
+  "name": ""
+ },
+ "nbformat": 3,
+ "nbformat_minor": 0,
+ "worksheets": [
+  {
+   "cells": [
+    {
+     "cell_type": "heading",
+     "level": 1,
+     "metadata": {},
+     "source": [
+      "Basic Programming Using Python: Handling Errors"
+     ]
+    },
+    {
+     "cell_type": "heading",
+     "level": 2,
+     "metadata": {},
+     "source": [
+      "Objectives"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "* Explain what an assertion is, and write assertions with customized error messages.\n",
+      "* Distinguish between pre-conditions, post-conditions, and invariants.\n",
+      "* Explain what defensive programming is, and write functions and programs in this way.\n",
+      "* Correctly raise and handle exceptions."
+     ]
+    },
+    {
+     "cell_type": "heading",
+     "level": 2,
+     "metadata": {},
+     "source": [
+      "Defensive Programming"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "We made several mistakes while writing the programs in our first few lessons.\n",
+      "How can we be sure that there aren't still errors lurking in the code we have?\n",
+      "And how can we guard against introducing new errors in code as we modify it?\n",
+      "\n",
+      "The first step is to use [defensive programming](glossary.html#defensive_programming),\n",
+      "i.e.,\n",
+      "to assume that mistakes *will* happen\n",
+      "and to guard against them.\n",
+      "One way to do this is to add [assertions](glossary.html#assertion) to our code\n",
+      "so that it checks itself as it runs.\n",
+      "We met assertions in [an earlier lesson](python-3-conditionals-defensive.ipynb);\n",
+      "as we said then,\n",
+      "each one states something that must be true at a certain point in the program's execution,\n",
+      "and optionally includes a customized error message explaining what's gone wrong if it fails:\n",
+      "\n",
+      "```\n",
+      "assert width > 0, 'Grid width must be positive'\n",
+      "assert 0 <= ablation(x, y) <= 1.0, 'Ablation must be normalized'\n",
+      "```\n",
+      "\n",
+      "Programs like the Firefox browser are littered with assertions:\n",
+      "10-20% of the code they contain\n",
+      "are there to check that the other 80-90% are working correctly.\n",
+      "Broadly speaking,\n",
+      "assertions fall into three categories:\n",
+      "\n",
+      "- A [precondition](glossary.html#precondition) is something that must be true\n",
+      "  in order for a piece of code to work correctly.\n",
+      "- A [postcondition](glossary.html#postcondition) is something that must be true\n",
+      "  at the end of a piece of code if it worked correctly.\n",
+      "- An [invariant](glossary.html#invariant) is something that is always true\n",
+      "  at a particular point inside a piece of code."
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "For example,\n",
+      "suppose we are representing rectangles using a list of four coordinates `[x0, y0, x1, y1]`.\n",
+      "In order to do some calculations,\n",
+      "we need to normalize the rectangle so that it is at the origin\n",
+      "and 1.0 units long on its longest axis.\n",
+      "This function does that:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "def normalize_rectangle(rect):\n",
+      "    x0, y0, x1, y1 = rect\n",
+      "    assert x0 < x1, 'Invalid X coordinates'\n",
+      "    assert y0 < y1, 'Invalid Y coordinates'\n",
+      "\n",
+      "    dx = x1 - x0\n",
+      "    dy = y1 - y0\n",
+      "    if dx > dy:\n",
+      "        scaled = float(dy) / dx\n",
+      "        upper_x, upper_y = 1.0, scaled\n",
+      "    else:\n",
+      "        scaled = float(dx) / dy\n",
+      "        upper_x, upper_y = scaled, 1.0\n",
+      "\n",
+      "    assert 0 < upper_x <= 1.0, 'Calculated upper X coordinate invalid'\n",
+      "    assert 0 < upper_y <= 1.0, 'Calculated upper Y coordinate invalid'\n",
+      "\n",
+      "    return [0, 0, upper_x, upper_y]"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [],
+     "prompt_number": 7
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "The first two assertions check that we've been given a legal rectangle,\n",
+      "while the last two check the output we're about to return to our caller.\n",
+      "Strictly speaking these post-conditions are redundant:\n",
+      "if the inputs and calculations are correct,\n",
+      "the last two assertions should always hold.\n",
+      "But those are pretty big ifs,\n",
+      "and having the program check itself can save us a lot of hunting around later."
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "<hr/>\n",
+      "### *Assertions and Bugs*\n",
+      "\n",
+      "<em>\n",
+      "A rule that many programmers follow is, \"Bugs become assertions.\"\n",
+      "Whenever we fix a bug in a program,\n",
+      "we should add some assertions to the program at that point to catch the bug if it reappears.\n",
+      "After all,\n",
+      "if we made the mistake once,\n",
+      "then we (or someone else) might well make it again.\n",
+      "Few things are as frustrating as\n",
+      "having someone delete several carefully-crafted lines of code that fixed a subtle problem\n",
+      "because they didn't realize what problem those lines were there to fix.\n",
+      "</em>\n",
+      "<hr/>"
+     ]
+    },
+    {
+     "cell_type": "heading",
+     "level": 2,
+     "metadata": {},
+     "source": [
+      "Handling Errors"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Assertions help us catch errors in our code,\n",
+      "but things can go wrong for other reasons.\n",
+      "In particular,\n",
+      "errors may have external causes,\n",
+      "like missing or badly-formatted files.\n",
+      "Most modern programming languages allow programmers to use [exceptions](glossary.html#exception) to handle errors\n",
+      "in a way that separates what's supposed to happen if everything goes right\n",
+      "from what the program should do if something goes wrong.\n",
+      "Doing this makes both cases easier to read and understand.\n",
+      "\n",
+      "For example,\n",
+      "here's a small piece of code that tries to read parameters and a grid from two separate files,\n",
+      "and reports an error if either goes wrong:"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "```python\n",
+      "try:\n",
+      "    params = read_params(param_file)\n",
+      "    grid = read_grid(grid_file)\n",
+      "except:\n",
+      "    log.error('Failed to read input file(s)')\n",
+      "    sys.exit(ERROR)\n",
+      "```"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "We join the normal case and the error-handling code using the keywords `try` and `except`.\n",
+      "These work together like `if` and `else`:\n",
+      "the statements under the `try` are what should happen if everything works,\n",
+      "while the statements under `except` are what the program should do if something goes wrong."
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "We have actually seen exceptions before without knowing it,\n",
+      "since by default,\n",
+      "when an exception occurs,\n",
+      "Python prints it out and halts our program.\n",
+      "For example,\n",
+      "trying to open a nonexistent file triggers a type of exception called an `IOError`,\n",
+      "while an out-of-bounds index to a list triggers an `IndexError`:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "open('nonexistent-file.txt', 'r')"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "ename": "IOError",
+       "evalue": "[Errno 2] No such file or directory: 'nonexistent-file.txt'",
+       "output_type": "pyerr",
+       "traceback": [
+        "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mIOError\u001b[0m                                   Traceback (most recent call last)",
+        "\u001b[0;32m<ipython-input-8-58cbde3dd63c>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mopen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'nonexistent-file.txt'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'r'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+        "\u001b[0;31mIOError\u001b[0m: [Errno 2] No such file or directory: 'nonexistent-file.txt'"
+       ]
+      }
+     ],
+     "prompt_number": 8
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "values = [0, 1, 2]\n",
+      "print values[999]"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "ename": "IndexError",
+       "evalue": "list index out of range",
+       "output_type": "pyerr",
+       "traceback": [
+        "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mIndexError\u001b[0m                                Traceback (most recent call last)",
+        "\u001b[0;32m<ipython-input-9-7fed13afc650>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[0mvalues\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mprint\u001b[0m \u001b[0mvalues\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m999\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+        "\u001b[0;31mIndexError\u001b[0m: list index out of range"
+       ]
+      }
+     ],
+     "prompt_number": 9
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "We can use `try` and `except` to deal with these errors ourselves\n",
+      "if we don't want the program simply to fall over.\n",
+      "Here,\n",
+      "for example,\n",
+      "we put our attempt to open a nonexistent file inside a `try`,\n",
+      "and in the `except`, we print a not-very-helpful error message:"
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "try:\n",
+      "    reader = open('nonexistent-file.txt', 'r')\n",
+      "except IOError:\n",
+      "    print 'Whoops!'"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        "Whoops!\n"
+       ]
+      }
+     ],
+     "prompt_number": 10
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "When Python executes this code,\n",
+      "it runs the statement inside the `try`.\n",
+      "If that works, it skips over the `except` block without running it.\n",
+      "If an exception occurs inside the `try` block,\n",
+      "though,\n",
+      "Python compares the type of the exception to the type specified by the `except`.\n",
+      "If they match, it executes the code in the `except` block."
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "`IOError` is the particular kind of exception Python raises\n",
+      "when there is a problem related to input and output,\n",
+      "such as files not existing\n",
+      "or the program not having the permissions it needs to read them.\n",
+      "We can put as many lines of code in a `try` block as we want,\n",
+      "just as we can put many statements under an `if`.\n",
+      "We can also handle several different kinds of errors afterward.\n",
+      "For example,\n",
+      "here's some code to calculate the entropy at each point in a grid:"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "```python\n",
+      "try:\n",
+      "    params = read_params(param_file)\n",
+      "    grid = read_grid(grid_file)\n",
+      "    entropy = lee_entropy(params, grid)\n",
+      "    write_entropy(entropy_file, entropy)\n",
+      "except IOError:\n",
+      "    log_error_and_exit('IO error')\n",
+      "except ArithmeticError:\n",
+      "    log_error_and_exit('Arithmetic error')\n",
+      "```"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Python tries to run the four functions inside the `try` as normal.\n",
+      "If an error occurs in any of them,\n",
+      "Python immediately jumps down\n",
+      "and tries to find an `except` of the corresponding type:\n",
+      "if the exception is an `IOError`,\n",
+      "Python jumps into the first error handler,\n",
+      "while if it's an `ArithmeticError`,\n",
+      "Python jumps into the second handler instead.\n",
+      "It will only execute one of these,\n",
+      "just as it will only execute one branch\n",
+      "of a series of `if`/`elif`/`else` statements."
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "This layout has made the code easier to read,\n",
+      "but we've lost something important:\n",
+      "the message printed out by the `IOError` branch doesn't tell us\n",
+      "which file caused the problem.\n",
+      "We can do better if we capture and hang on to the object that Python creates\n",
+      "to record information about the error:"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "```python\n",
+      "try:\n",
+      "    params = read_params(param_file)\n",
+      "    grid = read_grid(grid_file)\n",
+      "    entropy = lee_entropy(params, grid)\n",
+      "    write_entropy(entropy_file, entropy)\n",
+      "except IOError as err:\n",
+      "    log_error_and_exit('Cannot read/write' + err.filename)\n",
+      "except ArithmeticError as err:\n",
+      "    log_error_and_exit(err.message)\n",
+      "```"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "If something goes wrong in the `try`,\n",
+      "Python creates an exception object,\n",
+      "fills it with information,\n",
+      "and assigns it to the variable `err`.\n",
+      "(There's nothing special about this variable name&mdash;we can use anything we want.)\n",
+      "Exactly what information is recorded depends on what kind of error occurred;\n",
+      "Python's documentation describes the properties of each type of error in detail,\n",
+      "but we can always just print the exception object.\n",
+      "In the case of an I/O error,\n",
+      "we print out the name of the file that caused the problem.\n",
+      "And in the case of an arithmetic error,\n",
+      "printing out the message embedded in the exception object is what Python would have done anyway."
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "So much for how exceptions work:\n",
+      "how should they be used?\n",
+      "Some programmers use `try` and `except` to give their programs default behaviors.\n",
+      "For example,\n",
+      "if this code can't read the grid file that the user has asked for,\n",
+      "it creates a default grid instead:"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "```python\n",
+      "try:\n",
+      "    grid = read_grid(grid_file)\n",
+      "except IOError:\n",
+      "    grid = default_grid()\n",
+      "```"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Other programmers would explicitly test for the grid file,\n",
+      "and use `if` and `else` for control flow:"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "```python\n",
+      "if file_exists(grid_file):\n",
+      "    grid = read_grid(grid_file)\n",
+      "else:\n",
+      "    grid = default_grid()\n",
+      "```"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "It's mostly a matter of taste,\n",
+      "but we prefer the second style.\n",
+      "As a rule,\n",
+      "exceptions should only be used to handle exceptional cases.\n",
+      "If the program knows how to fall back to a default grid,\n",
+      "that's not an unexpected event.\n",
+      "Using `if` and `else`\n",
+      "instead of `try` and `except`\n",
+      "sends different signals to anyone reading our code,\n",
+      "even if they do the same thing."
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Novices often ask another question about exception handling style as well,\n",
+      "but before we address it,\n",
+      "there's something in our example that you might not have noticed.\n",
+      "Exceptions can actually be thrown a long way:\n",
+      "they don't have to be handled immediately.\n",
+      "Take another look at this code:"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "```python\n",
+      "try:\n",
+      "    params = read_params(param_file)\n",
+      "    grid = read_grid(grid_file)\n",
+      "    entropy = lee_entropy(params, grid)\n",
+      "    write_entropy(entropy_file, entropy)\n",
+      "except IOError as err:\n",
+      "    log_error_and_exit('Cannot read/write' + err.filename)\n",
+      "except ArithmeticError as err:\n",
+      "    log_error_and_exit(err.message)\n",
+      "```"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "The four lines in the `try` block are all function calls.\n",
+      "They might catch and handle exceptions themselves,\n",
+      "but if an exception occurs in one of them that *isn't* handled internally,\n",
+      "Python looks in the calling code for a matching `except`.\n",
+      "If it doesn't find one there,\n",
+      "it looks in that function's caller,\n",
+      "and so on.\n",
+      "If we get all the way back to the main program without finding an exception handler,\n",
+      "Python's default behavior is to print an error message like the ones we've been seeing all along."
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "This rule is the origin of the rule \"Throw Low, Catch High.\"\n",
+      "There are many places in our program where an error might occur.\n",
+      "There are only a few, though, where errors can sensibly be handled.\n",
+      "For example,\n",
+      "a linear algebra library doesn't know whether it's being called directly from the Python interpreter,\n",
+      "or whether it's being used as a component in a larger program.\n",
+      "In the latter case,\n",
+      "the library doesn't know if the program that's calling it is being run from the command line or from a GUI.\n",
+      "The library therefore shouldn't try to handle or report errors itself,\n",
+      "because it has no way of knowing what the right way to do this is.\n",
+      "It should instead just raise an exception,\n",
+      "and let its caller figure out how best to handle it."
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Finally,\n",
+      "we can raise exceptions ourselves if we want to.\n",
+      "In fact,\n",
+      "we *should* do this,\n",
+      "since it's the standard way in Python to signal that something has gone wrong.\n",
+      "Here,\n",
+      "for example,\n",
+      "is a function that reads a grid and checks its consistency:"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "```python\n",
+      "def read_grid(grid_file):\n",
+      "    '''Read grid, checking consistency.'''\n",
+      "\n",
+      "    data = read_raw_data(grid_file)\n",
+      "    if not grid_consistent(data):\n",
+      "        raise Exception('Inconsistent grid: ' + grid_file)\n",
+      "    result = normalize_grid(data)\n",
+      "\n",
+      "    return result\n",
+      "```"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "The `raise` statement creates a new exception with a meaningful error message.\n",
+      "Since `read_grid` itself doesn't contain a `try`/`except` block,\n",
+      "this exception will always be thrown up and out of the function,\n",
+      "to be caught and handled by whoever is calling `read_grid`.\n",
+      "We can define new types of exceptions if we want to.\n",
+      "And we should,\n",
+      "so that errors in our code can be distinguished from errors in other people's code.\n",
+      "However,\n",
+      "this involves classes and objects,\n",
+      "which is outside the scope of these lessons."
+     ]
+    },
+    {
+     "cell_type": "heading",
+     "level": 2,
+     "metadata": {},
+     "source": [
+      "Key Points"
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "- Use `assert` to embed pre-conditions, post-conditions, and invariants in programs.\n",
+      "- Use `raise` to signal an error, and `try`/`except` to handle errors.\n",
+      "- Throw low, catch high.\n",
+      "- Construct the most informative error message possible."
+     ]
+    }
+   ],
+   "metadata": {}
+  }
+ ]
+}
\ No newline at end of file
similarity index 51%
rename from lessons/swc-python/python-5-testing.ipynb
rename to lessons/swc-python/python-6-testing.ipynb
index fe176c3f2f8061eefa994932ce4fd845e3db5b18..7f9446c08f3eced5584cc6a1dfd8b761b91e74dd 100644 (file)
@@ -12,7 +12,7 @@
      "level": 1,
      "metadata": {},
      "source": [
-      "Basic Programming Using Python: Testing"
+      "Basic Programming Using Python: Unit Testing"
      ]
     },
     {
      "metadata": {},
      "source": [
       "* Explain why it is not practical to prove a program correct by testing it.\n",
-      "* Distinguish between pre-conditions, post-conditions, and invariants.\n",
-      "* Correctly raise and handle exceptions.\n",
-      "* Explain why exceptions are a better way to handle errors than special return codes.\n",
       "* Correctly write unit tests using an xUnit-style unit testing framework.\n",
-      "* Name and explain the three types of results a test can produce.\n",
-      "* Explain what test-driven development is, and use it to develop functions with well-specified behavior."
+      "* Name and explain the three types of results a test can produce."
      ]
     },
     {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
-      "We created, found, and fixed over half a dozen bugs\n",
-      "in our [previous lesson](python-4-files-lists.ipynb).\n",
-      "How can we be sure that others aren't still lurking in our code?\n",
-      "It's not an idle worry:\n",
-      "every year,\n",
-      "programmers find errors in software that has been in use for years,\n",
-      "and the number of papers that have been retracted\n",
-      "because of computational mistakes\n",
-      "is constantly growing."
+      "Like any other piece of experimental apparatus,\n",
+      "a complex program requires a much higher investment in testing than a simple one.\n",
+      "Putting it another way,\n",
+      "a small script that is only going to be used once,\n",
+      "to produce one figure,\n",
+      "probably doesn't need separate testing:\n",
+      "its output is either correct or not.\n",
+      "A linear algebra library that will be used by thousands of people\n",
+      "in twice that number of applications\n",
+      "over the course of a decade,\n",
+      "on the other hand,\n",
+      "definitely does."
      ]
     },
     {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
-      "The short answer is that it's practically impossible to prove that a program will always do what it's supposed to.\n",
+      "Unfortunately,\n",
+      "it's practically impossible to prove that a program will always do what it's supposed to.\n",
       "To see why,\n",
       "consider a function that checks whether a character strings contains only the letters 'A', 'C', 'G', and 'T'.\n",
       "These four tests clearly aren't sufficient:"
      "source": [
       "but no matter how many we have,\n",
       "we can always write a function that passes them,\n",
-      "but does the wrong thing in other cases."
+      "but does the wrong thing in other cases.\n",
+      "And as we add more tests,\n",
+      "we have to start worrying about whether the tests themselves are correct,\n",
+      "and about whether we can afford the time needed to write them.\n",
+      "After all,\n",
+      "if we really want to check that the square root function is correct for all values between 0.0 and 1.0,\n",
+      "we need to write over a billion test cases;\n",
+      "that's a lot of typing,\n",
+      "and the chances of us getting every one right are effectively zero."
      ]
     },
     {
       "we can catch and fix errors while the changes are still fresh in our minds."
      ]
     },
-    {
-     "cell_type": "heading",
-     "level": 2,
-     "metadata": {},
-     "source": [
-      "Defensive Programming"
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "The first step is to use [defensive programming](glossary.html#defensive_programming),\n",
-      "i.e.,\n",
-      "to put assertions in our programs so that they check their own execution as they run.\n",
-      "Programs like the Firefox browser are littered with assertions:\n",
-      "10-20% of the code they contain\n",
-      "are there to check that the other 80-90% are working correctly.\n",
-      "Broadly speaking,\n",
-      "assertions fall into three categories:\n",
-      "\n",
-      "- A [precondition](glossary.html#precondition) is something that must be true\n",
-      "  in order for a piece of code to work correctly.\n",
-      "- A [postcondition](glossary.html#postcondition) is something that must be true\n",
-      "  at the end of a piece of code if it worked correctly.\n",
-      "- An [invariant](glossary.html#invariant) is something that is always true\n",
-      "  at a particular point inside a piece of code."
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "For example,\n",
-      "suppose we are representing rectangles using a list of four coordinates `[x0, y0, x1, y1]`.\n",
-      "In order to do some calculations,\n",
-      "we need to normalize the rectangle so that it is at the origin\n",
-      "and 1.0 units long on its longest axis.\n",
-      "This function does that:"
-     ]
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "def normalize_rectangle(rect):\n",
-      "    x0, y0, x1, y1 = rect\n",
-      "    assert x0 < x1, 'Invalid X coordinates'\n",
-      "    assert y0 < y1, 'Invalid Y coordinates'\n",
-      "\n",
-      "    dx = x1 - x0\n",
-      "    dy = y1 - y0\n",
-      "    if dx > dy:\n",
-      "        scaled = float(dy) / dx\n",
-      "        upper_x, upper_y = 1.0, scaled\n",
-      "    else:\n",
-      "        scaled = float(dx) / dy\n",
-      "        upper_x, upper_y = scaled, 1.0\n",
-      "\n",
-      "    assert 0 < upper_x <= 1.0, 'Calculated upper X coordinate invalid'\n",
-      "    assert 0 < upper_y <= 1.0, 'Calculated upper Y coordinate invalid'\n",
-      "\n",
-      "    return [0, 0, upper_x, upper_y]"
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": [],
-     "prompt_number": 7
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "The first two assertions check that we've been given a legal rectangle,\n",
-      "while the last two check the output we're about to return to our caller.\n",
-      "Strictly speaking these post-conditions are redundant:\n",
-      "if the inputs and calculations are correct,\n",
-      "the last two assertions should always hold.\n",
-      "But those are pretty big ifs,\n",
-      "and having the program check itself can save us a lot of hunting around later."
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "<hr/>\n",
-      "### *Assertions and Bugs*\n",
-      "\n",
-      "<em>\n",
-      "Another rule that good programmers follow is, \"Bugs become assertions.\"\n",
-      "Whenever we fix a bug in a program,\n",
-      "we should add some assertions to the program at that point to catch the bug if it reappears.\n",
-      "After all,\n",
-      "if we made the mistake once,\n",
-      "then we (or someone else) might well make it again.\n",
-      "Few things are as frustrating as\n",
-      "having someone delete several carefully-crafted lines of code that fixed a subtle problem\n",
-      "because they didn't realize what problem those lines were there to fix.\n",
-      "</em>\n",
-      "<hr/>"
-     ]
-    },
-    {
-     "cell_type": "heading",
-     "level": 2,
-     "metadata": {},
-     "source": [
-      "Handling Errors"
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "Even when programmers are careful,\n",
-      "things can still go wrong.\n",
-      "Some errors have external causes,\n",
-      "like missing or badly-formatted files.\n",
-      "Others are internal,\n",
-      "like bugs in code.\n",
-      "Either way,\n",
-      "most modern programming languages handle errors in more or less the same way."
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "Let's start with a look at how people used to do error handling.\n",
-      "Back in the Dark Ages,\n",
-      "programmers wrote functions to return a [status code](glossary.html#status_code)\n",
-      "to indicate whether they had run correctly or not.\n",
-      "This led to programs like this:"
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "```python\n",
-      "params, status = read_params(param_file)\n",
-      "if status != OK:\n",
-      "    log.error('Failed to read', param_file)\n",
-      "    sys.exit(ERROR)\n",
-      "\n",
-      "grid, status = read_grid(grid_file)\n",
-      "if status != OK:\n",
-      "    log.error('Failed to read', grid_file)\n",
-      "    sys.exit(ERROR)\n",
-      "```"
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "The two function calls are all we really want;\n",
-      "the other six lines to check that files were opened and read properly,\n",
-      "and to report errors and exit if not."
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "A lot of code is still written this way,\n",
-      "but this coding style makes it hard to see the forest for the trees.\n",
-      "When we're reading a program,\n",
-      "we want to understand what's supposed to happen when everything works,\n",
-      "and only then think about what might happen if something goes wrong.\n",
-      "When the two are interleaved,\n",
-      "both are harder to understand.\n",
-      "The net result is that most programmers don't bother to check the status codes their functions return."
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "[Exceptions](glossary.html#exception) allow us to separate the \"normal\" flow of control\n",
-      "from the \"exceptional\" cases that arise when something goes wrong.\n",
-      "Using them produces code like this,\n",
-      "which is much easier to understand:"
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "```python\n",
-      "try:\n",
-      "    params = read_params(param_file)\n",
-      "    grid = read_grid(grid_file)\n",
-      "except:\n",
-      "    log.error('Failed to read', filename)\n",
-      "    sys.exit(ERROR)\n",
-      "```"
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "We join the normal case and the error-handling code using the keywords `try` and `except`.\n",
-      "These work together like `if` and `else`:\n",
-      "the statements under the `try` are what should happen if everything works,\n",
-      "while the statements under `except` are what the program should do if something goes wrong."
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "We have actually seen exceptions before without knowing it,\n",
-      "since by default,\n",
-      "when an exception occurs,\n",
-      "Python prints it out and halts our program.\n",
-      "For example,\n",
-      "trying to open a nonexistent file triggers a type of exception called an `IOError`,\n",
-      "while an out-of-bounds index to a list triggers an `IndexError`:"
-     ]
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "open('nonexistent-file.txt', 'r')"
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": [
-      {
-       "ename": "IOError",
-       "evalue": "[Errno 2] No such file or directory: 'nonexistent-file.txt'",
-       "output_type": "pyerr",
-       "traceback": [
-        "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mIOError\u001b[0m                                   Traceback (most recent call last)",
-        "\u001b[0;32m<ipython-input-8-58cbde3dd63c>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mopen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'nonexistent-file.txt'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'r'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
-        "\u001b[0;31mIOError\u001b[0m: [Errno 2] No such file or directory: 'nonexistent-file.txt'"
-       ]
-      }
-     ],
-     "prompt_number": 8
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "values = [0, 1, 2]\n",
-      "print values[999]"
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": [
-      {
-       "ename": "IndexError",
-       "evalue": "list index out of range",
-       "output_type": "pyerr",
-       "traceback": [
-        "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mIndexError\u001b[0m                                Traceback (most recent call last)",
-        "\u001b[0;32m<ipython-input-9-7fed13afc650>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[0mvalues\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mprint\u001b[0m \u001b[0mvalues\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m999\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
-        "\u001b[0;31mIndexError\u001b[0m: list index out of range"
-       ]
-      }
-     ],
-     "prompt_number": 9
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "We can use `try` and `except` to deal with these errors ourselves\n",
-      "if we don't want the program simply to fall over.\n",
-      "Here,\n",
-      "for example,\n",
-      "we put our attempt to open a nonexistent file inside a `try`,\n",
-      "and in the `except`, we print a not-very-helpful error message:"
-     ]
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "try:\n",
-      "    reader = open('nonexistent-file.txt', 'r')\n",
-      "except IOError:\n",
-      "    print 'Whoops!'"
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": [
-      {
-       "output_type": "stream",
-       "stream": "stdout",
-       "text": [
-        "Whoops!\n"
-       ]
-      }
-     ],
-     "prompt_number": 10
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "When Python executes this code,\n",
-      "it runs the statement inside the `try`.\n",
-      "If that works, it skips over the `except` block without running it.\n",
-      "If an exception occurs inside the `try` block,\n",
-      "though,\n",
-      "Python compares the type of the exception to the type specified by the `except`.\n",
-      "If they match, it executes the code in the `except` block."
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "`IOError` is Python's way of reporting several kinds of problems\n",
-      "related to input and output:\n",
-      "not just files that don't exist,\n",
-      "but also things like not having permission to read files,\n",
-      "and so on.\n",
-      "We can put as many lines of code in a `try` block as we want,\n",
-      "just as we can put many statements under an `if`.\n",
-      "We can also handle several different kinds of errors afterward.\n",
-      "For example,\n",
-      "here's some code to calculate the entropy at each point in a grid:"
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "```python\n",
-      "try:\n",
-      "    params = read_params(param_file)\n",
-      "    grid = read_grid(grid_file)\n",
-      "    entropy = lee_entropy(params, grid)\n",
-      "    write_entropy(entropy_file, entropy)\n",
-      "except IOError:\n",
-      "    log_error_and_exit('IO error')\n",
-      "except ArithmeticError:\n",
-      "    log_error_and_exit('Arithmetic error')\n",
-      "```"
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "Python tries to run the four functions inside the `try` as normal.\n",
-      "If an error occurs in any of them,\n",
-      "Python immediately jumps down\n",
-      "and tries to find an `except` of the corresponding type:\n",
-      "if the exception is an `IOError`,\n",
-      "Python jumps into the first error handler,\n",
-      "while if it's an `ArithmeticError`,\n",
-      "Python jumps into the second handler instead.\n",
-      "It will only execute one of these,\n",
-      "just as it will only execute one branch\n",
-      "of a series of `if`/`elif`/`else` statements."
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "This layout has made the code easier to read,\n",
-      "but we've lost something important:\n",
-      "the message printed out by the `IOError` branch doesn't tell us\n",
-      "which file caused the problem.\n",
-      "We can do better if we capture and hang on to the object that Python creates\n",
-      "to record information about the error:"
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "```python\n",
-      "try:\n",
-      "    params = read_params(param_file)\n",
-      "    grid = read_grid(grid_file)\n",
-      "    entropy = lee_entropy(params, grid)\n",
-      "    write_entropy(entropy_file, entropy)\n",
-      "except IOError as err:\n",
-      "    log_error_and_exit('Cannot read/write' + err.filename)\n",
-      "except ArithmeticError as err:\n",
-      "    log_error_and_exit(err.message)\n",
-      "```"
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "If something goes wrong in the `try`,\n",
-      "Python creates an exception object,\n",
-      "fills it with information,\n",
-      "and assigns it to the variable `err`.\n",
-      "(There's nothing special about this variable name&mdash;we can use anything we want.)\n",
-      "Exactly what information is recorded depends on what kind of error occurred;\n",
-      "Python's documentation describes the properties of each type of error in detail,\n",
-      "but we can always just print the exception object.\n",
-      "In the case of an I/O error,\n",
-      "we print out the name of the file that caused the problem.\n",
-      "And in the case of an arithmetic error,\n",
-      "printing out the message embedded in the exception object is what Python would have done anyway."
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "So much for how exceptions work:\n",
-      "how should they be used?\n",
-      "Some programmers use `try` and `except` to give their programs default behaviors.\n",
-      "For example,\n",
-      "if this code can't read the grid file that the user has asked for,\n",
-      "it creates a default grid instead:"
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "```python\n",
-      "try:\n",
-      "    grid = read_grid(grid_file)\n",
-      "except IOError:\n",
-      "    grid = default_grid()\n",
-      "```"
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "Other programmers would explicitly test for the grid file,\n",
-      "and use `if` and `else` for control flow:"
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "```python\n",
-      "if file_exists(grid_file):\n",
-      "    grid = read_grid(grid_file)\n",
-      "else:\n",
-      "    grid = default_grid()\n",
-      "```"
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "It's mostly a matter of taste,\n",
-      "but we prefer the second style.\n",
-      "As a rule,\n",
-      "exceptions should only be used to handle exceptional cases.\n",
-      "If the program knows how to fall back to a default grid,\n",
-      "that's not an unexpected event.\n",
-      "Using `if` and `else`\n",
-      "instead of `try` and `except`\n",
-      "sends different signals to anyone reading our code,\n",
-      "even if they do the same thing."
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "Novices often ask another question about exception handling style as well,\n",
-      "but before we address it,\n",
-      "there's something in our example that you might not have noticed.\n",
-      "Exceptions can actually be thrown a long way:\n",
-      "they don't have to be handled immediately.\n",
-      "Take another look at this code:"
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "```python\n",
-      "try:\n",
-      "    params = read_params(param_file)\n",
-      "    grid = read_grid(grid_file)\n",
-      "    entropy = lee_entropy(params, grid)\n",
-      "    write_entropy(entropy_file, entropy)\n",
-      "except IOError as err:\n",
-      "    log_error_and_exit('Cannot read/write' + err.filename)\n",
-      "except ArithmeticError as err:\n",
-      "    log_error_and_exit(err.message)\n",
-      "```"
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "The four lines in the `try` block are all function calls.\n",
-      "They might catch and handle exceptions themselves,\n",
-      "but if an exception occurs in one of them that *isn't* handled internally,\n",
-      "Python looks in the calling code for a matching `except`.\n",
-      "If it doesn't find one there,\n",
-      "it looks in that function's caller,\n",
-      "and so on.\n",
-      "If we get all the way back to the main program without finding an exception handler,\n",
-      "Python's default behavior is to print an error message like the ones we've been seeing all along."
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "This rule is the origin of the rule \"Throw Low, Catch High.\"\n",
-      "There are many places in our program where an error might occur.\n",
-      "There are only a few, though, where errors can sensibly be handled.\n",
-      "For example,\n",
-      "a linear algebra library doesn't know whether it's being called directly from the Python interpreter,\n",
-      "or whether it's being used as a component in a larger program.\n",
-      "In the latter case,\n",
-      "the library doesn't know if the program that's calling it is being run from the command line or from a GUI.\n",
-      "The library therefore shouldn't try to handle or report errors itself,\n",
-      "because it has no way of knowing what the right way to do this is.\n",
-      "It should instead just raise an exception,\n",
-      "and let its caller figure out how best to handle it."
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "Finally,\n",
-      "we can raise exceptions ourselves if we want to.\n",
-      "In fact,\n",
-      "we *should* do this,\n",
-      "since it's the standard way in Python to signal that something has gone wrong.\n",
-      "Here,\n",
-      "for example,\n",
-      "is a function that reads a grid and checks its consistency:"
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "```python\n",
-      "def read_grid(grid_file):\n",
-      "    '''Read grid, checking consistency.'''\n",
-      "\n",
-      "    data = read_raw_data(grid_file)\n",
-      "    if not grid_consistent(data):\n",
-      "        raise Exception('Inconsistent grid: ' + grid_file)\n",
-      "    result = normalize_grid(data)\n",
-      "\n",
-      "    return result\n",
-      "```"
-     ]
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "The `raise` statement creates a new exception with a meaningful error message.\n",
-      "Since `read_grid` itself doesn't contain a `try`/`except` block,\n",
-      "this exception will always be thrown up and out of the function,\n",
-      "to be caught and handled by whoever is calling `read_grid`.\n",
-      "We can define new types of exceptions if we want to.\n",
-      "And we should,\n",
-      "so that errors in our code can be distinguished from errors in other people's code.\n",
-      "However,\n",
-      "this involves classes and objects,\n",
-      "which is outside the scope of these lessons."
-     ]
-    },
     {
      "cell_type": "heading",
      "level": 2,
      "cell_type": "markdown",
      "metadata": {},
      "source": [
-      "Now that we understand how Python manages error,\n",
-      "we can return to the subject of testing.\n",
       "Most people don't enjoy writing tests,\n",
       "so if we want them to actually do it,\n",
       "it must be easy to:\n",
      "cell_type": "markdown",
      "metadata": {},
      "source": [
-      "- Use `assert` to embed pre-conditions, post-conditions, and invariants in programs.\n",
-      "- Use `raise` to signal an error, and `try`/`except` to handle errors.\n",
       "- Use a unit-testing framework to check and re-check code's correctness.\n",
       "- Put each unit test in its own small function.\n",
       "- Use test-driven development to define how functions should behave."