Spaghetti Code

Abstract

Problem: Why does spaghetti code β€” messy, poorly structured, duplicated code β€” exist in shipped games? Is it really because programmers are "stupid and lazy"?

Approach: Tim Cain draws on decades of experience as both a programmer and a designer to walk through a detailed, real-world example of how damage resistance (DR) code in an RPG evolves from a clean one-liner into an unmaintainable tangle β€” not through incompetence, but through repeated design spec changes.

Findings: The vast majority of spaghetti code is caused by design specifications changing over time without adequate time or budget allocated for refactoring. Each change is individually reasonable, but the cumulative effect β€” layered on top of code structured for the original spec β€” produces the tangled mess players and modders later judge harshly. Common proposed solutions (freezing design specs, mandatory refactoring time) have never reliably worked in practice.

Key insight: Spaghetti code is not a programmer problem β€” it's a process problem. Games are art as much as product, and design changes are inevitable. The code you judge in a shipped game has likely been rewritten multiple times under time pressure to accommodate changes no one predicted.

Source: https://www.youtube.com/watch?v=YG4i-F67YOk

The Myth of the Lazy Programmer

When people encounter spaghetti code β€” poorly structured code with duplicated methods, inconsistent interfaces, and tangled dependencies β€” the instinctive reaction is to blame the programmer. Tim Cain argues this explanation accounts for only a tiny percentage of cases. The real cause: design specs changing after code has already been written to the original spec.

The DR Example: Death by a Thousand Changes

Tim walks through a real (not hypothetical) example of how a simple damage resistance (DR) system devolves into spaghetti. Each step is individually reasonable:

The Original Spec

DR is part of armor. Players and humanoid NPCs wear one piece of armor. A clean function item_armor_get_dr() looks up the armor, gets its DR value, returns it. Beautiful, flawless code.

Change 1: Multiple Armor Pieces

Design decides players can now wear chest, arm, hand, leg, foot, and helmet pieces. DR is the sum across all pieces. The function gets a loop β€” still clean.

Change 2: Max Instead of Sum

Design changes the aggregation from sum to maximum. The loop stays, but the logic changes. Still manageable.

Change 3: Spell-Based DR via Status Effects

A spell designer adds temporary DR through the status effect system β€” a completely separate codebase. Now item_armor_get_dr() has to reach into the status effect system and add that DR too.

Change 4: The UI Conflict

The inventory UI designer complains: the UI is showing inflated DR because it now includes status effect bonuses. They want only item DR. Now the programmer faces a choice: add a boolean parameter? Make a separate function? Subtract the status effect portion for the UI only?

Change 5: Armor Degradation

Design adds armor health and degradation as a money sink. Now DR can't be calculated as a simple total β€” each piece's DR depends on its current health. The status effect system needs to know the origin of each DR bonus. Systems that were independent now need to know about each other.

Why "Just Fix It" Doesn't Work

By this point, there are bugs everywhere, angry designers, angry programmers, and everyone thinks the other side is stupid. Tim addresses the common proposed solutions:

"Don't Allow Design Changes"

No one has ever successfully designed a game perfectly on the first try. Even if the spec is implemented flawlessly and looks exactly right, it might not be fun. Changes are inevitable.

"Draw a Line β€” No Changes After Date X"

Tim has never seen this work. No one can agree on when the cutoff should be β€” alpha? Beta? Ship date?

"Give Programmers Refactoring Time"

This requires knowing in advance: What counts as a "major" change? How many will there be? How long will each refactor take? These are unanswerable questions. And every hour spent refactoring is an hour not spent on new features or bug fixes.

The Real Cost

The consequences of this cycle are visible in every shipped game:

  • Games ship late β€” refactoring and bug-fixing from spec changes eat the schedule
  • Games ship with missing features β€” the AI programmer couldn't add NPC cover behavior because they were busy rewriting DR code
  • Games ship with bugs β€” changes went in too fast to fully test and fix
  • Games ship with spaghetti code β€” some code simply never got refactored before the deadline

And after all of this, the programmer gets blamed. Designers wash their hands: "I didn't write that code." Players and modders look at the shipped code and say "wow, these developers are stupid and lazy" β€” not knowing the code has been rewritten five times under pressure.

The Core Lesson

Tim's central message, which he says he's been explaining since the 1980s: games are not products β€” they are as much art as they are product. Design changes, art changes, and code changes are not all predictable. Code is uniquely punished because you can look at it in isolation and judge it as "bad" without knowing its history. But that history β€” the accumulated weight of reasonable changes made under unreasonable time pressure β€” is almost always the real explanation.

Every game codebase Tim has ever looked at, including his own code, contains spaghetti. It's not a failure of individual skill. It's an inherent tension in how games are made.

References