Competent
Moving Features Between Objects (basic)
Organizing Data (basic)
Composing Methods (basic)
Simplifying Conditional Expressions (basic)
Moving Features Between Objects (basic)
Move Method (Move Function)
The heart of a good software design is its modularity—which is my ability to make most modifications to a program while only having to understand a small part of it. To get this modularity, I need to ensure that related software elements are grouped together and the links between them are easy to find and understand.
One of the most straightforward reasons to move a function is when it references elements in other contexts more than the one it currently resides in. Moving it together with those elements often improves encapsulation, allowing other parts of the software to be less dependent on the details of this module.
Deciding to move a function is rarely an easy decision. To help me decide, I examine the current and candidate contexts for that function. I need to look at what functions call this one, what functions are called by the moving function, and what data that function uses. Often, I see that I need a new context for a group of functions and create one with Combine Functions into Class or Extract Class.
Mechanics
- Examine all the program elements used by the chosen function in its current context. Consider whether they should move too.
- Check if the chosen function is a polymorphic method.
- Copy the function to the target context. Adjust it to fit in its new home.
- Perform static analysis.
- Figure out how to reference the target function from the source context.
- Turn the source function into a delegating function.
- Test.
- Consider Inline Function on the source function.
function trackSummary(points) {
const totalTime = calculateTime();
const totalDistance = calculateDistance();
const pace = totalTime / 60 / totalDistance ;
return {
time: totalTime,
distance: totalDistance,
pace,
};
function calculateDistance() {
let result = 0;
for (let i = 1; i < points.length; i++) {
result += distance(points[i1],
points[i]);
}
return result;
}
function distance(p1,p2) { /*...*/ }
function radians(degrees) { /*...*/ }
function calculateTime() { /*...*/ }
}
I’d like to move calculateDistance to the top level so I can calculate distances for tracks without all the other parts of the summary.
Moving between Classes
class Account {
get overdraftCharge() {}
}
to
class AccountType {
get overdraftCharge() {}
}
Move Field
class Customer {
get plan() { return this._plan; }
get discountRate() { return this._discountRate; }
}
to
class Customer {
get plan() { return this._plan; }
get discountRate() { return this._plan.discountRate; }
}
Programming involves writing a lot of code that implements behavior—but the strength of a program is really founded on its data structures. If I have a good set of data structures that match the problem, then my behavior code is simple and straightforward. But poor data structures lead to lots of code whose job is merely dealing with the poor data. And it’s not just messier code that’s harder to understand; it also means the data structures obscure what the program is doing.
So, data structures are important—but like most aspects of programming they are hard to get right. I do make an initial analysis to figure out the best data structures, and I’ve found that experience and techniques like domaindriven design have improved my ability to do that. But despite all my skill and experience, I still find that I frequently make mistakes in that initial design. In the process of programming, I learn more about the problem domain and my data structures. A design decision that is reasonable and correct one week can become wrong in another.
As soon as I realize that a data structure isn’t right, it’s vital to change it. If I leave my data structures with their blemishes, those blemishes will confuse my thinking and complicate my code far into the future.
I usually do Move Field in the context of a broader set of changes. Once I’ve moved a field, I find that many of the users of the field are better off accessing that data through the target object rather than the original source. I then change these with later refactorings. Similarly, I may find that I can’t do Move Field at the moment due to the way the data is used. I need to refactor some usage patterns first, then do the move.
Mechanics
- Ensure the source field is encapsulated
- Test
- Create a field (and accessors) in the target
- Run static checks
- Ensure there is a reference from the source object to the target object
- Adjust accessors to use the target field
- Test
- Remove the source field
- Test
Organizing Data (basic)
Encapsulate Field (Encapsulate variable)
If I move data around, I have to change all the references to the data in a single cycle to keep the code working. For data with a very small scope of access, such as a temporary variable in a small function, this isn’t a problem. But as the scope grows, so does the difficulty, which is why global data is such a pain.
So if I want to move widely accessed data, often the best approach is to first encapsulate it by routing all its access through functions. That way, I turn the difficult task of reorganizing data into the simpler task of reorganizing functions.
let defaultOwner = { firstName: 'Martin', lastName: 'Fowler' }
to
let defaultOwnerData = { firstName: 'Martin', lastName: 'Fowler' }
export function defaultOwner() { return defaultOwnerData; }
export function setDefaultOwner(arg) { defaultOwnewrData = arg; }
Encapsulating data is valuable for other things too. It provides a clear point to monitor changes and use of the data; I can easily add validation or consequential logic on the updates. It is my habit to make all mutable data encapsulated like this and only accessed through functions if its scope is greater than a single function. The greater the scope of the data, the more important it is to encapsulate. My approach with legacy code is that whenever I need to change or add a new reference to such a variable, I should take the opportunity to encapsulate it. That way I prevent the increase of coupling to commonly used data.
Keeping data encapsulated is much less important for immutable data. When the data doesn’t change, I don’t need a place to put in validation or other logic hooks before updates. I can also freely copy the data rather than move it—so I don’t have to change references from old locations, nor do I worry about sections of code getting stale data. Immutability is a powerful preservative.
Mechanics
- Create encapsulating functions to access and update the variable.
- Run static checks.
- For each reference to the variable, replace with a call to the appropriate encapsulating function. Test after each replacement.
- Restrict the visibility of the variable
- Test
- If the value of the variable is a record, consider Encapsulate Record
Encapsulate Collection
Access to a collection variable may be encapsulated, but if the getter returns the collection itself, then that collection’s membership can be altered without the enclosing class being able to intervene.
To avoid this, I provide collection modifier methods — usually add and remove~ —on the class itself. This way, changes to the collection go through the owning class, giving me the opportunity to modify such changes as the program evolves.
class Person {
get courses() { return this._courses; }
set courses(aList) { this._courses = aList; }
}
to
class Person {
get courses() { return this._courses.slice(); }
addCourse(aCourse) { /*...*/ }
removeCourse(aCourse) { /*...*/ }
}
Mechanics
- Apply Encapsulate Variable (132) if the reference to the collection isn’t already encapsulated
- Add functions to add and remove elements from the collection
- If there is a setter for the collection, use Remove Setting Method if possible. If not, make it take a copy of the provided collection
- Run static checks
- Find all references to the collection. If anyone calls modifiers on the collection, change them to use the new add/remove functions. Test after each change
- Modify the getter for the collection to return a protected view on it, using a readonly proxy or a copy
- Test
Composing Methods (basic)
Extract Method (Extract Function)
Extract Function is one of the most common refactorings
If you have to spend effort looking at a fragment of code and figuring out what it’s doing, then you should extract it into a function and name the function after the “what.” Then, when you read it again, the purpose of the function leaps right out at you, and most of the time you won’t need to care about how the function fulfills its purpose (which is the body of the function).
function printOwing(invoice) {
printBanner();
let outstanding = calculateOutstanding();
// print details
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
}
to
function printOwing(invoice) {
printBanner();
let outstanding = calculateOutstanding();
printDetails(outstanding);
function printDetails(outstanding) {
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
}
}
Mechanics
- Create a new function, and name it after the intent of the function (name it by what it does, not by how it does it).
- Copy the extracted code from the source function into the new target function.
- Scan the extracted code for references to any variables that are local in scope to the source function and will not be in scope for the extracted function. Pass them as parameters.
- Compile after all variables are dealt with.
- Replace the extracted code in the source function with a call to the target function.
- Test.
- Look for other code that’s the same or similar to the code just extracted, and consider using Replace Inline Code with Function Call to call the new function.
Inline Method (Inline Function)
this is inverse of: Extract Function
One of the themes of this book is using short functions named to show their intent, because these functions lead to clearer and easier to read code. But sometimes, I do come across a function in which the body is as clear as the name. Or, I refactor the body of the code into something that is just as clear as the name. When this happens, I get rid of the function. Indirection can be helpful, but needless indirection is irritating.
I also use Inline Function is when I have a group of functions that seem badly factored. I can inline them all into one big function and then reextract the functions the way I prefer.
I commonly use Inline Function when I see code that’s using too much indirection — when it seems that every function does simple delegation to another function, and I get lost in all the delegation. Some of this indirection may be worthwhile, but not all of it. By inlining, I can flush out the useful ones and eliminate the rest.
function getRating(driver) {
return moreThanFiveLateDeliveries(driver) ? 2 : 1;
}
function moreThanFiveLateDeliveries(driver) {
return driver.numberOfLateDeliveries > 5;
}
to
function getRating(driver) {
return (driver.numberOfLateDeliveries > 5) ? 2 : 1;
}
Mechanics
- Check that this isn’t a polymorphic method
- If this is a method in a class, and has subclasses that override it, then I can’t inline it
- Find all the callers of the function
- Replace each call with the function’s body
- Test after each replacement
- Remove the function definition
Inline Temp (Inline Variable)
let bestPrice = anOrder.bestPrice;
return (basePrice > 1000);
to
return (anOrder.bestPrice > 1000);
Variables provide names for expressions within a function, and as such they are usually a Good Thing. But sometimes, the name doesn’t really communicate more than the expression itself. At other times, you may find that a variable gets in the way of refactoring the neighboring code. In these cases, it can be useful to inline the variable.
Mechanics
- Check that the righthand side of the assignment is free of side effects
- If the variable isn’t already declared immutable, do so and test
- This checks that it’s only assigned to once
- Find the first reference to the variable and replace it with the righthand side of the assignment
- Test
- Repeat replacing references to the variable until you’ve replaced all of them
- Remove the declaration and assignment of the variable
- Test
Replace Temp with Query
Using functions instead of variables also allows me to avoid duplicating the calculation logic in similar functions. Whenever I see variables calculated in the same way in different places, I look to turn them into a single function.
Only some temporary variables are suitable for Replace Temp with Query. The variable needs to be calculated once and then only be read afterwards. In the simplest case, this means the variable is assigned to once, but it’s also possible to have several assignments in a more complicated lump of code—all of which has to be extracted into the query.
Furthermore, the logic used to calculate the variable must yield the same result when the variable is used later—which rules out variables used as snapshots with names like oldAddress.
const basePrice = this._quantity * this._itemPrice;
if (basePrice > 100) {
return basePrice * 0.95;
}
return basePrice * 0.98;
to
get basePrice() { return this._quantity * this._itemPrice; }
// ...
if (this.basePrice > 100) {
return this.basePrice * 0.95;
}
return this.basePrice * 0.98;
Mechanics
- Check that the variable is determined entirely before it’s used, and the code that calculates it does not yield a different value whenever it is used
- If the variable isn’t readonly, and can be made readonly, do so
- Test
- Extract the assignment of the variable into a function
- Test
- Use Inline Variable to remove the temp
Split Temporary Variable
let temp = 2 * (height + width);
console.log(temp);
temp = height * width;
console.log(temp);
to
const perimeter = 2 * (height + width);
console.log(perimeter);
const area = height * width;
console.log(area);
Simplifying Conditional Expressions (basic)
Decompose Conditional Expression
One of the most common sources of complexity in a program is complex conditional logic. As I write code to do various things depending on various conditions, I can quickly end up with a pretty long function. Length of a function is in itself a factor that makes it harder to read, but conditions increase the difficulty. The problem usually lies in the fact that the code, both in the condition checks and in the actions, tells me what happens but can easily obscure why it happens.
As with any large block of code, I can make my intention clearer by decomposing it and replacing each chunk of code with a function call named after the intention of that chunk. With conditions, I particularly like doing this for the conditional part and each of the alternatives. This way, I highlight the condition and make it clear what I’m branching on. I also highlight the reason for the branching.
This is really just a particular case of applying Extract Function (106) to my code, but I like to highlight this case as one where I’ve often found a remarkably good value for the exercise.
if(!aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd)) {
charge = quantity * plan.summerRate;
} else {
charge = quantity * plan.regularRate + plan.regularServiceCharge;
}
to
if(summer()) {
charge = summerCharge();
} else {
charge = regularCharge()
}
Mechanics
- Apply Extract Function on the condition and each leg of the conditional
Consolidate Conditional Expression
Sometimes, I run into a series of conditional checks where each check is different yet the resulting action is the same. When I see this, I use and and or operators to consolidate them into a single conditional check with a single result. Consolidating the conditional code is important for two reasons. First, it makes it clearer by showing that I’m really making a single check that combines other checks.
The sequence has the same effect, but it looks like I’m carrying out a sequence of separate checks that just happen to be close together. The second reason I like to do this is that it often sets me up for Extract Function (106). Extracting a condition is one of the most useful things I can do to clarify my code. It replaces a statement of what I’m doing with why I’m doing it.
if (anEmployee.seniority < 2) { return 0; }
if (anEmployee.monthDisabled > 12) { return 0; }
if (anEmployee.isPrtTime) { return 0; }
to
if(isNotEligableForDisability()) { return 0; }
function isNotEligableForDisability() {
return (
anEmployee.seniority < 2
|| anEmployee.monthDisabled > 12
|| anEmployee.isPrtTime;
);
}
Mechanics
- Ensure that none of the conditionals have any side effects
- Take two of the conditional statements and combine their conditions using a logical operator
- Test
- Repeat combining conditionals until they are all in a single condition
- Consider using Extract Function (106) on the resulting condition
Consolidate Duplicate Conditional Fragments (Slide statements)
Code is easier to understand when things that are related to each other appear together. If several lines of code access the same data structure, it’s best for them to be together rather than intermingled with code accessing other data structures. At its simplest, I use Slide Statements to keep such code together. A very common case of this is declaring and using variables. Some people like to declare all their variables at the top of a function. I prefer to declare the variable just before I first use it.
Usually, I move related code together as a preparatory step for another refactoring, often an Extract Function. Putting related code into a clearly separated function is a better separation than just moving a set of lines together, but I can’t do the Extract Function unless the code is together in the first place.
const pricingPlan = retrievePricingPlan();
const order = retreiveOrder();
let charge;
const chargePerUnit = pricingPlan.unit;
to
const pricingPlan = retrievePricingPlan();
const chargePerUnit = pricingPlan.unit;
const order = retreiveOrder();
let charge;
Mechanics
- Identify the target position to move the fragment to. Examine statements between source and target to see if there is interference for the candidate fragment. Abandon action if there is any interference
- Cut the fragment from the source and paste into the target position
- Test
Remove Control Flag
for (const p of people) {
if (! found) {
if ( p === "Don") {
sendAlert();
found = true;
}
}
}
to
for (const p of people) {
if ( p === "Don") {
sendAlert();
break;
}
}
Replace Conditional with Polymorphism
A common case for this is where I can form a set of types, each handling the conditional logic differently. I might notice that books, music, and food vary in how they are handled because of their type. This is made most obvious when there are several functions that have a switch statement on a type code. In that case, I remove the duplication of the common switch logic by creating classes for each case and using polymorphism to bring out the typespecific behavior.
Polymorphism is one of the key features of objectoriented programming—and, like any useful feature, it’s prone to overuse. I’ve come across people who argue that all examples of conditional logic should be replaced with polymorphism. I don’t agree with that view. Most of my conditional logic uses basic conditional statements—if/else and switch/case. But when I see complex conditional logic that can be improved as discussed above, I find polymorphism a powerful tool.
switch (bird.type) {
case 'EuropeanSwallow':
return "average";
case 'AfricanSwallow':
return (bird.numberOfCoconuts > 2) ? "tired" : "average";
case 'NorwegianBlueParrot':
return (bird.voltage > 100) ? "scorched" : "beautiful";
default:
return "unknown";
to
class EuropeanSwallow {
get plumage() {
return "average";
}
}
class AfricanSwallow {
get plumage() {
return (this.numberOfCoconuts > 2) ? "tired" : "average";
}
}
class NorwegianBlueParrot {
get plumage() {
return (this.voltage > 100) ? "scorched" : "beautiful";
}
}
Mechanics
- If classes do not exist for polymorphic behavior, create them together with a factory function to return the correct instance.
- Use the factory function in calling code.
- Move the conditional function to the superclass.
- If the conditional logic is not a selfcontained function, use Extract Function to make it so.
- Pick one of the subclasses. Create a subclass method that overrides the conditional statement method. Copy the body of that leg of the conditional statement into the subclass method and adjust it to fit.
- Repeat for each leg of the conditional.
- Leave a default case for the superclass method. Or, if superclass should be abstract, declare that method as abstract or throw an error to show it should be the responsibility of a subclass.