Rocket 2 and the importance of good software development practices, documentation, and testing
Hello! We are a team at UBC Launch Pad, a club that is devoted to building applications in a collaborative and professional environment. We were tasked with rewriting our internal Slack bot, which is responsible for member onboarding and club team management. These are a few things we tried and a few things we learned along the way while building Rocket 2.
🚧 We try to lower the barrier of entry.
Our original team knew a plethora of different programming languages, but we eventually decided on Python because of its simplicity and ease of learning. We tried to make running the project as simple as possible, so that developers can start hacking on Rocket as quickly as possible.
For example, the project utilizes pipenv as the package manager so you can run the project with a simple pipenv run launch (or a docker container, if it tickles your fancy). Similarly, we also tried to simplify Rocket’s configuration system. At first, all configuration options were in environmental variables, which were stored in .env files. Later in development, we switched to TOML files, which proved to be a slight mistake: we ended up with configuration file hierarchies that most of us didn’t completely understand. Few people knew where to put configuration options and simply opted to put them in all of the files just to be safe. It was a classic case of premature optimization — each TOML configuration file only had 2–6 variables. We reverted back to using .env files and further abstracted the configuration options. You no longer have to read the code to know about the configuration hierarchy, and instead just put your variables in a .env file. Each developer now knows immediately if they have included all of the necessary environmental options — the program will let you know the specific ones you have forgotten to include.
🤖 Like always, there is an emphasis on automation.
We talked about automating code linting and test suites. As a result, we run our code through a series of code linters. We require that every pull request follow the PEP8 style guide, have documented classes/functions everywhere, and have well-written markdown. Our documentation is built automatically per deployment, so there aren’t any frustrations with out-of-date documentation.
To avoid non-trivial typing issues sometimes caused by unruly asserts, we utilize the static type checker mypy to check for mismatched types (e.g. a string as parameter to a function that only accepts integers). This prevents team members from submitting code that assumes the type of a parameter, which has historically caused everything to crash.
📚 Documentation is not only encouraged, it is mandatory.
For team members who weren’t completely sure how some aspect of this project worked, we encouraged them to write about it to help them learn. This resulted in a lot of documentation about the different moving parts in this project which you can read on our documentation site here. It contains both information for developers (how to build and run the project, API references, etc.) and end users (how to use the different Slack commands).
This rigorous emphasis on documentation helped the project survive 4 semesters of 3 largely different teams of people. By having more eyes on the documentation, it was able to gradually improve — Each generation of developers is able to learn from past contributors.
✨ Refactoring and improving code quality is always on our minds.
We have been actively working on this project for around 2 academic years, and team members often suggest improvements to the code. We try to make sure that all the code is verbose and reasonably abstracted.
It recently came to our attention that some of our documentation was not very useful; it was only put into the code because of our strict (former) linting standards. The docstrings required at the top of every file were mostly useless and just padded out the line count. The same was said for the docstrings at the start of test suite setup functions, trivially short constructors, and verbose functions. In response to our observations, we removed the requirement. Of course, the culture of adding well-documented classes and functions has not left the project entirely — over 90% of the project still has documentation. The rule has simply transformed from a hard, do-this-or-your-build-fails rule into a soft, do-this-or-your-PR-gets-rejected rule.
When we initially designed Rocket 2, we wanted to use Python’s built-in argparse library to parse the slack slash commands as you would parse standard command-line commands. Unfortunately, we found that argparse wasn’t built for this: Whenever a user inputs an invalid command-line command, argparse wouldn’t just complain. It would take out the entire program by raising a SystemExit exception, which effectively shuts down the server. The solution we came up with was this: to catch the SystemExit exception and display help text — crude, yet effective. Plans to switch to a different library are already underway.
args = self.parser.parse_args(command_arg)
all_subcommands = list(self.subparser.choices.keys())
present_subcommands = [subcommand for subcommand in
if subcommand in command_arg]
present_subcommand = None
if len(present_subcommands) == 1:
present_subcommand = present_subcommands
return self.get_help(subcommand=present_subcommand), 200
The future and beyond
Rocket 2 is currently in maintenance mode (what I’d call “passive development”). In the past, we have had 3 separate teams working on it. Most of the core features and functionalities are already implemented. However, we still have a number of things in store, mostly things that help with the upkeep of the project, such as refactors and clean-ups. There is still a lot of room for improvement.
Check out the Rocket 2 repository if you want to have a look. And if you see something amiss, feel free to make an issue or pull request!