What Makes a Senior Software Developer?
By Bevan Mardiros
What is valuable to a software development company in senior developers? For this post, first I will list the ways that a senior develper can provide value, and then detail how this value can be demonstrated, especially by distinguishing a senior from a junior developer.
- Initiative
- Wholistic technical competence
- Specialized technical competence
- Mentoring
- Organization
- Planning
- Design
- Collaboration
- Communication
It must be noted that these categories are interconnected, and the recognition of this fact is itself a component of what can set a senior developer apart. For example, although developers should independently analyze the overall development process, seek to improve it, and proactively identify problems and solve them, this must be done within a collaborative atmostphere, so that the organizationally-determined priorities are respected, or else initiative may result in conflict and waste. Where possible, I will also point out interconnections between the different elements listed above.
For all statements below, these are simply my opinions, informed by my experience, observation, and occasionally things I have read. They are not absolute truth, and it is very possible for reasonable people to come to opposite conclusions based on their own experience.
Initiative
A major component of what can set a senior developer apart is their ability to indepedently analyze the development process, identify sources of waste, and anticipate problems. This may involve identifying a problematic module and suggesting it be refactored, identifying a security flaw, adopting a new tool, writing a script to automate a manual process, etc. Whereas a junior developer will typically work only within an existing system, completing only the tasks assigned, and only within the scope described, a senior developer should be able to adapt to a particular development process, while maintaining a critical attitude. As mentioned above, it is important that this be done collaboratively, and issues brought up, and their resolutions argued for, and the work inserted into the project plan; no one should take on major tasks without informing team members. There are some exceptions. Generally, refactoring should be considered a part of normal development work, and a senior should expected to independently identify suboptimal code within a module and correct it prior to working within that module; though larger refactoring projects must be discussed. Major security flaws or critical bugs are another example. It is okay to identify such issues and immediately begin rectification, setting aside other tasks, as long as the team is notified, if the problem is such that any objection to immediately prioritizing the work is unlikely.
Initiative connects to the other elements, because your ability to evaluate better options will scale with your knowledge of alternatives. So a senior developer, experienced with a modern development process, when transfering to another team, could hardly avoid bringing that modern process with them, such as suggesting adoption of linting, formatting, version control, CI, etc. if they are not already present (while being respectful and understanding of the team's other members and their perspectives), whereas a junior developer is more likely to simply integrate into the new teams practices.
Initiative is difficult to demonstrate in the moment; if events occur which demonstrate initiative, then your should type up the story so that you can tell it if it becomes relevant.
Wholistic technical competence
This category covers familiarity with tools and best practices (or opinionated practices) across the development process and the development stack. The DevOps handbook advocates for teams with "T-shaped" developers: those who are very experienced in a small number of fields, but which have substantial understanding of many fields. Understanding in these other fields will of course be shallower than in one's specializations as no person can be an expert in everything, but having such breadth allows a developer to avoid tunnel vision: "to a hammer, every problem looks like a nail". To a pure specialist (as the handbook puts it, an "I-shaped" developer) it is easy to make mistakes by not considering how a problem could be solved better by a different technology, even if they are the best person able to implement it in the selected technology. For example, a common error of a junior developer, would be to make the "late-processing" error by using an existing DB query function, but performing a complex backend (or worse frontend) processing operation on it, or filtering it after fetching. The result is inefficiency, as writing a new database query would most likely produce a solution that would run far faster, and would avoid transmitting superfluous data over the network.
This covers broad knowledge of the tech stack within an application, but the same is true more broadly - it is necessary for a senior developer to understand most or all of the tech within an application, but also containerization, orchestration, how to write good automated tests, best practices in the development process (though these will tend to be subjective), automation, deployment pipelines, monitoring, and cloud architecture. Further, a senior developer should be opinionated. Within any of the above categories, they should be aware of multiple alternative practices & technologies, and be able (and all too willing) to argue for their favourites using facts, and in reference to the explicit requirements of the project (and not their own aesthetic preference). Other opinionated developers will inevitably disagree; it is difficult to avoid having ego play a role in such disagreement, but by being careful to base the discussion on facts and having all parties be open to different ideas, the best option will tend to rise to the top. Not everyone will always be convinced, which is fine; concensus is nice but we must also often defer in the end to specialization, or experience, or hierarchical authority, etc.
Specialized technical competence
Specialized competence is extremely valuable; it is required in order to create optimal solutions, diagnose complex problems, develop quickly or reliably (if you have an excellent process you may even do both, whereas juniors will produce obfuscated buggy code, and do it slowly). Specialized knowledge allows one to train others in a given technology, or to outline the pros and cons of multiple solutions to a single problem. Specialized knowledge is also required to evaluate whether others have similar knowledge. This can also lead to a catch-22 in hiring: if you need someone with specific specialized knowledge then the first indication that a candidate lacks such knowledge may occur only after they are hired; when they fail to solve the enterprise's problems.
Mentoring
An essential task for a senior developer is mentoring. Mentoring is the main method by which junior developers rise to the competency of senior developers. There are other methods, especially in startups where it is common for tasks to be assigned beyond what a developer might otherwise be considered qualified for, and where initiative plays a far larger role in the direction of the organization. In such a "sink or swim" situation, the developers may rapidly gain competency because there is simply no alternative; certain tasks need to be done, and the experience pool of available developers is extremely limited. There may not be senior developers to guide the juniors but the developers must nonetheless do their best to build an effective process and develop the minimum viable product so that cash can eventually flow. They will make many mistakes in the process, and in identifying and correcting these mistakes the developers will become battle-hardened and skilled. This is limiting in the long term though, and may result in developers who have lopsided skillsets; they may be extremely competent in some areas, but lacking in others.
In a more advanced enterprise, critical tasks are more likely to be done by qualified developers, limiting junior's exposure to the possibility of making costly mistakes. The result is that juniors, if they are to advance, must be deliberately mentored. If senior developers do not specifically aim to share their knowledge and develop both wholistic and specialized technical knowledge in juniors as well as train them in effective development practices, then the junior developers' skills may stagnate over the long term. This phenomena is sometimes referred to as "1 year of experience 10 times", as if an individual does not continually push their limits and learn, then their years of experience may not translate into a qualitative change in their competence.
Some practices I have found effective in mentoring is:
- collective discussion of design decisions
- peer programming with the junior driving and the navigator asking illuminating questions instead of directly giving answers
- occasional presentations of knowledge
- assigned study and other forms of professional development (to occur on company time)
- specifically assigning tasks outside of the mentee's current skillset
- detailed, in-person code reviews (not for every PR, since they are time consuming)
Many of these can be done informally, or combining them. For example, if during peer programming a junior says something that reveals a knowledge gap, then the mentor should take the time to explain in detail whatever the junior may be missing, and possible assign some readings; either a book, or article, or official documentation.
When a mentee comes to the mentor for assistance, it can be worthwhile to put preconditions. Ex. asking: "what solutions have you tried so far?", and "what documentation or forum posts have you consulted about this thing?". This will teach effective problem solving techniques while also encouraging the junior to at least attempt to solve the problem independently. This is important for learning, since even if you could solve a problem in 5 seconds, it may be better for the organization in the long run if the junior takes an hour. Don't emplace to many requirements though, and yield if they seem tired or frustrated. It is important that developers feel their are productive, are contributing, and are making progress, so use your better judgement. Another approach during pair programming is to ask questions like "what state would you expect the program to be in at this point? Why would you expect that?". This will teach the junior to reason about software and to reconsider their assumptions. This is much more useful than simply saying "I see your bug on line 276, fix it like this." The problem will be solved, but nothing has been learned.
Organization
A senior developer will have a number of responsibilities that they will have to balance. This is to an extent a good thing, and unavoidable, but we must also keep in mind the confusion or over-complexity in one's job description is one potential source of inefficiency, as is overwork and excessive concurrent tasks. Although these problems, if they are present should be addressed, a senior developer should also be able to handle such situations temporarily while also recognizing the root problem and attempting to address it. That is to say, if the developer is overworked, or if their job description has too many areas of responsibility, then this should be escalated in the organization, but until it is fixed the senior must nonetheless be effective in their work; which requires effective personal organization and time management. Note that there is a difference between having many responsibilities and having excessive concurrent tasks. The latter is inevitable for a highly competent individual who can contribute in many ways, but the latter refers to having many things which must be worked on at the same time. This results in inefficiency due to context switching. So while one may have multiple responsibilities, one one task should be in progress at any moment. Refer to "The DevOps Handbook" for more info on the wastefulness of excessive in-progress work.
Even though only one task should be in progress at any time, it is still useful to keep a list of tasks in general, organized by area of responsibility and urgency. There is much discussion to be had about how such a list should be organized, or the benefits of any specific platform, but there are some pitfalls I have noticed:
- using a backlog as the ever-growing list of unprioritized tasks
- insufficient delegation of tasks
- failing to assign a consistent amount of time to process improvement & refactoring (DevOps handbook recommends 20%)
- failing to cohesively integrate non-functional requirement related tasks into featureful work, ex. by not writing tests, not refactoring, not optimizing when writing featureful code, but instead always leaving it to later, or complaining about management that such tasks are not prioritized, when such prioritization could (or must) be considered an essential part of all development work. Note that although it can be a part of such work, it need not be all included in the same PR, such that a single tech task may result in multiple PRs: add tests, refactor, add featureful code, optimize pipeline, etc.
Planning
Effectively planning and tracking the work of multiple people in accord with the priorities of a larger organization is very difficult. There are various methods that server to mitigate this difficulty. Sprint planning ensures that only small periods are planned in detail. This can be effective, as it avoids wasted time which may result from changing requirements and priorities, however I have also seen some anti-patterns in sprint planning:
- the evergrowing backlog: when non-functional requirement work is never prioritized, tickets are often created, lest a good idea be forgotten, but then never executed.
- multi-sprint planning: it is seldom effective to plan multiple sprints ahead. The usefulness of sprints is in recognizing the variability of tech tasks and the changability of priorities. Instead, one option is to maintain documentation on the near-term priorities and the reasoning for addressing one over another, but breakdown tech tasks only for current and next priority at a time. Additional tasks will naturally be created spontaneously when devs encounter problems whose solution does not fit well into their current tasks. The planning software should allow these to be categorized coherently.
Its also useful to use tooling to assist with planning. Example include:
- gathering bugfix metrics to prioritize refactoring consistently buggy modules.
- implement post-mortems to prioritize process changes whenever a big problem comes up, with the meeting aim being to prevent that kind/class of bug from ever recurring.
- use repository/productivity manager metrics to judge rough tendency toward productivity and product stability to estimate whether non-functional requirements should be prioritized or whether overall process changes should be considered.
Design
See "A Philosophy of Software Design".
The goal of design is to minimize complexity. "Complexity is anything related to the structure of a software system that makes it hard to understand and modify the system". Complexity is caused by dependencies and obscurity, and its symptoms are change amplification, cognitive load, and unknown unknowns. This is all form the book.
Aside from what the book lists as the many, many methods of reducing complexity, I have found the following are useful:
- frequent, incremental refactors.
- rare, major refactors.
- test driven development, which ensures that all code is testable, and the specification of software is formal and self-enforcing. This can reduce unknown unknowns.
- collaborative design sessions and including design criticism in code reviews.
Collaboration
Effective practices
- pair programming
- mentoring
- design sessions
- code review
- sprint planning
- retrospectives
- post-mortems
- module ownership
Pitfalls include
- knowledge silos
- informal decision making
- ineffective code review: code review which focuses on minute details without addressing overall design, or which doesn't consider non-funcitonal requirements, or code review that is deprioritized, resulting in significant work-in-progress concurrency
Concerns with collaboration:
-
differing skill & knowledge levels
-
differing opinions, preferences, values
-
differing priorities & perspectives
-
compromise
- timelines, proportions of work
-
conflict
- get to the root issue, base conversation in facts and tradeoffs
Communication
- in project planning, working on requirements and justifying design decisions
- with stakeholders, focus on timelines, features, and workflows
- compromise
- base technical disagreements in facts and tradeoffs
- ask questions to understand the other viewpoint, rather than just imposing your own ideas.
- need to customize language for the technical level and field of the audience.
- write useful comments: see "A Philosophy of Software Design"
- architectural decision records
- keep documentation in code, ex. modular readmes
- report all user-affecting problems to the project manager (or equivalent) when noticed
- reply to all emails promptly
- be tactful with code review criticism, your teammate worked hard on it, did their best.
- read all messages once before sending.
- have a monthly meeting with your direct superior and with each direct report.
- have regular stakeholder meetings to discuss project progress
- be concise and direct