August 25, 2014

Code Review: Cleaning up the Environment


I've been reviewing a lot of projects lately and I thought I should share what I look for when evaluating the code quality and maturity of a project. My team is often brought in to "clean up" an existing system. This might include bug fixes, new features, a rebranding, and scaling issues. This will be the first in, hopefully, a series of posts about our approach. I'd like to write down my findings in hope that if we all learn from these mistakes we'll see less of them.

If that ever happens we'll need to find new things to do. Good thing that there's always work to be done. =)

A note of warning, as projects mature they often obtain and then move away from some of these tenants. However, the principals and purposes often remain. I'm also going to be primarily talking about Rails projects but these aren't Rails specific issues.

Reduced time to startup

Maybe this should be called reducing complexity to startup. The theory is that the more "magic" one needs to retain in their head in order to run the application, the heavier the overhead on all developers on the project. This also slows down any new developer who wants to get started.

The first place I'm going to look for setup instructions is your Readme file. This is usually full of database setups and dependencies such as command line apps or specific versions of libraries. The older the project the more of these you'll see. This list is usually out of date (fixing this is a great first commit for a new developer) but it's also usually missing the "config file" which contains all the keys for all the 3rd party services you need to run the app locally. While I don't want this file to exist, without it I usually can't start the app up or load a single page.

Often missing is a good set of development data. Data that you can load into an empty database to allow someone to try out the application for purposes of modifying or adding new features. This is usually done with a rake task. Having this built with code as opposed to loading an SQL or CSV file is preferred. You should make use of factories and the objects in the system. The data will be a lot more likely to be valid if it exercised your code to end up in the database.

You should also make sure to generate users to be used in testing. At the minimum you'll want an admin and a 'normal' user.

Isolation of Environments

Don't, for the love of god, have your development environments use production api keys or share production services! (or ssl keys, or s3 buckets, or anything else!)

More to the point, don't hard code any keys! Use environment variables.

On the server this is easy. You can use a gem such as foreman, figaro, .env, or many, many more. The idea is that production credentials should only exist in production. (And maybe also exist in your encrypted password manager.) In development you can load them from a file either before you start your development environments (eg, foreman) or have your app load them from a file if you haven't already defined them (eg Figaro).

This works great for configuration. How should you connect to your database? Where should you store your uploads? However, this is usually when I wonder why you're requiring a 3rd party service for your app to function.

Remove dependencies on 3rd party services

Chances are, any third party service you rely on will go down. This means you need a way to function with it down. Maybe you hide that "latest tweet" box, or queue email to be sent later. I advocate for disabling features that rely on 3rd party services if you are unable to connect.

In development you can go a step further and either mock the service or change your app's behavior to handle not having it. For example, you can store images locally instead of on S3 if S3 isn't configured. You also don't actually have to send email or manage mailing lists if you don't know how to talk to sendgrid or mailchimp.

When you're developing a feature that relies on a 3rd party it's fine to talk to it during normal development. However, I bet you can just skip the call and pretend it was successful. This will allow the app to function without knowing any api keys and will help you identify the "mock points" for testing.

RVM: The Ruby Version Manager

RVM-like software exists for every single programming langugage that's worth a damn. You get to easily isolate your packages and choose a version of the language for the app. Node.js and NPM for example provides isolation of packages by default. For any ruby project a .ruby-version and a .ruby-gemset should already exist. I'd like your app to run in isolation on my dev machine but you should want it to do the same in production. This makes it easier to rebuild the running environment. If your production environment can't be easily rebuilt, you're in a world of trouble.


If you've done all this, I can type the following and have a mostly functional application.

git clone
cd project
bundle install
rails s

And then I can actually get on to evaluating your code.

-Francis © 2022.
Powered by NextJS and Vercel.