Decisions in Practice
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.
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}")
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.
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}")
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.
| Total | Abroad? | Expected cost | Why this case |
|---|---|---|---|
| 49.99 | no | 5 | just under the free limit |
| 50 | no | 0 | exactly on the limit |
| 99.99 | yes | 15 | just under the abroad limit |
| 100 | yes | 0 | exactly on the abroad limit |
- "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.
- 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