Refactoring by Martin Fowler: Review and Study Guide for .NET Developers
A friendly review and study guide for Martin Fowler's Refactoring, with C# before and after examples, code smells, and a clear 6-week plan.
What this book is
Refactoring: Improving the Design of Existing Code is a book by Martin Fowler. It teaches one big skill: how to change the structure of code that already works, without breaking it, and without changing what it does for the user.
The first edition came out in 1999 and shaped how a whole generation of programmers thinks. The second edition, from 2018, rewrote the examples in JavaScript and updated the catalog of moves. Of the 68 refactorings in the first edition, almost all are still here, and 17 new ones were added.
The examples are not in C#, but that does not matter much. The ideas are about code shape, not about one language. This page is a review plus a study guide. By the end you will know what the book teaches, see clean C# before-and-after examples, learn who should read it, and have a week-by-week plan.
A real-life way to think about it
Imagine your bedroom after a busy week. Clothes on the chair. Books on the floor. Cables everywhere. Nothing is broken. You can still find your shoes. But every time you want one thing, you have to climb over five other things first.
Now imagine you tidy it up. You hang the clothes. You shelve the books. You coil the cables. The room does the exact same job it did before. You did not add a new bed or paint the walls. You just made it easier to live in.
That is refactoring. The code still does the same thing for the user. But it becomes easier to read, easier to change, and less scary to touch. You are tidying the room so that next week's work is fast instead of painful.
And here is the key trick from the book: you do not tidy the whole room in one giant heroic afternoon. You hang one shirt. You check that the room is still fine. You shelve one book. You check again. Tiny steps. Always safe.
// Before: it works, but it is a messy room. Hard to read in one glance.
public decimal Total(Order o)
{
decimal t = 0;
foreach (var i in o.Items) t += i.Price * i.Qty;
if (o.Items.Count > 5) t = t * 0.9m;
return t;
}This method works. But it is cramped. We will tidy it up later in this guide, one small step at a time.
The two hats
Fowler gives a simple rule that keeps refactoring safe. At any moment you are wearing one of two hats.
- The adding-a-feature hat: you are making the program do something new. You add code. You add tests.
- The refactoring hat: you are only changing the shape of existing code. You add nothing new. The behaviour stays exactly the same.
You never wear both hats at once. If you are halfway through adding a feature and notice the code would be easier to extend if it were tidier, you stop. You take off the feature hat. You put on the refactoring hat. You tidy. Then you swap back and finish the feature.
This sounds small, but it is the heart of the whole discipline. Mixing the two hats is how people break things. Keeping them apart is how you stay safe.
Tiny steps and a green test bar
The book repeats one idea more than any other: take tiny steps. Each step is so small that the code keeps working the whole time. You run your tests after each step. If they pass, you keep going. If they fail, you just undo the last tiny step. You are never more than one undo away from working code.
This feels slow at first. It is actually faster. Big risky changes lead to long debugging sessions where nothing works and you cannot tell why. Tiny safe changes never put you in that hole.
So tests are not optional. They are the safety net. Before you refactor a piece of code, you make sure it has tests. Then every tiny step gets checked.
The refactoring loop
Steps
Green tests
Start with passing tests
Tiny change
One small refactoring move
Run tests
Still green? Good. Red? Undo.
Commit
Save the safe state, repeat
// A tiny safety net before you touch the code
[Fact]
public void Total_applies_ten_percent_discount_over_five_items()
{
var order = new Order(); // 6 items at 10 each
for (int i = 0; i < 6; i++) order.Items.Add(new Item { Price = 10m, Qty = 1 });
var result = new Pricer().Total(order);
Assert.Equal(54m, result); // 60 minus 10 percent
}With this test green, we can reshape Total and instantly know if we broke it.
Code smells: how you know it is time to tidy
The book has a famous chapter called "Bad Smells in Code". A code smell is not a bug. The program still runs. A smell is just a sign that the code might be hard to live with, like a funny smell in the kitchen that says "look here". When you spot a smell, you reach for a matching refactoring.
Here are some of the most common smells and the move you usually reach for.
| Code smell | What it looks like | A move that often helps |
|---|---|---|
| Long Function | A method that goes on for screens | Extract Function |
| Duplicated Code | The same lines copied in many places | Extract Function, then call it |
| Long Parameter List | A method asks for 8 separate values | Introduce Parameter Object |
| Mysterious Name | temp, data2, doStuff() | Rename Variable, Rename Function |
| Feature Envy | A method keeps poking another class's data | Move Function |
| Magic Number | A bare 0.9 with no name | Replace Magic Literal with a constant |
You do not need to memorise all of these. The skill is simply noticing "this is awkward to read", giving the awkward thing a name, and then looking up the matching recipe.
Named refactorings: the catalog
The biggest part of the book is a catalog of named refactorings. Each one is a step-by-step recipe. Giving the moves names is powerful. In a code review you can say "let's Extract Function here" and everyone knows exactly what you mean. It turns a vague feeling into a clear, shared action.
Let's walk through the most useful ones with C# before-and-after pairs.
Extract Function (Extract Method)
This is the most used move in the whole book. You take a chunk of code, give it a clear name, and pull it into its own method. The old spot just calls the new method. The name does the explaining, so you need fewer comments.
// BEFORE: one method doing several jobs at once
public decimal Total(Order o)
{
decimal subtotal = 0;
foreach (var i in o.Items) subtotal += i.Price * i.Qty;
if (o.Items.Count > 5) subtotal = subtotal * 0.9m;
return subtotal;
}// AFTER: each job has a name, so the top method reads like a sentence
public decimal Total(Order order)
{
var subtotal = Subtotal(order);
return ApplyBulkDiscount(subtotal, order.Items.Count);
}
private static decimal Subtotal(Order order)
=> order.Items.Sum(i => i.Price * i.Qty);
private static decimal ApplyBulkDiscount(decimal amount, int itemCount)
=> itemCount > 5 ? amount * 0.9m : amount;The behaviour is identical. The test from earlier still passes. But now anyone can read Total and understand it in two seconds.
Rename Variable and Rename Function
A name is the cheapest documentation you have. The book treats renaming as a real refactoring, with a recipe, because a good name can save a reader minutes of confusion.
// BEFORE: what is d? what is x?
var d = DateTime.Now - order.PlacedOn;
if (d.TotalDays > x) Flag(order);// AFTER: the names tell the whole story
var ageOfOrder = DateTime.Now - order.PlacedOn;
if (ageOfOrder.TotalDays > StaleAfterDays) Flag(order);Replace Magic Literal
A "magic" number is a bare value with no name. The reader has to guess what 0.9 or 5 means. Give it a name and the guessing stops.
// BEFORE: what is 0.9? what is 5?
if (order.Items.Count > 5) total *= 0.9m;// AFTER: the rule is now self-explaining
const int BulkOrderThreshold = 5;
const decimal BulkDiscountRate = 0.10m;
if (order.Items.Count > BulkOrderThreshold)
total *= (1m - BulkDiscountRate);Introduce Parameter Object
When a method asks for a long list of values that always travel together, bundle them into one small object. The call gets shorter and the values get a shared name.
// BEFORE: a long parameter list that is easy to mix up
public void Book(string from, string to, DateTime start, DateTime end, int guests)
{
// ...
}// AFTER: the related values now travel as one clear thing
public record Reservation(string From, string To, DateTime Start, DateTime End, int Guests);
public void Book(Reservation reservation)
{
// ...
}Decompose Conditional
A tangled if is hard to read. Pull the condition and each branch into well-named methods so the logic reads in plain words.
// BEFORE: what does this condition even mean?
if (date < season.Start || date > season.End)
charge = quantity * plan.RegularRate + plan.RegularFee;
else
charge = quantity * plan.SummerRate;// AFTER: each idea has a name
charge = IsOffSeason(date, season)
? RegularCharge(quantity, plan)
: SummerCharge(quantity, plan);How the moves fit together
You rarely do one refactoring alone. You chain them. You spot a Long Function smell, so you Extract Function a few times. While extracting you notice a Magic Number, so you replace it. Then a method now has a Mysterious Name, so you Rename it. Each step is tiny and safe, and they compound into a big clean-up.
How the book is laid out
Knowing the shape of the book helps you plan your reading.
| Part | Focus | What you get |
|---|---|---|
| Opening example | A worked clean-up | See refactoring in real time, step by step |
| Principles | The "why" | Two hats, tiny steps, the role of tests |
| Bad smells | When to refactor | The catalog of code smells |
| Building tests | Your safety net | Why tests come first |
| The catalog | The "how" | Dozens of named, step-by-step moves |
The first chapter is the best part for most people. Fowler takes a small messy program and tidies it in front of you, narrating every tiny step. Read that chapter slowly, with a keyboard open, and you will get refactoring in a way a summary can never give you.
Who this book suits
- Working .NET developers who keep meeting code they are scared to change. This book turns fear into a calm, repeatable process.
- People learning clean code and architecture. Refactoring is the everyday muscle that keeps a clean architecture or a modular monolith from rotting over time.
- Reviewers and tech leads who want a shared vocabulary. "Let's Extract Function" is clearer than "this feels messy".
It is less ideal as your very first programming book. You should be able to write C# classes, methods, and a simple unit test before you start.
A note on modern .NET
The book's ideas have aged beautifully because they are about code shape, not tools. On .NET 10, which is the current LTS release, your IDE does much of the safe-step work for you. Visual Studio and Rider have built-in commands for Extract Method, Rename, and Introduce Parameter, and they update every call site for you. That is the book's "tiny safe step" idea baked right into the tooling.
C# 14 has shipped, and C# 15 brings union types in the .NET 11 preview. New language features give you nicer targets to refactor toward, like record types for a Parameter Object, or pattern matching to clean up a tangled conditional. None of that changes the core method: small steps, lean on tests, name your moves.
One modern wrinkle worth knowing: some popular libraries, such as MediatR, MassTransit, and AutoMapper, have moved to commercial licenses. That is a tooling choice, not a refactoring one. Every lesson in this book works with nothing but the standard .NET SDK and a free test framework like xUnit.
A 6-week study plan
Here is a steady plan. One focused session per week, always with a keyboard open. Reading code is not the same as moving it.
6-week study plan
Steps
Week 1
The opening example, typed out
Week 2
Two hats and tiny steps
Week 3
Write tests as a safety net
Week 4
Learn the code smells
Week 5
Extract, Rename, Move
Week 6
Refactor your own real code
- Week 1 — The opening example. Type the first chapter's clean-up yourself in C#. Do not skip steps. Feel how each tiny move keeps the code working.
- Week 2 — The two hats. Read the principles chapter. On your own project, catch yourself wearing both hats at once. Practice swapping on purpose.
- Week 3 — Tests first. Take one untested method you own. Write a couple of tests that pin down what it does today. Now it is safe to change.
- Week 4 — Smells. Read the bad smells chapter. Open your own repo and find three smells. Just name them. Do not fix anything yet.
- Week 5 — The core moves. Learn Extract Function, Rename, and Move Function from the catalog. Apply each one to a smell you found in week 4.
- Week 6 — Your real code. Pick one messy class at work. Run the full loop: green tests, tiny step, run tests, commit. Notice how calm it feels.
Tips while you read
- Always keep the tests green. If a step turns them red, undo it. Do not push forward and "fix it later".
- Commit after every successful tiny step. Cheap commits mean a cheap undo.
- Let your IDE do the mechanical moves. Its Extract Method and Rename are safer than doing it by hand.
- Keep a sticky note of the five smells you meet most. You will start spotting them everywhere.
- Pair this book with our clean architecture folder structure guide, so your freshly tidied code has a good home.
Quick recap
- Refactoring changes the shape of code without changing what it does, like tidying a room that already works.
- Wear only one hat at a time: add a feature, or refactor. Never both at once.
- Take tiny steps and lean on tests as a safety net. You are always one undo from working code.
- Code smells are signs, not bugs. They tell you where to look and which move to reach for.
- The catalog gives named recipes like Extract Function, Rename, and Introduce Parameter Object. Names give your team a shared language.
- The ideas fit perfectly on .NET 10 and C# 14, and modern IDE tooling makes the safe steps almost automatic.
References and further reading
- Refactoring on Martin Fowler's site
- The Second Edition of Refactoring
- Changes for the 2nd edition
- Workflows of Refactoring
- Our clean architecture folder structure guide for where tidy code belongs.
- Our modular monolith article, where steady refactoring keeps module boundaries healthy.