What makes someone a good software engineer as opposed to a bad one? As software engineers, this is a question that we have to constantly ask ourselves and each other to build great teams and grow personally. Reading Ben Horowitz’s “Good Product Manager / Bad Product Manager” piece inspired me to write a similar piece for Software Engineers. Below is a list of traits of good engineers, in no particular order. This is certainly an unfinished list.
Disclaimer: Traits of great engineers vary based on seniority, specialization and companies. The following discussion strictly compares good engineers with bad ones, and does not intend to comment on great engineers.
Write good code
Good engineers follow a style guide. Good engineers name things appropriately. Good engineers document their code well while avoiding obvious comments. Good engineers write the minimum code necessary. Good engineers introduce the appropriate level of abstraction. Good engineers refactor often as the codebase grows. Good engineers separate business logic from other program logic.
Bad engineers only care about getting the expected output from the code they write. Their job is done when they see the output.
Move fast with high quality output
Good engineers know when to make the tradeoff of quality for time and when not to. They understand the burden of technical debt and pay back the debt as soon as possible before it gets unmanageable. Good engineers constantly improve themselves to maximize the amount of quality code they can write in a given amount of time. Being constrained for time is never an excuse for writing poor code.
GSD (Get Stuff Done) is not an excuse for writing poor code. Bad engineers write low-quality code and claim that they are optimizing for GSD. Code written by bad engineers becomes a liability for the organization, and eventually someone else is left to clean up the broken mess.
Good engineers do not blindly write code for a project. They question every problem with the goal of determining if the task at hand is, in fact, a problem or simply a symptom.
They step back and figure out the root problem which is causing the symptom. They are able to oscillate between understanding the details while seeing the big picture to make sure that they are solving the correct problem.
Bad engineers do whatever they are asked to do without questioning what problem they are trying to solve. Blind implementation adds zero to minimal value over time. This type of behavior optimizes for the short-term without consideration for long-term effects.
Here is an example of what a good engineer vs. a bad engineer would do in a given situation. A product manager notices that it takes a long time to load one of the pages of the company site. The engineers on the team are asked to cache the response to boost the performance. A bad engineer implements this change. A good engineer steps back, tries to understand the data and identifies the root problem that is causing the slow loading time. In her analysis, the good engineer may find that 90% of the total loading time is spent on one of the database queries being performed, and there is a missing database index which should be present. By stepping back before implementing a solution, the good engineer is able to solve the root problem, instead of its symptoms. Solving symptoms masks the root problem temporarily.
Good engineers break a large problem into small ones. These small problems are easier to conceive, understand, program, maintain and reuse. Good engineers recognize a pattern and generalize/abstract the pattern. Bad engineers start coding as soon as possible.
Make sound decisions
Good engineers know there is no perfect design/service/tool that works under all circumstances. Good engineers understand the available options well. Good engineers understand the constraints at hand and make appropriate tradeoffs.
Bad engineers blindly use present-day popular designs/services/tools. Bad engineers do not consider options beyond those that they are superficially familiar with.
Good engineers are good team players. They write code not only to communicate with machines, but also, and more importantly, to communicate with other engineers. They know that code spends the majority of its lifetime being read by others who are constantly improving it and building upon it.
They write test suites, not just to be confident about their code, but also to communicate how their code should behave, thereby making it easy for others to make changes over time.
Bad engineers do not care about the maintainability or readability of their code. Their job is done when the code “works” at the time that it was written. They write tests obligatorily.
Good engineers leverage code reviews and use them as opportunities to learn other parts of the codebase, share their style, and control the quality of the overall codebase. They understand and truly believe that code reviews are one of the most effective times to learn from each other and improve the entire engineering team’s output quality.
Good engineers comment not to prove that they are smart, but rather to genuinely help their team members grow. They consider overall design, edge cases, stylistic improvements, and other optimizations that increase the overall quality of the codebase. The comments usually answer the “Why” in addition to the “What”.
Good engineers have the long-term perspective that enables them to bear the ups and downs of software engineering. Engineers go through many de-motivational moments when they, for example, get calls for live server problems, deal with a high-debt codebase, or face tight deadlines. Even under these circumstances, good engineers can maintain a positive attitude, a high level of self-motivation and focused concentration similar to athletes.
Bad engineers easily get de-motivated and their motivation is not self-driven. They wear their stress on their sleeves and blame others. They get easily frustrated when things do not go well and start distracting others by asking their teammates without first trying to solve the problem themselves in earnest.