You’ve been working on a big project for several years already, and during that time team members have come and gone and your code has become vaguer and vaguer. Maybe it seems that it would be easier to just rewrite everything from scratch, but you understand that there isn’t much time for that. Refactoring might be another option, but you’re still not sure. In this article, we answer two questions: In which cases should you rewrite your application and what parts can be refactored? How should you go about refactoring your code?

What is rewriting?

As the name implies, rewriting code means writing your application again completely from scratch, using new approaches and technologies. A great thing about rewriting is that you can be completely free from any limitations in the legacy code that would remain if you just refactored your application.

When rewriting your app, you can change everything from languages to frameworks. You also get a chance to avoid mistakes you made last time and approach the development and testing process with a fresh look. You can cover your application with unit tests and make it well-structured and unified. This is why most developers root for a complete rewrite rather than refactoring.

But as flawless and pretty as a rewrite may seem in your head, there’s no guarantee it will go smoothly. Rewriting is an even more time-consuming and difficult process than refactoring. Keep in mind that you’ll need to support your legacy app while working on a new one, and you may lack the resources to do that. The solution is to hire new developers to work on the rewrite, but they may lack experience, and this will lead to a poorly designed and developed application.

It’s also not that easy to translate functionality between two different systems. There are some dark areas in any code that nobody knows in detail, and it can be a challenge to debug and transpose them.

For these reasons, though rewriting an application completely may seem like a very good idea, it may be risky and more costly in the end as it will take much time and nobody can guarantee that the new rewritten system will be perfect.

Now let’s talk about the alternative to rewriting.

When should you rewrite?

In some cases, rewriting the whole app from scratch is the only option. If the existing code is a complete mess, and you see that trying to refactor it is nearly impossible, that’s a good argument for rewriting. However, you should still consider another option because there’s a possibility that you’re just scared of all your legacy code and don’t want to deal with it. We should admit that this decision isn’t easy to make, and a developer usually can’t be 100% sure a rewrite is necessary.

If your app doesn’t work at all, however, then that’s a clear sign that the whole app needs to be rewritten. If there are so many bugs that you can’t even stabilize your application, you should rewrite it. Refactoring is possible only if the app mostly works correctly.

You can also choose both paths, starting by refactoring most of the code into components and encapsulating them. After that, you can decide between refactoring and rebuilding for each component.

What is refactoring?

Refactoring is a technique developers use to improve and restructure the design of existing code without changing its behavior. Refactoring improves non-functional attributes of software. The main advantages of refactoring are:

  • Improved code readability
  • Reduced code complexity

Refactoring can make code more maintainable and extensible, which works great in cases when the architecture or outdated technologies become an obstacle to further development.

Refactoring usually consists of small activities that are performed one after another, gradually and carefully. This way a developer changes the code slightly without changing the software’s behavior, or at least without affecting the software’s compliance with functional requirements.

There’s a lower risk of making errors or breaking the whole system if you perform refactoring in small steps. This approach allows you to take your time for refactoring. While refactoring is usually a long process, your software will still work while you’re doing it.

Refactoring can be extremely beneficial if done correctly. It can help you find hidden bugs or vulnerabilities in your code and simplify logic while getting rid of unnecessary complexity. However, if you don’t have the skills to refactor an application correctly, refactoring can bring more bad than good. For example, you can introduce even more bugs into your code or fail to continue meeting functional requirements.

Refactoring is also used to create applications from scratch. In this case, you don’t plan how your app will be designed from the very beginning but just begin coding, coming up with on-the-spot solutions and making them work. After that, you refactor what you’ve written so that it’s shaped and unified. Some coders who support Extreme Programming believe that this approach can work, and some developers using this approach actually end up with well-designed software.

When should you refactor?

Software development is a long process, and if the project is big, it’s natural that some parts of the code can become outdated or end up with a poor structure. Each time you add a new function, you may encounter obstacles that could be avoided if the structure were different.

You should refactor if your project is big and its messiness is starting to cause problems when you try to implement something new. Knowing how to refactor is just as important as choosing the right moment for it. While it’s relatively easy to teach someone to perform refactoring, there’s no certain guidance on when to start.

Usually, refactoring begins with reviewing code and identifying problems that could be solved by refactoring. These are some of the main problems that can be a green light for refactoring.

Main problems:

Duplicated code – If code repeats in different parts of your application, you should find a way to unify these repeating structures.

Long methods – Long procedures are hard to understand, and if a process can be compressed without losing functionality, it should be. Think about naming small methods so that they’re easy to understand without even looking at the body.

Large classes – Duplicated code is often found in large classes. Avoid too many instance variables and classes that do everything.

Long parameter lists – The more data you need, the more you change the parameter list, and this continues to the point when it’s too hard to understand and use the list.

