Refactoring: Organizing Data
Give raw values names, types, and rules — replace magic numbers, wrap primitives, encapsulate fields and collections, and turn type codes into classes.
Big idea
A lot of design debt hides in how data is represented: magic numbers, strings that mean more than strings, public fields anyone can set, and integer codes that switch behaviour. Organizing data means giving each concept a name, a type, and a guardian — so the rules travel with the value.
The techniques
| Technique | Move | Cures |
|---|---|---|
| Replace Magic Number with Symbolic Constant | 0.2 → VAT_RATE | Unexplained literals, duplicated knowledge |
| Replace Data Value with Object (Replace Primitive with Object) | "EUR 12.50" → Money(12.50, "EUR") | Primitive Obsession |
| Encapsulate Field | Public field → private + methods/property | Uncontrolled writes |
| Encapsulate Collection | Return a read-only view; add add_x/remove_x | Callers mutating your internals |
| Replace Type Code with Class | status = 2 → Status.SHIPPED (an enum) | Invalid codes, unclear meaning |
| Replace Type Code with Subclasses / State / Strategy | When the code changes behaviour | Repeated switches (OO Abusers) |
| Change Value to Reference / Reference to Value | Share one object vs copy immutable ones | Inconsistent copies; needless aliasing |
Think of it like
A pharmacy that stores pills in unlabelled jars with codes scrawled on the lid. Labelling the jars (constants), putting each medicine in a dosing pack that knows its maximum daily dose (value objects), and locking the cabinet so only the pharmacist dispenses (encapsulation) don't change the medicine — they make mistakes much harder.
In code
from dataclasses import dataclass
from enum import Enum
VAT_RATE = 0.20 # was a bare 0.2 in four places
@dataclass(frozen=True)
class Money: # was a float plus a separate currency string
amount: float
currency: str
def __add__(self, other):
if other.currency != self.currency:
raise ValueError(f"can't add {other.currency} to {self.currency}")
return Money(round(self.amount + other.amount, 2), self.currency)
def with_vat(self):
return Money(round(self.amount * (1 + VAT_RATE), 2), self.currency)
class Status(Enum): # was status = 0, 1, 2, 3
PLACED = "placed"
PAID = "paid"
SHIPPED = "shipped"
class Order:
def __init__(self):
self._lines = [] # encapsulated collection
self.status = Status.PLACED
def add_line(self, price):
if self.status is not Status.PLACED:
raise ValueError("order is locked")
self._lines.append(price)
def lines(self):
return tuple(self._lines)
def total(self):
total = Money(0, "EUR")
for line in self._lines:
total = total + line
return total.with_vat()
order = Order()
order.add_line(Money(10.00, "EUR"))
order.add_line(Money(5.50, "EUR"))
print("total:", order.total())
order.status = Status.PAID
for attempt in (lambda: order.add_line(Money(1, "EUR")), lambda: Money(1, "EUR") + Money(1, "USD")):
try:
attempt()
except ValueError as err:
print("refused:", err)
print("lines:", len(order.lines()), "| status:", order.status.value)output
total: Money(amount=18.6, currency='EUR')
refused: order is locked
refused: can't add USD to EUR
lines: 2 | status: paidRun and edit it in the app.
Mixing currencies, adding to a paid order, and typo'd status codes are now impossible or loud, instead of silent.
Why is Money(12.50, "EUR") better than a float 12.50 passed alongside a currency string?
A type code only labels data (a shirt size) and never changes behaviour. Which refactoring fits?
Watch it
How it relates
- The cure for Primitive Obsession and Data Clumps; Encapsulate Field is Encapsulation applied after the fact.
- Type codes that drive behaviour lead to State, Strategy, or Simplifying Conditionals.
- Value objects are usually immutable — safe to share, like a Flyweight.
Key terms
- Magic number — An unexplained literal value in code.
- Value object — A small immutable object defined by its values, such as Money or DateRange.
- Encapsulate collection — Hiding a collection behind methods so callers can't modify it directly.
- Type code — A raw value used to classify objects; better expressed as an enum or classes.