重新组织数据
拆分变量
一个变量只承担一种责任
1
2
3
4function distanceTravelled(){
let acc = 0
acc = ''
}1
2
3
4function distanceTravelled(){
let acc = 0
let str = ''
}做法
- 在待分解变量的声明及其第一次被赋值处,修改其名称
- 如果可能的话,把新变量声明为不可修改
- 以该变量的第二次赋值动作为分解,修改此前对该变量的引用,引用新变量
1
2
3
4
5
6
7
8
9
10
11
12
13function distanceTravelled(scenario, time){
let result
let acc = scenario.primaryForce / scenario.mass
let primaryTime = Math.min(time, scenario.delay)
result = 0.5 * acc * primaryTime * primaryTime
let secondTime = time - scenario.delay
if(secondTime > 0){
let primaryVelocity = acc * scenario.delay
acc = (scenario.primaryForce + scenario.secondForce) / scenario.mass
result += primaryVelocity * secondTime + 0.5 * acc * secondTime * secondTime
}
return result
}1
2
3
4
5
6
7
8
9
10
11
12
13function distanceTravelled(scenario, time){
let result
let primaryAcceleration = scenario.primaryForce / scenario.mass
let primaryTime = Math.min(time, scenario.delay)
result = 0.5 * primaryAcceleration * primaryTime * primaryTime
let secondTime = time - scenario.delay
if(secondTime > 0){
let primaryVelocity = primaryAcceleration * scenario.delay
let secondaryAcceleration = (scenario.primaryForce + scenario.secondForce) / scenario.mass
result += primaryVelocity * secondTime + 0.5 * secondaryAcceleration * secondTime * secondTime
}
return result
}字段改名
将不符合语义的字段名改为更加贴切的名字
1
const organization = { name: 'Acme', country: 'GB' }
1
2
3
4
5
6
7class Organization {
constructor(data){
this._title = data.title || data.name
}
get title() {return this._title }
set title(title) {this._title = title}
}做法
- 如果记录未封装,则先封装记录
- 对字段改名,并将引用处同步更新
1
const organization = { name: 'Acme', country: 'GB' }
1
2
3
4
5
6
7
8
9
10
11class Organization {
constructor(data){
this._title = data.title || data.name
this._country = data.country
}
get title() {return this._title }
set title(title) {this._title = title}
get country() {return this._country}
set country(country) {this._country = country}
}
const organization = new Organization({ name: 'Acme', country: 'GB' })以查询取代派生变量
可变对数据是软件中最大对错误源头,对数据对修改常常导致代码各个部分以丑陋的形式互相耦合
1
2
3
4
5
6
7
8
9
10
11class ProductionPlan {
constructor(adjustments){
this._adjustments = adjustments
this._production = this._adjustments.reduce((prev, item) => prev + item.amount, 0)
}
get production(){return this._production}
applyAdjustment(anAdjustment){
this._adjustments.push(anAdjustment)
this._production += anAdjustment.amount
}
}1
2
3
4
5
6
7
8
9class ProductionPlan {
constructor(adjustments){
this._adjustments = adjustments
}
get production(){return this._adjustments.reduce((prev, item) => prev + item.amount, 0)}
applyAdjustment(anAdjustment){
this._adjustments.push(anAdjustment)
}
}做法
- 识别出所有对变量做更新的地方
- 新建一个函数用于计算该变量的值
1
2
3
4
5
6
7
8
9
10
11class ProductionPlan {
constructor(adjustments){
this._adjustments = adjustments
this._production = this._adjustments.reduce((prev, item) => prev + item.amount, 0)
}
get production(){return this._production}
applyAdjustment(anAdjustment){
this._adjustments.push(anAdjustment)
this._production += anAdjustment.amount
}
}1
2
3
4
5
6
7
8
9class ProductionPlan {
constructor(adjustments){
this._adjustments = adjustments
}
get production(){return this._adjustments.reduce((prev, item) => prev + item.amount, 0)}
applyAdjustment(anAdjustment){
this._adjustments.push(anAdjustment)
}
}将引用对象改为值对象(将值对象改成引用对象)
更新一个引用对象的属性值,直接替换整个内部对象
1
2
3
4
5
6class Person {
constructor(){
this._tele = new TelephoneNumber()
}
}
class Tele{...}1
2
3
4
5class Person {
set officeAreaCode(arg){return this._tele = new Tele() }
set officeNumber(arg){return this._tele = new Tele() }
}
class Tele{...}做法
- 检查重构目标是否为不可变的对象,或者是否修改为不可变对象
- 修改对象中的设值函数,创建一个新的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class Person {
constructor(name){
this._name = name
this._telephoneNumber = new TelephoneNumber()
}
get name(){return this._name }
get telephoneNumber(){return this._telephoneNumber.toString() }
get officeAreaCode(){return this._telephoneNumber.areaCode }
set officeAreaCode(arg){return this._telephoneNumber.areaCode = arg }
get officeNumber(){return this._telephoneNumber.number }
set officeNumber(arg){return this._telephoneNumber.number = arg }
}
class TelephoneNumber{
get number(){return this._number}
set number(arg){return this._number = arg}
get areaCode(){return this._areaCode }
set areaCode(arg){return this._areaCode = arg}
toString(){return `(${this._areaCode}) ${this._number}`}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class Person {
constructor(name){
this._name = name
}
get name(){return this._name }
get telephoneNumber(){return this._telephoneNumber.toString() }
get officeAreaCode(){return this._telephoneNumber.areaCode }
set officeAreaCode(arg){return this._telephoneNumber = new TelephoneNumber(arg, this.officeNumber) }
get officeNumber(){return this._telephoneNumber.number }
set officeNumber(arg){return this._telephoneNumber = new TelephoneNumber(this.areaCode, arg) }
}
class TelephoneNumber{
constructor(areaCode, number){
this._areaCode = areaCode
this._number = number
}
get number(){return this._number}
get areaCode(){return this._areaCode }
toString(){return `(${this._areaCode}) ${this._number}`}
}将值对象改成引用对象(将引用对象改为值对象)
过多的副本会难以维护,可以创建一个仓库存储
1
2
3
4
5
6class Order {
constructor(data){
this._customer = new Customer(data.customer)
}
}
class Customer {}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17let _repository = {}
_repository.customer = new Map()
function registerCustomer(id){
if(!_repository.customer.has(id)){
_repository.customer.set(id, new Customer(id))
}
return findCustomer(id)
}
function findCustomer(id){
return _repository.customer.get(id)
}
class Order {
constructor(data){
this._customer = registerCustomer(data.customer)
}
}
class Customer {}做法
- 为相关的对象创建一个仓库
- 确保构造函数有办法找到关联对象的正确实例
- 修改宿主对象的构造函数,令其从仓库中获取关联对象
1
2
3
4
5
6
7
8
9
10
11
12
13class Order {
constructor(data){
this._number = data.number;
this._customer = new Customer(data.customer)
}
get customer(){return this._customer}
}
class Customer {
constructor(id){
this._id = id
}
get id(){return this._id}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28let _repositoryData
function initialize(){
_repositoryData = {}
_repositoryData.customer = new Map()
}
function registerCustomer(id){
if(!_repositoryData.customer.has(id)){
_repositoryData.customer.set(id, new Customer(id))
}
return findCustomer(id)
}
function findCustomer(id){
return _repositoryData.customer.get(id)
}
initialize()
class Order {
constructor(data){
this._number = data.number;
this._customer = registerCustomer(data.customer)
}
get customer(){return this._customer}
}
class Customer {
constructor(id){
this._id = id
}
get id(){return this._id}
}
简化条件逻辑
分解条件表达式
把复杂的条件表达式分解成多个独立的函数
1
2
3
4
5
6
7
8
9function price(){
...
if(aDate.isSummer || aDate.isSpring){
charge = quantity * aPlan.summerRate
}else{
charge = quantity * aPlan.rate
}
...
}1
2
3
4
5
6
7function price(aDate, aPlan){
...
return summerOrSpring() ? summerCharge() : orderCharge()
function summerOrSpring(){...}
function summerCharge(){...}
function orderCharge(){...}
}做法
- 对条件判断和每个条件分支都使用提炼函数
1
2
3
4
5
6
7
8
9
10function price(aDate, aPlan){
let charge
let quantity = 100
if(aDate.isSummer || aDate.isSpring){
charge = quantity * aPlan.summerRate
}else{
charge = quantity * aPlan.rate
}
return charge
}1
2
3
4
5
6
7
8
9
10
11
12
13function price(aDate, aPlan){
let quantity = 100
return summerOrSpring() ? summerCharge() : orderCharge()
function summerOrSpring(){
return aDate.isSummer || aDate.isSpring
}
function summerCharge(){
return quantity * aPlan.summerRate
}
function orderCharge(){
return quantity * aPlan.rate
}
}合并条件表达式
把相同返回的条件合并成一处
1
2
3if(state == 1) return 0
if(start == 2) return 0
return 11
2if(state == 1 || start == 2) return 0
return 1做法
- 确定条件表达式没有副作用
- 使用适当的运算符合并
1
2
3
4if(state == 1) return 0
if(start == 2) return 0
if(end == 3) return 0
return 11
2if(state == 1 || start == 2 || end == 3) return 0
return 1以卫语句取代嵌套条件表达式
多层的嵌套判断会减低可读性,可以使用卫语句进行提前返回
1
2
3
4
5
6
7
8
9
10
11function payAmount(employee){
if(employee.isSeparated){
...
}else{
if(employee.isRetired){
...
}else{
...
}
}
}1
2
3
4
5function payAmount(employee){
if(employee.isSeparated) return
if(employee.isRetired) return
return
}做法
- 选中外层需要被替换的条件逻辑,替换成卫语句
1
2
3
4
5
6
7
8
9
10
11
12
13function payAmount(employee){
let result
if(employee.isSeparated){
result = {amount: 0, reasonCode: 'SEP'}
}else{
if(employee.isRetired){
result = {amount: 0, reasonCode: 'RET'}
}else{
result = {amount: 1000, reasonCode: ''}
}
}
return result
}1
2
3
4
5function payAmount(employee){
if(employee.isSeparated) return {amount: 0, reasonCode: 'SEP'}
if(employee.isRetired) return {amount: 0, reasonCode: 'RET'}
return {amount: 1000, reasonCode: ''}
}以多态取代条件表达式
通过类的多态去改善比较复杂的条件表达式
1
2
3
4
5
6
7
8
9class Bird {
constructor(name, type){
switch (bird.type){
case 'E': this.plumage = 'e'
case 'N': this.plumage = 'n'
default: this.plumage = 'unknown'
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22class Bird {
get plumage(){
return 'unknown'
}
}
class E extends Bird{
get plumage(){
return 'e'
}
}
class N extends Bird{
get plumage(){
return 'n'
}
}
function createBird(...arg){
switch (arg[1]){
case 'E': return new E(...arg);
case 'N': return new N(...arg);
default: return new Bird(...arg);
}
}做法
- 如果现有的类不具备多态行为,就用工厂模式创建
- 在调用方代码中使用工程函数获得对象实例
- 将带有条件逻辑的函数移动到超类中
- 任选一个子类,在其中创建一个函数,使其复写超类中容纳条件表达式的那个函数
- 将于该子类相关的条件表达式分支复制到新函数中
- 处理完后将超类的函数声明为抽象函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31function plumages(birds){
return new Map(birds.map(b => [b.name, b.plumage]))
}
function speeds(birds){
return new Map(birds.map(b => [b.name, airSpeedVelocity(b)]))
}
function plumage(bird){
switch (bird.type){
case 'E': return 'a'
case 'A': return bird.counts > 2 ? 't' : 'a'
case 'N': return bird.voltage > 100 ? 's' : 'b'
default: return 'unknown'
}
}
function airSpeedVelocity(bird){
switch (bird.type){
case 'E': return 35
case 'A': return 40 - bird.counts
case 'N': return bird.voltage / 10 + 10
default: return null
}
}
class Bird {
constructor(name, type, counts, voltage){
this.name = name
this.type = type
this.counts = counts
this.voltage = voltage
this.plumage = plumage(this)
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52function plumages(birds){
return new Map(birds.map(b => [b.name, b.plumage]))
}
function speeds(birds){
return new Map(birds.map(b => [b.name, b.airSpeedVelocity]))
}
class Bird {
constructor(name, type, counts, voltage){
this.name = name
this.type = type
this.counts = counts
this.voltage = voltage
}
get plumage(){
return 'unknown'
}
get airSpeedVelocity(){
return null
}
}
class E extends Bird{
get plumage(){
return 'a'
}
get airSpeedVelocity(){
return 35
}
}
class A extends Bird{
get plumage(){
return this.counts > 2 ? 't' : 'a'
}
get airSpeedVelocity(){
return 40 - this.counts
}
}
class N extends Bird{
get plumage(){
this.voltage > 100 ? 's' : 'b'
}
get airSpeedVelocity(){
return this.voltage / 10 + 10
}
}
function createBird(...arg){
switch (arg[1]){
case 'E': return new E(...arg);
case 'A': return new A(...arg);
case 'N': return new N(...arg);
default: return new Bird(...arg);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47function rating(voyage, history){
const vpf = voyageProfitFactor(voyage, history)
const vr = voyageRisk(voyage)
const chr = captainHistoryRisk(voyage, history)
if(vpf * 3 > (vr + chr * 2)) return 'A'
return 'B'
}
function voyageRisk(voyage){
let result = 1
if(voyage.length > 4) result += 2
if(voyage.length > 8) result += voyage.length - 8
if(['china', 'east-indies'].includes(voyage.zone)) result += 4
return Math.max(result, 0)
}
function captainHistoryRisk(voyage, history){
let result = 1
if(history.length < 5) result += 4
result += history.filter(v => v.profit < 0).length
if(voyage.zone === 'china' && hasChina(history)) result -= 2
return Math.max(result, 0)
}
function hasChina(history) {
return history.some(v => v.zone === 'china')
}
function voyageProfitFactor(voyage, history){
let result = 2
if(voyage.zone === 'china') result += 1
if(voyage.zone === 'east-indies') result += 1
if(voyage.zone === 'china' && hasChina(history)){
result += 3
if(history.length > 10) result += 1
if(voyage.length > 12) result += 1
if(voyage.length > 18) result -= 1
}else{
if(history.length > 8) result += 1
if(voyage.length > 14) result -= 1
}
return result
}
const voyage = { zone: 'west-indies', length: 10 }
const history = [
{ zone: 'east-indies', profit: 5 },
{ zone: 'west-indies', profit: 15 },
{ zone: 'china', profit: -2 },
{ zone: 'west-africa', profit: 7 }
]
console.log(rating(voyage, history))1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73function rating(voyage, history){
return createRating(voyage, history).value
}
function createRating(voyage, history){
if(voyage.zone === 'china' && history.some(v => v.zone === 'china')) return new ExperienceChinaRating(voyage, history)
return new Rating(voyage, history)
}
class Rating {
constructor(voyage, history){
this.voyage = voyage
this.history = history
}
get value(){
const vpf = this.voyageProfitFactor
const vr = this.voyageRisk
const chr = this.captainHistoryRisk
if(vpf * 3 > (vr + chr * 2)) return 'A'
return 'B'
}
get voyageProfitFactor(){
let result = 2
if(this.voyage.zone === 'china') result += 1
if(this.voyage.zone === 'east-indies') result += 1
result += this.historyLengthFactor
result += this.voyageLengthFactor
return result
}
get voyageLengthFactor(){
return this.voyage.length > 14 ? 1 : 0
}
get historyLengthFactor(){
return this.history.length > 8 ? 1 : 0
}
get voyageRisk(){
let result = 1
if(this.voyage.length > 4) result += 2
if(this.voyage.length > 8) result += this.voyage.length - 8
if(['china', 'east-indies'].includes(this.voyage.zone)) result += 4
return Math.max(result, 0)
}
get captainHistoryRisk(){
let result = 1
if(this.history.length < 5) result += 4
result += this.history.filter(v => v.profit < 0).length
return Math.max(result, 0)
}
}
class ExperienceChinaRating extends Rating{
get captainHistoryRisk(){
const result = super.captainHistoryRisk - 2
return result
}
get voyageProfitFactor(){
return super.voyageProfitFactor + 3
}
get voyageLengthFactor(){
let result = 0
if(this.voyage.length > 12) result += 1
if(this.voyage.length > 18) result -= 1
return result
}
get historyLengthFactor(){
return this.history.length > 10 ? 1 : 0
}
}
const voyage = { zone: 'west-indies', length: 10 }
const history = [
{ zone: 'east-indies', profit: 5 },
{ zone: 'west-indies', profit: 15 },
{ zone: 'china', profit: -2 },
{ zone: 'west-africa', profit: 7 }
]
console.log(rating(voyage, history))引入特例
将多个相同的特殊情况取值收拢于一处
1
2
3
4
5
6
7
8
9function customerName(aCustomer){
if(aCustomer.toString() === 'unknown') return 'occupant'
return aCustomer.name
}
class Customer {
toString() {
return this.name || 'unknown'
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function customerName(aCustomer){
return aCustomer.name
}
class Customer {
toString() {
return this.name || 'unknown'
}
}
const customer = enrichCustomer(new Customer())
function enrichCustomer(aCustomer){
const unknownCustomer = {
name: 'occupant',
}
if(aCustomer.toString() === 'unknown') return unknownCustomer
return aCustomer
}做法
- 给重构目标添加检查特例的属性,令其返回false
- 创建一个特例对象,其中只有检查特例的属性,返回true
- 对“与特例值做对比”提炼函数,并确保客户端使用这个新函数
- 将新的特例对象引入代码中,可以从函数调用中返回,也可以在变换函数中生成
- 修改特例对比函数的主题,在其直接使用检查特例的属性
- 使用函数组合成类、函数组合成变换,把通用的特例处理逻辑搬移到新建的特例对象中
- 对特例对比函数使用内联函数,将其内联到仍然需要的地方
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function customerName(aCustomer){
if(aCustomer.toString() === 'unknown') return 'occupant'
return aCustomer.name
}
function customerPlan(aCustomer){
if(aCustomer.toString() === 'unknown') return 'basic plan'
return aCustomer.plan
}
class Customer {
constructor(name, plan) {
this.name = name
this.plan = plan
}
toString() {
return this.name || 'unknown'
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24function customerName(aCustomer){
return aCustomer.name
}
function customerPlan(aCustomer){
return aCustomer.plan
}
class Customer {
constructor(name, plan) {
this.name = name
this.plan = plan
}
toString() {
return this.name || 'unknown'
}
}
const customer = enrichCustomer(new Customer())
function enrichCustomer(aCustomer){
const unknownCustomer = {
name: 'occupant',
plan: 'basic plan'
}
if(aCustomer.toString() === 'unknown') return unknownCustomer
return aCustomer
}引入断言
通过断言告诉阅读者,程序执行到这一点是,对当前状态做了何种假设
做法- 如果你发现代码假设的某个条件始终为真,就可以加入一个断言说明情况
重构API
将查询函数和修改函数分离
区分处有副作用和无副作用的函数
1
2
3
4
5
6
7
8
9
10function setOffAlarms(){//setTimeout}
function alertForMiscreant(people) {
for (const p of people) {
if(p === 'oo'){
setOffAlarms()
return p
}
}
return ''
}1
2
3
4function alertForMiscreant(people) {
if(findMiscreant(people) !== '') setOffAlarms()
}
function findMiscreant(people) {// find }做法
- 复制整个函数,将其作为一个查询命名
- 从新建的查询中去掉所有存在副作用的语句
- 查找调用函数的地方,替换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const people = ['xx', 'yy', 'zz', 'oo', 'zzz', 'cc']
function setOffAlarms(){
setTimeout(() => {
console.log('!!!')
}, 100)
}
function alertForMiscreant(people) {
for (const p of people) {
if(p === 'oo'){
setOffAlarms()
return p
}
if(p === 'cc'){
setOffAlarms()
return p
}
}
return ''
}1
2
3
4
5
6
7
8
9
10
11
12
13function alertForMiscreant(people) {
if(findMiscreant(people) !== '') setOffAlarms()
}
function findMiscreant(people) {
for (const p of people) {
if(p === 'oo' || p === 'cc'){
return p
}
}
return ''
}
const found = findMiscreant(people)
alertForMiscreant(people)函数参数化
如果两个函数逻辑相似,只是字面量值不同,则可以合成一个函数,以参数的形式传入不同的值消除重复
1
2
3
4
5
6function tenPercentRaise(salary){
return salary * 1.1
}
function fivePercentRaise(salary){
return salary * 1.05
}1
2
3
4
5
6
7
8
9function raise(salary, factor=0){
return salary * (factor + 1)
}
function tenPercentRaise(salary){
return raise(salary, 0.1)
}
function fivePercentRaise(salary){
return raise(salary, 0.05)
}做法
- 从一组相似的函数中选择一个
- 把需要作为参数传入的字面量添加到参数列表中
- 修改函数所有的调用处,更新参数传入
- 修改函数体,使其使用新的参数
1
2
3
4
5
6
7
8
9
10
11
12
13function baseCharge(usage){
if(usage < 0) return 0
return topBand(usage) * 0.07 + bottomBand(usage) * 0.03 + middleBand(usage) * 0.05
}
function bottomBand(usage){
return Math.min(usage, 100)
}
function topBand(usage){
return usage > 200 ? usage - 200 : 0
}
function middleBand(usage){
return usage > 100 ? Math.min(usage, 200) - 100 : 0
}1
2
3
4
5
6
7
8
9function baseCharge(usage){
if(usage < 0) return 0
return withinBand(usage, 0, 100) * 0.03
+ withinBand(usage, 100, 200) * 0.05
+ withinBand(usage, 200, Infinity) * 0.07
}
function withinBand(usage, bottom, top){
return usage > bottom ? Math.min(usage, top) - bottom : 0
}移除标记参数
标记参数有时候会让人难以理解那些函数可以调用
1
2
3
4
5
6
7function deliveryDate(anOrder, isRush){
if(isRush){
...
}else{
...
}
}1
2
3
4
5
6function rushDeliveryDate(anOrder){
...
}
function regularDeliveryDate(anOrder){
...
}做法
- 针对参数的每一种可能值新建一个函数
- 对于字面量作为参数的函数调用者,改为调用新建的明确函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function deliveryDate(anOrder, isRush){
if(isRush){
let deliveryTime
if(['MA', 'CT'].includes(anOrder.deliveryState)) deliveryTime = 1
else if(['NY', 'NH'].includes(anOrder.deliveryState)) deliveryTime = 2
else deliveryTime = 3
return deliveryTime
}else{
let deliveryTime
if(['MA', 'CT', 'NY'].includes(anOrder.deliveryState)) deliveryTime = 2
else if(['ME', 'NH'].includes(anOrder.deliveryState)) deliveryTime = 3
else deliveryTime = 4
return deliveryTime
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14function rushDeliveryDate(anOrder){
let deliveryTime
if(['MA', 'CT'].includes(anOrder.deliveryState)) deliveryTime = 1
else if(['NY', 'NH'].includes(anOrder.deliveryState)) deliveryTime = 2
else deliveryTime = 3
return deliveryTime
}
function regularDeliveryDate(anOrder){
let deliveryTime
if(['MA', 'CT', 'NY'].includes(anOrder.deliveryState)) deliveryTime = 2
else if(['ME', 'NH'].includes(anOrder.deliveryState)) deliveryTime = 3
else deliveryTime = 4
return deliveryTime
}保持对象完整
把一个对象内的字段拆成几个变量,再传入函数中,不如保持完整传入函数中去解析
1
2
3const low = dayTempRange.low
const high = dayTempRange.high
function withinRange(low, high){...}1
function withinRange(aNumberRange){...}
做法
- 新建一个空函数,给它期望的参数列表
- 再新函数中调用旧函数,并将新参数映射到旧的参数列表中
- 逐一修改旧函数的调用者,令其使用新函数
- 使用内联函数将旧函数内联到新函数中,给新函数改名后,同时修改所有的引用点
1
2
3
4
5
6
7
8
9const dayTempRange = { low: 10, high: 40 }
const low = dayTempRange.low
const high = dayTempRange.high
if(withinRange(low, high)){
console.log('123')
}
function withinRange(low, high){
return low > 9 && 41 > high
}1
2
3
4
5
6if(withinRange(dayTempRange)){
console.log('123')
}
function withinRange(aNumberRange){
return aNumberRange.low > 9 && 41 > aNumberRange.high
}以查询取代参数
频繁的传递参数会让函数看起来变得复杂
1
2
3
4
5
6class Order {
get finalPrice(){
return this.discountedPrice(basePrice, discount)
}
discountedPrice(price, discount){...}
}1
2
3
4
5
6
7
8class Order {
get finalPrice(){
return this.discountedPrice()
}
get basePrice() {...}
get discount() {...}
discountedPrice(){...}
}做法
- 使用提炼函数将参数的计算过程提炼到一个独立的函数中
- 将函数体内引用该参数的地方改为调用新建的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class Order {
constructor(quantity, price){
this.quantity = quantity;
this.price = price;
}
get finalPrice(){
const basePrice = this.price * this.quantity
let discount
if(this.quantity > 100) discount = 2
else discount = 1
return this.discountedPrice(basePrice, discount)
}
discountedPrice(price, discount){
switch(discount){
case 1: return price * 0.9
case 2: return price * 0.8
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Order {
constructor(quantity, price){
this.quantity = quantity;
this.price = price;
}
get finalPrice(){
return this.discountedPrice()
}
get basePrice() { return this.price * this.quantity }
get discount() { return this.quantity > 100 ? 2 : 1 }
discountedPrice(){
switch(this.discount){
case 1: return this.basePrice * 0.9
case 2: return this.basePrice * 0.8
}
}
}以参数取代查询
当遇到引用复杂,需要调用者弄清参数意义时,需要用参数取代查询
1
2
3
4
5
6
7
8const thermostat = {}
class HeatingPlan {
get targetTemperature(){
if(thermostat.t > this.max) return this.max
else if(thermostat.t < this.min) return this.min
return thermostat.t
}
}1
2
3
4
5
6
7class HeatingPlan {
targetTemperature(t){
if(t > this.max) return this.max
else if(t < this.min) return this.min
return t
}
}做法
- 对执行查询操作对代码提炼变量,将其从函数体中剥离出来
- 现有的函数体不再执行查询操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20const thermostat = {
selectTemperature: 20
}
function setToHeat(){
thermostat.selectTemperature += 10
}
function setToCool(){
thermostat.selectTemperature -= 10
}
class HeatingPlan {
constructor(max, min){
this.max = max;
this.min = min;
}
get targetTemperature(){
if(thermostat.selectTemperature > this.max) return this.max
else if(thermostat.selectTemperature < this.min) return this.min
return thermostat.selectTemperature
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function setToHeat(){
thermostat.selectTemperature += 10
}
function setToCool(){
thermostat.selectTemperature -= 10
}
class HeatingPlan {
constructor(max, min){
this.max = max;
this.min = min;
}
targetTemperature(selectTemperature){
if(selectTemperature > this.max) return this.max
else if(selectTemperature < this.min) return this.min
return selectTemperature
}
}移除设值函数
如果不可变的数据,不暴露修改的方法
1
2
3
4class Person {
get name(){return this._name }
set name(arg){ return this._name = arg }
}1
2
3class Person {
get name(){return this._name }
}做法
- 使用私有字段的实现方式
- 移除设值函数
1
2
3
4
5
6
7class Person {
constructor(name){
this._name = name;
}
get name(){return this._name }
set name(arg){ return this._name = arg }
}1
2
3
4
5
6class Person {
constructor(name){
this._name = name;
}
get name(){return this._name }
}以工厂函数取代构造函数
构造函数在部分普通场合难以适用时,可以将其改为工厂函数
1
2
3
4
5
6class Employee{
constructor(name, typeCode){
this. _name = name
this._typeCode = typeCode
}
}1
2
3function createEngineer(name){
return new Employee(name, 'E')
}做法
- 新建一个工厂函数,让它地道用现有的构造函数
- 把调用构造函数的方法改为调用工厂函数
- 尽量修改构造函数的可见范围
1
2
3
4
5
6
7
8
9
10
11class Employee{
constructor(name, typeCode){
this. _name = name
this._typeCode = typeCode
}
get name(){return this._name }
get type(){return Employee.legalTypeCode[this._typeCode] }
static get legalTypeCode(){
return { 'E': 'Engineer', 'M': 'Manager', 'S': 'Salesman' }
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14class Employee{
constructor(name, typeCode){
this. _name = name
this._typeCode = typeCode
}
get name(){return this._name }
get type(){return Employee.legalTypeCode[this._typeCode] }
static get legalTypeCode(){
return { 'E': 'Engineer', 'M': 'Manager', 'S': 'Salesman' }
}
}
function createEngineer(name){
return new Employee(name, 'E')
}以命令取代函数
当函数里拥有许多复杂操作时,可以改成命令对象模式去处理
1
2
3function score() {
...
}1
2
3
4
5
6
7
8
9
10
11function score() {
return new Score().execute()
}
class Scorer{
execute(){
this.scoreSmoking()
this.stateWithLowCertification()
}
scoreSmoking(){}
stateWithLowCertification(){}
}做法
- 创建一个空类,包含其目标函数
- 给每个参数都创建一个字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function score(candidate, medicalExam, scoringGuide) {
let result = 0
let healthLevel = 0
let highMedicalRiskFlag = false
if(medicalExam.isSmoker){
healthLevel += 10
highMedicalRiskFlag = true
}
let certificationGrade = 'regular'
if(scoringGuide.stateWithLowCertification(candidate.originState)){
certificateGrade = 'low'
result -= 5
}
result -= Math.max(healthLevel - 5, 0)
return result
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32function score(candidate, medicalExam, scoringGuide) {
return new Score(candidate, medicalExam, scoringGuide).execute()
}
class Scorer{
constructor(candidate, medicalExam, scoringGuide){
this._candidate = candidate
this._medicalExam = medicalExam
this._scoringGuide = scoringGuide
}
execute(){
this._result = 0
this._healthLevel = 0
this._highMedicalRiskFlag = false
this.scoreSmoking()
this.stateWithLowCertification()
this._result -= Math.max(this._healthLevel - 5, 0)
return this._result
}
scoreSmoking(){
if(this._medicalExam.isSmoker){
this._healthLevel += 10
this._highMedicalRiskFlag = true
}
}
stateWithLowCertification(){
this._certificationGrade = 'regular'
if(this._scoringGuide.stateWithLowCertification(this._candidate.originState)){
this._certificationGrade = 'low'
this._result -= 5
}
}
}以函数取代命令
使用函数去完成比较简单的任务
1
2
3
4
5
6
7class ChargeCalculator{
get basePrice(){}
get charge(){}
}
function charge(){
return new ChargeCalculator().charge
}1
2
3function charge(){
...
}做法
- 对命令对象在执行阶段用到的函数使用内联函数
- 将构造函数中的参数转移到执行函数
- 对所有的字段在执行函数中找到引用的地方,并改为参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class ChargeCalculator{
constructor(customer, usage, provider){
this.customer = customer
this.usage = usage
this.provider = provider
}
get basePrice(){
return this.customer.baseRate * this.usage
}
get charge(){
return this.basePrice + this.provider.connectionCharge
}
}
function charge(customer, usage, provider){
return new ChargeCalculator(customer, usage, provider).charge
}1
2
3
4function charge(customer, usage, provider){
const basePrice = customer.baseRate * usage
return basePrice + provider.connectionCharge
}
处理继承关系
函数上移
如果某个函数在各个子类的函数体中相同,则将函数上移
1
2
3
4
5
6
7
8class Party {
}
class Employee extends Party {
annualCost(){ ... }
}
class Department extends Party {
annualCost(){ ... }
}1
2
3
4
5
6
7class Party {
annualCost(){ ... }
}
class Employee extends Party {
}
class Department extends Party {
}做法
- 检查待提升函数,确定完全一致
- 检查函数体内引用的所有函数调用和字段都能从超类调用
- 如果待提升签名不同,则都需要修改成超类的字段名
- 在超类中新建一个函数,将某个待提升函数的代码复制到超类中
- 移除待提升的函数
1
2
3
4
5
6
7
8
9
10
11class Party {
constructor(monthlyCost){
this.monthlyCost = monthlyCost
}
}
class Employee extends Party {
get annualCost(){ return this.monthlyCost * 12 }
}
class Department extends Party {
get totalAnnualCost(){ return this.monthlyCost * 12 }
}1
2
3
4
5
6
7
8
9
10class Party {
constructor(monthlyCost){
this.monthlyCost = monthlyCost
}
get annualCost(){ return this.monthlyCost * 12 }
}
class Employee extends Party {
}
class Department extends Party {
}字段上移
如果字段在各个子类中,字段可提升到超类
1
2
3
4
5
6
7
8class Party {
}
class Employee extends Party {
get annualCost(){ ... }
}
class Department extends Party {
get annualCost(){ ... }
}1
2
3
4
5
6
7class Party {
get annualCost(){ ... }
}
class Employee extends Party {
}
class Department extends Party {
}做法
- 针对待提升的字段,检查他们的所有使用点,确定以同样的方式使用
- 如果字段名称不同,则先用变量改名
- 在超类中新增字段
1
2
3
4
5
6
7
8
9
10
11class Party {
constructor(monthlyCost){
this.monthlyCost = monthlyCost
}
}
class Employee extends Party {
get annualCost(){ return this.monthlyCost * 12 }
}
class Department extends Party {
get totalAnnualCost(){ return this.monthlyCost * 12 }
}
1
2
3
4
5
6
7
8
9
10class Party {
constructor(monthlyCost){
this.monthlyCost = monthlyCost
}
get annualCost(){ return this.monthlyCost * 12 }
}
class Employee extends Party {
}
class Department extends Party {
}构造函数本体上移
子类中存在共同实例属性,则可以提升到超类
1
2
3
4
5
6
7
8
9
10
11
12
13
14class Party {
}
class Employee extends Party {
constructor(){
super()
this.name = name
}
}
class Department extends Party {
constructor(){
super()
this.name = name
}
}1
2
3
4
5
6
7
8
9class Party {
constructor(name){
this.name = name
}
}
class Employee extends Party {
}
class Department extends Party {
}做法
- 如果超类不存在构造函数,直接定义一个。确保子类调用超类到构造函数
- 使用移动语句将子类中构造函数中的公共语句,移动到超类到构造函数调用的语句
- 逐一移除子类间的公共代码,将其提升到超类的构造函数中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Party {
}
class Employee extends Party {
constructor(name, id, monthlyCost){
super()
this.name = name
this.id = id
this.monthlyCost = monthlyCost
}
}
class Department extends Party {
constructor(name, staff){
super()
this.name = name
this.staff = staff
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Party {
constructor(name){
this.name = name
}
}
class Employee extends Party {
constructor(name, id, monthlyCost){
super(name)
this.id = id
this.monthlyCost = monthlyCost
}
}
class Department extends Party {
constructor(name, staff){
super(name)
this.staff = staff
}
}函数下移
如果某个函数只一个或者几个子类调用,则将函数下移到子类中
1
2
3
4
5class Party {
annualCost(){...}
}
class Employee extends Party {}
class Department extends Party {}1
2
3
4
5class Party {}
class Employee extends Party {
annualCost(){...}
}
class Department extends Party {}做法
- 将超类的函数移动到目标子类中
1
2
3
4
5
6
7
8
9
10
11
12
13class Party {
constructor(monthlyCost){
this.monthlyCost = monthlyCost
}
annualCost(){ return this.monthlyCost * 12 }
}
class Employee extends Party {
get cost(){
return this.annualCost() * 10
}
}
class Department extends Party {
}1
2
3
4
5
6
7
8
9
10
11
12
13class Party {
constructor(monthlyCost){
this.monthlyCost = monthlyCost
}
}
class Employee extends Party {
annualCost(){ return this.monthlyCost * 12 }
get cost(){
return this.annualCost() * 10
}
}
class Department extends Party {
}字段下移
如果某个字段只被一个子类用到,就将其搬移到需要该字段到子类中
1
2
3
4
5class Party {
get annualCost(){...}
}
class Employee extends Party {}
class Department extends Party {}1
2
3
4
5class Party {}
class Employee extends Party {
get annualCost(){...}
}
class Department extends Party {}做法
- 将超类中到字段移动到目标子类
1
2
3
4
5
6
7
8
9
10
11
12
13class Party {
constructor(monthlyCost){
this.monthlyCost = monthlyCost
}
get annualCost(){ return this.monthlyCost * 12 }
}
class Employee extends Party {
get cost(){
return this.annualCost() * 10
}
}
class Department extends Party {
}1
2
3
4
5
6
7
8
9
10
11
12
13class Party {
constructor(monthlyCost){
this.monthlyCost = monthlyCost
}
get annualCost(){ return this.monthlyCost * 12 }
}
class Employee extends Party {
get cost(){
return this.annualCost * 10
}
}
class Department extends Party {
}以子类取代类型码
用多种子类替代类型码判断
1
2
3class Employee {
validateType(type){...}
}1
2
3
4
5
6
7
8
9
10
11
12
13class Employee {}
class Engineer extends Employee{
get type(){}
}
class Salesman extends Employee{
get type(){}
}
function createEmployee(name, type){
switch(type){
case 'engineer': return new Engineer(name)
case 'salesman': return new Salesman(name)
}
}做法
- 封装类型码字段
- 任选一个类型码取值,创建一个子类,复写类型码类的取值函数,令其返回类型码的字面量
- 创建一个选择器的逻辑,把类型码参数映射到新的子类
- 正对每个类型码都重复创建子类
1
2
3
4
5
6
7
8
9
10
11
12
13class Employee {
constructor(name, type){
this.validateType(type)
this._name = name
this._type = type
}
validateType(type){
if(!['engineer', 'salesman', 'manager'].includes(type)){
throw new Error('Invalid type')
}
}
toString() { return `${this._name} (${this._type})`}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class Employee {
constructor(name){
this._name = name
}
toString() { return `${this._name} (${this.type})`}
}
class Engineer extends Employee{
get type(){ return 'engineer' }
}
class Salesman extends Employee{
get type(){ return 'salesman' }
}
class Manager extends Employee{
get type(){ return 'manager' }
}
function createEmployee(name, type){
switch(type){
case 'engineer': return new Engineer(name)
case 'salesman': return new Salesman(name)
case 'manager': return new Manager(name)
default: throw new Error('Invalid type')
}
}移除子类
如果子类的用处太小,则可以移除子类,替换成超类的一个字段
1
2
3
4
5
6
7
8
9
10class Person{
get genderCode(){}
}
class Male extends Person{
get genderCode(){}
}
class Female extends Person{
get genderCode(){}
}
function isMale(aPerson){ return aPerson instanceof Male}1
2
3
4
5
6class Person{
constructor(){
this.genderCode = genderCode || 'X'
}
get isMale(){ ... }
}做法
- 把子类的构造函数包装到超类的工厂中
- 新建一个字段用于代表子类的类型
- 将原来针对子类的判断函数修改未使用新建类字段
1
2
3
4
5
6
7
8
9
10
11
12
13class Person{
constructor(name){
this.name = name
}
get genderCode(){ return 'X' }
}
class Male extends Person{
get genderCode(){ return 'X' }
}
class Female extends Person{
get genderCode(){ return 'F' }
}
function isMale(aPerson){ return aPerson instanceof Male}1
2
3
4
5
6
7class Person{
constructor(name, genderCode){
this.name = name
this.genderCode = genderCode || 'X'
}
get isMale(){ return this.genderCode === 'X' }
}提炼超类
如果两个类在做相似的事情,可以继承机制把他们的相似之处提炼到超类中
1
2class Employee {}
class Department {}1
2
3
4
5
6
7class Party{
constructor(name){
this.name = name
}
}
class Employee extends Party{}
class Department extends Party{}做法
- 为原本的类新建一个超类
- 把共同元素梳理出来移动到超类中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class Employee {
constructor(name, id, monthlyCost){
this.name = name
this.id = id
this.monthlyCost = monthlyCost
}
get annualCost(){ return this.monthlyCost * 12 }
}
class Department {
constructor(name, staff){
this.name = name
this.staff = staff
}
get totalMonthlyCost(){
return this.staff.map(e => e.monthlyCost).reduce((prev, cur) => prev + cur, 0)
}
get totalAnnualCost(){
return this.totalMonthlyCost * 12
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22class Party{
constructor(name){
this.name = name
}
get annualCost(){ return this.monthlyCost * 12 }
}
class Employee extends Party{
constructor(name, id, monthlyCost){
super(name)
this.id = id
this.monthlyCost = monthlyCost
}
}
class Department extends Party{
constructor(name, staff){
super(name)
this.staff = staff
}
get monthlyCost(){
return this.staff.map(e => e.monthlyCost).reduce((prev, cur) => prev + cur, 0)
}
}折叠继承体系
当子类与父类没多大差别时,直接折叠子类与超类
做法
- 选择要移除的类
- 将其移动到目标类中
以委托取代子类
继承存在局限性,应该用组合与继承相结合的方式去改造
1
2class Booking{}
class PremiumBooking extends Booking{}1
2
3
4
5
6
7
8
9class Booking{
_bePremium(){
this._premium = new PremiumBookingDelegate(this, extra)
}
}
class PremiumBookingDelegate{}
function createPremiumBooking(extra){
booking._bePremium(extra)
}做法
- 如果构造函数由多个调用者,先用工厂函数包裹起来
- 创建一个空的委托累,这个类的构造函数应该接受所有子类特有的数据项,并以参数的形式接受一个指回超类的引用
- 在超类中添加一个字段,用于安放委托对象
- 修改子类的创建逻辑,使其初始化上述的委托字段,放入一个委托对象实例
- 选择一个子类的函数,使用搬移函数移动函数放入委托类
- 如果被搬移函数还在子类之外被调用,就把留在源类中的委托代码从子类移到超类,并委托代码前加上卫语句,检查委托对象存在。
- 如果没有,则直接移除代码,重复上述过程,直到所有函数都移动到委托类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29class Booking{
constructor(show, date){
this.show = show
this.date = date
}
get hasTalkBack(){
return this.show.talkBack && !this.isPeakDay
}
get basePrice(){
let result = this.show.price
if(this.isPeakDay) result *= 1.15
return result
}
}
class PremiumBooking extends Booking{
constructor(show, date, extra){
super(show, date)
this.extra = extra
}
get hasTalkBack(){
return this.show.talkBack
}
get basePrice(){
return super.basePrice * this.extra.fee
}
get hasDinner(){
return this.extra.dinner && !this.isPeakDay
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43class Booking{
constructor(show, date){
this.show = show
this.date = date
}
get hasTalkBack(){
return this._premiumDelegate ? this._premiumDelegate.hasTalkBack : this.show.talkBack && !this.isPeakDay
}
get basePrice(){
let result = this.show.price
if(this.isPeakDay) result *= 1.15
return this._premiumDelegate ? this._premiumDelegate.extendBasePrice(result) : result
}
get hasDinner(){
return this._premiumDelegate ? this._premiumDelegate.hasDinner : false
}
_bePremium(extra){
this._premiumDelegate = new PremiumBookingDelegate(this, extra)
}
}
class PremiumBookingDelegate{
constructor(hostBooking, extra){
this.host = hostBooking
this.extra = extra
}
get hasTalkBack(){
return this.host.show.talkBack
}
get hasDinner(){
return this.extra.dinner && !this.host.isPeakDay
}
extendBasePrice(base){
return base * this.extra.fee
}
}
function createBooking(show, date){
return new Booking(show, date)
}
function createPremiumBooking(show, date, extra){
let result = new Booking(show, date)
result._bePremium(extra)
return result
}以委托取代超类
优先使用继承,当继承有问题,使用委托取代超类
1
2class CatalogItem{...}
class Scroll extends CatalogItem{...}1
2
3
4
5
6class CatalogItem{...}
class Scroll{
constructor(catalogId, catalog){
this.catalogItem = catalog.get(catalogId) // new CatalogItem()
}
}做法
- 在子类中新建一个字段,使其引用超类的一个对象,并将这个委托引用初始化为超类的实例
- 针对超类每个函数,在子类中创建一个转发函数,将请求转发给委托引用
- 当所有的超类函数都被转发函数覆盖后,去除继承关系
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21class CatalogItem{
constructor(id, title, tags){
this.id = id
this.title = title
this.tags = tags
}
hasTag(arg){ return this.tags.includes(arg) }
}
class Scroll extends CatalogItem{
constructor(id, title, tags, dateLastCleaned){
super(id, title, tags)
this.lastCleaned = dateLastCleaned
}
needsCleaning(targetDate){
const threshold = this.hasTag('revered') ? 700 : 1500
return this.daysSinceLastCleaning(targetDate) > threshold
}
daysSinceLastCleaning(targetDate){
return this.lastCleaned.until(targetDate)
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26class CatalogItem{
constructor(id, title, tags){
this.id = id
this.title = title
this.tags = tags
}
hasTag(arg){ return this.tags.includes(arg) }
}
class Scroll{
constructor(id, dateLastCleaned, catalogId, catalog){
this.id = id
this.catalogItem = catalog.get(catalogId)
this.lastCleaned = dateLastCleaned
}
get id(){return this.catalogItem.id}
get title(){return this.catalogItem.title}
get tags(){return this.catalogItem.tags}
hasTag(arg){ return this.catalogItem.hasTag(arg) }
needsCleaning(targetDate){
const threshold = this.hasTag('revered') ? 700 : 1500
return this.daysSinceLastCleaning(targetDate) > threshold
}
daysSinceLastCleaning(targetDate){
return this.lastCleaned.until(targetDate)
}
}