← Back to Blog

Refactoring in .NET

Blog

Psychology

Refactoring is for Humans

Refactoring—restructuring code without changing its behavior—can also be defined as a type of code transformation that concerns the human (programmer) but is of very little concern to a computer. Improved code structure and organization greatly benefit the programmer's reasoning, problem-solving, memory, and communication capacity. Some well-known concepts in psychology help explain this.

Chunking

Chunking in psychology is the process of grouping information into meaningful units to improve memory retention and recall. For example, we memorize a phone number as: area code – 3 digits – 4 digits, i.e., (436) 781-3304, and not as a series of individual numbers, i.e., 4 3 6 7 8 1 3 3 0 4. Programmers instinctively write code in such a way as to favor chunking. It benefits the author immediately, as well as any future reader ("maintainer").

Chunking can be as simple as writing each statement or command on a new line (a visual cue) or as complex as an approach to problem-solving. The most obvious example of chunking in code is a function—a single word replacing a block of statements.

This way, we can approach problem-solving as defining a sequence of more complex steps without initially thinking about how to solve each individual step. Imagine you need to implement a feature: "Offer a 20% discount on soon-to-expire items." You could describe the solution as:

if IsSoonToExpire(item){
    ApplyDiscount(item, 0.2)
}

At this point, you do not need to concern yourself with implementing IsSoonToExpire or ApplyDiscount functions. So, you will not start by thinking about all the individual implementation steps that might look akin to the following pseudocode:

expiryThreshold = repository.ExpiryThresholds.Find(t => t.category == item.category)
if expiryThreshold < item.ExpiryDate.AddDays(expiryThreshold){
    item.Price *= (1 - discount)
}

By leveraging chunking, we are able to reduce cognitive load—the amount of mental effort and working memory used when solving a problem—thus making problem-solving faster and more efficient.

Abstract Thinking

Another concept from psychology relevant to refactoring is abstract thinking. This is the ability to think in general terms, recognize patterns, and think creatively.

When you name a class ShoppingCart, you are using a real-world metaphor for a block of code to communicate its purpose. For anyone looking at the code, it will come as no surprise to find Add(item) and Remove(item) methods in that class. They will immediately have a good idea of the methods' purpose.

We can add a method called Total to the class, and thanks to our ability to think abstractly and creatively, it will immediately make sense, even though it does not have a direct parallel in the material world. In our head, we might even visualize a smart shopping cart with a screen showing the current total price and hear the squeaking of the wheels and the noise at the register. If you have a rich imagination, the code could come to life as you read it, perhaps reminding you of a silly episode when you crashed a shopping cart into a fruit stall.

Next to the ShoppingCart class, we expect to see Item and CartItem (again, no parallel in the material world) classes, all placed inside a Storefront module, thus creating another useful level of abstraction. For example, when deciding where to deploy the Storefront module or whether a module should depend on another, we do not need to know what classes and functions are contained inside it. If properly named and organized, the module's name will convey enough information for us to reason about it at that level. For humans, the best way to convey information and make it interesting is to tell a story.

Code Can Tell a Story

One complaint I've heard about thoroughly refactored code, even from experienced programmers, is that there is too much indirection—that the code doing the "real work" is buried inside verbose structures that hinder understanding. So why do we sometimes need to "step into" or "go to implementation" in code we're not currently working on?

This might be due to several factors. To fully "buy into" a metaphor, one must understand the domain and its jargon well enough. Sometimes, this understanding can be created in reverse—from the source code through inference—but this process typically requires significant effort. If domain knowledge is lacking, opacity of the metaphor is more of a hindrance than help.

Another reason for feeling that a structure is not helping code understanding might be poorly executed metaphors. The code simply does not communicate a solid metaphor. Typically, a metaphor is based on the problem domain and then expands into a virtual world of its own. For example, the following code struggles to communicate what domain rule it's implementing:

If (Passenger.FrequentFlierCategory in ([2, 3] & Flight.FirstClassAvailable())) {
    availableSeats = Flight.GetSeatsByClass(1)
    For seat in availableSeats {
        If (!seat.IsOccupied()) {
            Passenger.Seat = seat
            seat.SetOccupied(True)
            Flight.UpdateSeatAssignment(Passenger, seat)
            Break
        }
    }
}

On the other hand, the following lines are immediately clear:

if Passenger.FrequentFlierCategory in ([FrequentFlierCategories.Gold, FrequentFlierCategories.Diamond] & Flight.FirstClassAvailable()){
    Flight.Upgrade(Passenger)
}

Essentially, it does not read much differently from: "If seats are available, upgrade certain frequent fliers to first class." The main character is a passenger, the plot is about upgrading to a better class of seats, and the conclusion is that loyalty pays off.

When telling stories, patterns and common programming concepts come in handy. They extend our vocabulary with terms that are common knowledge inside the programming community. While the terms we pick should not be arcane, in the end, it's up to the reader to look them up if they do not sound familiar. Eloquence requires a richness of vocabulary. After all, you wouldn't complain that Tolstoy uses difficult words. A good novel does not only make us think; it stirs emotions. So, can coding stir emotions?

Refactoring, Enjoyment, and a Sense of Accomplishment

Some of the best human emotions we can get from programming are enjoyment and a sense of accomplishment. It's the feeling you get when something "finally works." The enjoyment we get from programming stems from the intrinsic pleasure of problem-solving.

On the other hand, the enjoyment we get from refactoring is similar to the enjoyment of telling a good story. And we are all storytellers. It might be a joke, a story of adventure, a bedtime story, or a success story told in a job interview. The enjoyment we get from telling a story comes from stirring emotions in others.

That's the enjoyment we get from refactoring. It's a collaborative, human affair.

Danijel Arsenovski

Microsoft MVP • MCSD • Software Architect

Author of Professional Refactoring in Visual Basic and Professional Refactoring in C# & ASP.NET. Danijel has pioneered refactoring on the .NET platform while overhauling enterprise banking systems.

Want to Learn More?

Dive deeper into refactoring techniques with the Professional Refactoring books.