Rescuing Rails apps

When you've finally had enough

Jon Cairns / @joonty

Our goals

We all want our code to:

  • be fast
  • be beautiful
  • work well and complete features

The reality

Projects tend towards chaos.

  • We rush implementing features
  • We tack on little bits of untested code to existing features
  • The project spec changes, leaving redundant or broken code in the codebase
  • Our test suite gets too big and uncontrollable
  • The project outgrows the framework, or the framework turns out to be broken

It's demoralising

Working on a problem project changes coding from something we love to something we hate.

Decision time

You have a few options:

  • Ignore it and carry on
  • Throw it out and start again
  • Refactor

1. Ignore it and carry on

  • You don't need any skill for this
  • It's really easy, and takes no time at all
  • The project gets worse and worse
  • Small changes take longer and longer
  • Eventually you'll be killed
  • Your body gets eaten by possums

Proof

2. Throw it out and start again

  • We all like a new project
  • This time it will all be great
  • You have visibility over all the features up to now
  • It's always far more work than you think
  • Projects die because of never-ending rewrites
  • Eventually you'll be killed
  • Your body gets eaten by possums

Proof

3. Refactor

  • The project can continue while you make gradual improvements
  • Time isn't wasted recreating existing features
  • It's encouraging watching a project get healthier
  • Often the most challenging approach
  • You have to get knee-deep in bad code

How to decide

There isn't one answer for each case. Ask the following:

  • Has the project spec changed drastically?
  • What's the future of the project?
  • Is the project getting regular new features?
  • How old is the Rails version?
  • What state is the test suite in?
  • Are personal preferences getting in the way?

So, you've decided to refactor?

Here's a process you can use, instead of diving straight in.

1. Static analysis

Find out the state of your application in quantitive terms.

  1. Code coverage (simplecov)
  2. Code quality (rubycritic, includes reek, flay and flog)
  3. Rails-specific code analysis (rails_best_practices)
  4. Rails security vulnerabilties (brakeman)
  5. Code style conformity (rubocop)

2. Rank the problem areas

Weigh the following up together:

  • Results from static analysis tools
  • The importance of the feature
  • The historic bugginess of the feature

3. Determine the problem

  • Does the code actually do the right job or not?
  • Are there helpful tests already in place or not?

If the code AND the implementation of the feature are bad, then the section needs rewriting.

4. Test first

Let's assume three types of test:

  • Unit tests (low-level, high detail)
  • Integration/function tests (mid-level, medium detail)
  • Acceptance/E2E tests (high-level, low detail)

Unit tests are the most detailed, but also the most likely to be thrown away in a refactor.

If you're refactoring a feature that already works

I.e. no major changes to existing functionality:

  1. Write as many E2E tests as possible
  2. Write integration tests if possible
  3. You'll probably have to delete or heavily modify any existing unit tests
  4. Use TDD to develop new unit tests and refactored code until the E2E tests pass

If you're rewriting a feature entirely

I.e. major changes to existing functionality:

  1. Delete existing tests covering the feature
  2. Write a couple of E2E tests to describe the new feature
  3. Use TDD to develop enough of the feature until the E2E tests pass
  4. Repeat steps 2 & 3 until finished

5. Check progress

Keep running the static analysis tools on a regular basis (preferably CI), and see the stats improve!

Time for a demo