Hot Take: 13 Tips for engineers new to coding on a team
Hot-Takes are quick brain-dumps on practices that catch my eye and my ire.
It’s extremely daunting to transition from writing code on your own to writing code in a team setting. It’s nerve-wracking knowing that each line of your code can come under the scrutiny of new eyes, and it may not always be clear what the “right” way is. This article includes some tips that should help build your confidence when contributing in a team environment.
First off, let’s talk about what’s different when working in a team:
- Unlike a small/personal project, there are many developers making contributions across multiple repositories
- You’ll often be making changes to repos that you don’t own and aren’t very familiar with
- You need to retain the ability for other engineers to quickly figure out what’s happening
- You’ve been assigned some scope of work that ideally describes the acceptance criteria for completing the task
Assuming you have a task that can be accomplished by contributing to one repo, these tips should help you with the process of updating the code and putting it out for review. There are also practices that will help keep the code in a healthy state while multiple people make their own contributions.
1. Transparency is key
Before writing the first line of code, your objectives should be clear and the rest of the team should be aware of what you’re working on. This is true for every level of engineer, but for junior engineers it may be tempting to break off from the pack early so you can “figure out” whatever you’re not sure about. From being on both sides, I can assure you it’s expected that you’ll need more details than engineers who are already involved in the project, and anything that’s not clear is actually a failing of the more senior engineers to clearly communicate what’s expected.
Ideally, you’ll be pointed toward the repository where you’ll make your edits. If the task involves working with multiple repositories, you may want to discuss the best order to proceed in. The tips below apply to making updates in a single repository, so you would need to repeat them for each repo.
2. Familiarize yourself with the toolchain
There are probably tools to help you run, test, and lint the repo you’ll be working in. You’ll need to be able to use all of them locally as you work, so take some time to familiarize yourself, and make sure to ask around if something won’t run.
3. Break up the work
As a general rule, you only want to work on implementing one thing at a time. Complex functionality can come from stringing together smaller modules. Start by identifying the entry point, like an API endpoint or event handler function, and work forward from there to create a list of everything that needs to happen. Then, take a look at the codebase you’re working on and see how things are broken up. You’ll want to mimic that breakout as you add your own code. With the list you made and the knowledge about the structure of the repo, you should start to get an idea of what you need to add and where you’ll add it.
4. Find the fastest path to integration
Once you know what needs to be built, map out the smallest amount of work that lets you see any result, no matter how small or mocked out. Using the tooling mentioned earlier, you’ll want to prove that you can affect a change in the places you expect to see results. Instead of waiting until all of the functionality is in place to try running the code, you should start by running the code and iteratively layering in functionality as you go. At each point where you’ve made a change, you should rerun the code to ensure that changes show up the way you would expect them to. It’s better to skip over things that add complexity, like connecting to remote services or using real logic, while you establish a base to work off of.
5. Write the code, matching the existing style
Since this is a team-based project, it’s more important to follow the conventions in the existing code than to write modern code in a new style. Picture the following two possibilities for a codebase that’s been around for 5+ years and has been updated by dozens of engineers: In one possible world, each engineer went in, saw outdated patterns that were not “best practice” and decided to add their own contributions using the latest patterns and styles. In the other possible world, each engineer modeled their contributions around the existing code, creating additions that follow the same style as the code that was already there. Based on this, you should see why writing new code that conforms to outdated standards is actually much more useful to future engineers. In the scenario where every engineer added the latest and greatest style to the code, you would find multiple conflicting styles alongside each other. It would certainly make it more difficult to figure out what’s going on and why things are structured the way they are. It would also be much more difficult for you, as an engineer seeing this codebase for the first time, to know which style to follow. This is part of coding in a team that can seem counter-intuitive. Reading the latest industry news and blogs will give you the impression that writing code in outdated styles is obviously wrong in all scenarios. However, the larger goal of the tech org is to make hundreds or even thousands of separate components and retain the ability for any engineer to quickly pull down the component they need to modify, figure out what’s happening, and make their changes.
6. Mock out remote dependencies
This won’t always be the case, but if you’re adding an integration to a remote dependency that requires credentials, such as a database or a service, then you should include a way to run and test the code locally without a live connection. This is for two reasons: One, engineers who work on this repo in the future won’t need to request credentials to run the code themselves; and Two, this will retain the ability to run and test the code in isolation. You should still have tests that prove that the code works correctly when the connection is live, but those should be part of integration tests that don’t need to run locally.
7. Use static analyzers
Using linters is common practice and probably something that’s already built into the repo you’re working on. Beyond that, it’s a good idea to use more feature-rich static code analyzers such as the ones that are built into IDE’s, or ones you install separately, to spot common issues with your code and eliminate them before your code is reviewed.
8. Reduce nesting
Each level of nesting in your code makes it exponentially more difficult to read
and understand. Try using continue
, break
, and return
s inside of your
for/if/while statements to avoid nesting code. Take code that must be nested and
break it out into functions that can be called in a cleaner way.
9. Use consistent variable names and terminology
Make sure variable names are as standardized as possible. If you have a variable
named match_count
that gets passed to another method, call it match_count
in
the other method. If you use a term to describe some aspect of the code, use it
consistently. If you have a class called CSVParser
, reuse the word parse
when creating functions. Instead of adding a method called read_csv
, call it
parse_csv
. If names and terms are already present in the code, continue using
them in your additions.
10. Test functionality, not functions
The best way to think about unit/functional tests is that they should guard against changes to how the code behaves from the point of view of what will be using that code. It’s absolutely possible to over-test, and this usually comes up when creating tests that will break when any changes are made to the arguments or structure of functions that don’t matter to the larger API your code defines.
11. Use git diffs to reread your code and revise it
Once you’ve written your code, take a step back and give it another look. Take a look at the git diff for your changes and make sure things make sense. Try to see the changes from the point of view of the reviewers. Ask yourself, can I remove any changes? Would it be clearer if I broke these changes up into smaller PRs?
12. Centralize documentation
To aid future engineers, keep all of your documentation centralized, usually in the README file. Assume that engineers will check the README, but won’t necessarily know to look for comments or docstrings. Also, assume that you can’t overload those engineers with information. If your contributions require a great deal of explanation, consider simplifying the code rather than writing multiple pages of docs.
13. Create your PR, reread it, and prepare to defend it
Congrats! You’ve made your changes and are ready for a review. When opening your PR, take the time to read through it once more. It’s common to spot things that can be cleaned up at that point. Also, be prepared to get different types of feedback. Be receptive to advice, but since you’ve written your code and understand it better than people seeing it for the first time, don’t be afraid to push back on suggestions that may seem incorrect or unnecessary. If you’ve followed the tips above, you should have code that passes linting and testing and uses the code you found as a model. You shouldn’t need to go back and make changes just because other developers would have done things slightly differently.