As I mentioned earlier in this blog series, there are a huge number of development tools and frameworks available and each seems to have their own benefits and use cases. As part of your development standards, you may select a handful of tools for your project and provide training for those tools so that your team has the same baseline understanding. This also ensures that your team is able to support one another with tasks and bugs given that everyone is using the same suite of tools. However, it’s important to make it easier for developers to onboard to new projects, regardless of the technology being used.
This part of my development standards blog series walks through recommendations for improving and streamlining the development processes at an organization in order to improve developer experience and reduce time spent on development related tasks. The next two parts of this blog series will discuss code reviews, a process topic that was large enough to cover on its own.
Self-Contained Development Environments
I have worked on a number of Python projects and there’s normally a requirements.txt file that specifies all of the Python package dependencies. I’ll install the dependencies using pip and then be on my way. Unfortunately there are some cases where the file is incomplete or inaccurate, and I need to spend more time determining what dependency is missing or resolve a dependency conflict that only occurs on my system. What can we do to avoid these situations? Use a virtual environment.
Virtual Environments
A virtual environment is a self-contained development environment for a project. In most cases, this means that the virtual environment contains all of the code dependencies and keeps the dependencies separate from the global installations local to your system. With virtual environments, you don’t have to worry about messing up other environments or resolving system dependency conflicts as everything is handled in that contained development environment.
I recently discovered pipenv and virtualenv, two of Python’s many modules for managing virtual environments and dependencies and was surprised at how easy it is to get projects up and running. By using either of these modules, a virtual environment is created and all dependencies are installed inside of the virtual environment. Additionally, pipenv tracks dependencies and provides a dependency lock file for creating deterministic builds. This ensures that there are no dependency conflicts and everything is installed locally for that codebase, making it easy to jump in and get started with a known working build.
Containerization
Some languages, like Javascript, do not need virtual environments as the dependencies are already kept at a project level. In this case, we still need to ask ourselves the question, “How can I ensure this project is transferable and does not require or conflict with global or system configurations?”. In some cases, the answer is to containerize the solution using a tool like Docker. Containerization is, just like it sounds, putting a solution into a box and then shipping (deploying) that box. A container has all the components necessary for a solution to run, including the operation system and dependencies, and it can be developed on different types of machines since everything is contained within the box. This makes development simple since developers won’t need to set up everything locally. It also makes deployments easier as everything is already configured within the container and just has to be deployed somewhere.
Docker allows developers to create a list of steps required to deploy and start the required environment and application dependencies. This list of steps is referred to as an image. Developers can launch images inside of Docker containers, allowing the developers to
- launch the project without any manual configuration of requirements;
- abstract the deployment steps into an automatic process;
- deploy inside of a self-contained environment that does not impact their system; and
- easily remove all project components by deleting the container(s).
Docker images and containers can greatly reduce onboarding time to a new project by removing the need for developers to set up their own local version of the project’s environment.
Setting up virtual environments and docker images can be overkill for smaller projects, but when working on bigger projects with larger teams, take the time to determine how you can best virtualize or componentize the solution in order to make it more manageable and approachable.
Standardized Workflows
There is more to the development workflow than pulling down and launching a project, and there are many opportunities to improve the development experience by standardizing components of that workflow. Here are four workflow standards that I find improve the development experience.
1. Add pre-commit hooks to the project
I previously talked about the benefits of adding a beautifier and linter to a project, and I skipped over one of the major cons I have noticed: Developers often forget to run the beautifier or linter and issues are not caught unless the code is reviewed by a team member or by an automatic development pipeline (we will talk about these later). A pre-commit hook attempts to solve this issue by defining a list of processes that will occur before a git commit runs. As an example, we can set up a pre-commit hook to run the Python beautifier and linter, black and pylint, against the codebase before the code is committed and pushed to the code repository. If either the beautifier or linter output errors, then the commit fails and the developer can resolve the issues before committing again. This makes it easier to incorporate these tools into the development workflow as they are automatically added to each developer’s process without any extra steps.
2. Define a Branching Convention
Developers on your team should all be able to find, name, and identify the purpose of code branches. At a minimum, determine which branches will get deployed to which environments (i.e. the test branch deploys to the staging environment, etc.) and validate that through your deployment pipelines. After that, work with your team to standardize on a development branch naming convention. I’ve identified two counterpoints when it comes to adding labels to branch names.
- Prefix the branch name with feature, bug, hotfix, or other labels to manage where branches are made and how they are managed.
Example name: feature/TASK12-add-skill-facet - Do not prefix the branch with any labels (like feature, bug, hotfix) as that information can be tracked in the project’s task management system and adds extra room for mistakes.
Example name: add-skill-facet
In the first example, we leverage the label (feature) and the ticket id from the task management system (TASK12) to make it easy to link code branches to project tasks. However, this puts pressure on the developers to always remember and use the standard format without any mistakes, as argued by example 2. EK recommends the below format.
- Start the name of the branch with the branch label as described in (1) above.
- After the label, add the ID of the relevant ticket in the project’s task management system.
- Finish the branch name with a short description of the task (perhaps the task title) as the rest of the branch name.
Recommended Format: <branch_type>/<ticket_id>-<title>
Recommended Format Example: bug/TASK37-fix-skill-facet-ordering
In addition to the benefits mentioned previously, these methods allow code reviewers to quickly look up the task details and enable developers to easily search for branches by label and task id. This is a smaller workflow change but it makes the code repository much cleaner to search and use.
3. Select a Project Standard IDE
You’re starting on a project so you pull down a codebase, follow the README instructions, run the code, and…the code does not work. You reach out to a colleague who is working on the project and they tell you some steps that worked for them. However, the steps they provide only work in their development IDE (integrated development environment, i.e. the tool they use to develop the project) and you need to try and convert those steps for your IDE of choice.
A few hours later, finally, it is working! In order to set up a beautifier and linter, you want to download some packages and set them up to automatically check your code when the code is saved. Again, you reach out to a colleague and the steps they give you are for their specific IDE and do not apply to yours. You do some googling and eventually come across a stackoverflow where answer #3 provides the steps you are looking for. You are finally ready to get coding, but the day has flown by and it is already time to stop working.
Moral of the Story
Standardize what development tools your team is using on a project. It makes everyone’s life easier when steps do not need to be converted and colleagues can support each other directly. Most IDEs allow you to export project settings, so some teams decide to directly commit these settings to the repository. Time is saved setting up, testing, debugging, and running a project when you can leverage the time that your colleagues have already spent.
4. Automate
Lastly, when optimizing your development workflows, make sure to leverage popular scripting techniques to abstract complex processes into simple one liners. For instance, it’s fairly common to use npm to manage dependencies and scripts on Javascript projects. Developers are encouraged to create build, test, and start scripts that automatically run the corresponding commands for the project. It was not until recently that I found that you can automate this for other languages as well using a Makefile. Makefiles allow you to easily set up aliases to run commands, similar to npm scripts. For example, the command, make install, could install all of the required code dependencies into a virtual environment without each developer needing to learn the details of each step. Similarly, the command, make test, could call our unit testing library with the agreed upon parameters and output a coverage report so that every developer sees the same output. This simple automation step makes it very easy for developers to onboard to projects without getting stuck on the intricacies of commands.
Conclusion
Setting up properly configured workspaces and workflows to support your development projects ensures that your organization is optimizing your team’s time as they collaborate on tasks and pull down new code bases. If you are interested in learning more about development standards and the benefits they can bring to your organization, check out the rest of the blogs in this series.
- Part 1: Benefits
- Part 2: Code Quality
- Part 4: Code Review Benefits
- Part 5: Code Review Best Practices
If you have any questions or want to discuss how your organization can implement development standards into your projects, reach out to us!