Extract Method

Dattatray Kale
Technogise
Published in
5 min readAug 21, 2019

Extract method is the most frequently used refactoring technique. It reorganises the code for better reuse and readability.

Ah! alright. Let’s proceed.

Use the extract method in following cases —

When your class contains a long method.

A long method does more than one thing and it is not considered as cohesive. The method more than 10 lines could be considered the as long method.

Before applying the extract method:

See below main method. It is doing more than one thing.

using System;
using System.Collections.Generic;

namespace NMart.Billing
{
internal class NMartStore
{
private static Dictionary<string, int> CategoryGstRatesInPercentage = new Dictionary<string, int>();
private static Dictionary<string, string> ItemsCategoryMapping = new Dictionary<string, string>();

private static void Main()
{
CategoryGstRatesInPercentage.Add("Food-grains", 0);
CategoryGstRatesInPercentage.Add("Furniture", 5);
CategoryGstRatesInPercentage.Add("Electronics", 18);
CategoryGstRatesInPercentage.Add("Cosmetics", 28);

ItemsCategoryMapping.Add("Rice", "Food-grains");
ItemsCategoryMapping.Add("Wheat", "Food-grains");
ItemsCategoryMapping.Add("Sofa", "Furniture");
ItemsCategoryMapping.Add("Chairs", "Furniture");
ItemsCategoryMapping.Add("TV", "Electronics");
ItemsCategoryMapping.Add("Mobile", "Electronics");
ItemsCategoryMapping.Add("Shampoo", "Cosmetics");
ItemsCategoryMapping.Add("Perfume", "Cosmetics");

Console.WriteLine("Welcome to NMart store");
Console.WriteLine("***************************");

Console.Write("Enter name of item: ");
string itemName = Console.ReadLine();
Console.Write("Enter quantity of item: ");
int itemQuantity = int.Parse(Console.ReadLine());
Console.Write("Enter rate per product item: ");
int ratePerUnitItem = int.Parse(Console.ReadLine());

string categoryName = "";

foreach (var item in ItemsCategoryMapping)
{
if (item.Key == itemName)
{
categoryName = item.Value;
break;
}
}

int gstPercentageForItem = 0;

foreach (var categoryGstRate in CategoryGstRatesInPercentage)
{
if (categoryGstRate.Key == categoryName)
{
gstPercentageForItem = categoryGstRate.Value;
break;
}
}

double finalPrice = itemQuantity * (ratePerUnitItem + ratePerUnitItem * gstPercentageForItem / 100.0);

string output = "*******************************************\n" +
"Billing Details for " + itemName + ":\n" +
"*******************************************\n" +
"Quantity: " + itemQuantity +
"\nPrice per unit: " + ratePerUnitItem +
"\nFinal rate: " + finalPrice;
Console.WriteLine(output);

Console.WriteLine("\n*********************************\n");
}
}
}

Setting up GST rates, item/category mapping, accepting user inputs, calculation and so on.

A long method is considered a code smell.

After applying the extract method:

To get rid of a long method, I have extracted out the main method into small methods which are doing things like setting up GST rates, item/category mapping, accepting user inputs, calculation and so on.

using System;
using System.Collections.Generic;

