function statement(customer, movies) { let totalAmount = 0; let frequentRenterPoints = 0; let result = `Rental Record for ${customer.name}\n`; for (let r of customer.rentals) { let movie = movies[r.movieID]; let thisAmount = 0; // determine amount for each movie switch (movie.code) { case "regular": thisAmount = 2; if (r.days > 2) { thisAmount += (r.days - 2) * 1.5; } break; case "new": thisAmount = r.days * 3; break; case "childrens": thisAmount = 1.5; if (r.days > 3) { thisAmount += (r.days - 3) * 1.5; } break; } //add frequent renter points frequentRenterPoints++; // add bonus for a two day new release rental if(movie.code === "new" && r.days > 2) frequentRenterPoints++; //print figures for this rental result += `\t${movie.title}\t${thisAmount}\n` ; totalAmount += thisAmount; } // add footer lines result += `Amount owed is ${totalAmount}\n`; result += `You earned ${frequentRenterPoints} frequent renter points\n`; return result; }
{ "name": "martin", "rentals": [ {"movieID": "F001", "days": 3}, {"movieID": "F002", "days": 1}, ] }
{ "F001": {"title": "Ran", "code": "regular"}, "F002": {"title": "Trois Couleurs: Bleu", "code": "regular"}, // etc }
In the original book, films were simply represented as objects in the structure of Java objects. For this article, I preferred to go to the json structure. It is assumed that some kind of global search such as Repository is not suitable for this application.Rental Record for martin
Ran 3.5
Trois Couleurs: Bleu 2
Amount owed is 5.5
You earned 2 frequent renter points
statement
function smells like Long Method . Only its size is already suspicious. But one bad smell is not a reason for refactoring. Badly factorized code is a problem because it is difficult to understand. If the code is difficult to understand, then it is difficult to change to add new features or correct errors. So if you don’t need to read or understand some code, then its bad structure doesn’t hurt you and you will be happy to leave it alone for a while. Therefore, to arouse our interest in this code, there must be some reason for changing it. Our reason, which I pointed out in the book, is to create an HTML version of the statement statement
with something like this: <h1>Rental Record for <em>martin</em></h1> <table> <tr><td>Ran</td><td>3.5</td></tr> <tr><td>Trois Couleurs: Bleu</td><td>2</td></tr> </table> <p>Amount owed is <em>5.5</em></p> <p>You earned <em>2</em> frequent renter points</p>
switch
. function statement(customer, movies) { let totalAmount = 0; let frequentRenterPoints = 0; let result = `Rental Record for ${customer.name}\n`; for (let r of customer.rentals) { let movie = movies[r.movieID]; let thisAmount = 0; // determine amount for each movie switch (movie.code) { case "regular": thisAmount = 2; if (r.days > 2) { thisAmount += (r.days - 2) * 1.5; } break; case "new": thisAmount = r.days * 3; break; case "childrens": thisAmount = 1.5; if (r.days > 3) { thisAmount += (r.days - 3) * 1.5; } break; } //add frequent renter points frequentRenterPoints++; // add bonus for a two day new release rental if(movie.code === "new" && r.days > 2) frequentRenterPoints++; //print figures for this rental result += `\t${movie.title}\t${thisAmount}\n` ; totalAmount += thisAmount; } // add footer lines result += `Amount owed is ${totalAmount}\n`; result += `You earned ${frequentRenterPoints} frequent renter points\n`; return result; }
thisAmount
calculated by the extracted code. I can initiate it inside a function and return it at the end.r
for the number of days of rental is checked in a cycle, I can pass it as a parameter.movie
is a rented movie
. Temporary variables like this usually get in the way of refactoring procedural code, so I would prefer to run Replace Temp with Query first to convert it into a function that I can call from any extracted code. function statement(customer, movies) { let totalAmount = 0; let frequentRenterPoints = 0; let result = `Rental Record for ${customer.name}\n`; for (let r of customer.rentals) { let thisAmount = 0; // determine amount for each movie switch (movieFor(r).code) { case "regular": thisAmount = 2; if (r.days > 2) { thisAmount += (r.days - 2) * 1.5; } break; case "new": thisAmount = r.days * 3; break; case "childrens": thisAmount = 1.5; if (r.days > 3) { thisAmount += (r.days - 3) * 1.5; } break; } //add frequent renter points frequentRenterPoints++; // add bonus for a two day new release rental if(movieFor(r).code === "new" && r.days > 2) frequentRenterPoints++; //print figures for this rental result += `\t${movieFor(r).title}\t${thisAmount}\n` ; totalAmount += thisAmount; } // add footer lines result += `Amount owed is ${totalAmount}\n`; result += `You earned ${frequentRenterPoints} frequent renter points\n`; return result; function movieFor(rental) {return movies[rental.movieID];} }
switch
. function statement(customer, movies) { let totalAmount = 0; let frequentRenterPoints = 0; let result = `Rental Record for ${customer.name}\n`; for (let r of customer.rentals) { const thisAmount = amountFor(r); //add frequent renter points frequentRenterPoints++; // add bonus for a two day new release rental if(movieFor(r).code === "new" && r.days > 2) frequentRenterPoints++; //print figures for this rental result += `\t${movieFor(r).title}\t${thisAmount}\n` ; totalAmount += thisAmount; } // add footer lines result += `Amount owed is ${totalAmount}\n`; result += `You earned ${frequentRenterPoints} frequent renter points\n`; return result; function movieFor(rental) {return movies[rental.movieID];} function amountFor(r) { let thisAmount = 0; // determine amount for each movie switch (movieFor(r).code) { case "regular": thisAmount = 2; if (r.days > 2) { thisAmount += (r.days - 2) * 1.5; } break; case "new": thisAmount = r.days * 3; break; case "childrens": thisAmount = 1.5; if (r.days > 3) { thisAmount += (r.days - 3) * 1.5; } break; } return thisAmount; } }
function statement(customer, movies) { let totalAmount = 0; let frequentRenterPoints = 0; let result = `Rental Record for ${customer.name}\n`; for (let r of customer.rentals) { const thisAmount = amountFor(r); frequentRenterPointsFor(r); //print figures for this rental result += `\t${movieFor(r).title}\t${thisAmount}\n` ; totalAmount += thisAmount; } // add footer lines result += `Amount owed is ${totalAmount}\n`; result += `You earned ${frequentRenterPoints} frequent renter points\n`; return result; … function frequentRenterPointsFor(r) { //add frequent renter points frequentRenterPoints++; // add bonus for a two day new release rental if (movieFor(r).code === "new" && r.days > 2) frequentRenterPoints++; }
function statement(customer, movies) { let totalAmount = 0; let frequentRenterPoints = 0; let result = `Rental Record for ${customer.name}\n`; for (let r of customer.rentals) { const thisAmount = amountFor(r); frequentRenterPoints += frequentRenterPointsFor(r); //print figures for this rental result += `\t${movieFor(r).title}\t${thisAmount}\n` ; totalAmount += thisAmount; } // add footer lines result += `Amount owed is ${totalAmount}\n`; result += `You earned ${frequentRenterPoints} frequent renter points\n`; return result; … function frequentRenterPointsFor(r) { let result = 1; if (movieFor(r).code === "new" && r.days > 2) result++; return result; }
function amountFor(rental) { let result = 0; switch (movieFor(rental).code) { case "regular": result = 2; if (rental.days > 2) { result += (rental.days - 2) * 1.5; } return result; case "new": result = rental.days * 3; return result; case "childrens": result = 1.5; if (rental.days > 3) { result += (rental.days - 3) * 1.5; } return result; } return result; } function frequentRenterPointsFor(rental) { return (movieFor(rental).code === "new" && rental.days > 2) ? 2 : 1; }
With these functions, I could do something else, especially with the amountFor
, and I really did something in the book. But for this article, I will no longer delve into the study of the body of these functions function statement(customer, movies) { let totalAmount = 0; let frequentRenterPoints = 0; let result = `Rental Record for ${customer.name}\n`; for (let r of customer.rentals) { const thisAmount = amountFor(r); frequentRenterPoints += frequentRenterPointsFor(r); //print figures for this rental result += `\t${movieFor(r).title}\t${thisAmount}\n` ; totalAmount += thisAmount; } // add footer lines result += `Amount owed is ${totalAmount}\n`; result += `You earned ${frequentRenterPoints} frequent renter points\n`; return result;
function statement(customer, movies) { let totalAmount = 0; let frequentRenterPoints = 0; let result = `Rental Record for ${customer.name}\n`; for (let r of customer.rentals) { frequentRenterPoints += frequentRenterPointsFor(r); result += `\t${movieFor(r).title}\t${amountFor(r)}\n` ; totalAmount += amountFor(r); } // add footer lines result += `Amount owed is ${totalAmount}\n`; result += `You earned ${frequentRenterPoints} frequent renter points\n`; return result;
function statement(customer, movies) { let totalAmount = 0; let frequentRenterPoints = 0; let result = `Rental Record for ${customer.name}\n`; for (let r of customer.rentals) { frequentRenterPoints += frequentRenterPointsFor(r); } for (let r of customer.rentals) { result += `\t${movieFor(r).title}\t${amountFor(r)}\n`; } for (let r of customer.rentals) { totalAmount += amountFor(r); } // add footer lines result += `Amount owed is ${totalAmount}\n`; result += `You earned ${frequentRenterPoints} frequent renter points\n`; return result;
Some programmers are worried about performance issues after such refactoring, in which case look at the old, but relevant, article about software performance. function statement(customer, movies) { let result = `Rental Record for ${customer.name}\n`; for (let r of customer.rentals) { result += `\t${movieFor(r).title}\t${amountFor(r)}\n`; } result += `Amount owed is ${totalAmount()}\n`; result += `You earned ${totalFrequentRenterPoints()} frequent renter points\n`; return result; function totalAmount() { let result = 0; for (let r of customer.rentals) { result += amountFor(r); } return result; } function totalFrequentRenterPoints() { let result = 0; for (let r of customer.rentals) { result += frequentRenterPointsFor(r); } return result; }
function totalFrequentRenterPoints() { return customer.rentals .map((r) => frequentRenterPointsFor(r)) .reduce((a, b) => a + b) ; } function totalAmount() { return customer.rentals .reduce((total, r) => total + amountFor(r), 0); }
Not sure which of these two types of chains I like best function statement(customer, movies) { let result = `Rental Record for ${customer.name}\n`; for (let r of customer.rentals) { result += `\t${movieFor(r).title}\t${amountFor(r)}\n`; } result += `Amount owed is ${totalAmount()}\n`; result += `You earned ${totalFrequentRenterPoints()} frequent renter points\n`; return result; function totalFrequentRenterPoints() { return customer.rentals .map((r) => frequentRenterPointsFor(r)) .reduce((a, b) => a + b) ; } function totalAmount() { return customer.rentals .reduce((total, r) => total + amountFor(r), 0); } function movieFor(rental) { return movies[rental.movieID]; } function amountFor(rental) { let result = 0; switch (movieFor(rental).code) { case "regular": result = 2; if (rental.days > 2) { result += (rental.days - 2) * 1.5; } return result; case "new": result = rental.days * 3; return result; case "childrens": result = 1.5; if (rental.days > 3) { result += (rental.days - 3) * 1.5; } return result; } return result; } function frequentRenterPointsFor(rental) { return (movieFor(rental).code === "new" && rental.days > 2) ? 2 : 1; } }
statement
. So it is easier to extract functions, since they can refer to the names inside the scope of the function, including each other (as amountFor
call movieFor
) and the corresponding customer
and movie
parameters. But I can't write a simple htmlStatement
function that references these functions. In order to support some other output formats using the same calculations, refactoring should be continued. Now I’ll open the dots when different refactoring options appear depending on how I want to convert the code. Then I will test each of these options, explain how each of them works, and when all four are ready, we will compare them.statement
function. I would like to start this refactoring by using Add Parameter , extract the existing code to format the text, and append the code at the beginning to refer to the extracted function when the parameter indicates this. function statement(customer, movies, format = 'text') { switch (format) { case "text": return textStatement(); } throw new Error(`unknown statement format ${format}`); function textStatement() { let result = `Rental Record for ${customer.name}\n`; for (let r of customer.rentals) { result += `\t${movieFor(r).title}\t${amountFor(r)}\n`; } result += `Amount owed is ${totalAmount()}\n`; result += `You earned ${totalFrequentRenterPoints()} frequent renter points\n`; return result; }
function statement(customer, movies, format = 'text') { switch (format) { case "text": return textStatement(); case "html": return htmlStatement(); } throw new Error(`unknown statement format ${format}`); function htmlStatement() { let result = `<h1>Rental Record for <em>${customer.name}</em></h1>\n`; result += "<table>\n"; for (let r of customer.rentals) { result += ` <tr><td>${movieFor(r).title}</td><td>${amountFor(r)}</td></tr>\n`; } result += "</table>\n"; result += `<p>Amount owed is <em>${totalAmount()}</em></p>\n`; result += `<p>You earned <em>${totalFrequentRenterPoints()}</em> frequent renter points</p>\n`; return result; }
function statement(customer, movies, format = 'text') { const dispatchTable = { "text": textStatement, "html": htmlStatement }; if (undefined === dispatchTable[format]) throw new Error(`unknown statement format ${format}`); return dispatchTable[format].call();
statement
function is that the evaluation functions are nested inside the statement
function. The obvious way out is to move them to the upper context.movieFor
. function topMovieFor(rental, movies) { return movies[rental.movieID]; } function statement(customer, movies) { // [snip] function movieFor(rental) { return topMovieFor(rental, movies); } function frequentRenterPointsFor(rental) { return (movieFor(rental).code === "new" && rental.days > 2) ? 2 : 1; }
function movieFor(rental, movies) { return movies[rental.movieID]; } function statement(customer, movies) { // [snip] function frequentRenterPointsFor(rental) { return (movieFor(rental, movies).code === "new" && rental.days > 2) ? 2 : 1; }
Similar changes are made inside amountFor
movies
. function statement(customer, movies) { let result = `Rental Record for ${customer.name}\n`; for (let r of customer.rentals) { result += `\t${movieFor(r, movies).title}\t${amountFor(r, movies)}\n`; } result += `Amount owed is ${totalAmount(customer, movies)}\n`; result += `You earned ${totalFrequentRenterPoints(customer, movies)} frequent renter points\n`; return result; } function totalFrequentRenterPoints(customer, movies) { return customer.rentals .map((r) => frequentRenterPointsFor(r, movies)) .reduce((a, b) => a + b) ; } function totalAmount(customer, movies) { return customer.rentals .reduce((total, r) => total + amountFor(r, movies), 0); } function movieFor(rental, movies) { return movies[rental.movieID]; } function amountFor(rental, movies) { let result = 0; switch (movieFor(rental, movies).code) { case "regular": result = 2; if (rental.days > 2) { result += (rental.days - 2) * 1.5; } return result; case "new": result = rental.days * 3; return result; case "childrens": result = 1.5; if (rental.days > 3) { result += (rental.days - 3) * 1.5; } return result; } return result; } function frequentRenterPointsFor(rental, movies) { return (movieFor(rental, movies).code === "new" && rental.days > 2) ? 2 : 1; }
htmlStatement
function. function htmlStatement(customer, movies) { let result = `<h1>Rental Record for <em>${customer.name}</em></h1>\n`; result += "<table>\n"; for (let r of customer.rentals) { result += ` <tr><td>${movieFor(r, movies).title}</td><td>${amountFor(r, movies)}</td></tr>\n`; } result += "</table>\n"; result += `<p>Amount owed is <em>${totalAmount(customer, movies)}</em></p>\n`; result += `<p>You earned <em>${totalFrequentRenterPoints(customer, movies)}</em> frequent renter points</p>\n`; return result; }
function htmlStatement(customer, movies) { const amount = () => totalAmount(customer, movies); const frequentRenterPoints = () => totalFrequentRenterPoints(customer, movies); const movie = (aRental) => movieFor(aRental, movies); const rentalAmount = (aRental) => amountFor(aRental, movies); let result = `<h1>Rental Record for <em>${customer.name}</em></h1>\n`; result += "<table>\n"; for (let r of customer.rentals) { result += ` <tr><td>${movie(r).title}</td><td>${rentalAmount(r)}</td></tr>\n`; } result += "</table>\n"; result += `<p>Amount owed is <em>${amount()}</em></p>\n`; result += `<p>You earned <em>${frequentRenterPoints()}</em> frequent renter points</p>\n`; return result; }
function htmlStatement(customer, movies) { let result = `<h1>Rental Record for <em>${customer.name}</em></h1>\n`; result += "<table>\n"; for (let r of customer.rentals) { result += ` <tr><td>${movie(r).title}</td><td>${rentalAmount(r)}</td></tr>\n`; } result += "</table>\n"; result += `<p>Amount owed is <em>${amount()}</em></p>\n`; result += `<p>You earned <em>${frequentRenterPoints()}</em> frequent renter points</p>\n`; return result; function amount() {return totalAmount(customer, movies);} function frequentRenterPoints() {return totalFrequentRenterPoints(customer, movies);} function rentalAmount(aRental) {return amountFor(aRental, movies);} function movie(aRental) {return movieFor(aRental, movies);} }
bind
. I leave it to you for your own research - this is not what I would use here, since the previous options seem to me more suitable.customer
. export default class Customer { constructor(data) { this._data = data; } get name() {return this._data.name;} get rentals() { return this._data.rentals;} }
import Customer from './customer.es6'; function statement(customerArg, movies) { const customer = new Customer(customerArg); let result = `Rental Record for ${customer.name}\n`; for (let r of customer.rentals) { result += `\t${movieFor(r).title}\t${amountFor(r)}\n`; } result += `Amount owed is ${totalAmount()}\n`; result += `You earned ${totalFrequentRenterPoints()} frequent renter points\n`; return result;
rental
. export default class Rental { constructor(data) { this._data = data; } get days() {return this._data.days} get movieID() {return this._data.movieID} }
import Rental from './rental.es6' export default class Customer { constructor(data) { this._data = data; } get name() {return this._data.name;} get rentals() { return this._data.rentals.map(r => new Rental(r));} }
movieFor
. But this function needs a list of movies as a context that needs to be made available for the rental
objects being created. function statement(customerArg, movies) { const customer = new Customer(customerArg, movies); let result = `Rental Record for ${customer.name}\n`; for (let r of customer.rentals) { result += `\t${movieFor(r).title}\t${amountFor(r)}\n`; } result += `Amount owed is ${totalAmount()}\n`; result += `You earned ${totalFrequentRenterPoints()} frequent renter points\n`; return result;
constructor(data, movies) { this._data = data; this._movies = movies } get rentals() { return this._data.rentals.map(r => new Rental(r, this._movies));}
constructor(data, movies) { this._data = data; this._movies = movies; }
function movieFor(rental) { return rental.movie; }
class Rental... get movie() { return this._movies[this.movieID]; }
function statement(customerArg, movies) { const customer = new Customer(customerArg, movies); let result = `Rental Record for ${customer.name}\n`; for (let r of customer.rentals) { result += `\t${r.movie.title}\t${amountFor(r)}\n`; } result += `Amount owed is ${totalAmount()}\n`; result += `You earned ${totalFrequentRenterPoints()} frequent renter points\n`; return result; function amountFor(rental) { let result = 0; switch (rental.movie.code) { case "regular": result = 2; if (rental.days > 2) { result += (rental.days - 2) * 1.5; } return result; case "new": result = rental.days * 3; return result; case "childrens": result = 1.5; if (rental.days > 3) { result += (rental.days - 3) * 1.5; } return result; } return result; } function frequentRenterPointsFor(rental) { return (rental.movie.code === "new" && rental.days > 2) ? 2 : 1; }
rental
. function statement(customerArg, movies) { const customer = new Customer(customerArg, movies); let result = `Rental Record for ${customer.name}\n`; for (let r of customer.rentals) { result += `\t${r.movie.title}\t${r.amount}\n`; } result += `Amount owed is ${totalAmount()}\n`; result += `You earned ${totalFrequentRenterPoints()} frequent renter points\n`; return result; function totalFrequentRenterPoints() { return customer.rentals .map((r) => r.frequentRenterPoints) .reduce((a, b) => a + b) ; } function totalAmount() { return customer.rentals .reduce((total, r) => total + r.amount, 0); }
get frequentRenterPoints() { return (this.movie.code === "new" && this.days > 2) ? 2 : 1; } get amount() { let result = 0; switch (this.movie.code) { case "regular": result = 2; if (this.days > 2) { result += (this.days - 2) * 1.5; } return result; case "new": result = this.days * 3; return result; case "childrens": result = 1.5; if (this.days > 3) { result += (this.days - 3) * 1.5; } return result; } return result; }
customer
. function statement(customerArg, movies) { const customer = new Customer(customerArg, movies); let result = `Rental Record for ${customer.name}\n`; for (let r of customer.rentals) { result += `\t${r.movie.title}\t${r.amount}\n`; } result += `Amount owed is ${customer.amount}\n`; result += `You earned ${customer.frequentRenterPoints} frequent renter points\n`; return result; }
get frequentRenterPoints() { return this.rentals .map((r) => r.frequentRenterPoints) .reduce((a, b) => a + b) ; } get amount() { return this.rentals .reduce((total, r) => total + r.amount, 0); }
rental
customer
, html- statement
. function htmlStatement(customerArg, movies) { const customer = new Customer(customerArg, movies); let result = `<h1>Rental Record for <em>${customer.name}</em></h1>\n`; result += "<table>\n"; for (let r of customer.rentals) { result += ` <tr><td>${r.movie.title}</td><td>${r.amount}</td></tr>\n`; } result += "</table>\n"; result += `<p>Amount owed is <em>${customer.amount}</em></p>\n`; result += `<p>You earned <em>${customer.frequentRenterPoints}</em> frequent renter points</p>\n`; return result; }
function statement(customerArg, movies) { const customer = createCustomer(customerArg, movies); let result = `Rental Record for ${customer.name()}\n`; for (let r of customer.rentals()) { result += `\t${r.movie().title}\t${r.amount()}\n`; } result += `Amount owed is ${customer.amount()}\n`; result += `You earned ${customer.frequentRenterPoints()} frequent renter points\n`; return result; } function createCustomer(data, movies) { return { name: () => data.name, rentals: rentals, amount: amount, frequentRenterPoints: frequentRenterPoints }; function rentals() { return data.rentals.map(r => createRental(r, movies)); } function frequentRenterPoints() { return rentals() .map((r) => r.frequentRenterPoints()) .reduce((a, b) => a + b) ; } function amount() { return rentals() .reduce((total, r) => total + r.amount(), 0); } } function createRental(data, movies) { return { days: () => data.days, movieID: () => data.movieID, movie: movie, amount: amount, frequentRenterPoints: frequentRenterPoints }; function movie() { return movies[data.movieID]; } function amount() { let result = 0; switch (movie().code) { case "regular": result = 2; if (data.days > 2) { result += (data.days - 2) * 1.5; } return result; case "new": result = data.days * 3; return result; case "childrens": result = 1.5; if (data.days > 3) { result += (data.days - 3) * 1.5; } return result; } return result; } function frequentRenterPoints() { return (movie().code === "new" && data.days > 2) ? 2 : 1; }
createCustomer
createRental
) JavaScript () . - . , . , , . , — .statement
call other functions to calculate the data they need. This can be done differently: pass this data to the report printing function in the data structure itself. With this approach, the calculation functions are used to transform the data structure in customer
such a way that it will contain all the data needed by the print function. function statement(customer, movies) { const data = createStatementData(customer, movies); let result = `Rental Record for ${data.name}\n`; for (let r of data.rentals) { result += `\t${movieFor(r).title}\t${amountFor(r)}\n`; } result += `Amount owed is ${totalAmount()}\n`; result += `You earned ${totalFrequentRenterPoints()} frequent renter points\n`; return result; function createStatementData(customer, movies) { let result = Object.assign({}, customer); return result; }
customer
, , Object.assign
. . , .rental
. function createStatementData(customer, movies) { let result = Object.assign({}, customer); result.rentals = customer.rentals.map(r => createRentalData(r)); return result; function createRentalData(rental) { let result = Object.assign({}, rental); return result; } }
createRentalData
createStatementData
, createStatementData
, . function statement(customer, movies) { const data = createStatementData(customer, movies); let result = `Rental Record for ${data.name}\n`; for (let r of data.rentals) { result += `\t${r.title}\t${amountFor(r)}\n`; } result += `Amount owed is ${totalAmount()}\n`; result += `You earned ${totalFrequentRenterPoints()} frequent renter points\n`; return result; //… function createStatementData(customer, movies) { // … function createRentalData(rental) { let result = Object.assign({}, rental); result.title = movieFor(rental).title; return result; } }
function statement(customer, movies) { const data = createStatementData(customer, movies); let result = `Rental Record for ${data.name}\n`; for (let r of data.rentals) { result += `\t${r.title}\t${r.amount}\n`; } result += `Amount owed is ${data.totalAmount}\n`; result += `You earned ${data.totalFrequentRenterPoints} frequent renter points\n`; return result; function createStatementData(customer, movies) { let result = Object.assign({}, customer); result.rentals = customer.rentals.map(r => createRentalData(r)); result.totalAmount = totalAmount(); result.totalFrequentRenterPoints = totalFrequentRenterPoints(); return result; function createRentalData(rental) { let result = Object.assign({}, rental); result.title = movieFor(rental).title; result.amount = amountFor(rental); return result; } }
statement
. createStatementData
. function statement (customer, movies) { // body … function createStatementData (customer, movies) { // body … function createRentalData(rental) { … } function totalFrequentRenterPoints() { … } function totalAmount() { … } function movieFor(rental) { … } function amountFor(rental) { … } function frequentRenterPointsFor(rental) { … } } }
createStatementData
statement
. function statement (customer, movies) { … } function createStatementData (customer, movies) { function createRentalData(rental) { … } function totalFrequentRenterPoints() { … } function totalAmount() { … } function movieFor(rental) { … } function amountFor(rental) { … } function frequentRenterPointsFor(rental) { … } }
statement
, . function htmlStatement(customer, movies) { const data = createStatementData(customer, movies); let result = `<h1>Rental Record for <em>${data.name}</em></h1>\n`; result += "<table>\n"; for (let r of data.rentals) { result += ` <tr><td>${r.title}</td><td>${r.amount}</td></tr>\n`; } result += "</table>\n"; result += `<p>Amount owed is <em>${data.totalAmount}</em></p>\n`; result += `<p>You earned <em>${data.totalFrequentRenterPoints}</em> frequent renter points</p>\n`; return result; }
createStatementData
, () . function htmlStatement(customer, movies) function textStatement(customer, movies) function totalAmount(customer, movies) function totalFrequentRenterPoints(customer, movies) function amountFor(rental, movies) function frequentRenterPointsFor(rental, movies) function movieFor(rental, movies)
→ function statement(customer, movies, format) function htmlStatement() function textStatement() function totalAmount() function totalFrequentRenterPoints() function amountFor(rental) function frequentRenterPointsFor(rental) function movieFor(rental)
→ function textStatement(customer, movies) function htmlStatement(customer, movies) class Customer get amount() get frequentRenterPoints() get rentals() class Rental get amount() get frequentRenterPoints() get movie()
→ function statement(customer, movies) function htmlStatement(customer, movies) function createStatementData(customer, movies) function createRentalData() function totalAmount() function totalFrequentRenterPoints() function amountFor(rental) function frequentRenterPointsFor(rental) function movieFor(rental)
→ top-level-functions
- a large number of repetitive transmission parameters. Each function should be given a data structure with movies, and level functionscustomer
— . , . , , . — . , , , , .parameter-dispatch
does this by capturing the context in the top-level parameter list, which is then available as a general context for all nested functions. This works especially well with the original code, making refactoring from a single function to nested functions easier than in a language that does not have nested functions.parameter-dispatch
begins to wobble when other general behaviors are required from my context, like an HTML response. I had to write something like a dispatcher to determine which function I want to call. Defining a format for a renderer is not very bad, but such a controller logic clearly smells bad. However, I wrote it, it still essentially copies the basic ability of a language to call a named function. And I'm heading for the path that quickly leads me to this nonsense: function executeFunction (name, args) { const dispatchTable = { //...
statement
… const someValue = statement(customer, movieList, 'text');
textStatement
htmlStatement
. .customer
and rental
. I set the context once, when I assert the objects, and then all further logic can use the general context.transform
, , . customer
rental
, transform
createStatementData
createRentalData
. List And Hash . create…Data
, .transform
, .transform
, . , . .transform
, . . - , / .
parameter-dispatch
. One could choose top-level-functions
, but they quickly spoil the impression of themselves when the overall context increases in size. Even with just two arguments, I'd rather have built in functions to achieve other alternatives. It is harder to choose between classes and transformation; both approaches make it possible to make the overall context and the separation explicit. I’m not interested in cockfights, so I’ll just throw a coin and have the winner choose the lot.amount
frequentRenterPoint
. , , , . , .parameter-dispatch
, . , top-level-functions
. Source: https://habr.com/ru/post/320280/
All Articles