重构:改善既有代码的设计-笔记(二)


theme: github

重构:改善既有代码的设计-购书链接

封装

  1. 封装记录

    用数据类取代记录型结构

    1
    const organization = { name:'Acme', country: "GB" }
    1
    2
    3
    4
    5
    6
    7
    8
    class Organization {
    constructor(data){
    this._name = data.name
    }
    get name(){ return this._name }
    set name(name){ this._name = name }
    }
    const organization = new Organization({...})

    做法

    1. 对持有记录的变量使用封装变量,将其封装在一个函数中
    2. 创建一个类,将记录包装起来,并将记录变量的只替换成该类的一个实例,然后在类上定义访问和修改的函数
    3. 新建一个函数,让它返回该类的对象,而非原始记录
    4. 替换项目中的使用点
    1
    2
    3
    4
    5
    6
    7
    const organization = { name:'Acme', country: "GB" }
    let result = ''
    result += `<h1>${organization.name} : ${organization.country}</h1>\n`
    organization.name = '121'
    organization.country = '121'
    result += `<h1>${organization.name} : ${organization.country}</h1>`
    console.log(result)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Organization {
    constructor(data){
    this._name = data.name
    this._country = data.country
    }
    get name(){ return this._name }
    get country(){ return this._country }
    set name(name){ this._name = name }
    set country(country){ this._country = country }
    }
    const organization = new Organization({ name:'Acme', country: "GB" })
    let result = ''
    result += `<h1>${organization.name} : ${organization.country}</h1>\n`
    organization.name = '121'
    organization.country = '121'
    result += `<h1>${organization.name} : ${organization.country}</h1>`
    console.log(result)
  2. 封装集合

    对可变数据进行封装,不返回源数据

    1
    2
    3
    4
    5
    6
    7
    class Person {
    get courses() { return this._courses }
    set courses(courses) { this._courses = courses }
    }
    class Course {
    ...
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Person {
    get courses() { return this._courses.slice() }
    set courses(courses) { this._courses = courses.slice() }
    addCourse(aCourse) { ... }
    removeCourse(aCourse) { ... }
    }
    class Course {
    ...
    }

    做法

    1. 如果集合对引用尚未封装,则先用封装变量封装
    2. 在类上增加用于增删对函数
    3. 查找集合的引用点,修改后测试
    4. 每次只返回一份只读的副本
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class Person {
    constructor(name){
    this._name = name
    this._courses = []
    }
    get name(){return this._name }
    get courses() { return this._courses }
    set courses(courses) { this._courses = courses }
    }
    class Course {
    constructor(name, isAdvanced){
    this._name = name
    this._isAdvanced = isAdvanced
    }
    get name(){return this._name }
    get isAdvanced(){return this._isAdvanced }
    }
    const CoursesName = [{ name:'Math', isAdvanced: true }, { name:'Chinese', isAdvanced: true }, { name:'English', isAdvanced: false }]
    const p = new Person('xy')
    p.courses = CoursesName.map(({ name, isAdvanced }) => new Course(name, isAdvanced))
    p.courses.push(new Course('history', true)) // 无法监测的行为!
    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
    class Person {
    constructor(name){
    this._name = name
    this._courses = []
    }
    get name(){return this._name }
    get courses() { return this._courses.slice() }
    set courses(courses) { this._courses = courses.slice() }
    addCourse(aCourse) { this._courses.push(aCourse) }
    removeCourse(aCourse){
    const index = this._courses.indexOf(aCourse)
    if(index === -1) throw new RangeError("not in")
    this._courses.splice(index, 1)
    }
    }
    class Course {
    constructor(name, isAdvanced){
    this._name = name
    this._isAdvanced = isAdvanced
    }
    get name(){return this._name }
    get isAdvanced(){return this._isAdvanced }
    }
    const CoursesName = [{ name:'Math', isAdvanced: true }, { name:'Chinese', isAdvanced: true }, { name:'English', isAdvanced: false }]
    const history = new Course('history', true)
    const p = new Person('xy')
    p.courses = CoursesName.map(({ name, isAdvanced }) => new Course(name, isAdvanced))
    p.addCourse(history)
    p.removeCourse(history)
  3. 以对象取代基本类型

    当基本数据类型不满足业务需求时,将其封装成类方便管理

    1
    2
    3
    4
    class Order{
    get priority(){return this._priority }
    }
    const priorities = ['low', 'normal', 'high', 'rush']
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Order{
    constructor(data){
    this._priority = new Priority()
    }
    }
    class Priority{
    toString(){return this._value}
    static legalValues(){return ['low', 'normal', 'high', 'rush']}
    }
    const priorities = Priority.legalValues()

    做法

    1. 如果变量未被封装起来,则先封装变量
    2. 为数据值创建一个简单的类,保存数据并提供取值函数
    3. 修改第一步得到的设值函数,令其创建一个新类的对象将其存入字段
    4. 修改取值函数,令其调用新类的取值函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Order{
    constructor(data){
    this._priority = data.priority
    }
    get priority(){return this._priority }
    }
    const priorities = ['low', 'normal', 'high', 'rush']
    let orders = new Array(10).fill(0).map((item, index) => new Order({ priority: priorities[index % 4] }))
    console.log(orders.filter(item => item.priority === 'high' || item.priority === 'rush'))
    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
    class Order{
    constructor(data){
    this._priority = new Priority(data.priority)
    }
    get priorityString(){return this._priority.toString() }
    get priority(){return this._priority}
    set priority(aString){this._priority = new Priority(aString)}
    }
    class Priority{
    constructor(value){
    if(value instanceof Priority) return value
    if(Priority.legalValues().includes(value)){
    this._value = value
    }else{
    throw new Error('invalid for Priority')
    }
    }
    toString(){return this._value}
    static legalValues(){return ['low', 'normal', 'high', 'rush']}
    get _index(){ return Priority.legalValues().findIndex(item => item === this._value) }

    equals(order) { return this._index === order._index }
    higherThan(order) { return this._index > order._index }
    lessThan(order) { return this._index < order._index }
    }
    const priorities = Priority.legalValues()
    const normal = new Priority('normal')
    let orders = new Array(10).fill(0).map((item, index) => new Order({ priority: priorities[index % 4] }))
    console.log(orders.filter(item => item.priority.higherThan(normal)))
  4. 以查询取代临时变量

    将临时变量中的计算逻辑移动到类/函数中,通过查询获取

    1
    2
    3
    4
    5
    6
    7
    class Order {
    get price() {
    let basePrice = ...
    let discountFactor = ...
    return basePrice * discountFactor
    }
    }
    1
    2
    3
    4
    5
    class Order {
    get price() {...}
    get basePrice() { ... }
    get discountFactor() {...}
    }

    做法

    1. 检查变量在是否完全计算完毕,是否存在副作用
    2. 如果变量不是只读的,可以先改造成只读变量
    3. 将变量赋值代码提炼成设值函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Order {
    constructor(quantity, item){
    this._quantity = quantity
    this._item = item
    }
    get price() {
    let basePrice = this._quantity * this._item.price
    let discountFactor = 0.98
    if(basePrice > 1000) discountFactor -= 0.03
    return basePrice * discountFactor
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Order {
    constructor(quantity, item){
    this._quantity = quantity
    this._item = item
    }
    get price() {
    return this.basePrice * this.discountFactor
    }
    get basePrice() { return this._quantity * this._item.price }
    get discountFactor() {
    let discountFactor = 0.98
    if(this.basePrice > 1000) discountFactor -= 0.03
    return discountFactor
    }
    }
  5. 提炼类(内联类)

    将较大的类中的模块分离出去成为独立的类

    1
    2
    3
    class Person {
    ...
    }
    1
    2
    3
    4
    5
    6
    7
    8
    class Person {
    constructor(name){
    this._telephoneNumber = new TelephoneNumber()
    }
    }
    class TelephoneNumber{
    ...
    }

    做法

    1. 决定如何分解类所负的责任
    2. 创建一个新的类,用以表现从旧类中分离出来的责任
    3. 构造旧类时创建一个新类的实例,建立联系
    4. 搬移字段,函数,去掉不再需要的函数接口
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Person {
    constructor(name){
    this._name = name
    }
    get name(){return this._name }
    get telephoneNumber(){return `(${this.officeAreaCode}) ${this.officeNumber}` }
    get officeAreaCode(){return this._officeAreaCode }
    set officeAreaCode(arg){return this._officeAreaCode = arg }
    get officeNumber(){return this._officeNumber }
    set officeNumber(arg){return this._officeNumber = arg }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class 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}`}
    }
  6. 内联类(提炼类)

    当一个类不再承担足够的责任时,将其直接内联到引用其的类中

    1
    2
    3
    4
    5
    6
    7
    8
    class Shipment {
    constructor(){
    this._trackingInformation = new TrackingInformation(company, number)
    }
    }
    class TrackingInformation {
    ...
    }
    1
    2
    3
    4
    5
    6
    class Shipment {
    constructor(){
    ...
    }
    ... //TrackingInformation
    }

    做法

    1. 对于内联类中的所有函数,在目标类中创建对应的函数
    2. 修改源类函数所有的引用点,令其调用目标类对应的委托方法
    3. 将所有的函数数据移动到目标类中
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class TrackingInformation {
    constructor(company, number){
    this._shippingCompany = company
    this._trackingNumber = number
    }
    get shippingCompany(){return this._shippingCompany}
    set shippingCompany(arg) {this._shippingCompany = arg}
    get trackingNumber(){return this._trackingNumber}
    set trackingNumber(arg) {this._trackingNumber = arg}
    get display(){ return `${this.shippingCompany} ${this.trackingNumber}` }
    }
    class Shipment {
    constructor(name, company, number){
    this._name = name
    this._trackingInformation = new TrackingInformation(company, number)
    }
    get trackingInfo(){return this._trackingInformation.display}
    get trackingInformation(){return this._trackingInformation}
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Shipment {
    constructor(name, company, number){
    this._name = name
    this._shippingCompany = company
    this._trackingNumber = number
    }
    get shippingCompany(){return this._shippingCompany}
    set shippingCompany(arg) {this._shippingCompany = arg}
    get trackingNumber(){return this._trackingNumber}
    set trackingNumber(arg) {this._trackingNumber = arg}
    get trackingInfo(){ return `${this.shippingCompany} ${this.trackingNumber}` }
    get trackingInformation(){return { _shippingCompany: this.shippingCompany, _trackingNumber: this._trackingNumber}}
    }
  7. 隐藏委托关系(移除中间人)

    隐藏跨级引用的情况,增加快速访问的接口

    1
    2
    3
    4
    5
    6
    class Person {
    get department(){return this._department }
    }
    class Department {
    get chargeCode(){return this._chargeCode }
    }
    1
    2
    3
    4
    5
    6
    class Person {
    get manager(){return this._department.manager }
    }
    class Department {
    get manager(){return this._manager}
    }

    做法

    1. 对于每个委托关系的函数,在服务对象端建立一个简单的委托函数
    2. 调整客户端,令其调用服务对象提供的函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Person {
    constructor(name){
    this._name = name;
    }
    get name(){return this._name }
    get department(){return this._department }
    set department(arg){this._department = arg}
    }
    class Department {
    get chargeCode(){return this._chargeCode}
    set chargeCode(arg){this._chargeCode = arg}
    get manager(){return this._manager}
    set manager(arg){this._manager = arg}
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Person {
    constructor(name){
    this._name = name;
    }
    get name(){return this._name }
    set department(arg){this._department = arg}
    get manager(){return this._department.manager }
    }
    class Department {
    get chargeCode(){return this._chargeCode}
    set chargeCode(arg){this._chargeCode = arg}
    get manager(){return this._manager}
    set manager(arg){this._manager = arg}
    }
  8. 移除中间人(隐藏委托关系)

    类中存在大量的委托函数,服务类变成类一个中间人

    1
    2
    3
    4
    5
    6
    class Person {
    get manager(){return this._department.manager }
    }
    class Department {
    get manager(){return this._manager}
    }
    1
    2
    3
    4
    5
    6
    class Person {
    get department(){return this._department }
    }
    class Department {
    get chargeCode(){return this._chargeCode }
    }

    做法

    1. 为受托对象创建一个取值函数
    2. 对于每个委托函数,是客户端转为连续的调用访问
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Person {
    constructor(name){
    this._name = name;
    }
    get name(){return this._name }
    set department(arg){this._department = arg}
    get manager(){return this._department.manager }
    get chargeCode(){return this._department.chargeCode}
    }
    class Department {
    get chargeCode(){return this._chargeCode}
    set chargeCode(arg){this._chargeCode = arg}
    get manager(){return this._manager}
    set manager(arg){this._manager = arg}
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Person {
    constructor(name){
    this._name = name;
    }
    get name(){return this._name }
    get department(){return this._department }
    set department(arg){this._department = arg}
    }
    class Department {
    get chargeCode(){return this._chargeCode}
    set chargeCode(arg){this._chargeCode = arg}
    get manager(){return this._manager}
    set manager(arg){this._manager = arg}
    }
  9. 替换算法

    使用更清晰的方式替换复杂的方式

    1
    2
    3
    4
    5
    6
    function findPerson(people){
    for(let i = 0; i < people.length; i++){
    if(people[i] === 'Don') return 'Don'
    }
    return ''
    }
    1
    2
    3
    function findPerson(people) {
    return people.find(p => ['Don'].includes(p)) || ''
    }

    做法

    1. 整理代替换的算法,确保它以及被抽取到一个独立的函数中
    2. 为函数准备测试,固定其行为
    3. 准备好另一个算法进行替换
    1
    2
    3
    4
    5
    6
    7
    8
    function findPerson(people){
    for(let i = 0; i < people.length; i++){
    if(people[i] === 'Don') return 'Don'
    if(people[i] === 'Join') return 'Join'
    if(people[i] === 'Kent') return 'Kent'
    }
    return ''
    }
    1
    2
    3
    4
    function findPerson(people) {
    const candidates = ['Don', 'Join', 'Kent']
    return people.find(p => candidates.includes(p)) || ''
    }

封装.png

搬移特性

  1. 搬移函数

    合理的把函数放置在它应该出现的位置

    1
    2
    3
    4
    function trackSummary(points){
    ...
    function distance(p1, p2){...}
    }
    1
    2
    function trackSummary(points){ ... }
    function distance(p1, p2){...}

    做法

    1. 检查函数在当前上下文引用的元素,考虑是否将他们一起搬移
    2. 检查待搬移函数是否具有多态性
    3. 将函数复制一份到目标的上下文中,调整函数
    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
    function trackSummary(points){
    const totalTime = calculateTime()
    const totalDistance = calculateDistance()
    const pace = totalTime / 60 / totalDistance
    return {
    time: totalTime,
    distance: totalDistance,
    pace
    }
    function calculateDistance(){
    let result = 0
    for(let i = 1; i < points.length; i++){
    result += distance(points[i - 1], points[i])
    }
    return result
    }
    function distance(p1, p2){
    const EARTH_RADIUS = 3959
    const dLat = radians(p2.lat) - radians(p1.lat)
    const dLon = radians(p2.lon) - radians(p1.lon)
    const a = Math.pow(Math.sin(dLat / 2), 2) + Math.cos(radians(p2.lat)) * Math.cos(radians(p1.lat)) * Math.pow(Math.sin(dLon / 2), 2)
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
    return EARTH_RADIUS * c
    }
    function radians(degrees){
    return degrees * Math.PI / 180
    }
    function calculateTime(){
    return 6
    }
    }
    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
    function trackSummary(points){
    const totalTime = calculateTime()
    const distance = totalDistance(points)
    const pace = totalTime / 60 / distance
    return {
    time: totalTime,
    distance,
    pace
    }
    function calculateTime(){
    return 6
    }
    }
    function totalDistance(points){
    let result = 0
    for(let i = 1; i < points.length; i++){
    result += distance(points[i - 1], points[i])
    }
    return result
    }
    function distance(p1, p2){
    const EARTH_RADIUS = 3959
    const dLat = radians(p2.lat) - radians(p1.lat)
    const dLon = radians(p2.lon) - radians(p1.lon)
    const a = Math.pow(Math.sin(dLat / 2), 2) + Math.cos(radians(p2.lat)) * Math.cos(radians(p1.lat)) * Math.pow(Math.sin(dLon / 2), 2)
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
    return EARTH_RADIUS * c
    }
    function radians(degrees){
    return degrees * Math.PI / 180
    }
  2. 搬移字段

    如果更新一个字段,需要同时在几个结构中进行修改,则表明该字段需要被搬移到一个集中的地点

    1
    2
    3
    4
    5
    6
    7
    class Customer {
    constructor(name, discountRate){
    this._discountRate = discountRate
    this._contact = new Contact()
    }
    }
    class Contact { ... }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Customer {
    constructor(name, discountRate){
    this._contact = new Contact(discountRate)
    }
    }
    class Contact {
    constructor(discountRate){
    this._discountRate = discountRate
    }
    }

    做法

    1. 确保源字段已经得到良好封装
    2. 在目标对象上创建一个字段
    3. 确保源对象能正常引用目标对象
    4. 调整源对象的访问函数,令其使用目标对象的字段
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class Customer {
    constructor(name, discountRate){
    this._name = name
    this._discountRate = discountRate
    this._contact = new Contact()
    }
    get discountRate(){return this._discountRate}
    becomePreferred(){
    return this._discountRate += 0.03
    }
    applyDiscount(amount){
    return amount - amount * this.discountRate
    }
    }
    class Contact {
    constructor(){
    this._startDate = new Date()
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    class Customer {
    constructor(name, discountRate){
    this._name = name
    this._contact = new Contact(discountRate)
    }
    get discountRate(){return this._contact.discountRate}
    _setDiscountRate(aNumber){return this._contact.discountRate = aNumber}
    becomePreferred(){
    return this._setDiscountRate(this._contact.discountRate += 0.03)
    }
    applyDiscount(amount){
    return amount - amount * this.discountRate
    }
    }
    class Contact {
    constructor(discountRate){
    this._startDate = new Date()
    this._discountRate = discountRate
    }
    get discountRate(){return this._discountRate}
    set discountRate(arg){this._discountRate = arg}
    }
  3. 搬移语句到函数(搬移语句到调用者)

    某些语句与一个函数放在一起更加像一个整体的时候,就可以将其搬移到函数中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function renderPerson(person){
    [`<p>title: ${aPhoto.title}</p>`, ...emitPhotoData(person.photo)]
    }
    function emitPhotoData(aPhoto){
    return [`<p>date: ${aPhoto.date}</p>`]
    }
    function renderPhoto(aPhoto){
    return [`<p>title: ${aPhoto.title}</p>`,
    `<p>date: ${aPhoto.date}</p>`].join('/n')
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    function renderPerson(person){
    [...emitPhotoData(person.photo)]
    }
    function emitPhotoData(aPhoto){
    return [`<p>title: ${aPhoto.title}</p>`, `<p>date: ${aPhoto.date}</p>`]
    }
    function renderPhoto(aPhoto){
    return [...emitPhotoData(person.photo)].join('/n')
    }

    做法

    1. 如果重复的代码距离目标函数有些距离,先用移动语句
    2. 如果目标函数仅被唯一一个源函数调用,则只需要将源函数中重复的代码段剪切复制到函数中
    3. 如果由多个调用点,则先选择对一个调用点应用提炼函数,将待搬移语句与目标函数提炼成一个新函数
    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
    function renderPerson(person){
    const result = []
    result.push(`<p>${person.name}</p>`)
    result.push(renderPhoto(person.photo))
    result.push(`<p>title: ${person.photo.title}</p>`)
    result.push(emitPhotoData(person.photo))
    return result.join('\n')
    }
    function emitPhotoData(aPhoto){
    const result = []
    result.push(`<p>location: ${aPhoto.location}</p>`)
    result.push(`<p>date: ${aPhoto.date.toDateString()}</p>`)
    return result.join('\n')
    }
    function renderPhoto(aPhoto){
    const result = []
    result.push(`<p>name: ${aPhoto.name}</p>`)
    result.push(`<p>color: ${aPhoto.color}</p>`)
    return result.join('\n')
    }
    function renderDiv(person){
    return [
    "<div>",
    `<p>title: ${person.photo.title}</p>`,
    emitPhotoData(person.photo),
    "</div>"
    ].join('\n')
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    function renderPerson(person){
    const result = []
    result.push(`<p>${person.name}</p>`)
    result.push(renderPhoto(person.photo))
    result.push(emitPhotoData(person.photo))
    return result.join('\n')
    }
    function renderPhoto(aPhoto){
    const result = []
    result.push(`<p>name: ${aPhoto.name}</p>`)
    result.push(`<p>color: ${aPhoto.color}</p>`)
    return result.join('\n')
    }
    function renderDiv(person){
    const result = ["<div>", emitPhotoData(person.photo), "</div>"]
    return result.join('\n')
    }
    function emitPhotoData(aPhoto) {
    return [
    `<p>title: ${aPhoto.title}</p>`,
    `<p>location: ${aPhoto.location}</p>`,
    `<p>date: ${aPhoto.date.toDateString()}</p>`
    ].join('\n')
    }
  4. 搬移语句到调用者(搬移语句到函数)

    当函数边界发生偏移,在某些调用点表现出不同的行为

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    function renderPerson(person){
    [...emitPhotoData(person.photo)]
    }
    function emitPhotoData(aPhoto){
    return [`<p>title: ${aPhoto.title}</p>`, `<p>date: ${aPhoto.date}</p>`]
    }
    function renderPhoto(aPhoto){
    return [...emitPhotoData(person.photo)].slice(1).join('/n')
    }
    ```

    ```js
    function renderPerson(person){
    [`<p>title: ${aPhoto.title}</p>`, ...emitPhotoData(person.photo)]
    }
    function emitPhotoData(aPhoto){
    return [`<p>date: ${aPhoto.date}</p>`]
    }
    function renderPhoto(aPhoto){
    return [...emitPhotoData(aPhoto)].join('/n')
    }

    做法

    1. 如果源函数很简单,且调用者也不多,则只需把要搬移的代码复制回去
    2. 若调用点不止一个,则先用提炼函数把不想搬移的代码提炼成一个新函数
    3. 对源函数应用内联函数,对提炼函数进行修改
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    function renderPerson(person){
    const result = []
    result.push(`<p>${person.name}</p>`)
    result.push(renderPhoto(person.photo))
    result.push(emitPhotoData(person.photo))
    return result.join('\n')
    }
    function renderPhoto(aPhoto){
    const result = []
    result.push(`<p>name: ${aPhoto.name}</p>`)
    result.push(`<p>color: ${aPhoto.color}</p>`)
    return result.join('\n')
    }
    function renderDiv(person){
    const result = ["<div>", emitPhotoData(person.photo), "</div>"]
    return result.join('\n')
    }
    function emitPhotoData(aPhoto) {
    return [
    `<p>title: ${aPhoto.title}</p>`,
    `<p>location: ${aPhoto.location}</p>`,
    `<p>date: ${aPhoto.date.toDateString()}</p>`
    ].join('\n')
    }
    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
    function renderPerson(person){
    const result = []
    result.push(`<p>${person.name}</p>`)
    result.push(renderPhoto(person.photo))
    result.push(emitPhotoData(person.photo))
    result.push(`<p>location: ${person.photo.location}</p>`)
    return result.join('\n')
    }
    function renderPhoto(aPhoto){
    const result = []
    result.push(`<p>name: ${aPhoto.name}</p>`)
    result.push(`<p>color: ${aPhoto.color}</p>`)
    return result.join('\n')
    }
    function renderDiv(person){
    const result = ["<div>",
    emitPhotoData(person.photo),
    `<p>location: ${person.photo.location}</p>`,
    "</div>"]
    return result.join('\n')
    }
    function emitPhotoData(aPhoto) {
    return [
    `<p>title: ${aPhoto.title}</p>`,
    `<p>date: ${aPhoto.date.toDateString()}</p>`
    ].join('\n')
    }
  5. 函数调用取代内联代码

    通过函数的方式去消除重复的逻辑

    1
    2
    3
    4
    5
    let appliesToMass = false;
    let states = ['MA']
    for(let s of states) {
    if(s === 'MA') appliesToMass = true;
    }
    1
    2
    let states = ['MA']
    let appliesToMass = states.includes('MA');

    做法

    1. 内联代码替代为对一个既有函数的调用
    1
    2
    3
    4
    5
    let appliesToMass = false;
    let states = ['MA']
    for(let s of states) {
    if(s === 'MA') appliesToMass = true;
    }
    1
    2
    let states = ['MA']
    let appliesToMass = states.includes('MA');
  6. 移动语句
    让存在关联的代码一起出现,使代码更加容易理解

    1
    2
    3
    4
    5
    6
    7
    if(stack.length === 0){
    ...
    stack.push(i)
    }else{
    ...
    stack.push(i)
    }
    1
    2
    3
    4
    5
    6
    if(stack.length === 0){
    ...
    }else{
    ...
    }
    stack.push(i)

    做法

    1. 确定待移动的代码片段应该搬完何处,搬移后是否会影响正常工作
    2. 搬移代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let result, stack = []
    for(let i = 0; i < 5; i++){
    if(stack.length === 0){
    result = 0
    stack.push(i)
    }else{
    result = stack[stack.length - 1]
    stack.push(i)
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    let result, stack = []
    for(let i = 0; i < 5; i++){
    if(stack.length === 0){
    result = 0
    }else{
    result = stack[stack.length - 1]
    }
    stack.push(i)
    }
  7. 拆分循环

    拆分身兼多职的循环,让代码更加易读

    1
    2
    3
    4
    for (const p of people) {
    if(p.age < youngest) youngest = p.age
    totalSalary += p.salary
    }
    1
    2
    let youngest = Math.min(...people.map(p => p.age))
    let totalSalary = people.reduce((prev, item) => prev + item.salary, 0)

    做法

    1. 复制一遍循环代码
    2. 识别移除循环中的重复代码,使得每个循环只做一件事
    3. 拆分后使用提炼函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const people = [
    { age: 20, salary: 10000 },
    { age: 30, salary: 10000 },
    { age: 25, salary: 20000 },
    { age: 22, salary: 50000 },
    { age: 26, salary: 60000 },
    ]
    let youngest = people[0] ? people[0].age : Infinity;
    let totalSalary = 0
    for (const p of people) {
    if(p.age < youngest) youngest = p.age
    totalSalary += p.salary
    }
    1
    2
    let youngest = Math.min(...people.map(p => p.age))
    let totalSalary = people.reduce((prev, item) => prev + item.salary, 0)
  8. 管道取代循环

    使用管道优化迭代结构,可读性更强

    1
    2
    3
    4
    5
    function acquireData(input) {
    for (const line of lines) {
    ...
    }
    }
    1
    2
    3
    function acquireData(input) {
    lines.slice(1).filter(line => line.trim() !== '')...
    }

    做法

    1. 创建一个新变量,用于存放和参与循环过程的集合
    2. 从顶部开始,将循环每一块行为都搬移出来,在上一步创建的集合变量用一种管道运算替代
    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
    const input = `office, country, telephone

    Chicago, USA, +1 312 373 1000
    Beijing, China, +86 4000 900 505
    Chicago, USA, +1 312 373 1000
    Beijing, China, +86 4000 900 505
    Chicago, USA, +1 312 373 1000
    Beijing, China, +86 4000 900 505`;
    function acquireData(input) {
    const lines = input.split('\n')
    let firstLine = true
    const result = []
    for (const line of lines) {
    if(firstLine) {
    firstLine = false
    continue
    }
    if(line.trim() === '') continue
    const record = line.split(',')
    if(record[1].trim() === 'China'){
    result.push({
    city: record[0].trim(),
    phone: record[2].trim(),
    })
    }
    }
    return result
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function acquireData(input) {
    const lines = input.split('\n')
    return lines
    .slice(1)
    .filter(line => line.trim() !== '')
    .map(line => line.split(','))
    .filter(record => record[1].trim() === 'China')
    .map(record => ({
    city: record[0].trim(),
    phone: record[2].trim(),
    }))
    }
  9. 移除死代码

    没用的东西大胆删除了他,不然越来越多

搬移特性.png