Fundamentals 23 min read

Refactoring Principles, Code Smells, and Techniques for Improving Code Quality

This article explains why and how to refactor legacy code by defining refactoring principles, describing common code smells, and presenting concrete techniques such as extracting functions, moving fields, introducing explanatory variables, and reorganizing data and inheritance hierarchies to produce cleaner, more maintainable software.

Architect's Tech Stack
Architect's Tech Stack
Architect's Tech Stack
Refactoring Principles, Code Smells, and Techniques for Improving Code Quality

1. Refactoring Principles

Refactoring is the disciplined technique of changing a software's internal structure without altering its observable behavior, aiming to improve understandability and reduce modification costs.

Reasons to refactor include preventing design decay, exposing bugs, and increasing development speed by making the code easier to read and modify.

When to Refactor

Refactoring should be done continuously rather than as a separate scheduled activity. The "Three‑times rule" suggests that after doing something twice you become tolerant, but the third time you should refactor.

Typical triggers are adding new features, fixing bugs, or when the existing design hinders easy extension.

Bad Code Smells

Common smells include duplicated code, long or large classes, long parameter lists, divergent changes, shotgun surgery, data clumps, primitive obsession, switch statements, parallel inheritance hierarchies, and redundant classes.

Each smell is described with examples and suggested remedies.

2. Reorganizing Functions

Extract Function : When a function is too long or its purpose is unclear, move the code into a new well‑named function and replace the original segment with a call.

Inline Function : If a function's body is as clear as its name, replace calls with the body and delete the function.

Inline Temporary Variable example:

double basePrice = anOrder.basePrice();
return (base > 10000 );

can be replaced with: return (anOrder.basePrice() > 1000); Replace Temporary with Query example:

double basePrice = quantity * timePrice;
if (basePrice > 1000) {
    return basePrice * 9.5;
} else {
    return basePrice * 0.98;
}

becomes:

if (basePrice() > 1000) {
    return basePrice() * 9.5;
} else {
    return basePrice() * 0.98;
}

Introduce Explaining Variable example:

final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1;
final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1;
final boolean wasResized = resize > 0;
if (isMacOs && isIEBrowser && wasInitialized() && wasResized) {
    // do something
}

These variables make complex conditions readable.

3. Moving Features Between Objects

Techniques include moving functions or fields to the class that uses them most, extracting new classes when a class has too many responsibilities, inlining classes that are too thin, hiding delegation, removing middle‑men, and adding extension functions when the original class cannot be modified.

Example of adding an extension function:

Date newStart = new Date(year, month, date + 1);

can be refactored to use a helper method:

private static Date nextDay(Date arg) {
    return new Date(arg.getYear(), arg.getMonth(), arg.getDate() + 1);
}
Date newStart = nextDay(nowDate);

4. Reorganizing Data

Encapsulate fields with getters/setters, replace primitive data with value objects, convert value objects to references, replace arrays with objects, eliminate data clumps, and hide magic numbers behind named constants.

Example of encapsulating a field:

private int low, high;
boolean includes(int arg) {
    return arg >= getLow() && arg <= getHigh();
}
int getLow() { return low; }
int getHigh() { return high; }

5. Simplifying Conditional Expressions

Break complex conditions into separate functions, merge similar tests, pull common code out of branches, replace control flags with early returns or breaks, and use polymorphism to eliminate type‑based conditionals.

6. Simplifying Function Calls

Rename unclear functions, add or remove parameters, split functions that both query and modify state, replace parameter‑driven behavior with dedicated functions, introduce parameter objects, and hide unused functions (make them private).

7. Handling Generalization Relationships

Move common fields or methods up to a superclass, pull up constructors, push down methods or fields that are only used by some subclasses, extract new subclasses for specialized behavior, extract superclasses for shared features, create interfaces for common subsets, collapse unnecessary inheritance hierarchies, and replace inheritance with delegation when appropriate.

Source: https://www.jianshu.com/p/3f04b6aebad2

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Software Engineeringcode qualityrefactoring
Architect's Tech Stack
Written by

Architect's Tech Stack

Java backend, microservices, distributed systems, containerized programming, and more.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.