Crawling Horrors: Fix and Future-Proof Your Legacy Ruby Apps, by Noah Gibbs
Abstract
"Fix that test with a sleep." "This works as long as that puts is there - don't take it out!" "We can't upgrade the JSON version. Fred knew why, but he's gone now." "Look, just never touch that class." Ruby is incredibly powerful... And its worst apps are really amazingly awful. Shall we talk about how to deal with those horrible legacy apps? Better yet, shall we talk about how to keep your apps from being remembered that way?
There's so much old Ruby code. Future-proof it and then get to the fun part of the job.
Details
The primary audience takeaway here is about when to add tests, when to upgrade the software, and when to leave well enough alone. Some primary factors are:
- are there tests? Is it easy to add tests? If no tests, refactoring and upgrades can be difficult and nasty.
- how much work is it to upgrade to a current version of the libraries and frameworks being used? Are there serious known security flaws in the app's current versions?
- can tools such as Suture, Reek or Flog help? Can we record the effects of a batch job or test run to verify that we haven't changed the output?
- how much work can we spend here? A massive overhaul can be exactly what's required with a large team, or a disaster if the app's maintenance is a one-person part-time task.
- what is the business value of this app? Does it make sense to increase its maintenance load? Might it make sense to just get rid of it?
- might we want to rewrite in something modern? As a rule, we usually do not.
We'll talk about a few example apps (legacy Analytics system, database scripts, Rails API server) and how these apply to them.
We'll also talk about some ways to "freeze an app in time" which can help reduce the decay, from freezing gems and "bundle vendor"-ing to requiring specific versions of Ruby, RubyGems and other dependencies.
Ruby's idiosyncrasies, rapid changes in features and many gems and dependencies make for some interesting trade-offs in legacy code. We'll explore some of those trade-offs in detail, and skim a lot of others.
Some additional techniques we'll quickly explore, given time:
- Using Docker or a VM to ensure known versions of system dependencies
- Free-standing integration tests and their use in monitoring and continuous integration
- Continuous Integration of full packages as a remedy for fragile external dependencies
All of these techniques work for future-proofing apps, not just remedying the problems of fragile legacy code.
Pitch
As Ruby continues to do well, there will be many old Ruby apps laying around, and many jobs maintaining them. It would be wonderful if Ruby was not remembered as a horrible bear of a legacy language. There are tricks that can help.
I've been working at Ruby-based jobs and companies since 2009, and updating legacy Ruby apps for longer yet. I've worked at five companies full-time and for one as a consultant during that time, and helped several other organizations with app security, performance and maintenance. I've set up infrastructure for Ruby applications and gems at multiple companies (e.g. Ubuntu package repositories, continuous integration for vendored gems and frameworks, Puppet and Chef manifests for creating "full packages" with all Ruby's dependencies.) I've done a lot of securing these build systems against Internet failures (e.g. RubyGems.org failures, Ubuntu failures, Node.js and NPM failures.)
I have led teams doing both Ruby development and Ruby-based DevOps. The examples above are composite, but based on real projects I've maintained.
All of this has given me a sneak preview of what the current Ruby will look like when it turns ancient and cobwebby and its many required servers have gone dark with age.
Bio
Noah is a Ruby Fellow for AppFolio, working on the core Ruby language and related tooling. As a trained massage therapist and hypnotherapist, he uses his powers of evil for good. He intends his children to rule in his stead - obey them as you would him. He also wrote the book "Rebuilding Rails," which is universally worshipped as "pretty good."