Refactoring at a Glance
Comprehensive Guide to Refactoring Techniques and Code Smells
Refactoring is the process of improving existing code without changing its external behaviour. It makes messy code clean and the design simpler. The goal is to fight technical debt and make future development easier and safer. Good refactoring is incremental: small changes followed by tests keep your software working while you improve it.
Source: This content is based on Refactoring.Guru, a comprehensive resource for refactoring techniques, design patterns, SOLID principles, and code smells. Learn more at refactoring.guru
Refactoring at a Glance
Refactoring is the process of improving existing code without changing its external behaviour. It makes messy code clean and the design simpler. The goal is to fight technical debt and make future development easier and safer. Good refactoring is incremental: small changes followed by tests keep your software working while you improve it.
Catalog of Refactoring
The catalog groups code smells and refactoring techniques. Smells describe problems in your code; techniques describe the steps to eliminate those problems. Below is an overview of each group and the individual items it contains.
Code Smells
Code smells highlight recurring patterns of bad design or coding practices. While they don't necessarily indicate bugs, they often signal deeper problems. Here are the main categories:

Bloaters
Bloaters are methods and classes that have grown so large that they are hard to maintain. They accumulate over time when no one tackles the excess.
- Long Method
- Large Class
- Primitive Obsession
- Long Parameter List
- Data Clumps

Object‑Orientation Abusers
These smells stem from incomplete or incorrect application of object‑oriented principles.
- Alternative Classes with Different Interfaces
- Refused Bequest
- Switch Statements
- Temporary Field

Change Preventers
These smells mean that making a change in one place requires changes in many other places, which makes modifications expensive.
- Divergent Change
- Parallel Inheritance Hierarchies
- Shotgun Surgery

Dispensables
Dispensables are pointless pieces of code whose absence would make the program cleaner and easier to understand.
- Comments
- Duplicate Code
- Data Class
- Dead Code
- Lazy Class
- Speculative Generality

Couplers
Smells in this group indicate excessive coupling between classes or misuse of delegation.
- Feature Envy
- Inappropriate Intimacy
- Message Chains
- Middle Man
Other Smells
Additional smells that don't fit neatly into the above groups.
- Incomplete Library Class
Refactoring Techniques
Refactoring techniques describe how to transform code to remove a smell or improve design. They are organised into thematic groups:

Composing Methods
These techniques help to break down long, complex methods into smaller, more readable pieces and remove duplication.
- Extract Method
- Inline Method
- Extract Variable
- Inline Temp
- Replace Temp with Query
- Split Temporary Variable
- Remove Assignments to Parameters
- Replace Method with Method Object
- Substitute Algorithm

Moving Features between Objects
When functionality is poorly distributed among classes, these techniques help move behaviour and data to where it belongs.
- Move Method
- Move Field
- Extract Class
- Inline Class
- Hide Delegate
- Remove Middle Man
- Introduce Foreign Method
- Introduce Local Extension

Organizing Data
These refactorings help replace primitive data structures with richer objects and clean up class relationships.
- Change Value to Reference
- Change Reference to Value
- Duplicate Observed Data
- Self Encapsulate Field
- Replace Data Value with Object
- Replace Array with Object
- Change Unidirectional Association to Bidirectional
- Change Bidirectional Association to Unidirectional
- Encapsulate Field
- Encapsulate Collection
- Replace Magic Number with Symbolic Constant
- Replace Type Code with Class
- Replace Type Code with Subclasses
- Replace Type Code with State/Strategy
- Replace Subclass with Fields

Simplifying Conditional Expressions
Conditionals often become tangled over time. These techniques simplify complex logic.
- Consolidate Conditional Expression
- Consolidate Duplicate Conditional Fragments
- Decompose Conditional
- Replace Conditional with Polymorphism
- Remove Control Flag
- Replace Nested Conditional with Guard Clauses
- Introduce Null Object
- Introduce Assertion

