Skip to content
FollowtheDamnCode

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

TechniqueMoveCures
Replace Magic Number with Symbolic Constant0.2 → VAT_RATEUnexplained literals, duplicated knowledge
Replace Data Value with Object (Replace Primitive with Object)"EUR 12.50" → Money(12.50, "EUR")Primitive Obsession
Encapsulate FieldPublic field → private + methods/propertyUncontrolled writes
Encapsulate CollectionReturn a read-only view; add add_x/remove_xCallers mutating your internals
Replace Type Code with Classstatus = 2 → Status.SHIPPED (an enum)Invalid codes, unclear meaning
Replace Type Code with Subclasses / State / StrategyWhen the code changes behaviourRepeated switches (OO Abusers)
Change Value to Reference / Reference to ValueShare one object vs copy immutable onesInconsistent 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.

A type code becomes a type

In code

Constants, a value object, an enum, and a guarded collection
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: paid

Run 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

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.