What makes a good developer?

Today I'm attempting to tackle a broad and difficult question by breaking down some of the most important skills a developer needs. I've been deliberately agnostic to language, technology, and type of project here and attempt to highlight skills which are universal. These are skills and qualities I look for when hiring developers, try to instil into developers I mentor, and try to adhere to and further develop myself.

What I provide here is far from a comprehensive set of skills and attitudes needed to succeed as a developer, but are some of the ones I find most important. A good senior developer should have almost all of these, and a good junior developer has all of the attitudes and is working on gaining the skills.


Attitude

I'll cover this first because in my view it's the most important factor in making a good developer, as it decides how well you work with a team. Unlike everything else I discuss in this article, a good attitude can't be taught. Here are some of the attitudes of an excellent developer:


Best practices

Entire books have been written on the subject of best practices, and any specific languages and tooling will layer on even further practices. I've simply picked a handful of the most universal concepts to focus on, as they are are vital to any developer.

Don't Repeat Yourself

DRY is more or less the golden rule of writing code and something any developer should keep in mind as they plan and write their code. DRY code shares common components into reusable, testable units. This avoids reinventing wheels, prevents mistakes when updating shared functionality, and avoids overlapping unit tests for the same functionality. Done correctly, your code is better organised and more readable.

Every instance of repeated code has the possibility of becoming out of date when one copy is changed, so whenever code is being added to it's worth considering how to make it more DRY – though as always, it's possible to take it too far and make a mess, so discretion is important.

Separation of concerns

Keeping concerns separate is another key consideration: this ties into how you structure your codebase, as well as how you keep your code readable and DRY.

Separation of concerns is largely about keeping high-level logic away from low-level logic and making sure abstraction happens at the right levels. For example, encapsulating low-level driver logic into its own package which presents a higher-level API, and ensuring higher level functionality only has to use that API.

Here's a very contrived example in python to illustrate the basic principle; note that the different layers of file IO, "transforming" data, and putting it all together are independent modules and functions. They represent different "levels" of operation and are also organised according to function.

## io.py
def write_data(data):
    with open('/tmp/output.txt', 'w') as f:
        f.write(data)

def read_data():
    with open('/tmp/input.txt', 'r') as f:
        return f.read()


## transform.py
def embiggen(raw_data):
    n = int(raw_data)
    return str(n * 10000)

## main.py
def main():
    data = io.read_data()
    bigger = transform.embiggen(data)
    io.write_data(bigger)

Organisation and readability

This is one of the hardest and most subjective parts of writing code, and likely where everyone will have their own interpretation of what good practice looks like. Certainly there are entire books you could read on individual concepts here, so for the sake of simplicity I'll give a brief word about some of the fundamental signs of well-organised, readable code:

Testability

Well written code is easy to test. All of the principles above directly contribute to making unit testing code easier. If you adopt a Test-Driven Development (TDD) approach, you'll be thinking about testing throughout the process of deciding your project structure and writing the code; with a strict TDD approach you'll write your unit tests before anything else. Whatever your approach, you'll ultimately be ensuring unit tests cover as much of your codebase as possible, and are fast, independent, and consistent.

You'll also need to consider other forms of testing, such as integration and end to end testing, and likely collaborating with QA engineers to ensure test coverage. Each progressive stage of testing (unit, integration, end to end testing) tends to involve fewer, slower tests covering progressively higher levels of logic. Having a set of fast and comprehensive unit tests gives you confidence that changes have not impacted existing logic, and having a set of end-to-end business logic tests gives you confidence that your project still does what it's meant to. A good developer pays attention to test coverage and quality and aims for a build process which is fast and effective.

Applying best practice

This is another area which is very subjective and is a test of experience: applying best practices in a pragmatic way. A common mistake is to consider buzzwords and "the letter of the law" when making decisions on team processes, coding standards, and other aspects of development. The reality is, applying a rigid definition of any practice is a recipe for disaster. It's important to understand the strengths of an approach like TDD or Agile and ensure your process and practices are designed to make use of those strengths, and avoid simply using them as buzzwords. The correct way to apply all of these practices will vary greatly from team to team and project to project. This also means trusting the experience of developers is vital – avoid "my IDE suggests doing this" or "the linter's default config is like this" or "Google does it like this" or "a blog says do it like this". Developers have more relevant insight into development practices on your team than the creators of your tooling do, and discussing best practices pays dividends in avoiding future techdebt.

A good senior developer avoids unnecessary buzzwords and can help a team understand the tangible gains from applying best practices, and decide whether the aims are being achieved. An example of this is TDD: the core principle is to think about testing first in order to guide structure of your application and ensure it can be adequately tested. This is a sound design principle in general. However, the strictest interpretation of TDD involves writing all your tests before writing any actual code. This works fine if you're adding a small feature to an existing component or trying to fix a bug, but when working on a greenfield application, this often means attempting to write a whole test suite before getting to grips with what a sensible API or structure will look like, and before encountering any of the challenges which naturally pop up during the development process. That means applying strict TDD for the sake of it is often a mistake, though it may be a helpful process for junior developers who don't yet have the experience to make a judgement call on when to bend the rules.

It's also worth considering that most buzzwords don't have a single universally accepted definition, and between teams, or even within teams, individuals may have varying understanding of what terms mean. That's naturally guided by past experience applying these principles and the fact that every team is different. While terms are useful to aid in understanding, my biggest piece of advice here is simply don't get bogged down with buzzwords.

Unfortunately for the same reason that strict definitions don't work, it's impossible to give one-size-fits-all advice on how to apply best practices here. An understanding of what combinations work and don't work will come from gaining experience in different teams and is a key facet of being an effective senior developer or technical lead. What specifically works for your team will likely be unique.


Working with business

Soft skills like communication and project management skills are often overlooked, and often developers would rather avoid them entirely and just focus on writing code. However these skills are important to developers at all levels and become increasingly important with seniority as a developer. Frequently most direct contact with the business side will be handled by the tech lead or the most senior developers. This ensures the whole team stays in the loop, the correct technical decisions are made, and indeed that developers have minimal distraction from writing code. Every developer should be able to effectively communicate with non-technical people, though, and it's especially important that all developers understand the business value in what they're building. Here are some key soft skills for developers, especially senior developers:


Summary

I've rattled off a lot of high-level ideas in this article, and undoubtedly missed some as well. The bottom line is a good developer requires a wide variety of skills, both "soft" and technical, to do the job well. A good developer and also needs to have the right attitude from the outset. What I've discussed here is a very high level view of what I look for when hiring and training developers, and what I feel every developer should learn and improve on in order to grow and succeed.

I also discuss some of these areas in more detail in other articles, and especially focus on some best practices in Scala specifically.