I am kidding; nothing against Principal Engineers; some companies require them. However, every engineering organization should have its principles, and These are the ones my team and I are following at Grover.
This article discusses a few principles I value in agile software development teams. It is by no means exhaustive and is very much influenced by my challenges at the time. Inspired by conversations I had with my friend and colleague Lev Fokin.
Slow is smooth, slow is fast.
This principle is about thinking and planning your next move.
It is not about overengineering, and it is not about overplanning. It is about doing something only once, avoiding multiple hotfixes, conflicts, and rework.
When picking up some work, it is usually smoother to think about it carefully, the impact, the edge cases, how others will depend on it, and the blockers. In that case, slow is faster.
Rushing into solving problem by problem as if you are consuming them from a queue and you only think about the one thing in front of you, well, it might feel productive, but can lead to situations that you need to come back and rethink about the whole. When that happens, fast is slower.
Integrate continuously, integrate often
Thinking carefully about your next move does not mean that you must accumulate considerable work before deploying something to production.
The practice of integrating continuously is a thing and one of the good ones. We should follow it.
Breaking down what we need to do is a mighty next step after figuring out what we want to do; in other words, combining "slow is smooth, slow is fast" with "integrate continuously, integrate often" is key to improving performance and impact.
Picture the following situation: we are implementing some endpoint that, to work correctly, has to call a bunch of complicated services that also need to be adjusted; on the other hand, someone else is expecting the endpoint to be there for them to test the consumption. In this case, it makes a lot of sense to split the initial changes into at least two:
You create the endpoint and deploy it, returning some mocked that or anything like that, deploy it even to production (if you can hide it behind a feature flag or it is just not being used at all).
Then you work on the rest—incremental pull requests.
Some of the consequences and circumstances that pervade this practice:
Small and easily reviewable pull requests.
Avoid unnecessary rebases and merge conflicts.
Avoid deployment surprises when "gluing" features.
More comprehensive bug investigations and hot fixes due to very atomic deployments.
Own the solution, end to end
One way to describe software engineering work is by saying that we are problem solvers, which come in various shapes and sizes, making it hard to put them in the same basket. Still, they have at least one thing in common: they are not solved by sending a pull request. No matter how elegantly your code is, how many edge cases you covered, or if you have 100% code coverage in tests.
You still need to get it approved, and tested, and deployed, and monitored in production.
Software engineers go beyond coding something well specified and forgetting about it. Instead, we must understand the problem, propose (or at least challenge) the solution, implement it and get all the way down to post-production steps.
On a company scale, the leadership team decides on some strategic direction, and product teams propose how they are getting there.
Product managers (usually) have the last say on which feature we are building on a tech product squad. In contrast, the engineering folks should decide how they are being constructed and refine the technical details.
Depending on your seniority, it is expected that you handle by yourself simple tasks or even entire features with multiple complex dependencies.
Whatever your scope of autonomy, get involved in refining your own work, validate your ideas, get buy-in from your colleagues and find consensus when possible. Good communication and a high sense of ownership make a difference for interns and principal engineers.
Team goals over task delivery ranking
Assembling a team is based on the premise that these people can achieve more than working separately together. But unfortunately, engineers quite often tend to work alone in specific situations, diminishing the returns of teamwork.
It is easy to grasp and acknowledge the importance of group discussions, pair and mob programming sessions, or parallelization of work. Still, when none of these are feasible or simply not the choice of the moment, programmers can fall into the trap of only seeing their own TODO list.
In well-functioning Agile-based software engineering teams, people usually start working on tasks that matter the most. One could also argue that all effort put into it is virtually for nothing until that task is fully finished. Based on these two factors:
it makes much sense to try and unblock almost finished work, even if it is your teammate's work, before starting other tasks.
In practical terms, if you use a Kanban board, look at the columns from right to left. If there is something to be deployed, can you do it? If there is something to be QAed, can you do it? Consider that before starting a new task from the backlog.
Unblocking the team and allowing it to deliver more is more valuable than simply implementing more story points alone.
Is software engineering an Art or a Science?
Sure, a magnificent piece of code can be elegant, pleasant to the eyes, and framed on the wall. Still, I would instead treat it as Science every single time.
Artists have the luxury of having pride, treating their masterpieces as perfect beings that deserve to be defended, even if they require some workaround to work correctly or if they don't fit in the room.
Scientists have to prove that something works; they expose themselves to constructive criticism and understand that what is being questioned is the idea, not themselves as human beings. They accept that their views are probably incomplete and perhaps even rejoice in being wrong. After all, it is an opportunity to learn.
Be a Scientist. Allow people to feel safe criticizing your pull requests. Celebrate when someone is refactoring a piece of code of yours or touching your service.
That said, there is no such thing as your service in a company; individuals should not own microservices and features. Teams should own them. Design and implement scalable solutions, yes. And work on easily maintainable ones.
Leave it better than how you found it, but with balance.
There is no such thing as "the website is done," there is always something else to do, a new feature, some business or legal requirement. In software engineering, it is impossible to run away from the constant trade-off between
Implementing something on top of what we already have;
Refactoring and improving the current infrastructure while (or before) you accomplish a new functionality;
Starting a greenfield project to achieve something new;
Unless you have strong reasons to choose the third option, with well-thought reasons and a realistic plan, the second option is usually the way to go. But there is a catch, don't overdo it.
Imagine the situation in which you need to implement a relatively minor feature improvement in a codebase you never worked with; when you start digging around, you realize that plenty of issues annoy you and, in your eyes, are a problem. As a result, you might be tempted to rewrite the whole service from scratch before implementing your improvement; this approach has several issues.
You will substantially slow down your project and your team; Think about the extra amount of work to implement and convince everyone necessary before getting your PR approval.
You will risk slowing down other engineers and teams contributing to this service; after all, any major refactoring requires adaptation.
You will risk creating many bugs as side effects in a domain you don't fully understand.
Ok, that is not the worst thing in the world. It is even a better option than seeing something terrible and doing nothing, but remember that there is no such thing as "the website is done," you will work on that again. Do things incrementally, and amortize the time needed for the improvements.
On the other hand, if you or anybody else work in that codebase again and this is the last opportunity you will have to refactor everything, well, in that case, is it at all necessary? Nobody will benefit.
In other words:
Try to incrementally improve codebases that you and your colleagues are working on and keep on working in the future; if that is not the case, use your time for something more substantial.
What if the application is an endless source of bugs?
You may consider starting from scratch if that makes sense, but it needs to be planned and estimated, and in any case, this is not entirely an exception because the bugs being endless means that you keep working on it.
Massive refactorings and rewrites are essential and apply in many situations, but they are not supposed to be an unexpected part of a simple implementation ticket.
Have you ever seen the situation when Backend and Frontend start working on the same feature simultaneously, agree on an endpoint contract, start working on their respective tickets, and fail graciously when connecting the parts?
What about a project divided into multiple teams, where one implements some feature that is blocking the other? Do these usually work out smoothly? As initially predicted?
And when you give someone feedback, and after a long time, you realize that it was misunderstood, thus wholly ineffective.
The factor in common in these situations is that you are probably not being explicit enough.
Complex crafts, such as software development, have so many opportunities to fail one way or another. However, if your colleagues are unclear about your expectations, it has very few chances to succeed.
Different situations require different techniques, but they exist:
When working on a new endpoint, write down the fields, names, and data structures. The contract must be rich and well-defined.
Use meeting notes when agreeing on the details of a cross-team collaboration in a meeting. Use it for every session.
When talking 1:1, ask for confirmation of the understanding in the other person's own words.
Suppose you don't want to be a micromanager, orchestrating every interaction in your team. Instead, you want to have an autonomous, self-sufficient, and self-managed team; everyone needs to be very explicit.
Maybe one day, I will update the principles list, addressing other types of issues.