DEV Community

Dimitri Merejkowsky
Dimitri Merejkowsky

Posted on • Originally published at dmerej.info on

Classes Suck

Introduction

A long time ago I received a lecture about the usage of databases and software design. More specifically, how to translate relationships like “one-to-one”, “many-to-many”, or “one-to-many” inside databases schemas.

Ten years later, I still remember one thing the teacher said at the beginning of the course:

When you are designing your database schema, your enemy is the real world. The real world is always more complicated than you anticipated.

This is not new, of course, but it raises some interesting questions: how can we model the real world in our code? Is it worth it? Is not all software made of abstract objects? Are classes and instances the correct way to do it?

A two-part exploration

In these two parts series, we’ll try and answer the last question.

We will use an exercise I’ve stolen from the exercism.io website, and the two parts will argue opposing points of view.

Let’s start with part one: classes suck. Bear in mind there we’ll be a part two called classes rock, so don’t leave angry comments below just yet :)

One last thing: this is a story based on real events: the code I’m about to show below was written this way by students I’ve just taught about instances and classes in Python.

The Robot factory exercise

Here are the specifications of the exercise:

You are a developer inside a robot factory. Your task is to manage robot settings. Here are the rules:

  • When robots come off the factory floor, they have no name.

  • The first time you boot them up, a random name is generated in the format of two uppercase letters followed by three digits, such as RX837 or BC811.

  • Every once in a while we need to reset a robot to its factory settings, which means that their name gets wiped. The next time you ask, it will respond with a new random name.

And here’s the code we’re given a starting point:

class Robot:
    # Put your code here:
    pass

def test_name_is_not_set_at_first():
    robot = Robot()
    assert robot.name() is None

def test_started_robots_have_a_name():
    robot = Robot()
    robot.start()
    actual_name = robot.name()
    assert re.match(r"^[A-Z]{2}\d{3}$", actual_name)

def test_name_does_not_change_when_rebooted():
    robot = Robot()
    robot.start()
    name1 = robot.name()

    robot.stop()
    robot.start()
    name2 = robot.name()
    assert name1 == name2

def test_name_changes_after_a_reset():
    robot = Robot()
    robot.start()
    name1 = robot.name()

    robot.stop()
    robot.reset()
    robot.start()
    name2 = robot.name()
    assert name1 != name2
Enter fullscreen mode Exit fullscreen mode

Let’s try to fix tests one by one, starting with the first one.

Making the first tests pass

All we need is a name() method that returns None:

class Robot:
    def name(self):
        return None
Enter fullscreen mode Exit fullscreen mode

For the next test, we need a start method that will cause the name to change. Let’s store the name in a private attribute _name and adapt the rest of the code:

def generate_name():
    # implementation omitted for brevity

class Robot:
    def __init__ (self):
        self._name = None

    def name(self):
        return self._name

    def start(self):
        self._name = generate_name()
Enter fullscreen mode Exit fullscreen mode

To fix the next test, we need to implement stop:

def generate_name():
    ...

class Robot:
    def __init__ (self):
        self._name = None

    def name(self):
        return self._name

    def start(self):
        self._name = generate_name()

    def stop(self):
        pass
Enter fullscreen mode Exit fullscreen mode

And then we get:

test_name_does_not_change_when_rebooted:
> assert name1 == name2
E AssertionError: assert 'KJ721' == 'JO813'
E - KJ721
E + JO813
Enter fullscreen mode Exit fullscreen mode

Making the test about reboot pass - first attempt

Clearly, we have a bug in our implementation. The robot's name must not change when it’s rebooted.

If we follow the “code objects must reflect real-world objects” rule, we may be tempted to say that robots have a state and that this state can be used to know when the name must be regenerated.

And we can choose to represent the state of the robot with a stopped attribute inside the Robot class:

class Robot:
    def __init__ (self):
        self._name = None
        # Better make sure robots are stopped when
        # they come out of the factory floor :)
        self.stopped = True

    def name(self):
        return self._name

    def start(self):
        self._name = generate_name()
        self.stopped = False

    def stop(self):
        self.stopped = True
Enter fullscreen mode Exit fullscreen mode

And that’s where classes have failed us. This stopped attribute does not help at all making the test pass.

We tried and model the real world in our class but it was useless. And then we realize that there’s no so such thing as a “boolean” in the real world. In the real world, robots have LEDs that are turned on or turned off!

Making the test about reboot pass - second try

Let’s revert the change that introduced the stopped attribute, and look at the implementation again. It becomes clear that to make the test pass, we just need an if in the start method:

class Robot:
    def __init__ (self):
        self._name = None

    def name(self):
        return self._name

    def start(self):
        if self._name is None:
            self._name = generate_name()

    def stop(self):
        pass
Enter fullscreen mode Exit fullscreen mode

Making the last test pass is easy: we only need to reset the ._name attribute when the robot is reset:

class Robot:
    def __init__ (self):
        self._name = None

    def name(self):
        return self._name

    def start(self):
        if self._name is None:
            self._name = generate_name()

    def stop(self):
        pass

   def reset(self):
        self._name = None
Enter fullscreen mode Exit fullscreen mode

Now all test pass and we’re done.

(Temporary) conclusion

Looking at the code, it looks like our object has nothing to do with the real world. The closest connection with the real world is the ._name attribute which is probably just a bunch of zeros and ones inside the memory chip of the robot.

Let’s face it, using classes to represent the real world is a myth, probably spread by overzealous Java developers, or clueless researchers locked inside their ivory towers.


Or is it? Stay tuned for part two to find out!

Oldest comments (6)

Collapse
 
johannesjo profile image
Johannes Millan • Edited

Thank you for the article. I'm having trouble to follow your train of thought here though. I prefer functional programming over class based approaches myself but from this article it's not really clear to my why classes suck. Because they nudge you towards overcomplicated solutions? Maybe you can flesh this out a little more?

Collapse
 
dmerejkowsky profile image
Dimitri Merejkowsky • Edited

it's not really clear to me why classes suck

Yeah, I went for click-bait here. I'm just pointing out that there's something weird going on with classes, software design, and the real world. It seems we can use classes to represent real-world objects, but it also seems doing so sometimes backfires.

they nudge you towards overcomplicated solutions?

They definitely do that too. For more, see the Stop Writing Classes video. But I'm not talking about complexity here, I'm talking about links between code and the real world.

Maybe you can flesh this out a little more?

Well, there will be a part two soon and I hope it will make things easier to understand.

Collapse
 
dmerejkowsky profile image
Dimitri Merejkowsky

Here's part two if you want to check it out.

Collapse
 
xowap profile image
Rémy 🤖

Classes are definitely not real-world objects, that's just how bad teachers teach it.

If you code in C you'll notice that you often end up working on a given struct/handler/pointer for various operations. Classes are syntactic sugar so self gets passed to all your methods automatically.

And then you have interfaces, which build upon the way classes are exposed to make abstraction of implementation details and let you create abstractions easily.

Sooooo, I'm not sure what's your point. Should main() not be a method from a useless class? Definitely. Are classes bad? I don't know, how bad are knives, pillows or airplanes?

Collapse
 
dmerejkowsky profile image
Dimitri Merejkowsky • Edited

Classes are definitely not real-world objects, that's just how bad teachers teach it.

Totally agree. But why? Surely there must be a reason why they tell us that, right?

I'm not sure what's your point.

That's because I'm not finished :) I wanted to have a cool cliffhanger in the middle of a two-part series instead of one big boring article. I hope you can forgive me.

Collapse
 
dmerejkowsky profile image
Dimitri Merejkowsky

Part two was just published, we can continue the discussion there if you want :)