Decomposition, testing and debugging
แนะนำใหม่| English | ความหมาย |
|---|---|
| decomposition/ˌdiːkɒmpəˈzɪʃn/ | การแยกย่อย |
| testing/ˈtestɪŋ/ | การทดสอบ |
| debugging/ˈdiːbʌɡɪŋ/ | การดีบัก |
Where is the bug in 300 lines?
- A shop program prints the wrong total. All 300 lines run in one long block, so the mistake could be anywhere, and every change risks a new one.
- Split the same program into small functions, each checked on its own, and the question shrinks to "which piece?". This lesson is about building programs you can check and fix.
One job per function
- Decomposition 分解 means splitting a problem until each piece does one clear job. A good piece is small enough to test alone.
- Give each function a short docstring that states its contract: what goes in, what comes out, and the units.
def add_fee(amount):
"""Return amount (pence) plus a fixed 3 pence fee."""
return amount + 3
def order_total(unit_pence, quantity):
"""Return the order total in pence, fee included."""
return add_fee(unit_pence * quantity)
Which split of a "print a receipt" program is the best decomposition?
ความยาก 1/5 · 100 เหรียญ + บอนัสน้อยๆ เมื่อทำสำเร็จครั้งแรกในวันนี้
Each piece has one job and a clear input and output, so each can be tested alone. Splitting by position in the code gives pieces with no clear contract.
Return in the middle, print at the edges
- A function that returns its result can be used by other functions and checked by a test. A function that only prints cannot: its caller gets
None. - Keep
input()andprint()at the edges of the program. The middle should be functions that take values and return values.
Trace order_total(250, 4)
order_total pauses while add_fee(1000) runs, then uses the value it returns. Because both functions return, each can be tested on its own.
def show(x): print(x + 1). What value does result = show(4) store in result?
ความยาก 1/5 · 100 เหรียญ + บอนัสน้อยๆ เมื่อทำสำเร็จครั้งแรกในวันนี้
show prints 5 but has no return, so the call gives None. Nothing can use or test the 5.
A test compares actual with expected
- Testing 测试 means running code on an input where you already know the right answer.
assert order_total(250, 4) == 1003is silent when it passes and raisesAssertionErrorwhen it fails. - Work out the expected value by hand before you run the code; otherwise you will just copy whatever the code printed.
Work it out by hand before running: what should order_total(120, 3) return, in pence?
ความยาก 2/5 · 140 เหรียญ + บอนัสน้อยๆ เมื่อทำสำเร็จครั้งแรกในวันนี้
$120 \times 3 = 360$ pence, plus the 3 pence fee, gives $363$. That number goes on the right of assert order_total(120, 3) == 363.
Test the boundaries
- Bugs hide at the edges: zero, one, empty, the largest value and the exact boundary of a rule. One typical case is not enough.
- Testing
average([])shows that the function divides by zero and raisesZeroDivisionError. A test ofaverage([4, 6])alone would never find that.
- Choose tests for
order_total(unit_pence, quantity). - Typical:
order_total(250, 4) == 1003. - Zero quantity:
order_total(250, 0) == 3. The fee still applies; is that what the shop wants? A boundary test makes you decide. - Zero price:
order_total(0, 4) == 3.
You are testing average(xs), which returns sum(xs) / len(xs). Which inputs are boundary cases worth adding? Select all that apply.
ความยาก 2/5 · 140 เหรียญ + บอนัสน้อยๆ เมื่อทำสำเร็จครั้งแรกในวันนี้
Empty finds the division by zero; one item and all zeros test the edges of the rule. Repeating a typical case under another name tests nothing new.
Write the test first
- Write a test that fails, run it and see it fail, write the simplest code that passes, then tidy the code while the tests stay green.
- The test is the contract written as code. If a later change breaks the contract, a test fails at once.

Put the test-first loop in order.
ความยาก 3/5 · 200 เหรียญ + บอนัสน้อยๆ เมื่อทำสำเร็จครั้งแรกในวันนี้
Seeing the test fail first proves that it really checks something; a test that never fails could be testing nothing.
Debug by halving the search
- Debugging 调试 means finding where what you believe and what the code does split apart. First reproduce the bug with the smallest input that fails.
- Then print a value halfway through the suspect code. If it is right, the bug is in the second half; if not, the first half. Each run halves the search, so 8 stages need only 3 runs.
A bug is somewhere in a pipeline of 16 stages. Each run with one well-placed print halves the search. How many runs do you need, at most, to find the stage?
ความยาก 2/5 · 140 เหรียญ + บอนัสน้อยๆ เมื่อทำสำเร็จครั้งแรกในวันนี้
$16 \to 8 \to 4 \to 2 \to 1$: four halvings, because $2^4 = 16$.
All tests pass, output wrong? Check the wiring
- When every function passes its tests but the program is still wrong, look between the functions: arguments in the wrong order, or a returned value that is thrown away.
discount(80, 25)is60.0, but a call writtendiscount(25, 80)gives5.0. Callingadd_fee(sub)without storing the result leavessubunchanged.
If every function passes its own tests, the whole program must give the right output.
ความยาก 1/5 · 100 เหรียญ + บอนัสน้อยๆ เมื่อทำสำเร็จครั้งแรกในวันนี้
The calls between functions can still be wrong: discount(25, 80) instead of discount(80, 25), or a returned value that is never stored.
Marks that slip away
- Printing instead of returning. The caller gets
None, and nothing can be tested. - Only typical tests. Always add zero, empty and boundary cases.
- Expected values copied from the output. Work them out by hand first.
- Changing many things at once. Fix one thing, re-run all the tests, then move on.
You've got it
- Decomposition: one job per function, a docstring contract, return values in the middle and print only at the edges.
- Testing:
assert actual == expected, with the expected value worked out first; cover zero, one, empty and boundary cases. - Debugging: reproduce with the smallest failing input, halve the search with one print per run, and check the wiring between tested functions.