How to Upgrade a Framework Without Going Insane

Tamer Dakhlallah
lendingtree-engineering
7 min readJan 3, 2017

Ahh, upgrading. Having access to shiny new features and toys, cleaner/more succinct APIs, seeing more framework muscle and less framework fat (hopefully). I think we all love the idea of upgrading to the latest version of whatever framework you want to upgrade, but what about the actual upgrade process? When you start to sit down and really think about what it’ll take to upgrade a framework or library dependency, do you anticipate it being fun and easy, or tedious and painful? Do you get an “ahh, this should be effortless” feeling or rather a knot in your stomach?

In the last two years, I’ve spent several days and weeks of my own time trying to upgrade one of our client-facing apps from Angular 1.2 to 1.3 (and then 1.4 and now 1.5). I wrote most of the app from scratch and I know its internals with a lot of familiarity, but the upgrade process hasn’t been so easy. What started out as an, “Oh, this should only take a few hours or day at most,” slowly turned into weeks and months of pain and suffering. Granted, it wasn’t a continual effort but rather a few hours a week here, a few there, skip a few months, rinse and repeat. How could the code I wrote and that I’m so familiar with be so incredibly inflexible when it comes to upgrading?

Suffice to say, I’ve learned a lot over that time when it comes to how to prepare for and execute an upgrade. I’d like to share several small, practical bits (in no particular order, and not tied to any particular framework) that’ll help you have a better upgrade process.

Before starting, have a solid set of unit tests (and E2E tests if possible)

You can’t have confidence in upgrading what you don’t have tests for. I cannot stress this enough — you don’t know what will break unless you have solid tests in place to ensure that you’re using the new framework appropriately. It’s not enough to “upgrade and see what doesn’t compile” — that mindset will come back to bite you, and it won’t be fun.

Expect the entire process to take longer than you think (and then double that)

Be sure to read up on what the breaking changes are for every major and minor release you’ve leapfrogged as to anticipate what issues may arise. For frameworks that are more popular, they’ll most likely have a list of breaking changes. But for smaller libraries, you may spend a lot of time reading through issues lists figuring out what exactly changed.

Use as little of the framework or library as possible to accomplish your needs

The less you’re dependent on the framework, the less your code will break when you upgrade — it’s as simple as that. If both the language and the framework have a way to do X, use the language’s way. If both a common library and the framework can do Y, use the common library’s way.

Try to only use the good parts of the framework

“Good” means a lot of things to a lot of different people, but we all know good code when we see it — clear APIs, parameters in the right order, complexity broken up, etc. Knowing what’s “good” and “bad” comes with experience, and you won’t get it right every time, but over time you should develop a feel for the parts of the framework that are complex/bound to change and ignore them.

Have unit tests for the most complex, framework-coupled parts of your app

Having to debug your complexity, combined with an unknown amount of complexity from the upgrade, is not smart, especially since you probably forgot exactly how your complex algorithm works. Sure, comments may help trigger your memories, but your (or someone else’s) comments may be old, not up to date, incoherent or irrelevant. It’s best to solidify your forward thinking, upgrade-potential position when the complex code is written by having associated tests.

Use the framework as the designers intended

If you absolutely have to modify the framework itself, write a unit test for it! Otherwise when you upgrade it, you’ll lose and forget about that modification, and your code will turn into a ticking time bomb.

Be prepared to upgrade 3rd party components

Just as your code depended on a specific framework version, their’s probably did too, and they probably used more of the framework’s features than you.

Run 3rd party component tests as part of your test suite

Those components are now a part of your library and you own them. They may or may not break, depending on how coupled they are to the framework, but think of them as if you wrote them yourself. 3rd party library authors have most likely updated their components to fix breaking issues for ongoing framework upgrades, and you may or may not have to do the same, depending on how much you’ve upgraded the framework.

Search and fix all code where the bug may manifest itself

This applies to mainly non-DRY code. If you notice one piece of code broken and have a hunch other code could be as well, do a global search and find/replace other parts of code.

Write as much common and reusable code as possible

Fix code in one spot and one spot only. There’s no greater satisfaction as a developer than not having to worry about duplicate code all over the place.

Understand that lesser known libraries are more likely a liability than an asset

To help ease a future upgrade process, you may want to start a new project with more well-known ones if they exist. Wading through a lesser known library’s codebase can be frustrating and time-consuming, especially if the documentation is poor/non-existent, the folder structure/coding style is non-standard, the library hasn’t been updated in months/years, etc.

Don’t upgrade too much at once

Focus on upgrading one framework completely, then another (if possible), then another (if possible). Obviously if there are dependencies that must be upgraded, you have no choice but try to upgrade as little as possible at the same time.

Always be in control of when to upgrade

Lock down your dependency versions and make sure your builds don’t pull down just any minor or major version. If you think the authors of your transitive dependencies are as cautious as you are, think again. Don’t trust the semver of any package, because it’s most likely wrong anyways.

Ensure all upgraded code is compliant with clients you support

This includes the base framework, 3rd party components, any new code you write to support the upgrade etc.

For example, if your project uses Angular, don’t upgrade to Angular 1.3+ or Bootstrap 4 if you must support IE8. Know what frameworks you can use and ones you can’t and live with it.

Know how much extra framework code you’ll be adding

This is more important for client-side frameworks, like Angular, but it applies to any framework really.

More code = more functionality = more liabilities = more kbs.

It’s extremely rare that frameworks get smaller over time (hopefully they get more modular though), so be sure the upgrade is worth the extra bytes transferred (if using a client-side framework) and extra time it’ll take to load your app.

Determine if upgrading will really be worth it

For example, if you’re upgrading a client-side framework and the upgrade will incur an extra 25kb of download, but once downloaded will ensure the app loads 40% faster for users, go for it! But if it’s 200kb of extra framework and 5% faster, the decision may not be so easy. Always know and be able to explain what the trade-offs are before starting the upgrade process.

Conclusion

Upgrading really starts with your mindset before you even choose a framework to begin with, before you write the first line of code and before you write your first test. My modus operandi during those times is mainly, “Will this framework/library I want to start using end up being an asset or liability?”

I admit that it can be hard to anticipate a framework’s future feature set. Or a library author’s vision for what the library API should look like, how big it’ll eventually get and how many breaking changes will be made. A lot of the times you won’t know exactly, but general wisdom tells us that we can start to develop a gut feel for libraries just by looking at their documentation, how best they follow standards, how many people have contributed to and believe in their project, how many unit tests they have (that pass) etc.

We can prevent a lot of framework/library upgrading headaches by simply spending more up-front time vetting them. And after you pick that candidate framework and start using it, be sure to write some good tests, write common/DRY code, know who your audience is and be in control of your framework (and not let it control you!).

Join Us

Thanks for reading about how we are doing things here at LendingTree. If you would like to join and help shape our company, please visit careers.lendingtree.com and contact us. Follow us on Twitter: @Careers_LT

--

--