Refactoring for clarity — Part 1

Pavle Pesic
codeburst
Published in
6 min readFeb 2, 2021

--

Every now and then, we run into an issue with code we wrote a while ago. This article explores how we might tackle such a situation when refactoring a huge legacy system.

Deep comprehension of a system is necessary when wanting to apply any important changes. On rare occasions, we only need to invest a short time to study the code before we can start improving it. More often, we have to do some debugging first, even to make some unplanned modifications before we can start working on the initial task. I call this process refactoring for clarity.

Refactoring for clarity means improving the code’s structure and readability while not enhancing the functionality. The main goal is to make the classes more readable and understandable, so adding new features would be more comfortable.

When we are talking about comprehending the code, we can look at it from two perspectives:

  1. Big picture — understanding a class as a whole.
  2. Detail — looking at particular variables and methods and knowing what their purpose is in the big picture.

Both of these aspects are significant. In the first part of this mini-series, we will discuss refactoring the code to understand the big picture. Here are some tips.

Removing dead code

At the outset, it’s important to recall why this code (and its issues) exists in the first place. Perhaps previous devs didn’t merge it well, perhaps they forget to delete it, or maybe they left it because it will be usable someday, etc.

https://imgs.xkcd.com/comics/good_code.png

Nevertheless, you don’t want to waste your time on it. When you start working on a legacy system, you should remove dead code. Many tools can help you with this. Pick one that suits you. We used this one. After the cleanup, we can start with real improvements.

If you’ve followed my tips about starting the refactoring process on the legacy code, you won have this problem.

Introducing the coding style

If we want to implement the changes quickly then we need clear goals. We have to know what kind of structure should our components have. Having a coding style will help you a lot. It will provide guidelines about code structure, variable naming, methods, and other rules you should apply.

File structure

All of your structs and classes should have the same structure. Structure implies rules for grouping methods and variables.

Usually, when adding a feature or fixing a bug, we need to understand a specific block, not the whole code. If we have the same structure throughout the codebase, we will navigate the code faster and find the part that interests us. Even if we need a deeper understanding of the whole object, the structure will help us find the critical sections, like public methods, constructors, dependencies, and life cycle methods.

Let us look at an example:

Map view controller before refactoring

As you can see, MapViewController has no structure. We don’t know which properties and methods should be public (most properties and methods have access level internal). Functions aren’t grouped. Properties are all over the place. UITableVIewDataSource and UITableViewDelegate are under one mark. If we want to have readable code, we need to fix that.

File structure — methods

Let’s start with rearranging and grouping methods first.

MapViewController after refactoring — methods

We’ve grouped methods using MARK notation. Just from taking a look at Picture2, we get an idea of what this view controller should do. We could be even more specific. We could add a mark for API calls. It’s up to you to decide how deep you want to go. The important thing is to follow these rules in all other classes:

  1. Initialization and lifecycle should be on the top
  2. Public methods should be above the private ones
  3. Private methods can have subsections (Analytic methods)
  4. Each extension should be inside a mark
  5. If we need to add helper methods inside the extension, we create a sub mark (private methods inside UITableViewDatasource)

If you don’t like this scheme, make your own. It’s not important how you group methods, but to imply the same rule in every file.

File structure — properties

Now we can examine properties too.

MapViewController after refactoring — variables

As you can see, we have three different groups: public properties, private properties, and private computed properties. Like when we’ve changed the methods, we should modify the access level here too. We have a better idea about changing those objects if needed, which modifications can cause effects outside the class, and which dependencies the view controller has.

You can’t see from the picture, but we’ve also changed some variables from vars to lets. Don’t neglect this vital yet straightforward refactor. Mutability can cause many bugs and unexpected behavior. Quite often, developers create properties as vars instead of lets because they are on autopilot. It wouldn’t cause any problems in the beginning. However, when somebody else looks at the class, he might change its value and start a sequence of unexpected events.

Tests

Now it’s time to run tests if you have those. The only thing that could go wrong is that the test class can’t access specific methods or properties because we made them private. Just make those methods protected, and everything should be all right.

Oh swift… If this happens, you could do one of two things. The easy way out is to change the access level to internal or public when needed. The better thing to do is create a new class — MapViewModel. Move untestable methods there, inject the view model into the controller, and do the same for the test class and tests. Because this isn’t refactoring for clarity, we won’t go into the details now.

Conclusion

This set of refactorings is quite useful and straightforward. I use it whenever I start working on code that isn’t conforming to the coding style, and that’s new to me. Moreover, if I have some dependency on the class I’m working on and don’t comprehend it, I refactor it. It may seem like a time waste, but it will save time for my team and me in the future because we will use the dependency eventually.

Understanding the big picture is critical because it gives us an idea about how the system works — what are inputs and outputs, what are the processes inside. We can’t work on the legacy system without comprehending it. Furthermore, we can’t memorize the whole code. That’s why we need to make the code more understandable, simple to read, and easy to navigate through. Make your life easier. Take care of the code and start with refactoring for clarity.

Finally, you may say that we’re nitpicking here and that these refactorings shouldn’t be a priority when working with legacy code. I entirely do not see it this way. Making code beautiful (meaning that it is easy to read and understand) is almost as important as functionality in the long run. And the beauty is in the details.

Resources

Here are some articles and books about the topics if you want to learn more about refactoring and structuring iOS apps.

Articles

Books

--

--