About Refactoring: 8 small bits — byte 1

Mehmet Egemen Albayrak
The Startup
Published in
10 min readMay 5, 2020

Refactoring was something I’ve always felt “not good enough” at it. Maybe it’s because of my perfectionism, probably it’s because of my perfectionism, I wanted to have a good quality code which I can show to other people and I know you share the same concern. Refactoring is one of the subjects which separates a good developer from a bad developer, so be ready about it. Before starting to learn you may want to learn how to learn, you can click here to learn about learning.

I’ll introduce you to 8 refactorings in this blog post, and other octets in other bytes. When I studied those refactorings I always wanted to precisely know what is refactoring for.

Why are refactorings important?

One of the reasons refactorings are done is for better readability. Better readability increases maintainability. Increased maintainability reduces technical debt(it’s an agile development term, click here to learn more about it). Refactorings must not change the behavior of the code. We need tests to ensure that.

Your memory is limited and your brain is excellent at forgetting. If you work on a large enough project you will forget some portion of it by time or if you don’t touch a codebase for months and would like to add something to it, after all, you will have a hard time to do it if haven’t refactored.

More importantly, new hires will join your team. Or you will leave the codebase to new developers and migrate to somewhere else. If you don’t make your code readable, self-documented, you will increase the time needed to work on it thus increasing the expenses of the company.

Refactoring should be made before adding a new feature. If you build software on a bad foundation, you will have a hard time to read and understand it in the future. And if you can’t read and understand well, you can extend the codebase. If you can’t extend, you cannot add new features.

I made a relatively simple definition of refactoring above. It is usually described as a process which changes the internal structure of a program but not external behavior. Reasons for this internal structure change are stated above paragraph. Our brains learn conceptually and make associations. This makes us understand better well-done abstractions, code blocks and well-named variables.

Why “refactoring”? As you can guess it comes from the mathematical term “factoring”. Changing a monolithic binomial equation(like x² + something) to a lower degree monomial micro expressions(like x + something) for understanding the equation better or conduct mathematical operations on it more easily are called factoring. And like factoring, refactorings are for better understanding or better operations.

So I’m refactoring now? Not so fast. You need a test-driven approach for preventing bugs. If you don’t run test suites after every little refactoring you’ve done — and yes, they must be very little changes — you won’t be able to tell that system still works correctly. We need to be precautious and tests are essential for that.

Have you ever wondered why it is called the spaghetti code? Because spaghetti is all entangled. You need to separate functions in your code and if you recklessly put every functionality in one function that function becomes entangled like spaghetti. The correct way of doing is putting distinct functionalities into distinct functions.

So you should refactor mercilessly. You should do it until you can’t do it. That way your code doesn’t rot, and new costs of untangling the code won’t be introduced. Though refactoring is not rewriting the codebase. As I said before refactoring happens in small steps.

I think we need to look at some agile development principles but first let’s look at some agile development methodologies in a very understandable way.

I think now you have some idea about agile development methodologies and we can look at some of the important terms.

Test-Driven Development

Test-Driven Development is basically writing tests for software, writing the minimal amount of code which makes the test pass, refactor the result and then start all over again. This process is also called Red, Green, Refactor.

In Javascript projects, I use Jest(click here to learn more about it), both on front-end and back-end. You can turn on “ — watch” flag on while running Jest in your console and have a workspace which reruns the tests after every autosave of your favorite IDE.

I gathered information from all over the internet and benefited from Martin Fowler’s refactoring book(click here to look at its 2nd edition, which is for Javascript). Let’s look at 8 of those refactorings.

Extract Function

Extract Function or Extract Method, depending on the execution context, is a refactoring most of us do without knowing. Usually, most of us know that different functionalities should be separate functions and it is referred to as “A function must do one thing”. Well, it is not like that. Extracting functions by their functionality, even though they just call another function in one line is something we should do for our sequential and conceptual minds.

