Static types won't save us from bad code

Modern programming languages can be categorised in many ways, but the most common approach is to slice them by how they handle Types. Is using strong static typing a prerequisite for writing clean, maintainable code?

My development experience has mostly been with dynamically (and often weakly) typed languages.

Early in my career I worked on large scale PHP applications with millions of weekly users. If you’ve done any reading about PHP you’ve probably read "a fractal of bad design” and decided that the only thing to be done with the language is to nuke it from orbit – I can confirm that position has its merits.

Now that it's at version 7, PHP isn’t a completely terrible language any more, but it does still have legacy issues to deal with. And it’s still all too easy to write bad code in it.

This example below is not a bug in PHP. That's the way the language is designed.

<?php  $foo = 5 * "10 Green Bottles"; // $foo is integer (50)

You might remember that Steve McConnell told us that we should try to programme into a language rather than programming in a language codecomplete; and if we take that approach we can write good code in any language, even PHP. But let’s not…

The case for and against static typing

At Instil we lean towards statically typed languages. We definitely favour strongly typed over weakly typed. One of my colleagues claims “If you slice me down the middle you’ll find the words ‘strongly typed’, like a stick of Portrush rock” – I'm a bit more agnostic.

But does it really matter? Is a statically and strongly typed language really better than dynamically and weakly typed one? Where are the benefits? Here are a couple of helpful articles that list some benefits on both sides:

Static typing, but at what cost?

Writing code that has some protections built in through its type system is usually a good thing. But those protections often come with a corresponding reduction in flexibility or readability. As the last post in the list above suggests, “static typing has a cost … there is no free lunch” and it’s up to you the developer to decide whether you need those protections.

Research suggests that static typing could prevent around 15% of bugs, so it definitely has some value. Tooling is typically better with static types too – compare the refactoring IntelliJ IDEA can do with Java against what PyCharm or PHPStorm can do with Python or PHP. And the more code you have the more protection you need, so large applications will benefit more from static typing that small ones.

But remember, we don’t just have type as a means to protect us from our own mistakes. We have other tools at our disposal.

Other ways to build confidence without static typing

Testing is fundamental

Remember that untested code is broken code. Good test coverage can more than make up for the lack of static type safety with regards catching bugs. As Bob Martin says in this post, “You don’t need static type checking if you have 100% unit test coverage.”

Pair or Mob programming

Pairing partnersi (or whole mobs) can quickly spot type-related issues during development. Even if full mob programming isn't feasible, you can achieve a similar effect through regular, asynchronous code reviews.

Thoughtful naming and code structure

By concentrating on the hardest problem in software development (naming things), you can save yourself future headaches. Add semantic meaning to your variables and keep routines small to make code easier to reason about:

# Not
for i, item in enumerate(list_of_people):
    print(f"person {i}'s name is {item.name}")

# Instead
for index, person in enumerate(list_of_people):
    print(f"person {index}'s name is {person.name}")

Additonally, encapsulating code in small, intention-revealing routines helps limit variable scope and increases confidence in correctness.

So, do we really need static typing?

If we have good test coverage, regularly exercise pair programming, require rigorous code reviews, and practise clean code principles, do we really need to rely on static typing? I don’t think so.

And for me at least, readability is the clincher.

Let’s say I have an application that needs to make comparisons between custom data types. Let’s say we have a number of different types but they all match the pattern of a Pair. Each Pair in our system has two properties of varying type. We need to be able to pick out the largest item in a set of these pairs.

pair = new Pair(a, b) // where a and b are variables of any type

In Java, as long as the properties of my pair implement the Comparable interface I can write a method that uses Generics to compare any type. Generics allow me to write shorter methods, once, with safety. Without them I either need multiple implementations or risky casts or deep sets of switch or if/else statements.

private static <T extends Comparable<T>, U extends Comparable<U>> boolean isLarger(Pair<T,U> p1, Pair<T,U> p2) {
    if (p1.getFirst().compareTo(p2.getFirst()) > 0) {
        return p1.getSecond().compareTo(p2.getSecond()) > 0;
    }
    return false;
}

What is this muck?! This code is hard to read. Perhaps the getFirst() and getSecond() methods don’t help, but it’s all the <T extends Comparable<T> gubbins that really gets in the waykotlin.

Here’s the same method written in a (better) language (Python)typeerror

def is_larger(first_pair, second_pair):
    return first_pair[0] > second_pair[0] and first_pair[1] > second_pair[1]

You can see that Python's nature as a dynamic language allows me to keep my code really clean. I don't need to jump through hoops to support variables of different types in my functions the way I would in Java. And its strong type support protects me at runtime from the weirdness that PHP and Javascript exhibit.

But I still need to write better tests!

Featured Photo by Ravi Singh on Unsplash, and it's (obviously) a reference to Duck Typing


  1. I can't find an online reference from Steve on this, but you can read more about programming into a language on this medium post or buy Code Complete itself

  2. I can write it in Kotlin, which helps a bit, but the method definition is still a problem.

    private fun <T : Comparable<T>, U : Comparable<U>> isLarger(p1: Pair<T, U>, p2: Pair<T, U>): Boolean {
        return (p1.first > p2.first) && (p1.second > p2.second)
    }

  3. This code will throw a TypeError at runtime if I call it with pairs that I can't compare (for example dates and integers) so if that was a possibility I'd need to add some additional checks around this code.

blog author

Ryan Adams

Software Trainer