Stop Writing Classes (Jack Diederich, PyCon US 2012)

In Software Engineering

https://us.pycon.org/2012/schedule/presentation/352/

Abstract

Classes must be nouns but not every noun must be a class. If your class only has two methods and one of them is init you probably meant to write a function.

MuffinMail recently refactored their API; it went from 20 classes scattered in 22 modules down to 1 class just 15 lines long. It was a welcome change, but we’ll further refactor that down to a single function 3 lines long.

The Python stdlib is an example of a namespace that is relatively flat. You won’t find packages that consist of a single module defining an exception, and you won’t find many exceptions at all – just 165 kinds in 200k lines of code. That’s a tiny ratio compared to most projects including Django.

Of course there are things, like containers, that should be classes. As a final example we’ll add a Heap type to the heapq module (admit it, you already have one in your utils.py).

Notes

A Greeting class

  • Looks like a class.
  • Name is a noun.
  • Takes arguments and stores data in init.
  • Has a method that uses that state to do something else.
  • Instantiates a Greeting, then uses it do something.

Key finding that this is not a class is that it has two signatures and one is init.
The class is instantiated just once, then thrown away.

This class can be better written as a function.

If you find that you’re always passing the same argument to that function, use functools.partial.

Benefits of classes?

  • Separation of concerns
  • Decoupling
  • Encapsulation
  • Implementation Hiding

If someone pulls out these terms, be careful.

Classes are overused because then think they might need it later (like MuffinHash below). They don’t. Or if they do, they should just make a class later.

What’s wrong with this class?

The problem: Just two methods, one of which is init. Not even trying to hide it: the other method is call.

MuffinAPI.API(key='SECRET_API_KEY').call(('Mailing', 'stats'), {id: 1})

can be aliased like

MuffinAPI.request = API(key='SECRET_API_KEY').call

and used like the function it should have been

MuffinAPI.request(('Mailing', 'stats'), {id: 1})

This is when you know that you shouldn’t have been using a class.

Here’s what the API request function should look like:

Namespaces are not for creating taxonomies.

Namespaces are for preventing name collisions.

If you’re just using a class to hold functions together, with no need to allow the user to decide the implementation of a function (e.g. with inheritance and overridden methods), just use a module. Use a class if you actually need a taxonomy.

Unnecessary custom exception classes

services.crawler.crawlerexceptions.ArticleNotFoundException

  • requires typing ‘crawler’ and ‘exception’ twice each! <- excessive!
  • EmptyBeer or BeerError or BeerNotFound
  • but not EmptyBeerNotFoundError <- excessive
  • but why not LookupError? <- just use standard library exceptions, except for specific conditions

Adding the word Exception to the name doesn’t help. Anything after a raise or except is going to be an exception.

The Python Standard Library

  • 200K SLOC
  • 200 top loevel modules
  • averages 10 files per package
  • defines just 165 Exceptions <- anytime you think you need to define an exception, remember that the stdlib only needs 165 for 200K SLOC

Sometimes you do want a class

  • containers are a great use case for classes
  • heapq (an array that always stays sorted) is a stdlib container without a class. But all 10 functions operate on the same data object, implying that it should be a class.

Classes encourage atrocities

  • Oauth2 Implementation
  • http://code.google.com/p/google-api-python-client/source/browse/
  • 10K SLOC, 115 modules, 207 classes

Conway’s Game of Life

This all looks really good. But this is a class with two functions, one of which is init.

That’s the whole thing.

Conclusions

  1. Don’t make classes with just one function that’s not init.
  2. Don’t make new Exceptions when you don’t need to.

Leave a Reply