Adam Zerner

Classes often aren't the simplest tool for the job

Sometimes, the elegant implementation is just a function. Not a method. Not a class. Not a framework. Just a function. — John Carmack

When programming, you should reach for the simplest tool for the job.

Well, that's not entirely true. Using the simplest tool for the job often helps in making the code clean and easy to understand, but that isn't always the case, and that isn't your only goal. Still, I think it is a very good rule of thumb, so let's run with it and see where it takes us.

Printing

Suppose you want to print "Hello world" to the screen. I think the simplest tool for the job would be something like console.log("Hello world") or print("Hello world").

On the other hand, in some languages like Java, you are forced to create a class in order to complete this task.

class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello world"); 
  }
}

Functions

Let's continue to pick on Java. Suppose you wanted a function that says hello to a specific person. Like "Hello Alice" or "Hello Bob". More generally, "Hello {name}". What is the simplest tool for the job here?

A function! It isn't a trick question.

function sayHello(name) {
  console.log("Hello " + name);
}

Well, in Java there are no first class functions, so you'd have to use a class.

class SayHello {
  public static void toPerson(String name) {
    System.out.println("Hello " + name);
  }
}

Namespaces

Suppose you want to namespace stuff. What would be the simplest tool for the job here? Again, not a trick question: a namespace! Here's how it'd look in Clojure.

(ns currentUser)
(def email "alice@example.com")
(def login (fn [] ...))

Not all languages have such pure namespacing though. For example, in JavaScript, you'd have to use an object.

export default {
  email: "alice@example.com",
  login: function () { ... }
};

Still, an object is a lighter weight thing than a class, so I'd prefer it as a namespace over a class with static methods and/or properties.

Creating objects

Suppose you want to create objects. Say, an employee with a firstName field and a lastName field. What is the simplest tool for the job here?

Some might say a class. I think we're getting close to the point where a class is the simplest tool for the job, but we're not there yet. A factory function is a simpler tool for the job here.

function createEmployee(firstName, lastName) {
  return {
    firstName: firstName,
    lastName: lastName,
  };
}

Creating objects with shared characteristics

Suppose you have different types of employees. There are full time employees, contractors, and hourly employees. And all three have first and last names. You could use three separate factory functions like this:

function createFullTimeEmployee(firstName, lastName) {
  return {
    firstName: firstName,
    lastName: lastName,
    generatePayCheck: function () { // logic for full time employees... }
  };
}

function createContractor(firstName, lastName) {
  return {
    firstName: firstName,
    lastName: lastName,
    generatePayCheck: function () { // logic for contractors... }
  };
}

function createHourlyEmployee(firstName, lastName) {
  return {
    firstName: firstName,
    lastName: lastName,
    generatePayCheck: function () { // logic for hourly employee... }
  };
}

But that involves writing a lot of repeated code for the first and last names. Looks like factory functions aren't really a tool that gets the job done here. So perhaps a class is the simplest tool for the job in this scenario.

class Employee {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
}
class FullTimeEmployee extends Employee { ... }
class Contractor extends Employee { ... }
class PartTimeEmployee extends Employee { ... }

Be careful though. One thing to think about is whether "is a" or "has a" better describes the relationship. Here it makes sense to say that a contractor "is a(n)" employee, but for "has a" relationships, composition is the preferred way of dealing with the shared characteristics.

Wrap up

I want to keep the scope of this post narrow. I don't want to start getting into the weeds about the pros and cons of using classes vs using alternatives to classes. My goal here is just to point out that simpler alternatives often do exist. Hopefully that perspective can help better inform the decisions you make when writing code, and the opinions you form about programming languages.


If you have any thoughts, I'd love to discuss them over email: adamzerner@protonmail.com.

If you'd like to subscribe, you can do so via email or RSS feed.

#code