Learn refactorings in just a few minutes - putting words to those neat, tidy things you hopefully do to your code
Refactoring is the process of changing and restructuring code without affecting its behavior. It's also a skill you'll need to have if you want to become senior as a developer/software engineer.
Nowadays we can literally ask a computer to write our code, which will undoubtedly lead to more code in the future. Also, because more and more junior developers enter the field, and considering ~80% of budgets for IT projects are in maintenance, there just is no way around the fact that being good at cleaning and maintaining code is a critical skill for getting better in this field. The ability to detect and mitigate bad code is only going to grow over time.
In this repo I've compiled resources, examples, and tables from the seminal book on refactoring, called... you guessed it: Refactoring: Improving the Design of Existing Code, written by Martin Fowler in 1999, now recently out in its second edition with code examples given in JavaScript. If you're a software engineer with any self-respect you owe it to yourself to get yourself a copythis book. Fowler is funny too, making the book pretty easy to digest. The reason it's a book, rather than a very long Markdown file like this, is because Fowler goes into detail about considerations and things to think about, for every single pattern. Buy it!
This is part of a series of bit-size educational resources I am putting together under the name "Five Minutes or Less", because you should be able to learn something new (every day?) in just a few minutes. I'm trying to make it as easy as possible to get such nuggets of new information in various software subjects.
Of course, in this case, you'd not learn all refactorings in 5 minutes, but one or a few would be doable, as most aren't very complicated!
If you're a Visual Studio Code user, you should know there are already some built-in functionality to assist with refactoring.
And while there are several plugins for refactoring, my favorite is Abracadabra, refactor this!. Funky name, right? You can see some of its full refactoring functionality at this page.
There's also a fantastic site at refactoring.guru that goes into far more detail than the list here. Highly recommended, and it has both lots of free content and paid material as well if you fancy going deep.
This list is reproduced from the book.
All these refactorings are generated by ChatGPT 3.5 in August 2023, using the prompt:
Give me fun, original, minimalist TypeScript examples for the following refactorings from Martin Fowler's book (2nd edition from 2018): [LIST]
To the best of my ability and knowledge they are validated as correct examples. I'll happily update if you find anything strange!
Also see Martin Fowler's Refactoring catalog for more.
You can either clone this repo or simply copy the individual examples into the TypeScript playground.
- Change Function Declaration (Rename Function; Rename Method; Add Parameter; Remove Parameter; Change Signature)
- Change Reference to Value
- Change Value to Reference
- Collapse Hierarchy
- Combine Functions into Class
- Combine Functions into Transform
- Consolidate Conditional Expression
- Decompose Conditional
- Encapsulate Collection
- Encapsulate Record (Replace Record with Data Class)
- Encapsulate Variable (Encapsulate Field; Self-Encapsulate Field)
- Extract Class
- Extract Function
- Extract Superclass
- Extract Variable (Introduce Explaining Variable)
- Hide Delegate
- Inline Class
- Inline Function (Inline Method)
- Inline Variable (Inline Temp)
- Introduce Assertion
- Introduce Parameter Object
- Introduce Special Case (Introduce Null Object)
- Move Field
- Move Function (Move Method)
- Move Statements into Function
- Move Statements to Callers
- Parameterize Function (Parameterize Method)
- Preserve Whole Object
- Pull Up Constructor Body
- Pull Up Field
- Pull Up Method
- Push Down Field
- Push Down Method
- Remove Dead Code
- Remove Flag Argument (Replace Parameter with Explicit Methods)
- Remove Middle Man
- Remove Setting Method
- Remove Subclass (Replace Subclass with Fields)
- Rename Field
- Rename Variable
- Replace Command with Function
- Replace Conditional with Polymorphism
- Replace Constructor with Factory Function (Replace Constructor with Factory Method)
- Replace Derived Variable with Query
- Replace Function with Command (Replace Method with Method Object)
- Replace Inline Code with Function Call
- Replace Loop with Pipeline
- Replace Nested Conditional with Guard Clauses
- Replace Parameter with Query (Replace Parameter with Method)
- Replace Primitive with Object (Replace Data Value with Object; Replace Type Code with Class)
- Replace Query with Parameter
- Replace Subclass with Delegate
- Replace Superclass with Delegate (Replace Inheritance with Delegation)
- Replace Temp with Query
- Replace Type Code with Subclasses (Extract Subclass; Replace Type Code with State/Strategy)
- Separate Query from Modifier
- Slide Statements (Consolidate Duplicate Conditional Fragments)
- Split Loop
- Split Phase
- Split Variable (Remove Assignments to Parameters; Split Temp)
- Substitute Algorithm
Not in the 2nd edition:
- Replace Control Flag with Break (Remove Control Flag)
- Replace Error Code with Exception
- Replace Exception with Precheck (Replace Exception with Test)
- Replace Magic Literal (Replace Magic Number with Symbolic Constant)
- Return Modified Value
Aliases: Rename Function; Rename Method; Add Parameter; Remove Parameter; Change Signature.
π In the Refactoring catalog.
// Before
function calculateCircleArea(radius: number): number {
return Math.PI * radius * radius;
}
// After
class Circle {
constructor(public radius: number) { }
calculateArea(): number {
return Math.PI * this.radius * this.radius;
}
}
π In the Refactoring catalog.
// Before
class Temperature {
constructor(public value: number) { }
getFahrenheit(): number {
return this.value * 9 / 5 + 32;
}
}
// After
class CelsiusTemperature {
constructor(public celsius: number) { }
getFahrenheit(): number {
return this.celsius * 9 / 5 + 32;
}
}
π In the Refactoring catalog.
// Before
class Product {
constructor(public name: string) { }
}
class Order {
constructor(public product: Product) { }
}
// After
class ProductRegistry {
private products: Map<string, Product> = new Map();
getProduct(name: string): Product | undefined {
return this.products.get(name);
}
addProduct(product: Product): void {
this.products.set(product.name, product);
}
}
class Order {
constructor(public productName: string) { }
}
π In the Refactoring catalog.
// Before
class Shape {
constructor(public name: string) { }
}
class Circle extends Shape {
constructor(public radius: number) {
super('Circle');
}
calculateArea(): number {
return Math.PI * this.radius * this.radius;
}
}
// After
class Circle {
constructor(public radius: number) { }
calculateArea(): number {
return Math.PI * this.radius * this.radius;
}
}
π In the Refactoring catalog.
// Before
function formatName(firstName: string, lastName: string): string {
return `${lastName}, ${firstName}`;
}
// After
class NameFormatter {
constructor(public firstName: string, public lastName: string) { }
format(): string {
return `${this.lastName}, ${this.firstName}`;
}
}
π In the Refactoring catalog.
// Before
function capitalizeWord(word: string): string {
return word.charAt(0).toUpperCase() + word.slice(1);
}
function formatTitle(title: string): string {
const words = title.split(' ');
const capitalizedWords = words.map(capitalizeWord);
return capitalizedWords.join(' ');
}
// After
class TitleFormatter {
constructor(public title: string) { }
format(): string {
const words = this.title.split(' ');
const capitalizedWords = words.map(this.capitalizeWord);
return capitalizedWords.join(' ');
}
private capitalizeWord(word: string): string {
return word.charAt(0).toUpperCase() + word.slice(1);
}
}
π In the Refactoring catalog.
// Before
function calculateBonus(salary: number, yearsWorked: number, isTopPerformer: boolean): number {
let bonus = 0;
if (isTopPerformer) {
bonus += salary * 0.2;
}
if (yearsWorked >= 5) {
bonus += salary * 0.1;
}
return bonus;
}
// After
function calculateBonus(salary: number, yearsWorked: number, isTopPerformer: boolean): number {
let bonus = 0;
if (isTopPerformer || yearsWorked >= 5) {
bonus += salary * 0.2;
}
return bonus;
}
π In the Refactoring catalog.
// Before
function calculateShippingCost(orderTotal: number, isPriority: boolean): number {
let shippingCost = 0;
if (orderTotal > 100) {
shippingCost = isPriority ? 10 : 20;
} else {
shippingCost = isPriority ? 15 : 30;
}
return shippingCost;
}
// After
class ShippingCalculator {
calculateShippingCost(orderTotal: number, isPriority: boolean): number {
if (orderTotal > 100) {
return isPriority ? 10 : 20;
} else {
return isPriority ? 15 : 30;
}
}
}
π In the Refactoring catalog.
// Before
class ShoppingCart {
items: string[] = [];
addItem(item: string): void {
this.items.push(item);
}
removeItem(item: string): void {
const index = this.items.indexOf(item);
if (index !== -1) {
this.items.splice(index, 1);
}
}
getItems(): string[] {
return this.items;
}
}
// After
class ShoppingCart {
private items: string[] = [];
addItem(item: string): void {
this.items.push(item);
}
removeItem(item: string): void {
const index = this.items.indexOf(item);
if (index !== -1) {
this.items.splice(index, 1);
}
}
getItemCount(): number {
return this.items.length;
}
}
Aliases: Replace Record with Data Class.
π In the Refactoring catalog.
// Before
const employee = {
firstName: "John",
lastName: "Doe",
age: 30,
salary: 50000,
};
// After
class Employee {
constructor(
public firstName: string,
public lastName: string,
public age: number,
public salary: number
) { }
}
const employee = new Employee("John", "Doe", 30, 50000);
Aliases: Encapsulate Field; Self-Encapsulate Field.
π In the Refactoring catalog.
// Before
let discount = 0.15;
function applyDiscount(price: number): number {
return price * (1 - discount);
}
// After
class DiscountManager {
private static discount = 0.15;
static applyDiscount(price: number): number {
return price * (1 - this.discount);
}
}
π In the Refactoring catalog.
// Before
class Order {
customerName: string;
customerAddress: string;
items: string[];
constructor(customerName: string, customerAddress: string, items: string[]) {
this.customerName = customerName;
this.customerAddress = customerAddress;
this.items = items;
}
printOrderDetails(): void {
console.log(`Customer: ${this.customerName}`);
console.log(`Address: ${this.customerAddress}`);
console.log(`Items: ${this.items.join(', ')}`);
}
}
// After
class Customer {
constructor(public name: string, public address: string) { }
}
class Order {
constructor(public customer: Customer, public items: string[]) { }
printOrderDetails(): void {
console.log(`Customer: ${this.customer.name}`);
console.log(`Address: ${this.customer.address}`);
console.log(`Items: ${this.items.join(', ')}`);
}
}
Aliases: Extract Method.
π In the Refactoring catalog.
// Before
function calculateTotalPrice(cart: number[], taxRate: number): number {
let total = 0;
for (const item of cart) {
total += item;
}
return total * (1 + taxRate);
}
// After
function calculateSubtotal(cart: number[]): number {
let subtotal = 0;
for (const item of cart) {
subtotal += item;
}
return subtotal;
}
function calculateTotalPrice(subtotal: number, taxRate: number): number {
return subtotal * (1 + taxRate);
}
π In the Refactoring catalog.
// Before
class Employee {
name: string;
id: number;
constructor(name: string, id: number) {
this.name = name;
this.id = id;
}
// Employee-specific methods...
}
class Manager extends Employee {
teamSize: number;
constructor(name: string, id: number, teamSize: number) {
super(name, id);
this.teamSize = teamSize;
}
// Manager-specific methods...
}
class Developer extends Employee {
programmingLanguage: string;
constructor(name: string, id: number, programmingLanguage: string) {
super(name, id);
this.programmingLanguage = programmingLanguage;
}
// Developer-specific methods...
}
// After
class Employee {
name: string;
id: number;
constructor(name: string, id: number) {
this.name = name;
this.id = id;
}
// Employee-specific methods...
}
class Manager extends Employee {
teamSize: number;
constructor(name: string, id: number, teamSize: number) {
super(name, id);
this.teamSize = teamSize;
}
// Manager-specific methods...
}
class Developer extends Employee {
programmingLanguage: string;
constructor(name: string, id: number, programmingLanguage: string) {
super(name, id);
this.programmingLanguage = programmingLanguage;
}
// Developer-specific methods...
}
class Salesperson extends Employee {
region: string;
constructor(name: string, id: number, region: string) {
super(name, id);
this.region = region;
}
// Salesperson-specific methods...
}
Aliases: Introduce Explaining Variable.
π In the Refactoring catalog.
// Before
// Before
function calculateTotalPrice(quantity: number, pricePerUnit: number, taxRate: number): number {
const totalPrice = quantity * pricePerUnit;
return totalPrice + totalPrice * taxRate;
}
// After
function calculateTotalPrice(quantity: number, pricePerUnit: number, taxRate: number): number {
const basePrice = quantity * pricePerUnit;
const taxAmount = basePrice * taxRate;
return basePrice + taxAmount;
}
π In the Refactoring catalog.
// Before
class Department {
manager: Employee;
constructor(manager: Employee) {
this.manager = manager;
}
getManager(): Employee {
return this.manager;
}
}
class Employee {
name: string;
constructor(name: string) {
this.name = name;
}
}
// After
class Department {
manager: Employee;
constructor(manager: Employee) {
this.manager = manager;
}
getManagerName(): string {
return this.manager.name;
}
}
π In the Refactoring catalog.
// Before
class Address {
street: string;
city: string;
country: string;
constructor(street: string, city: string, country: string) {
this.street = street;
this.city = city;
this.country = country;
}
}
class Customer {
name: string;
address: Address;
constructor(name: string, address: Address) {
this.name = name;
this.address = address;
}
}
// After
class Customer {
name: string;
street: string;
city: string;
country: string;
constructor(name: string, street: string, city: string, country: string) {
this.name = name;
this.street = street;
this.city = city;
this.country = country;
}
}
π In the Refactoring catalog.
// Before
function calculateTotal(price: number, quantity: number): number {
return applyDiscount(price * quantity);
}
function applyDiscount(amount: number): number {
return amount * 0.9;
}
// After
function calculateTotal(price: number, quantity: number): number {
return price * quantity * 0.9;
}
Aliases: Inline Temp.
π In the Refactoring catalog.
// Before
function calculateTotalPrice(quantity: number, pricePerUnit: number, taxRate: number): number {
const basePrice = quantity * pricePerUnit;
const taxAmount = basePrice * taxRate;
return basePrice + taxAmount;
}
// After
function calculateTotalPrice(quantity: number, pricePerUnit: number, taxRate: number): number {
return quantity * pricePerUnit + quantity * pricePerUnit * taxRate;
}
π In the Refactoring catalog.
// Before
function calculateDiscountedPrice(price: number, discount: number): number {
if (discount < 0 || discount > 1) {
throw new Error("Invalid discount percentage");
}
return price * (1 - discount);
}
// After
function calculateDiscountedPrice(price: number, discount: number): number {
console.assert(discount >= 0 && discount <= 1, "Invalid discount percentage");
return price * (1 - discount);
}
π In the Refactoring catalog.
// Before
function createOrder(customer: string, product: string, quantity: number): Order {
// ...
}
// After
class OrderInfo {
constructor(public customer: string, public product: string, public quantity: number) { }
}
function createOrder(orderInfo: OrderInfo): Order {
// ...
}
Aliases: Introduce Null Object.
π In the Refactoring catalog.
// Before
function calculateDiscountedPrice(price: number, discount: number): number {
if (discount === 0) {
return price;
}
return price * (1 - discount);
}
// After
class Discount {
constructor(public value: number) { }
apply(price: number): number {
if (this.value === 0) {
return price;
}
return price * (1 - this.value);
}
}
π In the Refactoring catalog.
// Before
class Customer {
name: string;
}
class Order {
customer: Customer;
constructor(customer: Customer) {
this.customer = customer;
}
}
// After
class Order {
constructor(public customer: Customer) { }
}
Aliases: Move Method.
π In the Refactoring catalog.
// Before
class Account {
// ...
}
class AccountType {
isPremium(): boolean {
return true;
}
}
// After
class Account {
constructor(private type: AccountType) { }
isPremium(): boolean {
return this.type.isPremium();
}
}
π In the Refactoring catalog.
// Before
function printInvoice(order: Order): void {
console.log("Invoice:");
console.log(`Customer: ${order.customer}`);
console.log(`Total: ${order.total}`);
}
// After
class Order {
printInvoice(): void {
console.log("Invoice:");
console.log(`Customer: ${this.customer}`);
console.log(`Total: ${this.total}`);
}
}
π In the Refactoring catalog.
// Before
function applyDiscount(order: Order): void {
if (order.total > 100) {
order.total *= 0.9;
}
}
// After
class Order {
applyDiscount(): void {
if (this.total > 100) {
this.total *= 0.9;
}
}
}
Aliases: Parameterize Method.
π In the Refactoring catalog.
// Before
function calculateTotal(price: number, quantity: number, taxRate: number): number {
return price * quantity * (1 + taxRate);
}
// After
function calculateTotal(price: number, quantity: number, taxRate: number): number {
return price * quantity * (1 + taxRate);
}
π In the Refactoring catalog.
// Before
function orderDelivery(customer: Customer): void {
const address = customer.address;
// ... process the address for delivery
}
// After
function orderDelivery(address: Address): void {
// ... process the address for delivery
}
π In the Refactoring catalog.
// Before
class Product {
constructor(public name: string) {
console.log("Product created.");
}
}
class Book extends Product {
constructor(name: string, public author: string) {
super(name);
}
}
// After
class Product {
constructor(public name: string) { }
}
class Book extends Product {
constructor(name: string, public author: string) {
super(name);
console.log("Product created.");
}
}
π In the Refactoring catalog.
// Before
class Vehicle {
// ...
}
class Car extends Vehicle {
engineType: string;
// ...
}
class Bicycle extends Vehicle {
// ...
}
// After
class Vehicle {
engineType: string;
// ...
}
class Car extends Vehicle {
// ...
}
class Bicycle extends Vehicle {
// ...
}
π In the Refactoring catalog.
// Before
class Employee {
// ...
}
class Salesperson extends Employee {
calculateCommission(salesAmount: number): number {
return salesAmount * 0.1;
}
}
class Manager extends Employee {
// ...
}
// After
class Employee {
calculateCommission(salesAmount: number): number {
return salesAmount * 0.1;
}
}
class Salesperson extends Employee {
// ...
}
class Manager extends Employee {
// ...
}
π In the Refactoring catalog.
// Before
class Vehicle {
// ...
}
class Car extends Vehicle {
color: string;
// ...
}
class Bicycle extends Vehicle {
// ...
}
// After
class Vehicle {
// ...
}
class Car extends Vehicle {
color: string;
// ...
}
class Bicycle extends Vehicle {
// ...
}
π In the Refactoring catalog.
// Before
class Employee {
// ...
}
class Salesperson extends Employee {
calculateCommission(salesAmount: number): number {
return salesAmount * 0.1;
}
}
class Manager extends Employee {
// ...
}
// After
class Employee {
// ...
}
class Salesperson extends Employee {
// ...
}
class Manager extends Employee {
calculateCommission(salesAmount: number): number {
return salesAmount * 0.05;
}
}
π In the Refactoring catalog.
// Before
function calculateTotal(price: number, quantity: number): number {
// Dead code, never used
const taxRate = 0.1;
return price * quantity * (1 + taxRate);
}
// After
function calculateTotal(price: number, quantity: number): number {
return price * quantity;
}
π In the Refactoring catalog.
// Before
function sendNotification(message: string, isUrgent: boolean): void {
if (isUrgent) {
// Send urgent notification
} else {
// Send normal notification
}
}
// After
function sendUrgentNotification(message: string): void {
// Send urgent notification
}
function sendNormalNotification(message: string): void {
// Send normal notification
}
π In the Refactoring catalog.
// Before
class Department {
manager: Employee;
constructor(manager: Employee) {
this.manager = manager;
}
getManager(): Employee {
return this.manager;
}
}
// After
class Department {
constructor(public manager: Employee) { }
}
π In the Refactoring catalog.
// Before
class Person {
name: string;
setName(name: string): void {
this.name = name;
}
}
// After
class Person {
constructor(public name: string) { }
}
Aliases: Replace Subclass with Fields.
π In the Refactoring catalog.
// Before
class Employee {
// ...
}
class Salesperson extends Employee {
// ...
}
class Manager extends Employee {
// ...
}
// After
class Employee {
// ...
}
π In the Refactoring catalog.
// Before
class Person {
fn: string;
ln: string;
constructor(firstName: string, lastName: string) {
this.fn = firstName;
this.ln = lastName;
}
}
// After
class Person {
constructor(public firstName: string, public lastName: string) { }
}
π In the Refactoring catalog.
// Before
function calculateTotal(prc: number, qty: number): number {
return prc * qty;
}
// After
function calculateTotal(price: number, quantity: number): number {
return price * quantity;
}
π In the Refactoring catalog.
// Before
class BankAccount {
balance: number;
constructor(initialBalance: number) {
this.balance = initialBalance;
}
withdraw(amount: number): void {
this.balance -= amount;
}
}
// After
class BankAccount {
balance: number;
constructor(initialBalance: number) {
this.balance = initialBalance;
}
deduct(amount: number): void {
this.balance -= amount;
}
}
π In the Refactoring catalog.
// Before
class Employee {
type: string;
monthlySalary: number;
commission: number;
constructor(type: string, monthlySalary: number, commission: number) {
this.type = type;
this.monthlySalary = monthlySalary;
this.commission = commission;
}
calculatePay(): number {
if (this.type === "fullTime") {
return this.monthlySalary;
} else if (this.type === "commissioned") {
return this.monthlySalary + this.commission;
}
}
}
// After
class Employee {
calculatePay(): number {
throw new Error("Abstract method");
}
}
class FullTimeEmployee extends Employee {
constructor(private monthlySalary: number) {
super();
}
calculatePay(): number {
return this.monthlySalary;
}
}
class CommissionedEmployee extends Employee {
constructor(private monthlySalary: number, private commission: number) {
super();
}
calculatePay(): number {
return this.monthlySalary + this.commission;
}
}
Aliases: Replace Constructor with Factory Method.
π In the Refactoring catalog.
// Before
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
// After
class Person {
private constructor(public name: string, public age: number) { }
static createPerson(name: string, age: number): Person {
return new Person(name, age);
}
}
π In the Refactoring catalog.
// Before
class Order {
items: number[];
constructor(items: number[]) {
this.items = items;
}
getTotal(): number {
let total = 0;
for (const item of this.items) {
total += item;
}
return total;
}
}
// After
class Order {
items: number[];
constructor(items: number[]) {
this.items = items;
}
getTotal(): number {
return this.items.reduce((sum, item) => sum + item, 0);
}
}
Aliases: Replace Method with Method Object.
π In the Refactoring catalog.
// Before
class Light {
isOn: boolean;
constructor() {
this.isOn = false;
}
turnOn(): void {
this.isOn = true;
}
turnOff(): void {
this.isOn = false;
}
}
// After
class Light {
isOn: boolean;
constructor() {
this.isOn = false;
}
commandOn(): LightCommand {
return new TurnOnCommand(this);
}
commandOff(): LightCommand {
return new TurnOffCommand(this);
}
}
interface LightCommand {
execute(): void;
}
class TurnOnCommand implements LightCommand {
constructor(private light: Light) { }
execute(): void {
this.light.isOn = true;
}
}
class TurnOffCommand implements LightCommand {
constructor(private light: Light) { }
execute(): void {
this.light.isOn = false;
}
}
π In the Refactoring catalog.
// Before
function calculateTotal(price: number, quantity: number): number {
const taxRate = 0.1;
return price * quantity * (1 + taxRate);
}
// After
function calculateTotal(price: number, quantity: number): number {
return applyTax(price * quantity);
}
function applyTax(amount: number): number {
const taxRate = 0.1;
return amount * (1 + taxRate);
}
π In the Refactoring catalog.
// Before
function getDiscountedPrices(products: Product[]): number[] {
const discountedPrices: number[] = [];
for (const product of products) {
const discount = calculateDiscount(product);
discountedPrices.push(product.price * (1 - discount));
}
return discountedPrices;
}
// After
function getDiscountedPrices(products: Product[]): number[] {
return products.map(product => product.calculateDiscountedPrice());
}
class Product {
constructor(public price: number) { }
calculateDiscountedPrice(): number {
const discount = calculateDiscount(this);
return this.price * (1 - discount);
}
}
π In the Refactoring catalog.
// Before
function calculatePrice(product: Product): number {
let price = product.basePrice;
if (product.isPromoEligible()) {
if (product.isPremium()) {
price *= 0.9;
} else {
price *= 0.95;
}
}
return price;
}
// After
function calculatePrice(product: Product): number {
if (!product.isPromoEligible()) {
return product.basePrice;
}
if (product.isPremium()) {
return product.basePrice * 0.9;
}
return product.basePrice * 0.95;
}
Aliases: Replace Parameter with Method.
π In the Refactoring catalog.
// Before
function calculateTotalPrice(price: number, quantity: number): number {
return price * quantity;
}
// After
function calculateTotalPrice(product: Product): number {
return product.getPrice() * product.getQuantity();
}
Aliases: Replace Data Value with Object; Replace Type Code with Class.
π In the Refactoring catalog.
// Before
function calculateTotalPrice(price: number, quantity: number): number {
return price * quantity;
}
// After
class Quantity {
constructor(private value: number) { }
multiply(price: number): number {
return this.value * price;
}
}
const totalPrice = new Quantity(quantity).multiply(price);
π In the Refactoring catalog.
// Before
function isDiscountEligible(orderTotal: number): boolean {
return orderTotal > 100;
}
function calculateTotal(orderTotal: number, discountRate: number): number {
if (isDiscountEligible(orderTotal)) {
return orderTotal * (1 - discountRate);
}
return orderTotal;
}
// After
function calculateTotal(orderTotal: number, discountRate: number, isDiscountEligible: boolean): number {
if (isDiscountEligible) {
return orderTotal * (1 - discountRate);
}
return orderTotal;
}
π In the Refactoring catalog.
// Before
class TeamLead extends Employee {
// ...
}
class Employee {
teamLead: TeamLead;
getTeamLead(): TeamLead {
return this.teamLead;
}
}
// After
class Employee {
delegate: TeamLeadDelegate;
getTeamLead(): TeamLeadDelegate {
return this.delegate;
}
}
class TeamLeadDelegate {
// ...
}
Aliases: Replace Inheritance with Delegation.
π In the Refactoring catalog.
// Before
class Circle extends Shape {
radius: number;
constructor(radius: number) {
super();
this.radius = radius;
}
// ...
}
// After
class CircleDelegate {
shape: Shape;
constructor(shape: Shape) {
this.shape = shape;
}
getRadius(): number {
// Return the radius using the shape data
}
// ...
}
π In the Refactoring catalog.
// Before
function calculateTotalPrice(price: number, quantity: number): number {
const totalPrice = price * quantity;
return totalPrice > 1000 ? totalPrice * 0.9 : totalPrice;
}
// After
function calculateTotalPrice(price: number, quantity: number): number {
return (price * quantity) > 1000 ? price * quantity * 0.9 : price * quantity;
}
Aliases: Extract Subclass; Replace Type Code with State/Strategy.
π In the Refactoring catalog.
// Before
class Employee {
type: string;
constructor(type: string) {
this.type = type;
}
}
// After
abstract class Employee {
abstract calculatePay(): number;
}
class FullTimeEmployee extends Employee {
calculatePay(): number {
// Calculation for full-time employee
}
}
class PartTimeEmployee extends Employee {
calculatePay(): number {
// Calculation for part-time employee
}
}
π In the Refactoring catalog.
// Before
class Account {
balance: number;
withdraw(amount: number): void {
if (amount > this.balance) {
throw new Error("Insufficient balance");
}
this.balance -= amount;
}
}
// After
class Account {
balance: number;
canWithdraw(amount: number): boolean {
return amount <= this.balance;
}
withdraw(amount: number): void {
if (!this.canWithdraw(amount)) {
throw new Error("Insufficient balance");
}
this.balance -= amount;
}
}
Aliases: Consolidate Duplicate Conditional Fragments.
π In the Refactoring catalog.
// Before
let discount: number = 0;
if (order.total > 100) {
discount = 0.1;
}
// After
if (order.total > 100) {
let discount: number = 0.1;
}
π In the Refactoring catalog.
// Before
function calculateTotalPrice(cart: Cart[]): number {
let total = 0;
for (const item of cart) {
total += item.price;
}
for (const item of cart) {
total -= item.discount;
}
return total;
}
// After
function calculateTotalPrice(cart: Cart[]): number {
let totalPrice = 0;
for (const item of cart) {
totalPrice += item.price;
}
let totalDiscount = 0;
for (const item of cart) {
totalDiscount += item.discount;
}
return totalPrice - totalDiscount;
}
π In the Refactoring catalog.
// Before
function processOrder(order: Order): void {
validateOrder(order);
updateInventory(order);
createInvoice(order);
}
// After
function processOrder(order: Order): void {
validateOrder(order);
updateInventory(order);
}
function updateInventory(order: Order): void {
// ...
}
function createInvoice(order: Order): void {
// ...
}
Aliases: Remove Assignments to Parameters; Split Temp.
π In the Refactoring catalog.
// Before
let fullName: string = `${user.firstName} ${user.lastName}`;
// After
let firstName: string = user.firstName;
let lastName: string = user.lastName;
π In the Refactoring catalog.
// Before
function foundPerson(people: string[]): string {
for (const person of people) {
if (person === "Don") {
return "Don";
}
if (person === "John") {
return "John";
}
if (person === "Kent") {
return "Kent";
}
}
return "";
}
// After
function foundPerson(people: string[]): string {
const candidates = ["Don", "John", "Kent"];
for (const person of people) {
if (candidates.includes(person)) {
return person;
}
}
return "";
}
Aliases: Remove Control Flag.
π In the Refactoring catalog.
// Before
function findValue(arr: number[], target: number): boolean {
let found = false;
for (const item of arr) {
if (item === target) {
found = true;
break;
}
}
return found;
}
// After
function findValue(arr: number[], target: number): boolean {
for (const item of arr) {
if (item === target) {
return true;
}
}
return false;
}
π In the Refactoring catalog.
// Before
function divide(a: number, b: number): number {
if (b === 0) {
return -1; // Error code for division by zero
}
return a / b;
}
// After
function divide(a: number, b: number): number {
if (b === 0) {
throw new Error("Division by zero");
}
return a / b;
}
Aliases: Replace Exception with Test.
π In the Refactoring catalog.
// Before
function calculateDiscountedPrice(price: number, discount: number): number {
try {
if (discount < 0 || discount > 1) {
throw new Error("Invalid discount");
}
return price * (1 - discount);
} catch (error) {
console.error(error);
return price;
}
}
// After
function calculateDiscountedPrice(price: number, discount: number): number {
if (discount < 0 || discount > 1) {
console.error("Invalid discount");
return price;
}
return price * (1 - discount);
}
Aliases: Replace Magic Number with Symbolic Constant.
π In the Refactoring catalog.
// Before
function calculateTotal(price: number, quantity: number): number {
return price * quantity * 1.1; // 1.1 is the tax rate
}
// After
const TAX_RATE = 1.1;
function calculateTotal(price: number, quantity: number): number {
return price * quantity * TAX_RATE;
}
π In the Refactoring catalog.
// Before
function modifyValue(value: number): number {
value *= 2;
value += 10;
return value;
}
// After
function modifyValue(value: number): void {
value *= 2;
value += 10;
}