Topic 04

Decisions in Practice

Hands-on

A rule with several conditions can be written as an if inside an if inside an if, and it will work. It will also be hard to read, easy to break, and painful to change when the rule changes, which rules always do. This page takes one realistic rule and writes it twice: first nested, the way it tends to come out, then flat, the way it can be checked at a glance.

It is like giving directions to a house. "Turn left, then if you see the church turn right, but if you have passed the bakery go back" works, but a numbered list of turns gets people there more reliably, and anyone can check it.

The Rule

An online shop charges for delivery like this. Orders delivered in the country cost 5 to deliver, and are free from 50 upwards. Orders delivered abroad cost 15, and are free from 100 upwards. Said aloud, that is two questions, where the order goes and how big it is, and four possible answers.

The Nested Version

A first attempt usually follows the sentence: first ask where the order is going, and inside each answer, ask about the total. It is correct. With an order of 60 going abroad, the outer question answers True, the inner one False, and the cost is 15.

shipping_nested.py
total = 60
abroad = True

if abroad:
    if total >= 100:
        cost = 0
    else:
        cost = 15
else:
    if total >= 50:
        cost = 0
    else:
        cost = 5

print(f"Delivery: {cost}")
Output
Delivery: 15

Tracing it by eye takes a moment, because the answer depends on which branch you are in at two levels. Add a third condition, such as a members' discount, and the nesting grows another level.

The Flat Version

The same rule reads more easily as a list of cases, checked in order, each on one line. Handle the free cases first, then the paid ones. Each condition now says everything about its case, joined with and, and there is only one level of indentation.

shipping.py
total = 60
abroad = True

if abroad and total >= 100:
    cost = 0
elif abroad:
    cost = 15
elif total >= 50:
    cost = 0
else:
    cost = 5

print(f"Delivery: {cost}")
Output
Delivery: 15

Both versions give the same answer for every order. The flat one is easier to check line by line against the rule in words, and adding a case means adding one line in the right place.

Testing a Rule by Hand

Rules break at their edges. An order of exactly 50 is the one most likely to be handled wrongly: is it at least 50, or more than 50? The rule in words says "from 50 upwards", so 50 must be free, and that is why the program says greater than or equal to rather than greater than.

To check a rule, choose inputs on and just around every boundary, work out the expected answer from the words, and run the program for each. Every branch should run at least once. The table lists the cases for this rule; both versions above give exactly these answers. Chapter 12 turns this habit into automatic tests that run in a second.

Test cases for the delivery rule
TotalAbroad?Expected costWhy this case
49.99no5just under the free limit
50no0exactly on the limit
99.99yes15just under the abroad limit
100yes0exactly on the abroad limit
Common Confusions
  • "Nested ifs are more precise than flat ones." They express the same rule. The flat version is simply easier to read and to check.
  • "Testing one input is enough." Rules break at their edges. Test on and just around every boundary, and make every branch run.
  • "Greater than and greater than or equal give the same results." They differ at exactly one value: the boundary. The rule in words decides which one you need.
Why It Matters
  • Real rules come with exceptions. Writing them flat keeps them readable when the next exception arrives.
  • Checking the boundaries is the habit behind most bugs that are caught before customers find them.

Knowledge Check

Under the delivery rule, what does an order of exactly 50, delivered in the country, cost?

  • 5, because 50 is not more than 50 itself
  • 0, because the rule says free from 50 upwards
  • 15, because the order is sitting right on a boundary
  • It depends on which of the two versions runs it

What does the flat version change compared with the nested one?

  • It gives different answers for orders that go abroad
  • It runs faster, which is the main reason to prefer it
  • It lists every case at one level, checked in order
  • It removes the need to test the rule at the boundaries

Which inputs are most worth testing for a rule with a limit of 50?

  • Very large totals, such as a million, to stress the program
  • Values on and just around 50, such as 49.99 and 50
  • Random totals chosen without looking at the rule at all
  • Only totals well below the limit, such as 5 or 10

Why does the flat version check abroad and total >= 100 before plain abroad?

  • Python requires and conditions to be written first
  • Because 100 is bigger than 15, the cost abroad
  • To make the program handle the domestic orders first
  • The narrower case must come first, or plain abroad catches it

You got correct