The Tension
Much of the tension in software engineering can be boiled down to two factors: Delivery and Engineering. We can visualize this with a quadrant chart:
quadrantChart x-axis Weak in Delivery --> Strong in Delivery y-axis Strong in Engineering --> Weak in Engineering quadrant-1 Code Cowboys quadrant-2 Junior Engineers quadrant-3 Over Engineers quadrant-4 Senior Engineers
The Axes
Both axes represent a developer’s ability to execute on project requirements according to functional and non-functional requirements. Different people might define both of these differently, so I will take a bit of time to explain these according to the way I use them.
Delivery
Delivery relates to the ability of an engineer to build working software that meets user needs. These needs are commonly known as requirements, and usually captured in User Stories or Business Requirements Documents (BRDs). These usually describe the intended behavior of a piece of software.
Between the two, delivery is usually the easiest to measure, because it is easier to verify. An example of this is validating that a button is clickable, or that a form is submittable. With robust testing processes, it is hard (but not impossible) to fake such behavior from an end-user perspective.
Performance capabilities might also fall under this axis in some situations. For example we might be able to validate that a page loads within a certain amount of time, when accessed from a certain geographical location.
Engineering
Engineering relates to the quality and maintainability of the code base, along with other non-functional requirements that the code base needs to satisfy.
This axis is often more intangible, because it impacts visible capabilities only indirectly. As an example, a button that submits a report might be the deliverable, but it is hard to know without technical experience whether the code that enables this is written well. Was the code written with consideration to software design approaches? Is there a clear separation of concerns between the UI and the business logic? Poor answers to these questions can cause issues when future extension or modification of the behavior is required.
There are ways to measure this, but they are not perfect – for example, we can implement code coverage metrics, but it doesn’t guarantee that the unit tests that provide the coverage are actually testing the right thing. Knowing this requires judgement.
One thing I like to ask when validating a developer’s engineering chops is this: If a competent software engineer starts working on a code base that this engineer has worked on in the past, how quickly can they become productive in it? Notice how the answer to this question is extremely hard to measure.
The Measure – Team Velocity
Before we discuss the quadrants, let’s first define something most tech people have seen before as a way to illustrate the differences between the quadrants: Scrum’s Velocity Chart.
Velocity as a metric
In Scrum, velocity is a self-reported metric of team productivity. Done honestly, it allows us to estimate how much work a team can take on, based on working tandem in the past. There are different ways to measure velocity depending on organisational context, but my preferred approach can be boiled down to 3 fundamental angles: amount of work, complexity and risk. The higher the amount of work, complexity or foreseen risk in the implementation of a piece of work, the higher the story points assigned to that piece.
In a healthy project, a (highly simplistic) velocity chart usually looks like this:
Well, more realistically:
In general, the x-axis size of the velocity chart doesn’t matter: what matters is the fluctuation. Stable delivery is the ideal state most scrum teams should aim for, because it contributes to predictability for roadmap planning.
For comparative purposes, I will use the first and simpler chart as the baseline.
The Quadrants
Junior Engineers
These are the engineers that are still finding their feet. They require either time and/or guidance to be productive and effective. Moving from a junior to a senior requires dedication, determination, reflection and a willingness to learn. There is no shortcut to this.
The transitionary phase usually involves several “eureka” moments, which I personally live for. These moments are the bread-and-butter of an engineer facing complex, interwoven, seemingly insurmountable problems that give them such sleepless nights that they end up dreaming of quitting and leaving everything behind, to go into the rurals and build a homestead and raise chickens… speaking for a friend, of course.
Senior and junior in this case can be highly contextual. A senior engineer in some cases can be considered junior in others, for example if they are just starting out in a new tech stack. Or returning back to coding after a hiatus as a product owner. The difference here is generally how quickly they can ramp up, which depends heavily on their technical fundamentals.
Velocity chart with a Junior Engineer
Junior developers start out slower, but as they gain experience they will tend to pick up more speed, and potentially even end up delivering more code than senior engineers on the team.
Why? Because the expectation for a junior engineer is that they will focus on learning the stack, and building their fundamentals. Freed from the constant context switching and added responsibilities that senior engineers usually face, their productivity code-wise is generally higher.
They are also usually more likely to not overthink solutions, resulting in cruder implementations that require more work spent by coach-type seniors in code review and mentorship to correct.
Code Cowboys
On the surface, Code Cowboys appear the most productive, but they are usually enormous generators of tech debt. These engineers care nothing for abstraction, organisation or longevity of the code base. Bugs can be solved with hacks, workarounds are the name of the game, and very quickly your code base becomes a mess of indecipherable sphaghetti.
Velocity chart with a Code Cowboy
Notice the steep climb in the beginning followed by a precipitous drop at the later stages of a project. Code cowboys are usually off to a great start, but they drag the rest of the team down by littering the code base with hacks, workarounds, and copy-paste jobs.
By my experience, non-technical stakeholders have a tendency to treat these developers like Senior Engineers, purely off of the sheer amount of output they chuck out at the start of a project. This is especially true in visibility heavy environments where non-functional requirements are paid lip-service. This is a trap. It is an easy way to fall into a situation where code is unmaintainable, and engineering turnover spikes.
How do you identify them? Among other factors, look out for developers that:
- Rarely or never push back on unrealistic timelines
- Rarely take time to do a proper code review
- Rarely communicate with other engineers on requirements and coding approaches
Over Engineers
A common problem in software engineering is complexity management. Products need to be oriented toward the needs of the end-user, commonly around use cases or jobs to be done.
The temptation to make a code base as abstract as possible is real for many engineers. This is especially true for brilliant engineers used to thinking in complexity. Counter-intuitively, this is actually a recipe for technical debt disaster. Adding abstraction for the sake of a highly flexible solution blows up the complexity of the code base – out of proportion to the actual value that can be achieved from that complexity.
All engineers (myself included!) have been guilty of this at different points in their careers, even if only in a micro-way. It takes experience to draw the line between over- and under-abstraction.
Velocity chart with an Over Engineer
Notice how the velocity of the engineer rises, but never goes above a certain point. In addition, the additional complexity that the rest of the team has to support because of the over-engineered solution slows the velocity of the team as a whole.
Over-engineers tend to view themselves as experts or highly brilliant because of their ability to tackle complexity all on their own. And this brilliance very likely is real, but their approach to it is a form of arrogance. To become better developers, these engineers need to learn how to consider the value of what they are building, as well as learn to see things from the perspective of their colleagues. Understanding this is the first step to earning the title of senior engineer.
Senior Engineers
These are engineers who know how to balance delivery and engineering. They are team-players that understand that good software needs to bring value to end-users, while also seeing that long-term sustainable development depends heavily on how “clean” the code base is. They are the ones who embody the adage that “all code is legacy code” and are willing to do something about it.
Velocity chart with a Senior Engineer
Good senior engineers are force multipliers. Notice how they are usually not the main delivery engines of the code all of the time. Instead, a good chunk of their time is spent on coding-adjacent tasks, such as mentoring, communicating and defining engineering standards, identifying solutions (open-source or commercial) that can reduce development effort, communicating with business stakeholders, and so on.
Good senior engineers are nonetheless heavy deliverers of code – it’s hard to mentor on development best practices, without skin in the game.
Some questions that senior developers ask themselves when they code:
- What should we work on that delivers the most value to the end-user?
- If a competent engineer comes by tomorrow and looks at this code, would they be able to understand what it does, and why it does it?
- How do we ensure the intangibles that pose long-term risks like security and QA concerns are addressed?
Conclusion
The use of this matrix was the result of dozens and dozens of technical interviews conducted over the course of a year for hiring.
Other factors go into influencing hiring decisions, but acknowledging this tension between delivery and engineering, and the sweet spot that makes a senior engineer, has been helpful to remind me that programming is an art that requires balance and dedication. Hope it helps you as a reader too.