Noun Verb Decomposition Object Oriented Design S O
You’ve stared at a blank editor, cursor blinking, wondering: should this be a class or just a function? It’s a question every intermediate coder has asked. You know Python well enough to write both, but no one ever taught you a systematic way to decide. So you guess. Sometimes you make a class that’s really just a function in disguise. Sometimes you write a function that should have been a class. And you never quite know if you got it right.
Let’s fix that. By the end of this tutorial, you’ll have a three-step mental checklist that answers that question every time — and you’ll see why the classic textbook answer is dangerously incomplete.
Our running example: a simple order-processing system. An order has items, a total, and a status. We need to apply discounts, check inventory, and send a confirmation. It’s concrete, relatable, and complex enough to show the trick — and its blind spot.
This is Part 3 of From Strategy to Code: Learning to Decompose Any Problem. Part 1 covered strategic decomposition (what to build). Part 2 covered functional decomposition (how to sequence steps) using a rolling-average example. Now we’re tackling the classic heuristic for mapping English descriptions to code — and the blind spot that’s been hiding in plain sight for forty years.
The Classic Trick: Abbott’s Textual Analysis (1983)
In 1983, computer scientist Russell Abbott published a paper called “Program Design by Informal English Descriptions” in the journal Communications of the ACM. His insight was simple and powerful: a program’s design is latent in its informal English description.
The rule: nouns in the description → candidate classes; verbs → candidate methods or operations.
Let’s apply it to our order system. Here’s the problem statement in plain English:
An Order contains LineItems. Each LineItem has a Product, a quantity, and a price. The Order calculates its total, applies a Discount, and sends a Confirmation.
Now circle the nouns: Order, LineItem, Product, Discount, Confirmation. Underline the verbs: contains, calculates, applies, sends.
Abbott says: Order, LineItem, Product, Discount, and Confirmation are candidate classes. The verbs become methods on those classes.
In plain English: You read your problem statement, circle the nouns, underline the verbs, and boom — you have a first draft of your class diagram.
It’s seductive because it feels like magic. You don’t need to think about architecture — just read and map. Let’s see what happens when we follow Abbott’s rule literally:
# Following Abbott's rule: every noun becomes a class
class Order:
def __init__(self):
self.line_items = []
self.status = "pending"
self.total = 0.0
def add_line_item(self, product, quantity, price):
item = LineItem(product, quantity, price)
self.line_items.append(item)
def calculate_total(self):
self.total = sum(item.price * item.quantity for item in self.line_items)
return self.total
def apply_discount(self, discount):
self.total = discount.apply(self.total)
def send_confirmation(self):
confirmation = Confirmation(self)
confirmation.send()
class LineItem:
def __init__(self, product, quantity, price):
self.product = product
self.quantity = quantity
self.price = price
class Product:
def __init__(self, name, price, inventory):
self.name = name
self.price = price
self.inventory = inventory
def check_inventory(self, quantity):
return self.inventory >= quantity
class Discount:
def __init__(self, percentage):
self.percentage = percentage
def apply(self, total):
return total * (1 - self.percentage / 100)
class Confirmation:
def __init__(self, order):
self.order = order
def send(self):
print(f"Confirmation sent for order with total: ${self.order.total:.2f}")
This code works. It’s not terrible. But something feels off. Let’s keep reading to see why.
Booch Makes It Mainstream (1986–1991)
Grady Booch took Abbott’s heuristic and turned it into a full-blown methodology. In his 1986 IEEE paper “Object-Oriented Development” and his 1991 textbook Object-Oriented Design with Applications, Booch codified the noun/verb approach as the standard first step in object-oriented analysis and design.
Booch’s version: “Identify the objects (nouns), then identify the operations (verbs), then establish the relationships.”
This became the standard curriculum worldwide through the 1990s. Generations of programmers learned that the first step to any design was: circle the nouns, underline the verbs.
Here’s the problem: It was taught as a complete method, not a starting heuristic. Students never learned when to break the rules.
In plain English: Booch took Abbott’s clever observation and turned it into a recipe. Generations of programmers learned to cook by that recipe — and never learned to taste.
The Blind Spot: When Nouns Become Classes That Shouldn’t Exist
Now here’s the interesting part. Look back at our order system. Abbott says Discount is a noun, so it should be a class. But what does Discount actually do? It takes a number (the total) and returns a new number. It doesn’t remember anything between calls. It has no state.
Same for Confirmation. It’s a noun, but it’s just a side-effect — send an email. It doesn’t need to remember anything.
Let’s check that against the data. The Discount class has:
__init__: stores a percentageapply: takes a total, returns a new total
That’s it. One method that takes a number and returns a number. No state that changes over time. No reason to exist as a class.
In plain English: The heuristic told you to make classes out of Discount and Confirmation. But they’re just verbs in disguise — actions that someone dressed up as nouns.
This is the core blind spot: the heuristic can’t distinguish between a noun that represents a persistent thing (an Order) and a noun that represents a transient action (a Discount).
Let’s see the difference side by side:
# The 'bad' class-based Discount
class Discount:
def __init__(self, percentage):
self.percentage = percentage
def apply(self, total):
return total * (1 - self.percentage / 100)
# Usage
discount = Discount(10)
final_total = discount.apply(100)
print(f"After discount: ${final_total:.2f}") # After discount: $90.00
# The simpler function-based version
def apply_discount(total, percentage):
"""Apply a percentage discount to a total."""
return total * (1 - percentage / 100)
# Usage
final_total = apply_discount(100, 10)
print(f"After discount: ${final_total:.2f}") # After discount: $90.00
What’s the difference? The function version is:
- Shorter: 3 lines vs. 8 lines
- Easier to test: no object instantiation needed
- More readable: you can see the data flow directly
- Less cognitive load: no class hierarchy to understand
Yegge’s ‘Kingdom of Nouns’ (2006): The Critique Arrives
In 2006, Steve Yegge published a famous blog post called “The Kingdom of Nouns” that named and shamed this blind spot. His central metaphor: “In the Kingdom of Nouns, Verbs are second-class citizens. They must be wrapped in Noun objects to be executed.”
Yegge’s critique was aimed at Java specifically, but the problem is universal in OO languages. Exhibit A: the Runnable interface. In Java, you can’t just pass a block of code. You have to create a class called Runnable, implement it, and instantiate it. That’s a lot of ceremony for what should be a simple function call.
Let’s see what that looks like in Python:
# Java-style: wrapping a verb in a noun
class Runnable:
def run(self):
print("Running a task")
# Python-style: just use a function
def run_task():
print("Running a task")
# Or even simpler: use a lambda
run_task = lambda: print("Running a task")
In plain English: Yegge pointed out that the noun/verb heuristic had become a straightjacket. If all you have is a hammer, everything looks like a class.
But here’s the catch: Yegge didn’t offer a practical fix. He diagnosed the disease but didn’t prescribe the treatment.
The Missing Step: The STATE Question
This is the hardest part of design — and the most important. Here’s the fix: after identifying a noun as a candidate class, ask one simple question:
Does this need to remember anything between calls?
If NO → it’s a plain function, not a class. Full stop.
If YES → it’s a genuine class (or at least a candidate for one).
Let’s apply this to our order system:
- Discount: remembers nothing → function
- Confirmation: remembers nothing → function
- Order: remembers items, status, total → class
- LineItem: remembers product, quantity → class
- Product: remembers name, price, inventory → class
In plain English: The STATE question is your bullshit detector for unnecessary classes. If a noun doesn’t need to remember anything, it’s just a verb wearing a noun costume.
This directly answers Yegge’s critique: you don’t need to abandon OO, you just need a better heuristic.
Let’s See What Happens: Refactoring the Order System
Now let’s refactor our order system using the STATE question. We’ll keep Order, LineItem, and Product as classes. We’ll convert Discount and Confirmation to functions.
Here’s the refactored design:
# Refactored: STATE question applied
def apply_discount(total, percentage):
"""Apply a percentage discount to a total."""
return total * (1 - percentage / 100)
def send_confirmation(order):
"""Send a confirmation for an order."""
print(f"Confirmation sent for order with total: ${order.total:.2f}")
class Order:
def __init__(self):
self.line_items = []
self.status = "pending"
self.total = 0.0
def add_line_item(self, product, quantity, price):
item = LineItem(product, quantity, price)
self.line_items.append(item)
def calculate_total(self):
self.total = sum(item.price * item.quantity for item in self.line_items)
return self.total
class LineItem:
def __init__(self, product, quantity, price):
self.product = product
self.quantity = quantity
self.price = price
class Product:
def __init__(self, name, price, inventory):
self.name = name
self.price = price
self.inventory = inventory
def check_inventory(self, quantity):
return self.inventory >= quantity
Let’s compare the two versions:
| Metric | Class-heavy version | Refactored version |
|---|---|---|
| Lines of code | 58 | 42 |
| Number of classes | 5 | 3 |
| Number of functions | 0 | 2 |
| Test complexity | Higher (need to instantiate classes) | Lower (functions are simpler) |
In plain English: The refactored version is about 27% shorter, easier to test, and more readable. You can see the flow of data without digging through class hierarchies.
This is the hardest part — knowing when to say no to a class. Your gut, trained by years of OO pedagogy, will scream at you to make everything a class. The STATE question gives you permission to resist.
When the STATE Question Gives a Tricky Answer: The Edge Cases
The STATE question isn’t a silver bullet. Some cases are genuinely ambiguous. Let’s look at an edge case: a DiscountCalculator that caches discount rules from a database.
# Ambiguous: DiscountCalculator with caching
class DiscountCalculator:
def __init__(self):
self._cache = {} # remembers discount rules between calls
def get_discount(self, customer_id):
if customer_id not in self._cache:
# Simulate database lookup
self._cache[customer_id] = 10.0 # 10% discount
return self._cache[customer_id]
# Alternative: module-level state
_discount_cache = {}
def get_discount(customer_id):
if customer_id not in _discount_cache:
_discount_cache[customer_id] = 10.0
return _discount_cache[customer_id]
In plain English: The STATE question doesn’t always give a clean yes/no. Sometimes the answer is ‘sort of’ — and that’s when you need judgment, not a rule.
Here’s the guidance: when in doubt, prefer a module-level function with a closure or a simple module variable over a class. Classes add ceremony. Only pay that cost when you need multiple instances or inheritance.
What This Actually Means for Your Daily Coding
Let’s bring it all together. Here’s your three-step mental checklist for any design problem:
- READ: Scan the problem description for nouns and verbs (Abbott’s step)
- MAP: Tentatively map nouns → classes, verbs → methods (Booch’s step)
- FILTER: For each noun, ask the STATE question. If no state → convert to a function
Here’s a code template you can copy into your own projects:
# Three-step checklist template
# Step 1: READ - identify nouns and verbs
# Nouns: [list them here]
# Verbs: [list them here]
# Step 2: MAP - tentative class/function assignment
# Class candidates: [nouns that might be classes]
# Function candidates: [verbs that might be functions]
# Step 3: FILTER - apply the STATE question
# For each class candidate, ask: does it need to remember anything between calls?
# If NO -> make it a function
# If YES -> keep it as a class
# Example:
# - Order: remembers items, status, total -> class
# - Discount: remembers nothing -> function
def apply_discount(total, percentage):
"""Apply a percentage discount to a total."""
return total * (1 - percentage / 100)
class Order:
def __init__(self):
self.line_items = []
self.status = "pending"
self.total = 0.0
def add_line_item(self, product, quantity, price):
item = LineItem(product, quantity, price)
self.line_items.append(item)
def calculate_total(self):
self.total = sum(item.price * item.quantity for item in self.line_items)
return self.total
class LineItem:
def __init__(self, product, quantity, price):
self.product = product
self.quantity = quantity
self.price = price
Practical advice: Start with Step 1 and 2 to get a first draft. Then immediately run Step 3. You’ll delete half your classes.
This is not anti-OO. We’re not saying classes are bad. We’re saying classes are expensive. Only use them when you need the state.
In plain English: Think of the STATE question as a tax audit for your class diagram. It finds the fake nouns and converts them back to verbs — saving you complexity, bugs, and testing time.
Recap: What You Learned Today
- Abbott’s noun/verb heuristic (1983) is a powerful starting point for design — but it’s incomplete.
- Booch (1986–1991) made it mainstream, but it was taught as a complete method, not a heuristic.
- Yegge (2006) critiqued the over-privileging of nouns, but didn’t offer a practical fix.
- The fix: add the STATE question — “Does this need to remember anything between calls?” — before defaulting a noun to a class.
- The STATE question directly answers Yegge’s critique: it converts fake nouns back to verbs.
Next in the series: Part 4: The Dependency Inversion Principle — Why Your Code Should Depend on Abstractions, Not Concrete Classes
Check Your Understanding
Remember: What is Abbott’s noun/verb heuristic? (Answer: nouns in the problem description become candidate classes; verbs become candidate methods.)
Understand: Why does the STATE question fix Yegge’s critique? (Answer: It prevents fake nouns — actions dressed as objects — from becoming unnecessary classes.)
Apply: Given this problem statement — “A ShoppingCart holds Items. The Cart calculates the total, applies a coupon, and generates a receipt.” — apply the STATE question. Which nouns become classes and which become functions?
Analyze: Compare the class-heavy and refactored versions of the order system. What are the trade-offs in terms of testability, readability, and extensibility?
Evaluate: When might the STATE question give a misleading answer? (Hint: think about the DiscountCalculator example with caching.)
Create: Write a function that takes a problem description as a string and outputs a suggested class/function breakdown using the three-step checklist.
Apply What You Learned is for Supporter and Insider subscribers.
Subscribe to unlock the exercises on this post.
See plansRelated articles
- Python Engineering Under review
Python Generators: How to Process Massive Datasets Without Crashing Your Computer
Learn how Python generators and the yield keyword let you stream massive datasets in constant memory, avoiding MemoryError without loading everything into RAM.
- Python Engineering Under review
Stepwise Refinement Wirth S 1971 Cure For Blank Pa
Before we touch any code, let's build intuition with something you already know: planning a dinner party.
- Python Engineering Under review
Why Programmers Freeze Before Writing Any Code An
Let's name the feeling. You have a task: "Clean this messy CSV and compute monthly revenue per customer." You've done this before. You know pandas. You know how to group data.
- Python Engineering Under review
When Automl Beats A Hand Tuned Model And When It Q
You've been there. You dropped your data into AutoGluon, walked away for lunch, came back to a 0.96 accuracy score, and felt like a genius. You deployed the model.
Looking for something else?
Search every article by title, summary or topic.