5 extra steps to take after your code works

Photo by Vi Nowak on Pexels.com
Audio recording of this post

You’ve written that final line of code and are pretty sure it should work now, you run it to double-check the code does what you were aiming to do, aaaand… It works! Yahoo! You did it! Now it feels like a good time to commit, push for review, sit back, breathe out, celebrate a bit, and jump to the next awesome feature in your long list of fascinating ideas! But wait for a second, there might be a few more things that would be nice to do before you ship your solution.

First of all, congrats! You wrote a code that works! This is a necessary step in the process of creating software, yet not the final one. 

There is a nice post titled “Always do extra”. Although it’s focused on mastering hard skills and growing the expertise, the idea that outstanding developers always do extra steps resonated with me. Here I’m sharing five more steps great developers take after their code works.

1. Take care of the readers

Good product leaders know their customers and care about them, good writers know and care about their readers. There is a lot in common between writing and software development, so good developers know and care about their code consumers.

Any fool can write code that a computer can understand. Good programmers write code that humans can understand

Martin Fowler

That quote clearly states there are two types of consumers of your code: computers and humans. While the code you just created can be successfully digested by a computer, it works after all! Can you tell the same about other developers? Will they understand your awesome idea? How much effort is required to understand your solution?

Those are great questions to ask yourself. In most cases, software development is a team sport. You’ll improve code written by your fellows, and they will build on top of your solutions. Chances are high there are different players in your team with different skills, experience, and domain area knowledge. So it’s time to make sure the code reads well by people with different backgrounds. 

Although it may sound simple there are many things under that cover and it’s not about code style or prettifying its look, I assume there are linters and formatters already in place to automatically make sure team agreements about code style are followed. There are things like naming variables and methods, commenting on unobvious solutions or decisions, extracting pieces of functionality into reusable functions, applying the right design patterns, and many more. If you’re not familiar with those I’d recommend starting with Clean Code by Robert C. Martin (aka Uncle Bob).

If you already know the theory and have experience in making code readable, here is the tip from professional writers – take a pause before starting code cleanup and switch to something else for a while. When you get back you’ll be able to read the code with fresh eyes and more effectively spot areas for improvement.

2. Respect other contributors

Unless you just started with a clean project, the enhancement you made is not the first one to be shipped. So the reasonable question to ask yourself is are old features working after my change? I doubt your fellow developers and precious users will be happy to discover one day that the feature they love and successfully used yesterday doesn’t work anymore.

To answer that question you’d need to run some tests. How you do it depends on the state and size of the project, the specifics of the code changed, available tools, the overall dev process, and some additional factors. 

In the best case, you have a good continuous integration pipeline (CI) with a decent set of automated tests that will run every time changes are pushed into the repository and the merge will be blocked if at least one of them fails. However, even if you have them you can save your time by running locally a subset of the automated tests covering the areas of the code which might be affected by your change. This will save you from getting back to the code after CI catches some mistakes for you. But it is worth doing only if the time and effort to launch the test environment locally is relatively small, otherwise, delegate this task to CI.

If you’re not so lucky and don’t have a good CI, there are at least two things that would be nice to do. First – try to run all automated tests you have or at least test critical paths of the project which potentially might be affected by introduced changes. Second – start talking to your team about the values of CI and automated testing, you may find a few good hints in this post.

3. Protect your solution

No matter if you were lucky or not on the previous step, I hope you see the value of CI and automated testing. So it’s time to make sure your own code has some protection.

The first kind of protection – automated tests – is needed to signal other developers if they unintentionally break your code. Depending on the scope of the feature, its importance, and complexity you might need unit, integration, end-to-end tests, or a mix of them. There is no single right answer on how much and which tests you need to write, you’d need to decide on the right balance of those yourself. Ideally, tests for the happy path, some expected unhappy paths, and maybe edge cases would be added. Doing so also highlights that those unhappy paths and edge cases were taken into consideration.

Tip: thinking about how to test your code early in the process leads to less coupled and better-structured solutions. Read TDD by Example book for more thoughts and examples on this approach even if you are not going to follow the test-driven development process. 

The second kind of protection – error boundaries – is needed to prevent the whole application from exploding if there is a mistake in your code. Depending on what are you working on this kind of protection might be not needed, e.g. if unhandled exceptions are a necessary part of the solution. However, consciously thinking through them and adding or not adding catchers where necessary is worth doing.

4. Add extra clues

Photo by Carolyn on Pexels.com

There are good chances that the solution you’ve just made will be used not only by developers in your team or other teams in your org, and in the case of a popular product, there will be lots of people relying on your code. So it’s worth taking care of them as well by writing some docs, creating demos, or any other hints which would explain your solution from the product user perspective.

For example, QA folks would be happy with testing instructions to reproduce and test new changes, and end-users would be happy by getting up-to-date instructions in the product docs. Community members could benefit if a new library feature is listed in the docs with a good demo of how to apply it. 

