Wellfire Interactive // Expertise for established Django SaaS applications

"Leaning on the compiler" and working effectively with legacy Django code (This Old Pony #48)

In his book, “Working Effectively With Legacy Code”, Michael Feathers offers a recommendation for safer refactoring: leaning on the compiler.

It’s a tried and true method for verifying changes before deployment, after all if the code won’t even compile you know you have a problem! The compiler does a lot of work for you.

In a runtime interpreted language like Python, we don’t have that benefit. It’s just one of the tradeoffs we get. However that doesn’t mean that you have to wait until deployment to find out if refactoring was safe or not.

It’s the tests, stupid

Let’s get this out of the way first: testing will uncover a lot of problems. A significant benefit of testing is just exercising code, and even if the tests aren’t otherwise particularly meaningful, they’ll often unearth other non-obvious runtime errors.

Static analysis

So tests are obviously great, but we don’t always have them. And while we may want to add them, it may be necessary to make changes to the code before they can be written. At the least it’s often necessary to make small changes just to make the code _testable _to start with.

So how can you prevent errors now? Well, taking great care is a start. But static analysis tools that analyze the source code and identify otherwise obvious bugs are a _huge _help here. Standalone tools like pyflakes[0] and composite tools like PyCharm[1] can quickly identify errors like missing imports and undefined names - errors all too common in legacy code and refactoring alike.

Ye old type signatures

This one might come as a shock to some people. “But Python is a dynamic language!”. It is, and that’s a significant reason for it’s popularity. It’s part of the reason why we can build apps with Django so damn quickly

So let’s step back. PEP 484[2] introduced _optional _type hinting. These hints, in either the form of inline type hints (Python 3.5+ only) or comments (all Python versions) allow us to specify types for function parameters, return types, and even variable declarations. And it has zero effect on the runtime - none. The only thing it materially affects is the type checker, e.g. standalone tools like mypy[3] or composite tools like PyCharm.

I’m not going to tell you that adding type hints is of no help when building a project, I am, however, going to tell you that type hints are of immense value when working on an existing project. They make it _easier _to lean _more _on tooling for identifying issues. This means it’s quicker to spot when what was supposed to be a minor change could actually result in a runtime error, and refactoring is a lot more transparent.

At Wellfire we first dove into type hints with a greenfield project a couple years back and it was helpful, sure. Did they add a major, noticeable difference? They might have, but nothing that stuck in my memory. What did happen is that we needed to make some changes over a year later and we found ourselves looking at code that somebody else might have written. The active context that you have in your head when you’re creating something was gone a year later. It was at _this _point that the type hints were golden. By informing both the developer (reading) and the type checker (er, type checking) they saved a lot of time and likely quite a few errors.

So there you have it. We have no compiler to build our Django projects before deployment but that doesn’t mean we can’t fake “leaning on the compiler” and at minimal cost.

Iterably yours,

[0] https://pypi.org/project/pyflakes/ or just use flake8 which includes it
[1] PyCharm https://www.jetbrains.com/pycharm/
[2] Type hinting https://www.python.org/dev/peps/pep-0484/
[3] mypy http://mypy-lang.org/

Learn from more articles like this how to make the most out of your existing Django site.