Programming foundations The limits of procedural code

The limits of procedural code

Where functions and shared data start to hurt, and what structure we need next.

  • 12 min read
  • 6 chapters
  • Lesson 4 of 4

What you will learn

  1. 01What procedural code does well
  2. 02Pain 1: behavior that varies by type
  3. 03Pain 2: the change that touches everything
  4. 04Pain 3: rules live apart from data
  5. 05Pain 4: hard to swap and hard to test
  6. 06What we need instead
01

What procedural code does well

In procedural style a program is a set of functions that read and change shared data. It is direct and easy to start, and for scripts and small tools it is often the best choice. Nothing in this lesson says it is wrong. It says where it strains, so you know when to change approach.

02

Pain 1: behavior that varies by type

Our parking lot now accepts bikes and trucks. The obvious change is a branch inside the fee function.

function fee(kind: string, minutes: number): number {
  const hours = Math.ceil(minutes / 60);
  if (kind === "car") return hours * 20;
  if (kind === "bike") return hours * 10;
  if (kind === "truck") return hours * 40;
  throw new Error("unknown kind");
}

It works and is easy to read at this size.

if carif bikeif truckfee()LLDbranches on kindCar ruleREQ20 per hourBike ruleREQ10 per hourTruck ruleREQ40 per hour

Swipe sideways to pan · drag a box to move it

100%
One branch for one vehicle type is fine.
03

Pain 2: the change that touches everything

Now the same kind appears elsewhere: choosing a slot size, checking height limits, printing the ticket. Each of those functions gets its own chain of if on the vehicle kind.

if carif bikeif truckfee()LLDbranches on kindCar ruleREQ20 per hourBike ruleREQ10 per hourTruck ruleREQ40 per hour

Swipe sideways to pan · drag a box to move it

100%
Each new type adds a branch to every function that cares.

Adding a motorized scooter means finding and editing every one of those chains. Miss one and the bug appears only for scooters, at runtime. This is sometimes called shotgun surgery: one idea, many scattered edits.

if carif bikeif truckfee()LLDbranches on kindCar ruleREQ20 per hourBike ruleREQ10 per hourTruck ruleREQ40 per hour

Swipe sideways to pan · drag a box to move it

100%
One new idea, many scattered edits.
04

Pain 3: rules live apart from data

A ticket has rules: the entry time cannot be in the future, and an id is never empty. In procedural code the data is a plain shape and the rules are in functions somewhere else. Any code can build an invalid ticket, because nothing ties the data to its rules.

const bad: Ticket = { id: "", plate: "", enteredAt: -5 }; // compiles fine
05

Pain 4: hard to swap and hard to test

Because functions reach directly into shared data and call each other by name, you cannot easily replace a part. Switching to a different pricing rule for holidays means editing the function, not supplying a new one. Testing means setting up the global state first and cleaning it afterwards.

06

What we need instead

Each pain points to a tool, which the coming parts of this course introduce.

  • Bundle data together with the rules that protect it, so invalid data cannot be built. This is an object or a type with behavior.
  • Let each kind of thing carry its own behavior, so a new kind adds code instead of editing old code. This is polymorphism.
  • Depend on small interfaces so parts can be replaced. This is abstraction and dependency injection.
  • Name the repeated shapes of these solutions. These are design patterns.

These tools have a cost: more types, more indirection, and more to read. For a ten line script they are not worth it. The skill is noticing the moment the pain outweighs the cost.