In many cases, this step might be not needed, but creating a habit of taking a pause and thinking about it will positively impact the product. People will love your solutions and product much more if it’s clear and easy to use them.

5. Take care of reviewers

We are humans and we’re not immune to mistakes, that’s why the code review process exists, and hope you have it in your team as well. Taking some steps to help your reviewer will make the process faster and more efficient.

Here are a few things to consider:

  • Commit messages.
  • PR description. 
  • Find and ping the right reviewer.

Let’s start with commit messages. Good commit messages are a valuable source of useful information for the reviewer. Those are hints and a history of work progress on the issue. So worth reviewing them before pushing changes to the remote repository and editing where necessary. Interactive rebase is your best friend in this process.

PR description. It is usually the first thing reviewer will read to understand what’s the PR is about. They have plenty of their own work to do so it’s naive to assume that PR reviewer always has the same or more deep knowledge of the issue you were working on. Despite that lots of information could be derived from commit messages, PR description is an important element to onboard a reviewer. It’s a good idea to have a clear and concise title of the PR, a link to the issue it fixes, a short description of main changes introduced, testing instructions to run/reproduce old and new behavior, screenshots in case of visual changes, links to designs and other important discussions where key decisions were made, and so on. The key point is to save the reviewer’s time searching for all of this info to understand your work and properly review your code.

Once your PR is ready for review, the final important step towards effective review is to pick the right reviewer. Unless this process is fully automated or documented in your team you’d need to make a decision here as well. Hope by that time you have a good understanding of which parts of the codebase are affected by your changes, so requesting a review from a teammate who is familiar and knowledgeable in that domain is a good idea. Taking reviewer experience into account is also important, e.g. it would be an arguable choice to ask a new or a junior developer to solely review complex change affecting many parts of the codebase. Sometimes asking two or more people to review the changes is reasonable, especially if those are critical ones.


If you’ve done all the steps above, you must be in a good position! Go ahead and send your awesome change for the review! You’ve done a great job by taking those extra steps, however, that’s not the end of the journey yet. The code review is a really nice and interesting process and deserves its own story.

In the end, I want to share a great quote made by WordPress one day – Code is poetry. It’s not a coincidence you’re called an author even if you don’t feel like a traditional writer at all. No matter if your code is a poem, novel, short passage, or anything else, you have the readers! And it would be nice to think about them and love them as I do now while writing this post! 

Take care of the consumers of your code!

Debugging CI builds in Travis

Photo by Marten Newhall on Unsplash

Sometimes a Travis job fails and after looking through the output you have no idea why. The things are getting even more mysterious if the script works fine on the local machine. I’ve been there recently when had to debug a job running E2E tests.

There is a solution on how to debug a Travis job locally in docker container but there is a better and more reliable way – running Travis job in debug mode.

Launching Travis job in debug mode

To start a job in debug mode it should be enabled for GitHub repository. Things are way easier if your repository is private, debug mode is enabled there by default. Just navigate to the job page in the web UI and click the Debug job button at the top right corner to start.

For public repositories you have to send a request to support@travis-ci.com and specify the list repositories where you’d like to make debug mode available. It can take some time but needs to be done only once for the repository so it shouldn’t be a problem. I was lucky enough and my request was handled pretty fast (within several minutes).

Note: Switching repository from private to public will disable debug mode mentioned above. So you’ll have to send a request to support.

Once the debugging is enabled for the repository the only way to launch a job in a debug mode for a public repository is via API call. Here is the example with curl:

