代码重构

基本原则

1 易读性优先

2 不要为了优化而优化

如果代码没有造成性能瓶颈,那么就不要为了性能而修改代码

var array = [] 
for(let i=0;i<array.length;i++){} 
复制代码

有人就说,代码有问题,每循环一次就会调用一次 array.length 会造成性能上的浪费。因此他会写作

for(let i=0, l=array.length;i<l;i++){} 
复制代码

这样每次读一个常数就好了,但是如果不是性能瓶颈,就不需要去优化。

3 复杂性守恒原则

无论你怎么写代码,复杂性都不会消失

代码不会降低事物本身的复杂。代码和逻辑的复杂性是成正比的。

命名

程序员三大难题:

  1. 变量命名
  2. 缓存失效
  3. 循环边界

1 注意词性

  • 普通变量/属性用「名词」
var person = {
    name: 'Jack'
}
var student = {
    class: 3,
    grade: 2
}
复制代码
  • bool变量/属性用「形容词」或「be动词」或「情态动词」或「hasX」
var person = {
    dead: false,    // 形容词可以直接命名
    canSpeak: true,     // 情态动词有 can、should、will、need,后加动词
    isVip: false,     // is、was加名词
    hasChildren: true   // has 后加名词
}
复制代码
  • 普通函数/属性用「动词」
var person = {
    run(){},   //不及物动词
    drinkWater(){}  // 及物动词
}
复制代码
  • 回调函数/钩子函数用「介词」开头,或者用「动词的现在完成时态」
var person = {
    beforeDie(){},
    afterDie(){},
    // 或者
    willDie(){},
    dead(){}   // 过去分词
}
button.addEventListener('click', onButtonClick)
// vue 中的钩子函数
var component = {
    beforeCreate(){},
    created(){},
    beforeMount(){},
    mounted(){},
    beforeUpdate(){},
    updated(){},
    activated(){},
    beforeDestroy(){},
    destroyed(){}
}
复制代码
  • 容易混淆的地方加「前缀」
div1.classList.add('active')   // DOM 对象
div2.addClass('active')     // jQuery对象
//  不如改成
domDiv1 或 elDiv1.classList.add('active')
$div2.addClass('active')
复制代码
  • 属性访问器可以用「名词」
$div.text()   // $div.getText()
$div.text('hello')  // $div.setText('hello')
复制代码

2 注意一致性

介词一致性

如果你使用了 before + after,那么就在代码的所有地方都坚持使用

如果你使用了 before + 完成时,那么就坚持使用

如果你改来改去,就「不一致」了,不一致将导致「不可预测」

顺序一致性

比如 updateContainerWidth 和 updateHeightOfContainer 的顺序就令人很别扭,同样会引发「不可预测」

时间一致性

有可能随着代码的变迁,一个变量的含义已经不同于它一开始的含义了,这个时候你需要及时改掉这个变量的名字。这一条是最难做到的,因为写代码容易,改代码难。如果这个代码组织得不好,很可能会出现牵一发而动全身的情况。

表里一致性

函数名必须完美体现函数的功能,既不能多也不能少。比如

 function getSongs(){
      return $.get('/songs).then((response){
          div.innerText = response.songs
      })
  }
复制代码

就违背了表里一致性,getSongs 表示获取歌曲,并没有暗示这个函数会更新页面,但是实际上函数更新了 div,这就是表里不一,正确的写法是:

// 要么纠正函数名
  function getSongsAndUpdateDiv(){
      return $.get('/songs').then((response){
          div.innerText = response.songs
      })
  }
  
// 要么写成两个函数
function getSongs(){
      return $.get('/songs')
  }
  function updateDiv(songs){
      div.innerText = response.songs
  }
  getSongs().then((response)=>{
      updateDiv(response.songs)
  })
复制代码

改代码

我们首先来看一个轮播的代码。

// html
<div class="slidesWindow">
    <div class="slides">
        <div class="slide"></div>
        <div class="slide"></div>
        <div class="slide"></div>
        <div class="slide"></div>
        <div class="slide"></div>
    </div>
</div>