function doSomething(param) {
try {
...
} catch (err) {
console.error(`Some error occured: ${err.stack}`);
}
}

to

function doSomething(param) {
try {
...
} catch (err) {
printErrorWithStack(err);
}
}
function printErrorWithStack(err) {
console.error(`Some error occured: ${err.stack}`);
}

So as you can see our extracted function makes only one call we could put in the main function but now we can understand the code at the first glance instead of figuring out what does that console method with some template literal in it do.

Extracting Functions usually happens in this way: First, you identify different groups of functionalities in a function, extract them to separate functions and call those extracted functions in the first function. Let’s see an example of multiple extractions from one function body, it will be trickier than usual.

function printCubeVolume(edgeLength) {
const volume = edgeLength * edgeLength * edgeLength;
console.log(volume);
}

can be turned to

function printCubeVolume(edgeLength) {
console.log(cubeVolume(edgeLength));
}
function cubeVolume(edgeLength) {
return rectangleArea(edgeLength) * edgeLength;
}
function rectangleArea(edgeLength) {
return edgeLength * edgeLength;
}

By extracting functions we gave meaning to every distinguishable part of the variable, let functions do one thing, and made them reusable. Naming is important, names should be concise. So let’s learn how to refactor names.

Change Function Declaration

A good code should tell you what function does from the function’s name and parameters. Keeping in mind functions will be reusable and building blocks of one another, naming a function has huge importance for understanding the code without reading the method’s body and you need this ability for the next time you read the code. You will see that finding a good name is hard but not impossible and the chance of success increases in every new refactoring session so don’t worry if you can’t find a good name at first.

Changing function parameters can help you to decrease the coupling between functions. Why is this important? I am sure you heard of “tightly coupled” and “loosely coupled” terms before. It has many reasons to be important but in the context of refactoring it is important because decreasing coupling decreases the amount of information of other functions you need to keep in your brain to understand the function you are reading.

function prtShoeSz(person) {
const shoeSize = getShoeSizeFromPersonObject(person);
console.log(shoeSize);
}
function getShoeSizeFromPersonObject(personObject) {
return personObject.shoeSize;
}

There are several refactorings can be done to this terrible code. First of all, using abbreviations is a bad idea. The second stinky thing is a too long and verbose function name. The third is an unnecessary variable. Now look at a better version:

function printShoeSize(aPerson) {
console.log(shoeSize(aPerson));
}
function shoeSize(aPerson) {
return aPerson.shoeSize;
}

I told you refactoring is hard. And hard things usually don’t succeed at first try so give yourself some space and time to find a good name. But what if you already used that abbr. function in 100+ places? Even though IDEs can help you significantly, there is a little trick called “migration”.

function prtShoeSz(person) {
printShoeSize(person);
}
function printShoeSize(aPerson) {
console.log(shoeSize(aPerson));
}
function shoeSize(aPerson) {
return aPerson.shoeSize;
}

Why aPerson? Because if we do it that way we can understand the type of the object, a person object. It blinks you to use classes but this is not always the case. And if you use Typescript you will have an easier time with your parameters not named that way.

Rename Variable

Renaming variables share lots of things — they are almost identical — with renaming functions. You should avoid using abbreviations if the variable’s purpose is not crystal clear. Avoid too verbose names. Be consistent with your character style usage. Don’t do this in the same code:

const HLL_STR = "hello";const byeString = "bye bye";

Encapsulate Variable

I wanted to put refactorings — and will — other than Fowler’s order. This refactoring is also essential for our other refactorings so I had to put it. Encapsulate variable is basically encapsulating a variable within a function and calling this function instead of the variable in where you use. This is done because if the variable is mutable, references to it can change or functions use it may demand some further data manipulation or check so you can put that logic into encapsulated variable’s function. For example:

let pet = {
breed: "German Shepherd",
weight: 35
}

to

