Clean code and refactoring
Lesson 3 of 3
3Clean code and refactoring
The big pictureClean code is not personal taste. It is code your colleague (or you in six months) understands quickly and so changes without fear. Refactoring is changing the shape without changing the behaviour.
What the picture shows: Safe refactoring: small steps, with the tests run after each one.
Map of this part
The concepts in order, click any one to jump to it:
Two ways into the same idea
Like tidying a kitchen: you do not change the recipes; you put the spices together, label the jars and throw out what is unused. Cooking afterwards is faster with fewer mistakes. And you tidy one shelf at a time, not the whole kitchen in a day.
Practical principles: names reveal intent (days_until_expiry, not d), a function does one thing at one level of abstraction, early returns (guard clauses) instead of deep nesting, named constants instead of magic numbers, DRY with care (duplication twice is cheaper than the wrong abstraction), and separating pure calculation from side effects (I/O). Code smells: a long function, a long parameter list, comments explaining what a name should say. Refactorings (Fowler): Extract Function, Rename, Replace Magic Number, Introduce Parameter Object; each step is small with the tests run after, and shape changes go in separate commits from behaviour changes.
Before You Start
🧪Unit tests
The testing lesson in this track.
The Core Idea
1Before and after
A realistic function: vague names, magic numbers, deep nesting. We refactor it step by step and check the results did not change:
def calc(o):
t = 0
if o:
if o["items"]:
for i in o["items"]:
t += i["p"] * i["q"]
if o["member"]:
t = t * 0.9
if t > 200:
t = t - 15
return round(t, 2)
MEMBER_DISCOUNT = 0.10
BIG_ORDER_THRESHOLD = 200
BIG_ORDER_REWARD = 15
def subtotal(items):
return sum(item["p"] * item["q"] for item in items)
def order_total(order):
if not order or not order["items"]:
return 0
total = subtotal(order["items"])
if order["member"]:
total *= 1 - MEMBER_DISCOUNT
if total > BIG_ORDER_THRESHOLD:
total -= BIG_ORDER_REWARD
return round(total, 2)
cases = [None, {"items": [], "member": False},
{"items": [{"p": 14, "q": 2}], "member": True},
{"items": [{"p": 50, "q": 5}], "member": False}]
for case in cases:
assert calc(case) == order_total(case)
print("same behaviour on", len(cases), "cases:", [order_total(c) for c in cases])2Names and small functions
A good name replaces a comment. If you need a comment explaining what a block of code does, extract it into a function named after that comment. And a function that needs the word "and" to describe it ("calculates and sends") does two things.
3Duplication and abstraction
The rule of three: the first time write it, the second time notice the repetition, the third time extract it. Early abstraction ties together things that are alike by chance; then they drift apart and it fills up with conditions.
Worked example
Problem: if user: if user.active: if user.email: send(...). Three levels of nesting. Refactor: if not user or not user.active or not user.email: return, then send(...). Check: the same three tests (None user, inactive, no email) pass. The code is now two lines read top to bottom.
Myth vs Reality
| Myth | Reality |
|---|---|
| Clean code = short code. | One clever, cryptic line is worse than five clear ones. |
| Refactoring needs a dedicated week. | The best is small and continuous: leave the file a little cleaner than you found it. |
Test your understanding
Which name is best for a variable holding the days until a subscription expires?
The name says what it holds and in what unit.
What makes a refactor safe?
Without tests you cannot know whether you changed the behaviour.
When do you extract repeated code into a function?
The rule of three avoids the wrong early abstraction.
Quick review cards
Check Your Understanding
Why separate the refactor commit from the feature?
So each is easy to review and can be reverted alone if needed.
A function called process_data. What is wrong?
The name says nothing. Name it for what it actually does.
When is a comment useful?
When it explains why (a decision or an outside constraint), not what the code does.
Resources for this lesson
Grouped by type. Start with the official docs for precision, or an article or video for the explanation.
Official docs2
Done with this lesson?
Sign in to save your progress.