// js    优化前
let currentIndex = 0
let $slides = $('.slides')
let $slidesWindow = $('.slidesWindow')
function playNextSlide(){
    $slides.css({
        transform: `translateX(${-400*(currentIndex+1)}px)`
    })
    currentIndex += 1
}
function playPrevSlide(){
    $slides.css({
        transform: `translateX(${-400*(currentIndex-1)}px)`
    })
    currentIndex -= 1
}
// 下一张
buttonNext.onclick = ()=>{
    playNextSlide()
}
// 上一张
buttonPrev.onclick = ()=>{
     playPrevSlide()
}
// 每隔3秒自动播放下一张
let timerId = setInterval(()=>{
    playNextSlide()
}, 3000)
// 鼠标移入暂停播放
$slidesWindow.on('mouseenter', ()=>{
    window.clearInterval(timerId)
})
// 鼠标移出 恢复播放
$slidesWindow.on('mouseleave', ()=>{
    timerId = setInterval(()=>{
        playNextSlide()
    }, 3000)
})
复制代码

使用函数来改代码

步骤:

  1. 将一坨代码放到一个函数里
  2. 将代码依赖的外部变量作为参数
  3. 将代码的输出作为函数的返回值
  4. 给函数取一个合适的名字
  5. 调用这个函数并传入参数
  6. 如果这个函数里的代码超过5行,则依然有优化的空间,请返回第一步
  7. 使用上述步骤来优化轮播的代码。
//  优化后
// 里面的变量与函数都可以叫做闭包
!function(){   // 全局变量优化
    let currentIndex = 0
    let timerId
    let $slides = $('.slides')
    let $slidesWindow = $('.slidesWindow')
    let playNext = () => { playSlide(currentIndex+1) }
    let playPrev = () => { playSlide(currentIndex-1) }
    let clearTimer = () => { window.clearInterval(timerId) }
    let resetTimer = () => { timerId = autoPlay() }
    
    function bindEvents(){
        let events = [
            {el: buttonNext, event: 'click', fn: playNext},
            {el: buttonPrev, event: 'click', fn: playPrev},
            {el: $slidesWindow, event: 'mouseenter', fn: clearTimer},
            {el: $slidesWindow, event: 'mouseleave', fn: resetTimer},
        ]
        events.forEach((eventObject)=>{
            $(eventObject.el).on(eventObject.event, eventObject.fn)
        })
    }
    function autoPlay(){
        // 每隔3秒自动播放下一张
       return setInterval(()=>{
            playSlide(currentIndex+1)
        }, 3000)
    }
    function playSlide(index){
        index = fixIndex(index)
        $slides.css({
            transform: `translateX(${-400*(index)}px)`
        })
        currentIndex = index
        return index
    }
    function fixIndex(index){
        if(index < 0){
            index = 4
        }else if(index > 4){
            index = 0
        }
        return index
    }
    
    bindEvents()
    timerId = autoPlay()
}()
复制代码

使用对象来改代码

将上述代码使用对象来改。