Simplifying Method Calls
These refactorings reduce the complexity of method signatures and make calls easier to understand.
- Add Parameter
- Remove Parameter
- Rename Method
- Separate Query from Modifier
- Parameterize Method
- Introduce Parameter Object
- Preserve Whole Object
- Remove Setting Method
- Replace Parameter with Explicit Methods
- Replace Parameter with Method Call
- Hide Method
- Replace Constructor with Factory Method
- Replace Error Code with Exception
- Replace Exception with Test

Dealing with Generalization
Abstraction and inheritance require careful management. These refactorings adjust hierarchies and relationships.
- Pull Up Field
- Pull Up Method
- Pull Up Constructor Body
- Push Down Field
- Push Down Method
- Extract Subclass
- Extract Superclass
- Extract Interface
- Collapse Hierarchy
- Form Template Method
- Replace Inheritance with Delegation
- Replace Delegation with Inheritance
Clean Code
The main purpose of refactoring is to fight technical debt. It transforms a mess into clean code and simple design.
Nice! But what's clean code, anyway? Here are some of its features:
Clean code is obvious for other programmers. Poor variable naming, bloated classes and methods and magic numbers all make code sloppy and difficult to grasp.
Clean code doesn't contain duplication. Each time you need to change duplicate code you must repeat the change everywhere. This increases cognitive load and slows down progress.
Clean code contains a minimal number of classes and other moving parts. Less code means less to keep in your head, lower maintenance cost and fewer bugs.
Clean code passes all tests. You know your code is dirty when only 95% of your tests pass. You're truly in trouble when your test coverage is zero.
Clean code is easier and cheaper to maintain
Technical Debt
Everyone does their best to write excellent code from scratch. There probably isn't a programmer out there who intentionally writes unclean code to the detriment of the project. But at what point does clean code become unclean? The metaphor of "technical debt" was originally suggested by Ward Cunningham: borrowing time by cutting corners is like taking a loan – you pay interest later when the debt slows development down.
Causes of Technical Debt
Business pressure. Sometimes business circumstances force you to roll out features before they're finished. Patches and kludges appear to hide the unfinished parts of the project.
Lack of understanding of the consequences. When management doesn't understand that debt accumulates interest it becomes difficult to allocate time for refactoring.
Failing to combat strict coherence. A monolithic project makes any change affect many parts. Team work becomes hard because individual work isn't isolated.
Lack of tests. Absence of immediate feedback encourages quick but risky workarounds. In worst cases hot‑fixes are deployed into production without any prior testing.
Lack of documentation. Poor docs slow down the onboarding of new people and can grind development to a halt if key people leave the project.
Lack of interaction between team members. When the knowledge base isn't distributed across the company people work with outdated understanding of processes and systems.
Long‑term simultaneous development in several branches. The more changes made in isolation the greater the total debt when those changes are merged.
Delayed refactoring. Requirements change over time. Old code becomes obsolete and must be redesigned. Meanwhile new code depends on the obsolete parts. The longer you wait the more dependent code has to be reworked later.
Lack of compliance monitoring. When everyone writes code as they see fit the project deteriorates.
Incompetence. Sometimes developers simply don't know how to write decent code.
When to Refactor
Rule of Three
- When you're doing something for the first time, just get it done
- When you're doing something similar for the second time, cringe at having to repeat but do the same thing anyway
- When you're doing something for the third time, start refactoring
When Adding a Feature
- Refactoring helps you understand other people's code. If you have to deal with someone else's dirty code try to refactor it first. Clean code is much easier to grasp. You will improve it not only for yourself but also for those who will use it after you.
- Refactoring makes it easier to add new features. It's much easier to make changes in clean code.
When Fixing a Bug
Bugs in code behave just like those in real life: they live in the darkest, dirtiest places. Clean your code and the errors will practically discover themselves. Managers appreciate proactive refactoring because it eliminates the need for special refactoring tasks later. Happy bosses make happy programmers.
During a Code Review
The code review may be the last chance to tidy up the code before it becomes available to the public. It's best to perform such reviews in a pair with the author. That way you can fix simple problems quickly and gauge the effort required for the more difficult ones.
How to Refactor
Refactoring should be done as a series of small changes, each of which makes the existing code slightly better while still leaving the program in working order.
Checklist of Refactoring Done the Right Way
The code should become cleaner. If the code remains just as unclean after refactoring you've wasted your time. This often happens when you bundle many refactorings into one big change. In extremely sloppy code it may be better to rewrite parts entirely after writing tests and allocating sufficient time.
New functionality shouldn't be created during refactoring. Don't mix refactoring and the development of new features. Separate these processes at least within individual commits.
All existing tests must pass after refactoring. Tests can fail because you made an error during refactoring or because the tests were too low‑level. In the latter case refactor the tests or write higher‑level tests. Behaviour‑driven development (BDD)–style tests help avoid this situation.
Example: Refused Bequest
Let's explore a specific code smell example: Refused Bequest. If a subclass uses only some of the methods and properties inherited from its parents, the hierarchy is off-kilter. The unneeded methods may simply go unused or be redefined and give off exceptions.
Signs and Symptoms
If a subclass uses only some of the methods and properties inherited from its parents, the hierarchy is off-kilter. The unneeded methods may simply go unused or be redefined and give off exceptions.
⚠️ Warning Signs: When you see a subclass that overrides many parent methods with empty implementations, throws exceptions in overridden methods, or only uses a small fraction of inherited functionality, you're likely dealing with a Refused Bequest code smell.
Reasons for the Problem
Someone was motivated to create inheritance between classes only by the desire to reuse the code in a superclass. But the superclass and subclass are completely different.
Code Reuse Motivation
Inheritance was created primarily to reuse code, not because there's a true "is-a" relationship between classes.
Conceptual Mismatch
The superclass and subclass represent different concepts, making the inheritance relationship semantically incorrect.
Over-Inheritance
Too much functionality is inherited, but only a small portion is actually needed by the subclass.
Treatment
There are two main approaches to fix the Refused Bequest code smell, depending on whether inheritance makes sense in your specific case.
Replace Inheritance with Delegation
If inheritance makes no sense and the subclass really does have nothing in common with the superclass, eliminate inheritance in favor of Replace Inheritance with Delegation.
💡 When to use: When the classes are conceptually different and inheritance was only used for code reuse. Delegation provides a cleaner, more explicit relationship.
Extract Superclass
If inheritance is appropriate, get rid of unneeded fields and methods in the subclass. Extract all fields and methods needed by the subclass from the parent class, put them in a new superclass, and set both classes to inherit from it (Extract Superclass).
💡 When to use: When there is a legitimate inheritance relationship, but the current hierarchy includes too much unnecessary functionality. Create a more focused common ancestor.
Decision Matrix
Classes are conceptually different
→ Use Replace Inheritance with Delegation
Inheritance is appropriate but too broad
→ Use Extract Superclass
Payoff
Fixing the Refused Bequest code smell brings significant benefits to your codebase's clarity and maintainability.
Improved Code Clarity
You will no longer have to wonder why the Dog class is inherited from the Chair class (even though they both have 4 legs).
Better Organization
Class hierarchies become more logical and easier to understand. Relationships between classes reflect actual conceptual relationships rather than just code reuse.
Reduced Confusion
Developers won't be confused by inheritance relationships that don't make semantic sense. Code becomes more intuitive and self-documenting.
✅ Key Takeaway: Proper inheritance should represent an "is-a" relationship, not just a mechanism for code reuse. When you fix Refused Bequest, you're making your code's intent clearer and more maintainable.
Explore Refactoring.Guru
Browse the complete refactoring catalog directly from Refactoring.Guru. Click the maximize button to view in fullscreen.
Related Resources
Explore more about refactoring techniques and code smells from Refactoring.Guru.