Divergent change – This occurs when one class is commonly changed in different ways for different reasons. Each object is changed only as a result of one kind of change. Any change to handle a variation should change a single class, and all typing in the new class should express the variation. To clean this up, you should identify everything that changes for a particular cause and extract class to put the changes together.

Shotgun surgery – This is the opposite of divergent change. Every time you make a change, you have to make a lot of little changes to a lot of different classes. When the changes are all over the place, they’re hard to find, and it’s easy to miss changing something important.

Feature envy – This occurs when one object performs computations for another object rather than asking that other object to do the computation itself.

Data clumps – If some pieces of data always go together, you should extract a class and make an object out of them.

Primitive obsession – This refers to using primitive data types to represent domain ideas. For example, using a String to represent a message, an Integer to represent an amount of money, or a Struct/Dictionary/Hash to represent a specific object.

Switch statements – One of the most obvious symptoms of object-oriented code is its comparative lack of switch (or case) statements. The problem with switch statements is essentially that of duplication. Often, you find the same switch statement scattered across a program in different places.

Parallel inheritance hierarchies –These are really a special case of shotgun surgery. In this case, every time you make a subclass of one class, you also have to make a subclass of another. The general strategy for eliminating duplication is to make sure that instances of one hierarchy refer to instances of the other.

Lazy classes – These are classes of things that were planned but not made. Get rid of them when you refactor.

Message chains – When a client asks for one object after another, you can get a huge chain.

Comments are good indicators. After refactoring, you’ll find that many comments that explain what parts of the code does are unnecessary. Make sure you make your code as clear as possible so that it doesn’t even need comments.

You can also have other problems specific to your code.

Refactoring process

The first step in refactoring is always the same. You need to build a solid set of tests for the section of code that you’ll be refactoring. These tests are essential even if you follow refactoring structures to avoid most opportunities for introducing bugs.

As you perform refactoring, you’ll lean on these tests. It’s essential for refactoring that you have good tests. It’s worth spending the time to build tests because tests give you the security you need to change the program later.

When you refactor, you should definitely use unit testing and continuous integration. Without running these tests after each little step of refactoring, you risk introducing bugs.

After testing, you can make a series of small changes, each of which makes the existing code slightly better while still leaving your program in working order. Don’t mix a whole bunch of refactorings into one big change.

There are many code refactoring techniques. You can pick those you feel are the most useful for your code. Examples of techniques are the red-green refactor pattern, preparatory refactoring, composing methods, branching by abstraction, and simplifying method calls.

Make sure that new functionality isn’t created during refactoring. Don’t mix refactoring and direct development of new features. Try to separate these processes at least within the confines of individual commits.

How to estimate time for refactoring

Developers should estimate refactoring the same way they estimate any feature: review the facts of the feature/code (which in this case would include the duplicated code report) and make an intuitive estimate of how long it will take to do the work in whatever units you normally use, for example hours, days, weeks etc.

If refactoring can be completed (implemented and delivered) in pieces, break features up into one issue for each piece and estimate those pieces.

An estimate should take full advantage of a developer’s knowledge of the code. If the developer doesn’t know the code well, their estimate will be less accurate.

To estimate the refactoring effort for an entire codebase, base the estimate either on code quality reports for the entire codebase or on manual reviews of sample sections of the codebase.

As with any project, if having an estimate is valuable, revisit the estimate periodically to account for what you’ve learned from work done thus far.

Refactoring shouldn’t be a separate phase; it’s part of everyday development.

Final thoughts

Deciding whether to rewrite or refactor an application can be tough. From our experience, we would say that if you can refactor, you probably should. You likely won’t need to refactor small projects, but if your project is big, you’ll need to review and refactor it periodically. For example, every 200 development hours your development team might review their code and refactor if needed. This helps keep your code readable and maintainable.

Sometimes, our clients don’t see the importance of refactoring. But we guarantee that in the long run it will save money. The older a project gets, the harder the maintenance becomes. You can compare maintaining a software product to taking care of your teeth: it’s better to brush them regularly and check them at the dentist’s than to spend big bucks on filling cavities or removing teeth altogether.

Some development companies do regular code reviews; those that don’t would be better off if they did. Code reviews help spread knowledge across a development team. Reviews help more experienced developers pass knowledge to less experienced developers. They help more people understand more aspects of large software systems. They’re also very important in writing clear code. Code may look clear to an individual developer but not to their team.

At Mobindustry, we take code maintainability seriously and try to save our customers’ time and money. If you need help maintaining your existing application or if you want to be sure the app you develop won’t be abandoned, contact us.

Article written by:
Vladyslav Kazakov
Valentin Ostapolets

Request Callback

Request Callback

+