Software Development Guidelines
Drew LeskeIt can take a while to learn a team’s culture, and there’s no quick solve for that: it’s the culmination not only of the group’s shared experience but the years of lived experience each individual brings in, and it evolves over time and is always unique. Expected practice, on the other hand, can and should be clarified–and if your team culture has “inclusion” as a core tenet, then setting clear expectations is essential to keeping everybody on the same page.
I have each member of the team read this as one of their first tasks, and have them review it again and provide practical feedback as one of their last.
Framing the guidelines
(In other words, guidelines for the guidelines.)
First, let’s clarify intention: these are not rules. While I don’t believe rules are made to be broken, I also don’t believe they’re made to restrict people from doing good things, and it’s generally impossible to foresee all possible scenarios when devising them.
I’m also reminded of a professor of tech writing I had many moons ago, who taught me one of the most valuable lessons about writing.
Understand the rules. Break them if you have to, but know why you’re breaking them.
So let’s consider them guidelines.
Second, this needs to be short. The last thing I want to do is squash an excited new team member’s spirit with an indigestible list of commandments.
Third, each guideline needs a justification. In an early job developing for a large engineering department, there was a well-designed coding standard all employees were expected to follow (and if they didn’t, code reviews would get them in order). I kept a copy of the standards for years, although I rarely had the opportunity to make use of them, because they were an excellent model of how such things should be written. Each standard was clearly written in simple language, followed by a description of why this standard was set.
In my experience, most people are okay with following a rule, even if they disagree with it, if they are given a reasonable justification, even if they disagree at some level with the justification.
Here’s a personal example from the same job, when the coding standards were
updated for a major new undertaking involving C++ (previously the department
was assembler and C). Class and member naming would use camel case instead of
underscores, in most cases, including acronyms. So a member variable
describing the user’s universally unique identifier (UUID) might be named
myPersonalUuid
. This drove me nuts at first; I wanted to name it
myPersonalUUID
. But if the acronym appears in the middle of the variable
name, the separation of terms indicated by the camel casing is muddied.
I understood this, but I just didn’t like it. Soon enough though I realized that I was being silly. Readability and consistency are fundamental in collaborative work, the rule made sense, and at worst, it was the lesser of two evils. I accepted it, and although I twitched a bit using it for a couple of days, I got used to it.
Here’s another example from right now. In my spare time, I’m messing about with Go (the language). It uses tabs instead of spaces–and I thought Python’s PEP 8 standard of 4 spaces for indentation was way too much screen real estate. Tabs is like, what what WHAAAT–at least for me.
But as Go language developers explain for another issue, on braces and semicolons:
More important—much more important—the advantages of a single, programmatically mandated format for all Go programs greatly outweigh any perceived disadvantages of the particular style.
Enough meta, let’s get on with the show.
I’ll revisit this as I go along. I believe in living documents and revisiting principles as part of individual and collaborative evolution.
Guidelines
-
Everything is to be in Git. Anything we produce must be in an appropriate repository, and for code and code-adjacent documentation, that’s Git.
If it’s only on your workstation, it doesn’t exist.
-
Commit changes regularly. Any significant change needs to be recorded, and better to err on the side of more commits than fewer. Significant here means anything you may want to revisit for any reason.
“Save early, save often.” It was true for Leisure Suit Larry and it’s true for you. You record your progress so you can revisit earlier versions for reference or perhaps to return to previously working code. The log of commit messages can inform others of the code’s evolution and how and why it got to its current state.
-
Use effective and descriptive commit messages. Follow the 50-72 rule and other well-established conventions for commit messages. This blog post describes the purpose of a commit message, and how to get there, very well. I also found this video helpful even though I don’t lean towards those sorts of resources.
The point of a commit message is to communicate why a change was made. With a long list of commits it’s also important to be able scan them to find the change you’re looking for. Following these guidelines helps make the codebase much easier to navigate.
-
Use code comments. Be more liberal than conservative. It is better to provide an unnecessary comment, such as describing what a variable is for when the variable name is perfectly adequate, than not to provide a comment necessary to understand the code.
-
All code is reviewed by at least one other person before being merged to the main branch or another principal branch.
It is important to understand what others’ code is doing, and it is important to get at least one other set of eyes on your code so your assumptions are challenged. Having a fresh perspective from somebody who hasn’t been in the thick of it can reboot your thinking.
-
Documentation is important.
- Every project has a README at its root, describing the purpose of the software, how to use it (essential and informative use cases, or if appropriate, just the usage text), and how to build a development environment in order to contribute.
- Document enough that the rest of the team can quickly get up to speed on it, but don’t spin your wheels on it.
- Don’t try to polish it every time, but try to improve every time.
-
Knowledge sharing is important and knowledge and experience are to be celebrated.
-
Every team member is to write a blog entry every month describing something they’ve learned. This can be technical or about the science at the heart of the project, or even about the team or collaboration. It does not need to be perfect and I’ll gladly help with editing, but it does need to be sincere.
-
Regular meetings where team members describe what’s gone well for them and what hasn’t are critical for not only sharing knowledge but also for avoiding imposter syndrome and the like, and for increasing team cohesion.
-
-
Use standards where available.
- Use the accepted formatting standards of the language you’re using. This means four spaces for Python indentation and tabs for Golang.
- Use OpenAPI to define REST APIs, and if this can be used to generate code stubs then great.
-
Use tools and environments used by those around you when appropriate.
Adoption by others should generally be an endorsement of its utility and quality, and means if you get stuck there is somebody around to bounce ideas off of.
-
If you use a tool longer than a day, whether you wrote it yourself or not, share it with the team. If we don’t have a place to share it, let’s find a place.
If you have a need and find a way to meet that need, there’s a good chance somebody else has a similar need and can benefit from your solution. Even if the need doesn’t quite match theirs or the solution doesn’t quite match their preferences, it may spark a more general or elegant solution. Even if it just gets a conversation started about workflow, sharing provided a benefit.
-
Mark off known technical debt in code with a flag. Use
# TODO(dcl) There must be a more elegant way to do this
or something similar. A CI job should then scan for these and tabulate them, creating a badge or some other visible reminder of the number of TODOs in the code.- Maybe new members of the team, as part of their familiarization with the code, can tackle a number of these TODOs. This encourages getting into the code immediately without as much pressure, and since the tests are already in place (RIGHT??) and there’s nothing to document, there is less “other” work around it to do.
-
All code must have test coverage. Automate this as much as possible–hopefully 100% because testing is boring. All code must be linted where possible to ensure compliance with formatting and other standards, as well as to find bad patterns and potential bugs.