!function () {
    let slides = {
        currentIndex: 0,
        timerId: undefined,
        $slides: $('.slides'),
        $slidesWindow: $('.slidesWindow'),
        $buttonNext: $('#buttonNext'),
        $buttonPrev: $('#buttonPrev'),
        //  为什么会不能改,对象没有this上下文,只有函数里面才有this
        events() {
            return [
                { el: this.$buttonNext, event: 'click', fn: this.playNext },
                { el: this.$buttonPrev, event: 'click', fn: this.playPrev },
                { el: this.$slidesWindow, event: 'mouseenter', fn: this.clearTimer },
                { el: this.$slidesWindow, event: 'mouseleave', fn: this.resetTimer },
            ]
        },

        // 另一种
        events: [
            { el: '#buttonNext', event: 'click', fn: 'playNext' },
            { el: '#buttonPrev', event: 'click', fn: 'playPrev' },
            { el: '.slidesWindow', event: 'mouseenter', fn: 'clearTimer' },
            { el: '.slidesWindow', event: 'mouseleave', fn: 'resetTimer' },
        ],
        bindEvents: function () {
            this.events().forEach((eventObject) => {
                $(eventObject.el).on(
                    eventObject.event, 
                    this[eventObject.fn].bind(this) // ()=>{this[eventObject.fn].call(this)}
                )
            })
        },
        init() {
            this.bindEvents()
            this.timerId = this.autoPlay()
        },
        playNext() { this.playSlide(this.currentIndex + 1) },
        playPrev() { this.playSlide(this.currentIndex - 1) },
        clearTimer() { window.clearInterval(this.timerId) },
        resetTimer() { this.timerId = this.autoPlay() },

        bindEvents: function () {
            this.events().forEach((eventObject) => {
                $(eventObject.el).on(eventObject.event, eventObject.fn)
            })
        },
        autoPlay() {
            // this === slides
            // 每隔3秒自动播放下一张
            // return setInterval(function() {
            //     this.playSlide(this.currentIndex + 1)
            // }.bind(this), 3000)
            return setInterval(() => {
                this.playSlide(this.currentIndex + 1)
            }, 3000)
        },
        playSlide(index) {
            index = this.fixIndex(index)
            this.$slides.css({
                transform: `translateX(${-400 * (index)}px)`
            })
            this.currentIndex = index
            return index
        },
        fixIndex(index) {
            if (index < 0) {
                index = 4
            } else if (index > 4) {
                index = 0
            }
            return index
        }
    }
    slides.init()   // slides.init.call(slides)
}()
复制代码

一些固定的套路

1. 表驱动编程(《代码大全》里说的:所有一一对应的关系都可以用表来做

// 打出每个月的天数
// 优化前
function howManyDays(month) {
    if (month === 1 ||
        month === 3 ||
        month === 5 ||
        month === 7 ||
        month === 8 ||
        month === 10 ||
        month === 12
    ) {
        return 31
    } else if(month === 2){
        return 28
    }else{
        return 30
    }
}

// 优化后
function howManyDays(month){
    var table = {
        1: 31,
        2: 28,
        3: 31,
        4: 30,
        5: 31,
        6: 30,
        7: 31,
        8: 31,
        9: 30,
        10: 31,
        11: 30,
        12: 31
    }
    return table[month]
}
复制代码

根据分数计算等级

function caculateGrade(score) {
    if (score >= 90) {
        return 'A'
    } else if (score >= 80) {
        return 'B'
    } else if (score >= 70) {
        return 'C'
    } else if (score >= 60) {
        return 'D'
    } else {
        return 'E'
    }
}

function caculateGrade(score) {
    var table = {
        10: 'A',
        9: 'A',
        8: 'B',
        7: 'C',
        6: 'D',
        others: 'E'
    }
    var level = parseInt(score / 10, 10)
    return table[level] || table['others']
}
复制代码

上述表驱动编程,可以使if else 荡然无存。

2. 自说明代码(以 API 参数为例)

把别人关心的东西放在显眼的位置

class Dialog(){
    constructor(options){
        let defaultOptions: {
            title: '',
            content: '<h2>请写content<h2>',
            buttons: [
                {text:'确定', action: function(close){close()}},
                {text:'取消', action: function(close){close()}}
            ]
        }
        this.options = Object.assign({}, defaultOptions, options)
    }
}
复制代码

代码的 bad smell

  • 表里不一的代码
  • 过时的注释
  • 逻辑很简单,但是看起来很复杂的代码
  • 重复的代码
  • 相似的代码
  • 总是一起出现的代码

破窗效应

此理论认为环境中的不良现象如果被放任存在,会诱使人们仿效,甚至变本加厉。一幢有少许破窗的建筑为例,如果那些窗不被修理好,可能将会有破坏者破坏更多的窗户。最终他们甚至会闯入建筑内,如果发现无人居住,也许就在那里定居或者纵火。一面墙,如果出现一些涂鸦没有被清洗掉,很快的,墙上就布满了乱七八糟、不堪入目的东西;一条人行道有些许纸屑,不久后就会有更多垃圾,最终人们会视若理所当然地将垃圾顺手丢弃在地上。这个现象,就是犯罪心理学中的破窗效应。

写在最后

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下,您的支持是我坚持写作最大的动力,多谢支持。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享