The two-step approach to big code modifications

We all have to make significant code changes from time to time. Most of these code changes are large. Consider the scenario that you merged one such significant change, and then other team members made a few more changes on top. Then a major bug is detected. You desperately make the fix. It makes it in. You declare a victory, and a few hours later, your colleague notices another bug/crash/performance regression. Your commit cannot be reverted. It isn’t just about you. Many others have built on top of the change you madeā€”the code sloths along in this broken state for a few days before you eventually fix it. Everyone has faced this issue at some point or the other.

If the code change is small, this is a non-issue, you can revert it and fix it at your own pace. Therefore, when making significant code changes, always try to do it in two different commits (pull requests). In the commit, you add the new code, add a switch (command-line flag or a constant) to turn it on, and keep that switch off by default. In the second commit, turn the switch on. Now, if there is a problem, you immediately turn the switch off and start working on a fix. No one else will deal with the broken code. It would be even better if the switch is a command-line flag since you can turn on the flag for 10% of the machines (or users) and see the behavior for a few days before rolling it out to 100%. It big teams, it is usually good to add a comment to switch mentioning when it should expire or else you will end with Uber scale problem.

There are a few cases where this cannot be done, for example, during big code refactoring. I think big code refactoring touching code written by multiple teams is almost rarely justified in a single commit.

Examples of some cases where this is useful

  1. Switching over from consuming data via v1 of some API to v2
  2. Switching over from returning computed results to stored results (for faster response time but possibly inaccurate outcome)
  3. Switching over from JSON to Protocol Buffer/Thrift
  4. Switching over from VMs to Kubernetes (don’t delete the old code yet, you might regret it)

Incremental testing: save time and money on CI for monorepo

To use monorepo or not is an eternal debate. Each has its pros and cons. Let’s say you decide to go with monorepo, one major issue you will face over time is slow testing. Imagine a monorepo, consisting of an Android app, an iOS app, some backend code, some web frontend code. In only very few occasions will someone modify more than one of those simultaneously.

Further, add to the fact that most of these projects confined to their directories would be using different build systems as well, for example, gradle for Android, yarn/npm for Javascript, go/rust/java/npm for the backend. The total build time, as well as test time, will only grow over time. It annoys developers making small modifications to their part of the codebase. And it slows down the development velocity drastically.

Read More

How to deploy side projects as web services for free

In 2020, the web is still the most accessible permission-less platform. For the past few months, I have been playing and building side-projects to simplify my life. I started with a Calendar Bot for scheduling events, DeckSaver for downloading decks from Docsend, AutoSnoozer for email management, and StayInTouch for maintaining follow-ups.

When I started on this journey, I had the following in my mind.

  1. Cost of domain ~ 12$ a year or 1$ a month
  2. Cost of a VM ~ 10$ a month

Read More

Docker 101: A basic web-server displaying hello world

A basic webserver

Docker containers are small OS images in themselves which one can deploy and run without worrying about dependencies or interoperability. All the dependencies are packed in the same container file. And the docker runtime takes care of the interoperability. You are not tied to using a single language or framework. You can write code in Python, Go, Java, Node.js, or any of your favorite languages and pack it in a container.

Consider a simple example of a Go-based webserver

Read More

Troublshooting Android Emulator: “Emulator: Process finished with exit code 1”

Emulator: Process finished with exit code 1

You opened AVD Manager in Android Studio and tried to start an AVD, you got
“Emulator: Process finished with exit code 1”. Following are the steps to debug
this

  1. Find out the name of the emulator.
    Click the down arrow šŸ”½ which is to the right of play arrow ā–¶ļø, to find out the name of the AVD. Let’s say the name is “Nexus_5X_API_28_x86”.
  2. Try starting the AVD directly from command-line
    It fails with another cryptic error, but that’s at leastĀ more actionable

    Let’s retry in the verbose mode to see a detailed error

    So, the system image is missing.
  3. Install system image
  4. Now try starting the AVD again.

    Turns out there is another version of emulator installed in $(dirname $(dirname $(which android)))/emulator/emulator. And the emulator I was using is a stray one.

Android: Using “Die with me” app without killing the phone’s battery

Die with meĀ is a chat app which can be used only when the phone’s battery is below 5%.

Here is a fun way to use the app without draining your phone’s battery. Connect the phone via ADB or start Android emulator and fake the battery level to 4%.

And now, you can use the app. After playing with the app, reset the battery level with,

 

The first two statements of your BASH script should be…

The first statement is a Mac, GNU/Linux, and BSD portable way of finding the location of the bash interpreter. The second statement combines

    1. “set -e” which ensures that your script stops on first command failure. By default, when a command fails, BASH executes the next command. Looking at the logs, you might feel that the script executed successfully while some commands might have failed. Caveat: Be careful about applying it to existing scripts.
    2. “set -u” which ensures that your script exits on the first unset variable encountered. Otherwise, bash replaces the unset variables with empty default values.
    3. “set -o pipefail” which ensures that if any command in a set of piped commands failed, the overall exit status is the status of the failed command. Otherwise, the exit status is the status of the last command.

References:

  1. Unofficial Bash strict mode
  2. ExplainShell

Keep your dotfiles bug-free with Continuous Integration

Update: As of April 2020, I have switched over to GitHub Actions. Travis CI has become buggy and flaky over time and I got tired of trying to keep the builds green. My GitHub action scripts can be seen here.

Just like many software engineers, I maintain my config files for GNU/Linux and Mac OS in a git repository. Given that, I wrote a fair bit of them in interpreted code, notably, Bash, it is a bit hard to ensure that it is bug-free. The other problem I face is that packages on homebrew, the Mac OS package manager becomes obsolete and gets deleted from time to time.

I added CI testing on Travis CI to prevent these breakages and to ensure that my dotfiles are always in good shape for installation. The great thing about Travis CI is that it is entirely free for open-source repositories even for testing on Mac OS containers.

Read More

Circle CI vs Travis CI

I maintain a somewhat popular Android developer tool (adb-enhanced). The tool is written in Python, supporting both Python 2 and 3. Testing the tool requires both Python runtime as well a running Android emulator. I, initially, used Travis CI for setting up continuous testing of this tool. Later, I felt that Travis CI was too slow and when I came across Circle CI, I decided to give it a try. As of now, both Travis and Circle CI are used for testing. Here is what I learnedĀ from my experience.

Read More

Subscribe For Latest Updates

Signup for our newsletter and get member-only articles