POST DIRECTORY
Category software development

I’m coming up on the tail end of a UI/UX rescue project that was a major overhaul. The existing codebase deviated notably from conventions, the most significant was a heavy reliance on hand-rolled JavaScript that did a lot of work you’d normally expect to find elsewhere. For example, on one page where multiple day-specific forms were displayed in a carousel, JavaScript was cloning the form that initially rendered, clearing data from the inputs, changing the date specific text, and among other things disabling the inputs to prevent clicks until the form was center-slide in the carousel. The bulk of that process could have been handled by a request whose response leveraged the same _form partial that was being cloned.

This giant JavaScript octopus had its tentacles everywhere, and made for a very brittle frontend. When I first started on this project even simple HTML and CSS changes had ripple effects that would cause rendering failures. Time-sink traps were everywhere, a lot of time was spent chasing errors. Accurately estimating tasks was near impossible. Along the way though, I picked up some useful approaches for developing in such conditions.

Perfect is the enemy of good

Functionally speaking, this app worked pretty well for its users when we picked up the project. The challenges I was facing were issues of maintainability. In a perfect world, that’s enough for me to gut and replace the existing code. In the real world, however, budgets and timelines are a reality that can’t go unaccounted for when deciding upon an approach to making changes.

The question for me on each comp became whether or not it was faster to gut the relevant JS and replace it’s functionality with more conventional patterns, or Boy Scout Rule the existing code so it was easier for myself and other devs to work with on future passes. I found that taking time to do an assessment before writing code can help answer that question. I called them ‘look aheads’, which are simply the activity of getting a clear sense of all the relevant files, poking the code, understanding what it was doing, and what parts of it (if any) were reusable for the new design.

It’s good to be as thorough as possible. In one case there were four visually similar pages that were up for revamp. When I looked at the relevant JS for the first two views I was heavily leaning towards gut and replace. When I got to the third however, I discovered that a different approach had been taken than the first two, and was probably written by a different author. Taking pieces from each of these and combining them into a refactor was going to be faster than gutting and replacing.

Convert view templates if needed

The views in the existing codebase were HTML. A co-worker had the simple idea of converting them to something we’re more used to and faster at working with - Slim. This was quick and easy, and would certainly speed up development. There are conversion gems for Slim and Haml. Some editors have a package, and there are also online converters. I found it helpful to keep a copy of the original HTML commented out at the bottom of the file so I could refer back to it. Easy enough to delete it before a commit.

Micro-iterations

With the new design being so different from what we started with, we didn’t have to worry about incremental releases. This wasn’t going to production until the new design was done, and that meant that any given common visual element, such as page headers, buttons, etc., didn’t need to be implemented across the board in a single task. I could address constructing a new element’s design the first time it appeared in a comp, and leave the old code in place everywhere else.

A positive side effect of this is the ability to iterate and improve on the new implementation of those common elements as they become needed elsewhere. Developing these elements globally wasn’t prohibited, but more work could have been wasted if a significant design change was introduced along the way.

Client communication and managing expectations

When the original app is actually functioning it can be difficult to explain to a non-developer client that there are maintainability issues that should be addressed. However it does still need to be communicated, and I regret not starting this conversation earlier. Once we got better at picking the spots to talk about it, and repeating it, I felt the client’s comfort with the process increased. It became easier to talk about the estimates of tasks that involved legacy code because we all knew thar be dragons. Having that understanding earlier on probably would have enabled me to do more of the much needed gutting and replacing.

Patience

With these types of projects the best approach for any given task isn’t always obvious until you’ve gone down the wrong path (or a couple of them). You know what you wish you had to work with in a codebase, but it simply isn’t there and it may not make sense to add it given the project’s constraints. At times it can be frustrating, but ultimately I’m inclined to think of rescues as a specialized craft where specific insights aren’t known because they simply aren’t needed elsewhere. So in that sense, becoming more effective at them is an ongoing process that can always be improved. When it finally came time for the release, I don’t think I’ve ever been happier to see one. The satisfaction in seeing the difference between where the app was at and where I helped take it is not something I’ve experienced on other projects.

''