Refactoring & Logic Mistakes

Thomas J. Kennedy

Contents:

1 Wait… Mistakes?

We would like to continue the example from the previous lecture, i.e., Module-08/Painting-5-Adding.

On the first offering of this course… a student pointed out that 4 * length * width is four (4) times the area of a room’s floor instead of the surface area of the room’s walls.

Instead of rewriting the lectures and examples… I figured that this example was the perfect opportunity for a discussion in how code can

and still suffer from logic and computation errors.

2 Fixing the Mistake

Let us start in compute_paint.py.


def wall_surface_area(length: float, width: float) -> float:
    """
    Compute the surface area of all four walls in a room. For the purposes of
    this estimation... it is assumed that

      1. each wall is a rectangle with no doorways or windows
      2. there are four (4) walls in the room

    Args:
        length: room length
        width: room width

    Returns:
        Wall surface area for a single room
    """

To compute the wall surface area… we need ceiling height. Let us…

def wall_surface_area(length: float, width: float, height: float) -> float:
    """
    Compute the surface area of all four walls in a room. For the purposes of
    this estimation... it is assumed that

      1. each wall is a rectangle with no doorways or windows
      2. there are four (4) walls in the room

    Args:
        length: room length
        width: room width
        height: room (ceiling) height

    Returns:
        Wall surface area for a single room
    """

The new computation can then replace the old one…

    area_long_wall = length * height
    area_wide_wall = width * height

    area_four_walls = (2 * area_long_wall) + (2 * area_wide_wall)

    return area_four_walls

3 Running Tests

Now… we run into an issue. Almost every test in test_compute_paint.py now fails. Each test needs to be updated to supply the new third (height) argument to wall_surface_area.

FAILED tests/test_compute_paint.py::test_wall_surface_area[1-1-4] - \
  TypeError: wall_surface_area() missing 1 required positional argument: 'height'
FAILED tests/test_compute_paint.py::test_wall_surface_area[1-2-8] - \
  TypeError: wall_surface_area() missing 1 required positional argument: 'height'
FAILED tests/test_compute_paint.py::test_wall_surface_area[2-2-16] - \
  TypeError: wall_surface_area() missing 1 required positional argument: 'height'
FAILED tests/test_compute_paint.py::test_wall_surface_area[10-12-480] - \
  TypeError: wall_surface_area() missing 1 required positional argument: 'height'
FAILED tests/test_compute_paint.py::test_wall_surface_area[10.5-10-420] - \
  TypeError: wall_surface_area() missing 1 required positional argument: 'height'

The first step is to update the test data in tests/test_compute_paint.py.

test_data = [
    (1, 1, 1, 4),
    (1, 2, 8, 48),
    (2, 2, 8, 64),
    (10, 12, 8, 352),
    (10.5, 10, 10, 410),
]

Take note of how the third element in each tuple now corresponds to height. The fourth element is an updated expected wall surface area.

@pytest.mark.parametrize("length, width, height, surface_area", test_data)
def test_wall_surface_area(length, width, height, surface_area):
    assert_that(wall_surface_area(length, width, height), close_to(surface_area, 1e-1))

The test_wall_surface_area function needed to have height added in three places…

Now… our entire test suite is passing. Note how we picked fixed wall surface areas for use in all other test functions. At the end of the day… the tests in test_compute_paint.py are unit tests (which means each test focuses on a single function).

4 Running the Actual Program

Now it is time to run the actual program with…

python3.11 estimate_paint.py

After entering each value…

Enter the room length: 4
Enter the room width: 5
Enter the cost per gallon of paint:  27.25

…the program crashes…

Traceback (most recent call last):
  File "Module-08/Painting-6-Logic/estimate_paint.py", line 112, in <module>
    main()
  File "Module-08/Painting-6-Logic/estimate_paint.py", line 98, in main
    area = compute_paint.wall_surface_area(length, width)

TypeError: wall_surface_area() missing 1 required positional argument: 'height'

We need to add a prompt for height. Our first modifications will be in estimate_paint.py within the gather_input function.

def gather_input(args: list[str]) -> tuple[float, float, float, float]:

The function now returns a tuple containing four (4) floats.

    """
    Check the supplied `args` for four (4) user supplied arguments (for a total
    length of 4). If three arguments were not supplied then prompt the used for
    length (in feet), width (in feet), height (in feet), and price per gallon.

    Args:
        args: command line arguments to process

    Returns:
        a four-tuple in the form (length, width, height price_per_gallon)
    """

The pydoc documentation needs to be updated to include height.

    # Command Line Arguments were supplied
    if len(args) == 5:
        length = float(args[1])
        width = float(args[2])
        height = float(args[3])

        price_per_gallon = float(args[4])

We now need to check for four (4) user-supplied command line arguments

    else:
        length = float(input("Enter the room length: "))
        width = float(input("Enter the room width: "))
        height = float(input("Enter the room height: "))

        price_per_gallon = float(input("Enter the cost per gallon of paint:  "))

We need to add an interactive prompt for height.

    return length, width, height, price_per_gallon

And… the return statement needs to be modified to include height.

5 Another Attempt

After updating gather_inputmain needs to be updated…

    length, width, price_per_gallon = gather_input(sys.argv)

needs to be updated to…

    length, width, height, price_per_gallon = gather_input(sys.argv)

That leaves one last line to update… from…

    area = compute_paint.wall_surface_area(length, width)

to…

    area = compute_paint.wall_surface_area(length, width, height)

The program now runs to completion!

Enter the room length: 4
Enter the room width: 5
Enter the room height: 8
Enter the cost per gallon of paint:  27.28
For 2 coats...
  You will need to buy 1 gallons of paint.
  You will spend $ 27.28.

For 3 coats...
  You will need to buy 2 gallons of paint.
  You will spend $ 54.56.

For 4 coats...
  You will need to buy 2 gallons of paint.
  You will spend $ 54.56.

6 Final Code

The corrected code can be found in Module-08/Painting-6-Logic.