DEV Community

Cover image for How to Refactor Spaghetti Code - One Bite at a Time!
Monty Harper
Monty Harper

Posted on

How to Refactor Spaghetti Code - One Bite at a Time!

This week, I’ve been learning things the hard way!

I’m refactoring my calendar app, partly because I’m applying for jobs now, and I want to present a well-constructed app in my portfolio, but also…

The Problem

More importantly, I’ve hit the “spaghetti point” - that magical moment at which adding, testing, and debugging new features has become difficult because the base code is not so well-organized. And I do have a lot features I want to add or tweak, so I feel it’s worth the effort to do some re-organizing first.

I’ve gotten feedback from a couple of fantastic mentors, one of whom I connected with here on Dev. Daria suggested I should break up my big classes into smaller objects, each of which would have a singular purpose. That’s just good SOLID practice! (I’ll write about SOLID principles another time.)

The Plan

I knew I was stepping into a long dark tunnel by doing things this way, but… I decided to re-write one of my mega-classes FROM SCRATCH!

Luckily, I had already learned one lesson the hard way: think about version control (Git) before launching into a big new refactor. So I…

  1. …committed the project in Git, so I wouldn’t lose any recent changes.
  2. …started a new branch on which to mess things up, leaving a working version of the app as a back-up on my main branch.
  3. …deleted my giant class (called SolarEventManager) and opened a new, blank file.

Now I had a non-working app with a long list of items to replace before it would even build again.

I decided to limit the functionality of my new SolarEventManager to “maintaining an array of solar events.” (Makes sense, right?) I created a separate NetworkManager to actually make the network requests and a ScreenStops struct, which calculates the resulting gradients.

One reason I decided to start from scratch was that I had a lot of code in there I didn’t need at all: a bunch of CoreData stuff (which was a requirement from my Udacity course) when really, UserDefaults would do, plus some code duplicating the function of the system’s location manager. So I needed to replace all that with simpler implementation.

And I also decided to change the way I was making the SolarEventManager available to my views.

So it was a lot.

Stumbling Blocks

I’m not sorry I started from scratch; it was a good way to reinforce everything I’ve learned writing the original code, while solidly (oops, a pun!) comprehending the new structure I was building.

However, it did mean days of typing code with no verification that any of it would actually work!

And please note that I easily could have tackled each of the individual issues listed above one at a time. But no...

Bravely I forged ahead, or… back, I suppose. Finding my way back to ground zero meant hitting three milestones:

  1. Building the app without incurring any errors.
  2. Running the app without crashes.
  3. Getting the app to behave properly.

Because I changed so many things at once, finding and fixing problems at each stage presented a huge challenge. Here are a few of the issues I encountered once I finally got the thing to build again:

  • Crashes due to an @EnvironmentObject not being available in the environment.
  • Custom navigation buttons not working. One of them crashed the app.
  • UserDefault key with a typo, which caused weird behavior and was quite difficult to track down. (I made an enum for those keys and updated the entire app accordingly.)
  • Banner messages not showing up, due to a logic error in some if-else blocks.
  • Crash due to loss of internet.
  • Failure to update when the internet was turned back on.
  • No background view!

That last one stumped me for a while — all my assumptions about where my background went were wrong, wrong, wrong. Turns out, a combination of errors was the culprit:

  • My JSON decoder had the wrong Type,
  • My recursive fetching function wasn’t passing its closure on to the next iteration, and
  • I had a var property defined in the wrong spot so its scope wasn’t what it needed to be.

Those last three bugs were simple oversights. But tracking them down took a WHOLE LOT of detective work, since the error (or errors as it turns out) could have been anywhere in the large swath of code I had changed since the last time things were working correctly.

Lessons Learned

Change one thing at a time. That is all. Change, build, test, fix, commit, change, build, test, fix, commit. Making relatively smaller changes and bringing the app back to working order after each would have helped me isolate problems and fix them more quickly. After all, a brand new problem can only be due to something you just changed, so… yeah, small changes make errors much easier to track down.

The class I deleted was a mess, and I did believe I would end up writing cleaner code by starting from scratch. Maybe that’s true, but also, looking back, to be honest, I think I would have arrived at the same place, and maybe a bit faster. Or maybe a lot faster!

What do you think? Is there ANY advantage to scrapping a whole section of code from a currently working app, and re-building it from the ground up? Or is it always better to change one bit of functionality at a time? Please post your thoughts!

Top comments (11)

Collapse
 
efpage profile image
Eckehard • Edited

I´m not sure, SOLID is useful for all cases. Classes can be a way to organize your code, but if you have a class that serves only a single purpose and covers a single state, you will end up with the same spagehtti, just wrapped in classes.

If you use classes, they should have a clear focus, maybe we can say: a single task. But this task may consist of a larger number of functions. Think of a class that encapsulates all the database handling. It will need more than one function to do so, but can still be a consistent unit.

Try to split your codebase into smaller units. If you manage to run and test each unit separately, you are sure there are no unexpected dependencies.

Refractoring can be hard work. But if your code is split into smaller units with different topics, it will run much more stable. You will encounter all the sloppy spaghetties that tie one unit to the other. Even if you find it is necessary to rebuild a complete part, this will be easier than rewriting everything.

And the most important advice: If you are confused, your code will be not better. Wait some time, drink a beer, take a break and revisit your code some day´s later. Write down what you have done and analyze your work. This will help to find better solutions.

Collapse
 
montyharper profile image
Monty Harper

Thanks - sounds like great advice, especially the part about taking a break! Interesting thought about ending up with the same spaghetti if your classes are too specialized - I think that's what you mean, and I can picture that for sure.

Collapse
 
efpage profile image
Eckehard • Edited

No, it´s just that the SOLID principles seem to apply to a very specific case. If you look how classes have been used in large projects, this is a different picture. I can agree that a class should have a well defined task, but in SOLID this sounds different:

A class should have one and only one reason to change, meaning that a class should have only one job.

Possibly this is simply an ambigous wording, but classes have been used in a very different way in the past. Often we are not talking about single classes, but about a class hierarchy.

Image description

All methods and properties implemented in a class will be inherited by the childs, that can make some adjustments on the existing methods or add new features. If you find you added something that could be useful for the siblings, just shift it to the parent class, so all siblings will inherit the new methods. As this method is not used in the parent class, it will be called "abstract".

Done right you can assure, that each method is only implemented once in a class hierarchy, so it acts as a "single source of truth", there is only one place you have to edit, if you need to refractor your code.

Many OS-APIS are organized as class trees. If you check the Android API or the Windows GDI, you will find deeply nested class hierarchies that give each child access to hundreds of properties. This is quite useful, as it makes the class part of an ecosystem. I simply cannot find any parallels to the SOLID principles in this.

Can this help you, to write better code?

When I started coding in C, I found my projects went great until the source code reached a size of about 30kB. Beyond that, things got creepy, unwanted side effects where hard to find. Things where simply too complex to keep all the strings in hand. Using classes allowed me to isolate parts of my code, put it in a self contained unit that was less complex. With well defined interfaces this classes could be used like LEGO-blocks. Often I found that a well designed class could be used in later projects, so it saved me some time.

There are different ways to organize your code. OOP is only one way. The most important advice is to keep things as simple as possible. Don´t trust yourself, make things idiot-proof, as you will find that - how slim you may be today - tomorrow you will be the idiot, that simply forgot a comma that crashed your whole project :-)

Thread Thread
 
montyharper profile image
Monty Harper

Thanks @efpage,
I had to read a couple of times, but it all makes good sense. Yeah, the classes I'm using in SwiftUI do not have any inheritance behind them. In UIKit, all the UI elements are classes with a lot of inheritance, but in SwiftUI, UI elements are structs. I'm using classes for data because they are reference types, so all my views can have access to the same instance of the data.

Collapse
 
danielrendox profile image
Daniel Rendox

I've hit the wall quite a few times on my previous project. Sometimes, I'd rewrite the whole thing from scratch and then realize the previous solution was actually better. 😁 In the end, I came to the same conclusion — refactor it step by step: add more test cases, make the code cleaner, run the app, and commit to save the history.

Now, the biggest challenge for me is to make the right decisions from the outset, be a lazy developer, plan smarter, and do everything faster. Because I sometimes waste my time on something that could be done a lot simpler or isn't even necessary. 😔

Collapse
 
montyharper profile image
Monty Harper

Hi Daniel, thanks for your response. Glad you came to the same conclusion about refactoring step-by-step.

As for the need to refactor sometimes - it's all part of the learning process.

I have a feeling making the "right decisions from the outset" is a good thing to strive for, but also might be one of those goals nobody ever fully reaches, since there is always another tool or method or philosophy you just didn't know about at the time.

Also, I imagine as soon as you start thinking, "I basically do everything the best way from the start", then you're closing yourself off to learning, and I wouldn't want to do that.

I know from songwriting there's a basic level of competence to be reached where you can say, "I almost always start off in generally the right direction."

I used to waste a lot of time trying to write songs that never worked out. These days I can take a song idea and quickly figure out whether it will work well and how to organize it into a song structure that has promise. I still re-write and polish - I don't think that can ever be avoided - but I don't usually get stuck or backtrack or waste time on an idea that just won't work.

I'm sure competency at app-writing follows a similar curve. And I know I still have a ways to go, but having got there before in another creative pursuit, I can say with confidence that I'll get there with app development as well. It just takes time and experience.

I have no doubt you'll get there too, Daniel! Don't be too hard on yourself along the way!

Collapse
 
bwca profile image
Volodymyr Yepishev

You might want to look into some unit testing before actually refactoring. Those sometimes help catching bugs early when introducing changes to the existing codebase.

Collapse
 
montyharper profile image
Monty Harper

Yes, thanks! Unit testing is on my list of things to learn next.

Collapse
 
joebloggs profile image
Joe

Missed an opportunity in the headline, should have been "One byte at a time" 🤣

Collapse
 
montyharper profile image
Monty Harper

Actually, do you mind if I change it to that?

Collapse
 
montyharper profile image
Monty Harper

You're so right! 😲

Some comments may only be visible to logged-in visitors. Sign in to view all comments.