<jupyter_start><jupyter_text>Defensive ProgrammingSo far we have focused on the basic tools of writing a program: variables, lists, loops, conditionals, and functions.We haven't looked very much at whether a program is getting the right answer (and whether it continues to get the right answer as we change it).**Defensive programming** is the practice of expecting your code to have mistakes, and guarding against them.To do this, we will write some code that *checks its own operation*.This is generally good practice, that speeds up software development and helps ensure that your code is doing what you intend.  AssertionsA common way to make sure that a program is running correctly is to use an *assertion*.* 80-90% of the `Firefox` browser code is *assertions* to ensure that the application runs correctly!With an *assertion*, we `assert` that some condition is true, as an indicator that the program is running correctly. That is to say that, if the condition is not met, we would consider the program to be running *incorrectly* (it is not a *guarantee* of correct operation).In `Python`, the syntax for this is:```pythonassert , "Some text describing the problem"```as in the code below.<jupyter_code>numbers = [1.5, 2.3, 0.7, -0.001, 4.4]
total = 0.0
for n in numbers:
    assert n > 0.0, 'Data should only contain positive values'
    total += n
print('total is:', total)<jupyter_output><empty_output><jupyter_text>Categories of AssertionGenerally-speaking, *assertions* are one of three types:* **preconditions**: something must be true at the *start* of a function (so it can work correctly)* **postconditions**: something must be true at the *end* of a function (to establish that the function worked)* **invariants**: something that is always true (at some point in the code)The code below has both *preconditions* and *postconditions*<jupyter_code>def normalize_rectangle(rect):
    '''Normalizes a rectangle so that it is at the origin and 1.0 
    units long on its longest axis.'''
    assert len(rect) == 4, 'Rectangles must contain 4 coordinates'
    x0, y0, x1, y1 = rect
    assert x0 < x1, 'Invalid X coordinates'
    assert y0 < y1, 'Invalid Y coordinates'

    dx = x1 - x0
    dy = y1 - y0
    if dx > dy:
        scaled = float(dx) / dy
        upper_x, upper_y = 1.0, scaled
    else:
        scaled = float(dx) / dy
        upper_x, upper_y = scaled, 1.0

    assert 0 < upper_x <= 1.0, 'Calculated upper X coordinate invalid'
    assert 0 < upper_y <= 1.0, 'Calculated upper Y coordinate invalid'

    return (0, 0, upper_x, upper_y)<jupyter_output><empty_output><jupyter_text>The function takes $xy$ co-ordinates for opposite corners of a rectangle, and *normalises* it, so that it is positioned at the origin, and the longest axis has length `1.0`. The first three assertions are *preconditions* that catch invalid inputs.```pythonassert len(rect) == 4, 'Rectangles must contain 4 coordinates'assert x0 < x1, 'Invalid X coordinates'assert y0 < y1, 'Invalid Y coordinates'``` We can test this by supplying input that we know to be 'bad': three co-ordinates, instead of four.<jupyter_code>print(normalize_rectangle( (0.0, 1.0, 2.0) )) # missing the fourth coordinate<jupyter_output><empty_output><jupyter_text>The last two are *postconditions* that tell us whether the calculations were correct.```pythonassert 0 < upper_x <= 1.0, 'Calculated upper X coordinate invalid'assert 0 < upper_y <= 1.0, 'Calculated upper Y coordinate invalid'``` We *think* our code is correct, so we are not expecting any errors.<jupyter_code>print(normalize_rectangle( (0.0, 0.0, 5.0, 1.0) ))<jupyter_output><empty_output><jupyter_text>We see an *unexpected* error. This tells us that our calculations are wrong.Some inspection of the code should tell us that we need to swap over ```pythonif dx > dy:        scaled = float(dx) / dy```to```pythonif dx > dy:        scaled = float(dy) / dx```<jupyter_code>def normalize_rectangle(rect):
    '''Normalizes a rectangle so that it is at the origin and 1.0 
    units long on its longest axis.'''
    assert len(rect) == 4, 'Rectangles must contain 4 coordinates'
    x0, y0, x1, y1 = rect
    assert x0 < x1, 'Invalid X coordinates'
    assert y0 < y1, 'Invalid Y coordinates'

    dx = x1 - x0
    dy = y1 - y0
    if dx > dy:
        scaled = float(dy) / dx
        upper_x, upper_y = 1.0, scaled
    else:
        scaled = float(dx) / dy
        upper_x, upper_y = scaled, 1.0

    assert 0 < upper_x <= 1.0, 'Calculated upper X coordinate invalid'
    assert 0 < upper_y <= 1.0, 'Calculated upper Y coordinate invalid'

    return (0, 0, upper_x, upper_y)
print(normalize_rectangle( (0.0, 0.0, 5.0, 1.0) ))<jupyter_output>(0, 0, 1.0, 0.2)
StarcoderdataJupyterStructuredCleanDedup