namespace NMart.Billing
{
internal class NMartStore
{
private static Dictionary<string, int> CategoryGstRatesInPercentage = new Dictionary<string, int>();
private static Dictionary<string, string> ItemsCategoryMapping = new Dictionary<string, string>();

public static void Main()
{
SetupGstRateForCategories();

SetupItemsPerCategory();

Console.WriteLine("Welcome to NMart store");
Console.WriteLine("***************************");

string itemName;
int itemQuantity, ratePerUnitItem;

AcceptUserInput(out itemName, out itemQuantity, out ratePerUnitItem);

double finalPrice = CalculateFinalRate(itemName, itemQuantity, ratePerUnitItem);

PrintBillingDetails(itemName, itemQuantity, ratePerUnitItem, finalPrice);
}

private static void AcceptUserInput(out string itemName, out int itemQuantity, out int ratePerUnitItem)
{
Console.Write("Enter name of item: ");
itemName = Console.ReadLine();
Console.Write("Enter quantity of item: ");
itemQuantity = int.Parse(Console.ReadLine());
Console.Write("Enter rate per product item: ");
ratePerUnitItem = int.Parse(Console.ReadLine());
}

private static double CalculateFinalRate(string itemName, int itemQuantity, int ratePerUnitItem)
{
string categoryName = GetCategory(itemName);

int gstPercentageForItem = GetGstPercentage(categoryName);

double gstRatePerItem = ratePerUnitItem * gstPercentageForItem / 100.0;

double finalPrice = itemQuantity * (ratePerUnitItem + gstRatePerItem);

return finalPrice;
}

private static string GetCategory(string itemName)
{
string categoryName = "";

foreach (var item in ItemsCategoryMapping)
{
if (item.Key == itemName)
{
categoryName = item.Value;
break;
}
}

return categoryName;
}

private static int GetGstPercentage(string categoryName)
{
int gstPercentageForItem = 0;

foreach (var categoryGstRate in CategoryGstRatesInPercentage)
{
if (categoryGstRate.Key == categoryName)
{
gstPercentageForItem = categoryGstRate.Value;
break;
}
}

return gstPercentageForItem;
}

private static void PrintBillingDetails(string itemName, int itemQuantity, int ratePerUnitItem, double finalPrice)
{
string output = "*******************************************\n" +
"Billing Details for " + itemName + ":\n" +
"*******************************************\n" +
"Quantity: " + itemQuantity +
"\nPrice per unit: " + ratePerUnitItem +
"\nFinal rate: " + finalPrice;
Console.WriteLine(output);

Console.WriteLine("\n*********************************\n");
}

private static void SetupGstRateForCategories()
{
CategoryGstRatesInPercentage.Add("Food-grains", 0);
CategoryGstRatesInPercentage.Add("Furniture", 5);
CategoryGstRatesInPercentage.Add("Electronics", 18);
CategoryGstRatesInPercentage.Add("Cosmetics", 28);
}

private static void SetupItemsForCosmetics()
{
ItemsCategoryMapping.Add("Shampoo", "Cosmetics");
ItemsCategoryMapping.Add("Perfume", "Cosmetics");
}

private static void SetupItemsForElectronics()
{
ItemsCategoryMapping.Add("TV", "Electronics");
ItemsCategoryMapping.Add("Mobile", "Electronics");
}

private static void SetupItemsForFoodGrains()
{
ItemsCategoryMapping.Add("Rice", "Food-grains");
ItemsCategoryMapping.Add("Wheat", "Food-grains");
}

private static void SetupItemsForFurniture()
{
ItemsCategoryMapping.Add("Sofa", "Furniture");
ItemsCategoryMapping.Add("Chairs", "Furniture");
}

private static void SetupItemsPerCategory()
{
SetupItemsForFoodGrains();
SetupItemsForFurniture();
SetupItemsForElectronics();
SetupItemsForCosmetics();
}
}
}

It is divided into small, cohesive, fine-grained methods which are doing one and only one thing. They are conveying their intent well.

When you favor a comment to express the intent of the code.

Before

After

The code block for which the comment was written, I have extracted that code block into a function: “ParseInput”.

When the extract method helps you to improve readability.

Do you know that people extract method for a single line too? Because it adds to the readability of the code. Sometimes a statement may be difficult to interpret. In that case, you could use the extract method to convey intent instead of taking the aid of comment.

When there is a code duplication or copy-paste.

Consider below example, it is formatting date and code is duplicated. Perhaps, lines are copied and pasted.

Before

var formattedStartDate = startDate.ToString("dd MMMM yyyy"); 
var formattedEndDate = endDate.ToString("dd MMMM yyyy");

As soon as you see copy and paste of lines of code then go for an extract method refactoring even if it is a small portion of code.

After

var formattedStartDate = FormatDate(startDate); 
var formattedEndDate = FormatDate(endDate); public string
function FormatDate(DateTime date) {
return date.ToString("dd MMMM yyyy");
}

The advantage

  • Code becomes more readable.
  • Fine-grained methods make code easier to understand.
  • You can easily plug-in and plug out the implementation for some routines.
  • When your method becomes cohesive, you can easily spot the bugs in the code.
  • Your calling method reads more like a sentence.

Points to Ponder —

  • Remember, keep the method short, well-named, and cohesive to get more benefits out of it.
  • Your calling method should read more like sentences.
  • When extracting a method, a developer should think about the cohesiveness of the class. should ask a question to oneself: Does the extracted method really belong to this class? If Not, then move it to the appropriate class that should perform that operation.
  • Small methods really work only when you have good names.
  • Don’t just extract method if you can’t give the meaningful name.
Extract method only if you can give an intuitive name.

Originally published at https://beingcraftsman.com on July 31, 2018.

--

--

Dattatray Kale
Technogise

Software Craftsman & Cloud Engineer with 13+ years' expertise. Proficient in React, Angular, .NET, Node.js, Java, AWS, Azure. Proven leader in team building.