Created
March 8, 2023 04:23
-
-
Save revskill10/01a30c460abbb2b6d4fbb5737cd3ba97 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Extract fragment of code into its own function named after its purpose. | |
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}`) | |
} | |
} | |
//Get rid of the function when the body of the code is just as clear as the name | |
function rating(aDriver) { | |
return moreThanFiveLateDeliveries(aDriver) ? 2 : 1 | |
} | |
function moreThanFiveLateDeliveries(aDriver) { | |
return aDriver.numberOfLateDeliveries > 5 | |
} | |
//to | |
function rating(aDriver) { | |
return aDriver.numberOfLateDeliveries > 5 ? 2 : 1 | |
} | |
//Add a name to an expression | |
//price is base price quantity discount + shipping | |
return order.quantity * order.itemPrice - | |
Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 + | |
Math.min(order.quantity * order.itemPrice * 0.1, 100) | |
//to | |
const basePrice = order.quantity * order.itemPrice | |
const quantityDiscount = Math.max(0, order.quantity - 500) * order.itemP | |
const shipping = Math.min(basePrice * 0.1, 100) | |
return basePrice - quantityDiscount + shipping; | |
//Remove variable which doesn't really communicate more than the expression itself. | |
let basePrice = anOrder.basePrice | |
return (basePrice > 1000) | |
//to | |
return anOrder.basePrice > 1000 | |
//Rename a function, change list of parameters | |
function circum(radius) {...} | |
//to | |
function circumference(radius) {...} | |
//Encapsulate a reference to some data structure | |
let defaultOwner = {firstName: "Martin", lastName: "Fowler"} | |
//to | |
// defaultOwner.js | |
let defaultOwnerData = {firstName: "Martin", lastName: "Fowler"} | |
export function defaultOwner() { return defaultOwnerData } | |
export function setDefaultOwner(arg) { defaultOwnerData = arg } | |
//Make shared variable's name can self-explain | |
let a = height * width | |
//to | |
let area = height * width | |
//Replace groups of data items that regularly travel together with a single data structure | |
function amountInvoiced(startDate, endDate) {} | |
function amountReceived(startDate, endDate) {} | |
function amountOverdue(startDate, endDate) {} | |
//to | |
function amountInvoiced(aDateRange) {} | |
function amountReceived(aDateRange) {} | |
function amountOverdue(aDateRange) {} | |
//Form a class base on group of functions that operate closely on a common data | |
function base(aReading) {} | |
function taxableCharge(aReading) {} | |
function calculateBaseCharge(aReading) {} | |
//to | |
class Reading() { | |
base() {} | |
taxableCharge() {} | |
calculateBaseCharge() {} | |
} | |
//Takes the source data as input and calculates all the derivations, putting each derived value as a field in the output data | |
function base(aReading) {} | |
function taxableCharge(aReading) {} | |
//to | |
function enrichReading(argReading) { | |
const aReading = _.cloneDeep(argReading) | |
aReading.baseCharge = base(aReading) | |
aReading.taxableCharge = taxableCharge(aReading) | |
return aReading | |
} | |
//Split code which do different things into separate modules | |
const orderData = orderString.split(/\s+/) | |
const productPrice = priceList[orderData[0].split("-")[1]] | |
const orderPrice = parseInt(orderData[1]) * productPrice | |
//to | |
const orderRecord = parseOrder(orderString) | |
const orderPrice = price(orderRecord, priceList) | |
function parseOrder(aString) { | |
const values = aString.split(/\s+/) | |
return { | |
productID: values[0].split("-")[1], | |
quantity: parseInt(values[1]) | |
} | |
} | |
function price(order, priceList) { | |
return order.quantity * priceList[order.productID] | |
} | |
//Create record (class) from object | |
organization = {name: "Acme gooseberries", country: "GB"} | |
//to | |
class Organization { | |
constructor(data) { | |
this._name = data.name | |
this._country = data.country | |
} | |
get name() { return this._name } | |
set name(arg) { this._name = arg } | |
get country() { retrun this._country } | |
set country(arg) { this._country = arg } | |
} | |
//A method returns a collection. Make it return a read-only view and provide add/remove methods | |
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) | |
} | |
//Create class for data | |
orders.filter(o => "high" === o.priority || "rush" === o.priority) | |
//to | |
orders.filter(o => o.priority.higherThan(new Priority("normal"))) | |
//Extract the assignment of the variable into a function | |
const basePrice = this._quantity * this._itemPrice | |
if (basePrice > 1000) { | |
return basePrice * 0.95 | |
} else { | |
return basePrice * 0.98 | |
} | |
//to | |
get basePrice() { return this._quantity * this._itemPrice } | |
//... | |
if (this.basePrice > 1000) { | |
return this.basePrice * 0.95 | |
} else { | |
return this.basePrice * 0.98 | |
} | |
//Extract class base on a subset of data and a subset of methods | |
class Person { | |
get officeAreaCode() { return this._officeAreaCode } | |
get officeNumber() { return this._officeNumber } | |
} | |
//to | |
class Person { | |
get officeAreaCode() { return this._telephoneNumber.areaCode } | |
get officeNumber() { return this._telephoneNumber.number } | |
} | |
class TelephoneNumber { | |
get areaCode() { return this._areaCode } | |
get number() { return this._number } | |
} | |
//Merge class if class isn't doing very much. Move its feature to another class then delete it. | |
class Person { | |
get officeAreaCode() { return this._telephoneNumber.areaCode } | |
get officeNumber() { return this._telephoneNumber.number } | |
} | |
class TelephoneNumber { | |
get areaCode() { return this._areaCode } | |
get number() { return this._number } | |
} | |
//to | |
class Person { | |
get officeAreaCode() { return this._officeAreaCode } | |
get officeNumber() { return this._officeNumber } | |
} | |
//A client is calling a delegate class of an object, create methods on the server to hide the delegate. | |
manager = aPerson.department.manager | |
//to | |
manager = aPerson.manager | |
class Person { | |
get manager() { | |
return this.department.manager | |
} | |
} | |
//Client call the delegate directly | |
manager = aPerson.manager | |
class Person { | |
get manager() { | |
return this.department.manager | |
} | |
} | |
//to | |
manager = aPerson.department.manager | |
//Replace complicated algorithm with simpler algorithm | |
function foundPerson(people) { | |
for (let i = 0; i < people.lenth; i++) { | |
if (people[i] === "Don") { | |
return "Don" | |
} | |
if (people[i] === "John") { | |
return "John" | |
} | |
if (people[i] === "Kent") { | |
return "Kent" | |
} | |
} | |
return "" | |
} | |
//to | |
function foundPerson(people) { | |
const candidates = ["Don", "John", "Kent"] | |
return people.find(p => candidates.includes(p)) || "" | |
} | |
//Move a function when it references elements in other contexts more than the one it currently resides in | |
class Account { | |
get overdraftCharge() {} | |
} | |
class AccountType {} | |
//to | |
class Account {} | |
class AccountType { | |
get overdraftCharge() {} | |
} | |
//Move field from once class to another | |
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 } | |
} | |
//When statement is a part of called functions (always go togeter), move it inside the function | |
result.push(`<p>title: ${person.photo.title}</p>`) | |
result.concat(photoData(person.photo)) | |
function photoData(aPhoto) { | |
return [ | |
`<p>location: ${aPhoto.location}</p>`, | |
`<p>date: ${aPhoto.data.toDateString()}</p>` | |
] | |
} | |
//to | |
result.concat(photoData(person.photo)) | |
function photoData(aPhoto) { | |
return [ | |
`<p>title: ${aPhoto.title}</p>`, | |
`<p>location: ${aPhoto.location}</p>`, | |
`<p>date: ${aPhoto.data.toDateString()}</p>` | |
] | |
} | |
//4. Move Statements To Callers | |
emitPhotoData(outStream, person.photo) | |
function emitPhotoData(outStream, photo) { | |
outStream.write(`<p>title: ${photo.title}</p>\n`) | |
outStream.write(`<p>location: ${photo.location}</p>\n`) | |
} | |
//to | |
emitPhotoData(outStream, person.photo) | |
outStream.write(`<p>location: ${photo.location}</p>\n`) | |
function emitPhotoData(outStream, photo) { | |
outStream.write(`<p>title: ${photo.title}</p>\n`) | |
} | |
//Replace the inline code with a call to the existing function | |
let appliesToMass = false | |
for (const s of states) { | |
if (s === "MA") appliesToMass = true | |
} | |
//let appliesToMass = false | |
//for (const s of states) { | |
// if (s === "MA") appliesToMass = true | |
//} | |
appliesToMass = states.includes("MA") | |
//Move related code to near each other | |
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 | |
//Split the loop which does two different things | |
let averageAge = 0 | |
let totalSalary = 0 | |
for (const p of people) { | |
averageAge += p.age | |
totalSalary += p.salary | |
} | |
averageAage = averageAge / people.length | |
//to | |
let totalSalary = 0 | |
for (const p of people) { | |
totalSalary += p.salary | |
} | |
let averageAge = 0 | |
for (const p of people) { | |
averageAge += p.age | |
} | |
averageAge = averageAge / people.length | |
//Replace loop with collection pipeline, like map or filter | |
const names = [] | |
for (const i of input) { | |
if (i.job === "programmer") { | |
names.push(i.name) | |
} | |
} | |
//to | |
const names = input.filter(i => i.job === "programmer"). | |
map(i => i.name) | |
//9. Remove Dead Code | |
if (false) { | |
doSomethingThatUsedToMatter() | |
} | |
//to | |
//Any variable with more than one responsibility should be replaced with multiple variables, one for each responsibility | |
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) | |
//2. Rename Field | |
class Organization { | |
get name() {} | |
} | |
//to | |
class Organization { | |
get title() {} | |
} | |
//Remove anny variables which cloud be easily calculate | |
get discountedTotal() { return this._discountedTotal } | |
set discount(aNumber) { | |
const old = this._discount | |
this._discount = aNumber | |
this._discountedTotal += old - aNumber | |
} | |
//to | |
get discountedTotal() { return this._baseTotal - this._discount } | |
set discount() { this._discount = aNumber } | |
//Treat data as value. When update, replace entire inner object with a new one | |
class Product { | |
applyDiscount(arg) { | |
this._price.amount -= arg | |
} | |
} | |
//to | |
class Product { | |
applyDiscount(arg) { | |
this._price = new Money(this._price.amount - arg, this._price.currency) | |
} | |
} | |
//When need to share an object in different place, or have duplicated objects | |
let customer = new Customer(customerData) | |
//to | |
let customer = customerRepository.get(customerData.id) | |
//Decomposing condition and replacing each chunk of code with a function call | |
if (!aDate.isBefore(plan.summerStart) && !aData.isAfter(plan.summerEnd)) { | |
charge = quantity * plan.summerRate | |
} else { | |
charge = quantity * plan.regularRate + plan.regularServiceCharge | |
} | |
//to | |
if (summer()) { | |
charge = summerCharge() | |
} else { | |
charge = regularCharge() | |
} | |
//Consolidate different condition check which the result action is same to a single condition check with single result | |
if (anEmployee.seniority < 2) return 0 | |
if (anEmployee.monthsDisabled > 12) return 0; | |
if (anEmployee.isPartTime) return 0 | |
//to | |
if (isNotEligableForDisability()) return 0 | |
function isNotEligableForDisability() { | |
return ((anEmployee.seniority < 2) | |
|| (anEmployee.monthsDisabled > 12) | |
|| (anEmployee.isPartTime)) | |
} | |
//If condition is unusual condition, early return (Guard Clauses) and exist the function | |
function getPayAmount() { | |
let result | |
if (isDead) { | |
result = deadAmount() | |
} else { | |
if (isSeparated) { | |
result = separatedAmount() | |
} else { | |
if (isRetired) { | |
result = retiredAmount() | |
} else { | |
result = normalPayAmount() | |
} | |
} | |
} | |
return result | |
} | |
//to | |
function getPayAmount() { | |
if (isDead) return deadAmount() | |
if (isSeparated) return separatedAmount() | |
if (isRetired) return retiredAmount() | |
return normalPayAmount() | |
} | |
//Using object oriented class instead of complex condition | |
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 'unknow' | |
} | |
//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' | |
} | |
} | |
//Bring special check case to a single place | |
if (aCustomer === "unknown") { | |
customerName = "occupant" | |
} | |
//to | |
class UnknownCustomer { | |
get name() { | |
return "occupant" | |
} | |
} | |
//Make the assumption explicit by writing an assertion | |
if (this.discountRate) { | |
base = base - (this.discountRate * base) | |
} | |
//to | |
assert(this.discountRate >= 0) | |
if (this.discountRate) { | |
base = base - (this.discountRate * base) | |
} | |
//Separate function that returns a value (query only) and function with side effects (example: modify data) | |
function alertForMiscreant (people) { | |
for (const p of people) { | |
if (p === "Don") { | |
setOffAlarms() | |
return "Don" | |
} | |
if (p === "John") { | |
setOffAlarms() | |
return "John" | |
} | |
} | |
return "" | |
} | |
//to | |
function findMiscreant (people) { | |
for (const p of people) { | |
if (p === "Don") { | |
return "Don" | |
} | |
if (p === "John") { | |
return "John" | |
} | |
} | |
return "" | |
} | |
function alertForMiscreant (people) { | |
if (findMiscreant(people) !== "") setOffAlarms(); | |
} | |
//Combine function with similar logic and different literal value | |
function tenPercentRaise(aPerson) { | |
aPerson.salary = aPerson.salary.multiply(1.1) | |
} | |
function fivePercentRaise(aPerson) { | |
aPerson.salary = aPerson.salary.multiply(1.05) | |
} | |
//to | |
function raise(aPerson, factor) { | |
aPerson.salary = aPerson.salary.multiply(1 + factor) | |
} | |
//Remove literal flag argument by clear name functions | |
function setDimension(name, value) { | |
if (name === 'height') { | |
this._height = value | |
return | |
} | |
if (name === 'width') { | |
this._width = value | |
return | |
} | |
} | |
//to | |
function setHeight(value) { this._height = value } | |
function setWidth(value) { this._width = value } | |
//Passing whole object instead of multiple parameters | |
const low = aRoom.daysTempRange.low | |
const high = aRoom.daysTempRange.high | |
if (aPlan.withinRange(low, high)) {} | |
//to | |
if (aPlan.withInRange(aRoom.daysTempRange)) {} | |
//5. Replace Parameter with Query | |
availableVacation(anEmployee, anEmployee.grade) | |
function availableVacation(anEmployee, grade) { | |
// calculate vacation... | |
} | |
//to | |
availableVacation(anEmployee) | |
function availableVacation(anEmployee) { | |
const grade = anEmployee.grade | |
// calculate vacation... | |
} | |
//Replace internal reference with a parameter | |
targetTemperature(aPlan) | |
function targetTemperature(aPlan) { | |
currentTemperature = thermostat.currentTemperature | |
// rest of function... | |
} | |
//to | |
targetTemperature(aPlan, thermostat.currentTemperature) | |
function targetTemperature(aPlan, currentTemperature) { | |
// rest of function... | |
} | |
//Make a field immutable by removing setting method | |
class Person { | |
get name() {...} | |
set name(aString) {...} | |
} | |
//to | |
class Person { | |
get name() {...} | |
} | |
//8. Replace Constructor with Factory Function | |
leadEngineer = new Employee(document.leadEngineer, 'E') | |
//to | |
leadEngineer = createEngineer(document.leadEngineer) | |
//Encapsulate function into its own object | |
function score(candidate, medicalExam, scoringGuide) { | |
let result = 0 | |
let healthLevel = 0 | |
// long body code | |
} | |
//to | |
class Scorer { | |
constructor(candidate, medicalExam, scoringGuide) { | |
this._candidate = candidate | |
this._medicalExam = medicalExam | |
this._scoringGuide = scoringGuide | |
} | |
execute() { | |
this._result = 0 | |
this._healthLevel = 0 | |
// long body code | |
} | |
} | |
//10. Replace Command with Function | |
class ChargeCalculator { | |
constructor (customer, usage){ | |
this._customer = customer | |
this._usage = usage | |
} | |
execute() { | |
return this._customer.rate * this._usage | |
} | |
} | |
//to | |
function charge(customer, usage) { | |
return customer.rate * usage | |
} | |
//Move similar methods in subclass to superclass | |
class Employee {...} | |
class Salesman extends Employee { | |
get name() {...} | |
} | |
class Engineer extends Employee { | |
get name() {...} | |
} | |
//to | |
class Employee { | |
get name() {...} | |
} | |
class Salesman extends Employee {...} | |
class Engineer extends Employee {...} | |
//Pull up similar field to superclass to remove duplication | |
class Employee {...} | |
class Salesman extends Employee { | |
private String name; | |
} | |
class Engineer extends Employee { | |
private String name; | |
} | |
//to | |
class Employee { | |
protected String name; | |
} | |
class Salesman extends Employee {...} | |
class Engineer extends Employee {...} | |
//You have constructors on subclasses with mostly identical bodies. | |
//Create a superclass constructor; call this from the subclass methods | |
class Party {...} | |
class Employee extends Party { | |
constructor(name, id, monthlyCost) { | |
super() | |
this._id = id | |
this._name = name | |
this._monthlyCost = monthlyCost | |
} | |
} | |
//to | |
class Party { | |
constructor(name){ | |
this._name = name | |
} | |
} | |
class Employee extends Party { | |
constructor(name, id, monthlyCost) { | |
super(name) | |
this._id = id | |
this._monthlyCost = monthlyCost | |
} | |
} | |
//If method is only relevant to one subclass, moving it from superclass to subclass | |
class Employee { | |
get quota {...} | |
} | |
class Engineer extends Employee {...} | |
class Salesman extends Employee {...} | |
//to | |
class Employee {...} | |
class Engineer extends Employee {...} | |
class Salesman extends Employee { | |
get quota {...} | |
} | |
//If field is only used in one subclass, move it to those subclasses | |
class Employee { | |
private String quota; | |
} | |
class Engineer extends Employee {...} | |
class Salesman extends Employee {...} | |
//to | |
class Employee {...} | |
class Engineer extends Employee {...} | |
class Salesman extends Employee { | |
protected String quota; | |
} | |
//6. Replace Type Code with Subclasses | |
function createEmployee(name, type) { | |
return new Employee(name, type) | |
} | |
//to | |
function createEmployee(name, type) { | |
switch (type) { | |
case "engineer": return new Engineer(name) | |
case "salesman": return new Salesman(name) | |
case "manager": return new Manager (name) | |
} | |
} | |
//You have subclasses do to little. Replace the subclass with a field in superclass. | |
class Person { | |
get genderCode() {return "X"} | |
} | |
class Male extends Person { | |
get genderCode() {return "M"} | |
} | |
class Female extends Person { | |
get genderCode() {return "F"} | |
} | |
//to | |
class Person { | |
get genderCode() {return this._genderCode} | |
} | |
//If 2 classes have similar behaviors, create superclass and move these behaviors to superclass | |
class Department { | |
get totalAnnualCost() {...} | |
get name() {...} | |
get headCount() {...} | |
} | |
class Employee { | |
get annualCost() {...} | |
get name() {...} | |
get id() {...} | |
} | |
//to | |
class Party { | |
get name() {...} | |
get annualCost() {...} | |
} | |
class Department extends Party { | |
get annualCost() {...} | |
get headCount() {...} | |
} | |
class Employee extends Party { | |
get annualCost() {...} | |
get id() {...} | |
} | |
//Merge superclass and subclass when there are no longer different enough to keep them separate | |
class Employee {...} | |
class Salesman extends Employee {...} | |
//to | |
class Employee {...} | |
//"Favor object composition over class inheritance" (where composition is effectively the same as delegation | |
class Order { | |
get daysToShip() { | |
return this._warehouse.daysToShip | |
} | |
} | |
class PriorityOrder extends Order { | |
get daysToShip() { | |
return this._priorityPlan.daysToShip | |
} | |
} | |
//to | |
class Order { | |
get daysToShip() { | |
return (this._priorityDelegate) | |
? this._priorityDelegate.daysToShip | |
: this._warehouse.daysToShip | |
} | |
} | |
class PriorityOrderDelegate { | |
get daysToShip() { | |
return this._priorityPlan.daysToShip | |
} | |
} | |
//If functions of the superclass don’t make sense on the subclass, replace with with delegate | |
class List {...} | |
class Stack extends List {...} | |
//to | |
class Stack { | |
constructor() { | |
this._storage = new List(); | |
} | |
} | |
class List {...} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment