Does "Extract Method" Actually Hurt Your Readability?
We’ve all been there. A feature starts simple, maybe 20 lines. But after three or four iterations, that same function has ballooned to 200 lines, a tangled mess of nested if-else blocks.
Does that reality sound familiar?
When faced with this, we have two main choices. One way is to create tech debt, a task we’ll never really get to because we will always have more urgent priorities from the business. The other way was shown in the foundational book, Refactoring by Kent Beck and Martin Fowler. This path treats refactoring as a continuous action, not a tech debt item in the backlog.
But if we choose to refactor continuously, what does that really mean, and are our tools helping or hurting?
My Context and the “Easy” Button
Working in a Java/Kotlin environment, my tool of choice is IntelliJ IDEA. It’s an incredibly powerful IDE with a host of features designed to help.
When facing a 200-line monster method, the most obvious solution is right in the refactoring menu: “Extract Method”. It seems perfect. It makes the original method smaller, which is exactly what I want.
Right?
Introducing the Core Concept: Readability-Driven Refactoring
The main goal of refactoring shouldn’t just be “smaller methods.” For me, the main goals are readability and, secondarily, decoupling.
In fact, readability is arguably more important than adhering to a specific architecture or design pattern. While good architecture often improves readability, it’s not its primary goal. If I have a choice between perfect pattern adherence and readability, I will prefer readability. Working on a typical web application, it’s readability that helps me daily when I look at different parts of the code.
This is where the simple “Extract Method” tool falls short. It often just moves the mess, failing to improve readability.
A more powerful technique for guiding this process is Test-Driven Development (TDD). Instead of just extracting code, we use TDD to describe our expectations for the new, refactored code before we write it. This small shift in process fundamentally changes the quality of the refactoring.
Practical Application: A TDD-Led Refactoring
Let’s look at a practical example.
The Problem Code
Imagine we have this block of code in a method. It’s searching for properties, then mapping them to calculate Avios points, with error handling mixed in .
summaries =
shc
.psSearch(
startDate = startDate,
nights = nights,
hotelCodes = it,
adults = adultsParam,
children = childrenParam,
infants = infantsParam,
).toTypedArray()
.mapNotNull { tbh ->
kotlin
.runCatching {
aviosEarn = aviosAdapter.calculateAviosEarn(BigDecimal(tbh.summary!!.totalPrice!!))
tbh.toAccommodationSummary(aviosEarn)
}.onFailure { e ->
SASAdapter.Companion.log.warn("Skipping", e)
}
.getOrNull()
}.toList()Common Pitfall: The ‘Extract Method’ Trap
If we use the “Extract Method” feature in our IDE, we get this:
Original method:
summaries = requestSummariesAndCalculateAviosEarn(startDate, nights, it, adultsParam, childrenParam, infantsParam)New private method:
private fun requestSummariesAndCalculateAviosEarn(
startDate: LocalDate,
nights: Int,
it: List<String>,
adultsParam: String,
childrenParam: String,
infantsParam: String,
): List<AccommodationSummary> =
shc
.psSearch(
startDate = startDate,
nights = nights,
hotelCodes = it,
adults = adultsParam,
children = childrenParam,
infants = infantsParam,
).toTypedArray()
.mapNotNull { tbh ->
calculateAviosEarnAndMapToAccommodationSummary(tbh)
}.toList()
private fun calculateAviosEarnAndMapToAccommodationSummary(tbh: TBH): AccommodationSummary? {
var aviosEarn: Int
return runCatching {
aviosEarn =
aviosAdapter.calculateAviosEarn(BigDecimal(tbh.summary!!.totalPrice!!))
tbh.toAccommodationSummary(aviosEarn)
}.onFailure { e ->
log.warn(”Skipping”, e)
}
.getOrNull()
}Is this good? Not exactly. It makes the original method smaller, but it doesn’t improve readability. We’ve just created a new private method that takes a mess of parameters.
The Better Way: The TDD-Led Flow
Instead of using the IDE tool, let’s use the TDD technique.
Describe Expectations: We start by writing a test for the logic we want to have. We don’t want to just test a private method; this logic feels like it belongs in its own service.
Define the “To-Be” Service: We’ll create a test for a new
SummaryAdapter. At first, this service is “red” (it doesn’t exist).Discover the Parameter Problem: As we write the test and describe the method we want to call, we see the problem clearly: it needs too many parameters.
The Solution: The test itself shows us what we need. Instead of passing 6 individual parameters, we should pass a single
SearchCriteriaobject. We define this object as an expectation of our test.Implement: We now implement the new service, moving the logic from the old method.
The Result:
By extracting the logic to a new service and passing a parameter object, the original code now looks like this:
summaries = SummaryAdapter.requestSummariesAndCalculateAviosEarn(searchCriteria, it)Did we improve readability? Yes. And not just because the method is smaller, but because we are no longer passing an excessive number of parameters, as we were with the simple “Extract Method”.
A Technique Over a Tool
IDE tools are wonderful, and techniques like TDD are powerful.
Of course, we could have used the IDE tools to change the method signature, create a new class, and move the method there. What the tool can’t do is help us understand what we want to do in the first place. We can’t describe our expectations to the tool.
TDD gives us that option: we describe our expectations before the work. This key difference is what truly changes the quality of our refactoring.
By knowing different techniques, we can understand when and which tool to use. Don’t let the tool lead the refactoring; let your technique guide the tool.


Hi Nik, a bigger problem I see with this fragment of code is that it seems to encode a procedure in its name: doThisThenThisAndThat(). I find it better when we are able to encode the procedure in actual code. For example:
SummaryAdapter.requestSummariesAndCalculateAviosEarn(searchCriteria, it)
trips := allTrips().with(startDate, duration).withHotelCode(hc).withParticipants(2, 0, 0).find().summaries().withAvioPoints().calculate()