Your development environment has a big influence on how good you can perform as a software engineer. So you should try to optimize it to increase your productivity. This post lays out some strategies on how you could do that.

Flow

As a software engineer you probably agree that the most productive time is when you are in that state called flow. Flow allows you to give all your focus to the specific problem you are solving. Flow is a real multiplier of your performance. It is fair to say that when you are programming and not in flow, you are wasting time. You will not get anything significant done compared to being in flow.

As flow is the biggest multiplier of your performance, you should strive to optimize it.

Optimizing for flow

You usually need around 15 minutes to get into flow. A great way to speed this overhead up is listening to music. Music is a great catalyst for flow

When you are already in flow the biggest issue you are having is to stay in flow. Once you get interrupted you have to waste 15 minutes again to get back in flow. So make sure you will not get interrupted when you want to get something done. This includes putting the internet and your smartphone on stand-by. Also make sure email is turned off.

Try to allocate a single slot of multiple hours for programming. Three one-hour sessions are not as effective as a single 3 hour session. Make sure meetings are allocated around your programming sessions and not in between your programming sessions.

Fast feedback

If your application always needs 30 seconds to build you will get distracted during these 30 seconds. Distraction is a flow killer, so make sure you get feedback as fast as possible. This means fast build times or - even better - live reloading. Slow feedback can really destroy your performance, so make sure you are never waiting for your computer. The computer should wait for you, not the other way around. Remember that computer time is cheap, human time is not.

This also includes unit and functional tests and other common tasks. These processes better be fast, otherwise you are getting distracted. If the unit tests are not running fast you will just push your commits. You will let the CI server run the tests in the background, in the meantime you will switch branches. Five minutes later the CI is reporting back: The tests are failing. But you are already working on something else. Now you have to switch back the context and fix the bug. With fast tests you can save the time of these context switches.

Process

As your project gets more complex you will inenvitable introduce some processes. Some of these are automated (e.g. running CI before merging a commit), some others are not.

A great way to increase productivity is to transform the non-automated processes into automated processes.

Let’s say you are working on a javascript frontend application. Before one can open this frontend application the javascript modules need to be transpiled into an older javascript version, so that all web browsers are supported. You also want to process your sass stylesheets and compiled them to css files. Or your project dependencies managed by bundler need to be updated after you have changed the Gemfile.lock.

If these tasks cannot be done by running a single command, these are not automated processes. They are very human error prone: if a developer forgets about one step he might get stuck. For example when not running bundler before starting your ruby application, the application might crash because some library is missing. Then he might get stuck and lose flow. By automating these processes (e.g. with a build system like make, rake or gulp) you will make these errors impossible by design.

Make sure your processes actually work. When your CI server randomly let’s the tests fail, people will not trust the CI server anymore. Then the whole process is needless.

When some of your automated processes requires a specific tool to be installed, make sure the error message shown when the tool is not installed is not a show stopper. Let’s say you have a git hook running facebooks flow type checker before you can commit. When the tool is not installed you will get something along the lines of zsh: command not found: flow. This is not helpful, it will not tell the developer how to proceed. Error messages should always help the user to recover by providing a possible solution. In our example we could just show an error like flow binary not found: Just execute 'brew update && brew install flow' and you can proceed[0] to help out. So before running the flow tool we just run which -s flow || (echo "flow binary not found: Just execute 'brew update && brew install flow' and you can proceed" && exit 1)[1] which will print out the error message and exits when the flow binary is not in the PATH.

Debugging is wasting time

As an engineer your goal is to build a great application. So when you are debugging you are not moving closer to the goal of getting it done. A good way to increase productivity is to make bugs impossible by design. It’s not possible all the time, and sometimes debugging is necessary, but many bugs can be avoided.

Let’s say we have a function findUser which finds a user by id and returns it or returns null if the user cannot be found. An example call in php could look like this:

<?php
$user = findUser($id);
// ... some big chunk of more code
echo $user->getName();

Do you see the mistake? The code will fail when the user cannot be found because then the value of $user is null. The php interpreter will say the error has happend on the $user->getName() call, altough the error is caused by not handling the error case somewhere above. You now have to find the real root cause (not handling the error case above) of this bug first, before you can fix it. With exceptions you could have avoided this. Just let findUser throw an exception and use a generic not-found-error handler somewhere at the entry point of the application. Then the null-check cannot be forgotten by humans because there is no need for it. We have successfully avoided bugs here by design. Take a look on how this looks in real world code if you are interested.

Another good way to avoid debugging is to place some asserts in your code. Debugging is a process in which you validate your hypothesis of what the state of your program should be against what it really is, so why not write down these hypotheses in form of asserts.

Takeaways

The key goal of a great development environment is to increase your flow time. You can archieve this by removing distractions and possible source of interruptions. Make sure your processes are as automated as possible to avoid human errors. Make sure your tools and processes are fast and actually work. Make sure your error messages are helpful. Avoid bugs by design to avoid spending too much time on fixing them.

Thanks for reading :) Follow me on twitter if you’re interested in more of this stuff!

If you want to read more about flow check out my friend Alex Gonçalves blog post, which is a response to this post.

[0] Obviously this is designed for just one package manager. Just pick the package manager you are recommending to your junior developers. More experienced developers will just use their favorite package manager of choice when they read this error message.

[1] Take a look at the manual of which. It’s really helpful for tasks like this.