curl -s -X POST \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Travis-API-Version: 3" \
  -H "Authorization: token ${TRAVIS_TOKEN}" \
  -d "{\"quiet\": true}" \
  https://api.travis-ci.com/job/$JOB_ID/debug

As you can see there are two things required to send such a request:

  • Travis token. You can grab one from Profile > Settings > Settings tab on .

  • Job id. It can be taken from the Travis job page url – the numeric part at the end of the url. E.g. 123456789 for the url https://travis-ci.com/github/.../jobs/123456789.

Little hack: You can add the utility function to your ~/.bashrc:

travis_debug() {
  if [ $# -eq 0 ]; then
    echo "Job id is required"
    return -1;
  fi
  JOB_ID=$1
  curl -s -X POST \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Travis-API-Version: 3" \
  -H "Authorization: token ${TRAVIS_TOKEN-$2}" \
  -d "{\"quiet\": true}" \
  https://api.travis-ci.com/job/$JOB_ID/debug
}

Now to launch the job in debug mode run in the terminal:

TRAVIS_TOKEN= travis_debug 

// or

travis_debug  

Debugging the job

Once the job started in debug mode go to the job log and take ssh connection string, similar to this one:

...
Use the following SSH command to access the interactive debugging environment:
ssh DwBhYvwgoBQ2dr7iQ5ZH34wGt@ny2.tmate.io
...

Paste it into the terminal as instructed to connect to the Travis container running the job. Starting from here you are connected to the fresh instance and can do almost anything you want to debug the script like run commands one by one, change environmental variables, edit scripts or run custom commands and scripts.

To make things a bit easier Travis provides handy functions of format travis_run_ e.g. travis_run_before_install, to run scripts of a specific phase as per job config. See the full list of functions here.

It’s worth mentioning that the debug session is tmate session rather than fully functional terminal. Which means you can’t use things like scp to download files to your machine so other workarounds are required in case you need it.

Another unpleasant thing is that closing the session terminates the job and you’ll have to start debugging from scratch in case the session is accidentally closed. Follow these instructions if you need to keep debug session output on exit.

Besides that debug mode is super handy and can save you a ton of time while debugging and fixing failed jobs.

Happy debugging!

Why to test software

Photo by National Cancer Institute on Unsplash

> Why do we test software? Most people would say: “So we know it works.” > > — From this blog

I like this answer because it’s short, simple and matches my point of view 🙂 I do want to know the software I create works!

It takes time and effort to master testing skills and learn necessary tools. Many developers don’t write tests and they have their own excuses for it. And there’s a bunch of positions between these two poles: haven’t decided yet, just heard something, once tried it, but it didn’t go smoothly, etc. Anyway, before investing your time and effort IMO it would be good to know why and how testing can make your life better. Further you’ll find the benefits I see and utilize with tests.

Mistakes

Every developer makes mistakes. And it’s not because they are bad or inexperienced, but because they are humans and humans makes mistakes.

Moreover modern software is built incrementally by adding smaller changes and combining many independent units. And the more pieces the software has the higher is the probability to introduce a mistake with new changes.

=> Tests are our validators for the software. Creating more tests validates more scenarios hence reduces amount of mistakes left unnoticed.

Product quality

Product quality is a multidimensional topic. While stability is only one of the quality aspects it’s hard to overestimate its influence: it’s very unlikely you would call a product a good quality one if it constantly breaks while working or after each update. And vice versa we can say higher quality products are more stable and have less mistakes.

=> Tests reduce amount of mistakes and raise product quality!

Confidence

Developers are humans, I know I’ve already told this but this time it means each developer is a living person! And I doubt many developers are looking for stress and sleepless nights at their work due to buggy software or poorly tested product releases, constant fights with fires and so on. It might sound a bit dramatic and I haven’t been in such situations but I’ve read about them in the books (e.g. DevOps Handbook) and suppose it’s real.

Tests validate that software behaves the way it is expected. Running tests gives a confidence that expectations are valid, confidence reduces stress and less stress leads to a healthier and happier life. One really important thing here is the tests are useful as long as expectations described in the tests match the way the software should behave from the consumer point of view. Poorly written tests validate expectations meaningless to how software is consumed.

=> Running tests gives a confidence that expectations are valid, confidence reduces stress and less stress leads to a healthier and happier life.

Architecture

Software is not a building, its architecture changes and evolves during the lifetime. So it’s quite common to add functionality to existing modules, split large modules into smaller ones, break dependencies and rearrange the things. All those changes can potentially break some existing functionality.

=> Tests allow us to catch those regressions before they reach a production, making refactoring and architecture improvements more stable and reliable.

There is another way how tests can improve architecture if you run them before (TDD way) or right after the new code.

=> Writing tests forces developer to think about how to put the code under test and how is it going to be consumed leading to better code structure and modularity.

Documentation

Writing docs is not the most pleasant work for developers and they are usually not so good at it. It’s quite hard to maintain the docs up to date especially if they live far from the source code. Even if docs are written withing the code it’s quite easy to forget to update them once the code is changed just by mistake. On the other hand if tests are out of sync with code they fail and signal you that changes are required.

=> Tests are a good source of developer documentation with examples of how to use modules and how edge cases are handled by the software.

Time & Money

Everything above is correct for tests in general but why automated tests? Because the more features the software has the more cases have to be tested. And the more scenarios you test the more time, and mental efforts it requires from tester. Testers are humans as well and we’re back to mistakes ☝️. Scaling manual testing efforts is way more expensive as compared to scaling computational resources for automated testing.

On the other hand machines are way faster and better than humans at doing mundane and repeating tasks. And most of tests are like this. Automated tests can be run super fast and very often providing all benefits mentioned above: less mistakes, better product, happier team, happier customers.

=> Automated testing allows to spend more time and money on innovations and improvements by saving costs for maintenance and bug fixing.

Conclusion

Real software might be complex and might have many various requirements that’s why there are many different ways to check the software works as expected. Automated testing is only one of the ways and it’s not a silver bullet. It has its own costs and tradeoffs and if done wrong can even harm the project.

However, mindful, disciplined approach, patience and practice pay back allowing you to get all the benefits mentioned above making it extremely hard to switch back to creating software without tests.