let _pet = {
breed: "German Shepherd",
weight: 35
};
function pet() {return _pet;}
function setPet(newPet) {_pet = newPet;}
function printPet() {
console.log(`My pet is a ${pet().breed} and weighs ${pet().weight} kg`);
}

We put an underscore before the variable name. You can do it like that or append data at the end of the name. You can do whatever you want as long as you are consistent.

We put _pet inside a function so when we need to change the variable, we don't have to change it in 100 places in our code, just inside the pet function.

And in the printPetfunction you see the usage of the encapsulated variable.

Replace Nested Conditional with Guard Clauses

This is my favorite refactoring because I made this mistake tons of times and while doing it I knew something was wrong. You will recognize this too.

function isUserBanned(userId) {
let result;
if (doesUserExist(userId)) {
if (getUser(userId).isBanned) {
result = true;
} else {
result = false;
}
} else {
result = null;
}
return result;
}

There is a very beautiful solution to it:

function isUserBanned(userId) {
if (!doesUserExist(userId)) return null;
if (getUser(userId).isBanned) return true;
return false;
}

Just let the logic do its magic.

Combine Function into Class

As a person who is introduced to Javascript in the ES5 days, maybe ES3 days you don’t have the habit of using syntactic sugar classes much, its applications are not widespread in repositories. Classes are very powerful abstractions of both data and functions together.

With advances like ES6 and Typescript, syntactically you don’t have to stick to the old prototypal inheritance of functions. A more organized class-based software will be more understandable. And we can turn some functions which consume a type of object to a class and migrate functions to class’s methods.

let screen1 = {
width: 1024,
height: 768
};
function calculateTotalPixels(aScreen) {
return aScreen.width * aScreen.height;
}
console.log(calculateTotalPixels(screen1));

or

function Display(aScreen) {
this.width = aScreen.width;
this.height = aScreen.height;
}
let display1 = new Display({width: 1024, height 768});function calculateTotalPixels(aDisplay) {
return aDisplay.width * aDisplay.height;
}

We could also put calculateTotalPixels() function into Display constructor, then we would have a nested function which is not desirable. We can do it:

class Display {
constructor(aScreen) {
this.width = aScreen.width;
this.height = aScreen.height;
}
get totalPixels() {
return this.width * this.height;
}
}
let display1 = new Display({width: 1024, height: 768});console.log(display1.totalPixels); // Outputs the result of 1024 * 768

get and set are pretty handy for creating more readable code so keep them in mind.

Replace Loop with Pipeline

When I use built-in methods of Javascript instead of trying to solve everything with for..of loop, I feel very happy, satisfied. Using built-in pipeline methods such as map, filter, reduce etc. increases the readability of the code significantly because of Functional Programming's declarative nature. The following code squares every number in an array and sums them up.

let result = 0;
let numbers = [1,2,4,6,7]
for (let number of numbers) {
const squaredNumber = number * number;
result += squaredNumber;
}

If we would like to turn this loop to a pipeline, we could do this:

let numbers = [1,2,4,6,7];
let result = numbers
.map(number => number * number)
.reduce((accumulator, currentValue) => accumulator + currentValue);

Instead of trying to understand imperative code which doesn’t have a familiar pattern in your mind thus requires more mental effort, we use declarative pipeline which we are familiar of and have a very easy time to read the code.

Remove Dead Code

Removing dead code may seem obvious but I know many of you prefer to comment out the code and push it to a repository like that. I know, I did it too and I also felt it was wrong at the moment I did it. Don’t think you will need it and will lose it if you don’t keep the code. Just use a version control system like Git or SVN and look at the differences between revisions. If you think you may really need it in the future, Fowler says you should write a comment indicating the revision number for finding it later, not the code itself.

We learned about refactoring, agile development, test-driven development, and 8 essential refactorings. This series will continue with more bytes. If you want to ask something or just say your good wishes, you can comment. You can share the post with others if you think they will benefit. I wish you a good day.
— — — — —
Originally from https://mehmetegemen.com/about-refactoring-byte-1/

--

--