Solve The Problem You’ve Got, Not The One You Want

So, I’m demonstrating basic principles of modularity with a very simple example of some code that calculates the prices of fitted carpets.

public class CarpetQuote {
public double calculate(double width, double length, double pricePerSqM) {
double area = width * length;
return Math.ceil(area * pricePerSqM);
}
}
view raw CarpetQuote.java hosted with ❤ by GitHub

The premise of the demonstration is that our customer has told us that not all rooms are rectangular, and the code will therefore need to handle different room shapes when calculating the area of carpet required.

Now we could handle this with an enum and a switch statement, and have parameters for all the different kinds of room dimensions.

public class CarpetQuote {
public double calculate(Shape shape,
double pricePerSqM,
double width,
double length,
double radius,
double side,
double a,
double b,
double height) {
double area = 0.0;
switch(shape){
case Rectangle:
area = width * length;
break;
case Circle:
area = Math.PI * radius * radius;
break;
case EquilateralTriangle:
area = (Math.sqrt(3)/4) * side * side;
break;
case Trapezium:
area = ((a + b) / 2) * height;
break;
}
return Math.ceil(area * pricePerSqM);
}
}
view raw CarpetQuote.java hosted with ❤ by GitHub

But the drawback of that approach is that every time we need to add a new room shape, we have to modify code that was – at some point – working, and also change the method signature, breaking client code. Switch statements have a tendency to grow, as do long parameter lists. This design is rigid – difficult to change – and brittle – easy to break.

A refactored, more modular version makes it possible to extend the design much more easily, with no impact on the rest of the code.

public class CarpetQuote {
public double calculate(double pricePerSqM,
Shape shape) {
return Math.ceil(shape.getArea() * pricePerSqM);
}
}
view raw CarpetQuote.java hosted with ❤ by GitHub

Each type of shape calculates its own area, and they all implement a Shape interface. To add a new type of room shape, we just write a new class that implements that interface, and no other code is affected.

Now, this might all seem quite reasonable to you. But there’s always one person who will say something like “No, you should put it all in a single static method because it will be faster“, or “The area calculations should run in their own microservices so it will be more scalable“.

And I’ll reply “It prices fitted carpets. How fast or scalable does it need to be?”

And then there are the people who say “What if the rooms can change shape?” or “What if we want to start selling laminate flooring?”

And I’ll reply “What if they can’t?” and “What if we don’t?”

More generally, there’s a tendency for developers to try to reframe the problem to fit their desired solution. One person, for example, asked “What if that code is running inside a loop doing real-time 3D rendering?” It prices fitted carpets, for goodness’ sake!

I strongly encourage developers to solve the problem in front of them, and not to meander off into “Ah, but what if…?” territory.

The carpet quote code is optimised for extension, not for speed, because we know it needs to handle multiple room shapes – the customer specifically requested it.

Indeed, we know that code that gets used almost always gets changed, so we should optimise for easy changes unless we have a genuine need to balance that against other design goals like speed and scalability.

Most code doesn’t need to be super-fast. Most code doesn’t need to scale to Netflix levels. And when it does, that should be explicitly part of its performance requirements, and not up to the whims of developers who would rather be solving those problems than calculating prices for boring old fitted carpets.