专栏名称: 守候i
web前端开发
目录
相关文章推荐
51好读  ›  专栏  ›  守候i

重构 - 改善代码的各方面问题

守候i  · 掘金  · 前端  · 2018-04-23 00:41

正文

重构 - 改善代码的各方面问题

重构不是对以前代码的全盘否定,而是利用更好的方式,写出更好,更有维护性代码。不断的追求与学习,才有更多的进步。

1.前言

做前端开发有一段时间了,在这段时间里面,对于自己的要求,不仅仅是项目能完成,功能正常使用这一层面上。还尽力的研究怎么写出优雅的代码,性能更好,维护性更强的代码,通俗一点就是重构。这篇文章算是我一个小记录,在此分享一下。该文章主要针对介绍,例子也简单,深入复杂的例子等以后有适合的实例再进行写作分享。如果大家对怎么写出优雅的代码,可维护的代码,有自己的见解,或者有什么重构的实力,欢迎指点评论。

关于重构,准备写一个系列的文章,不定时更新,主要针对以下方案:逻辑混乱重构,分离职责重构,添加扩展性重构,简化使用重构,代码复用重构。其中会穿插以下原则:单一职责原则,最少知识原则,开放-封闭原则。如果大家对重构有什么好的想法,或者有什么好的实例,欢迎留言评论,留下宝贵的建议。

2.什么是重构

首先,重构不是重写。重构大概的意思是在不影响项目的功能使用前提下,使用一系列的重构方式,改变项目的内部结构。提高项目内部的可读性,可维护性。

无论是什么项目,都有一个从简单到复杂的一个迭代过程。在这个过程里面,在不影响项目的使用情况下,需要不断的对代码进行优化,保持或者增加代码的可读性,可维护性。这样一来,就可以避免在团队协作开发上需要大量的沟通,交流。才能加入项目的开发中。

3.为什么重构

衣服脏了就洗,破了就补,不合穿就扔。

随着业务需求的不断增加,变更,舍弃,项目的代码也难免会出现瑕疵,这就会影响代码的可读性,可维护性,甚至影响项目的性能。而重构的目的,就是为了解决这些瑕疵,保证代码质量和性能。但是前提是不能影响项目的使用。

至于重构的原因,自己总结了一下,大概有以下几点

  1. 函数逻辑结构混乱,或因为没注释原因,连原代码写作者都很难理清当中的逻辑。
  2. 函数无扩展性可言,遇到新的变化,不能灵活的处理。
  3. 因为对象强耦合或者业务逻辑的原因,导致业务逻辑的代码巨大,维护的时候排查困难。
  4. 重复代码太多,没有复用性。
  5. 随着技术的发展,代码可能也需要使用新特性进行修改。
  6. 随着学习的深入,对于以前的代码,是否有着更好的一个解决方案。
  7. 因为代码的写法,虽然功能正常使用,但是性能消耗较多,需要换方案进行优化

4.何时重构

在合适的时间,在合适的事情

在我的理解中,重构可以说是贯穿整一个项目的开发和维护周期,可以当作重构就是开发的一部分。通俗讲,在开发的任何时候,只要看到代码有别扭,激发了强迫症,就可以考虑重构了。只是,重构之前先参考下面几点。

  • 首先,重构是需要花时间去做的一件事。花的时间可能比之前的开发时间还要多。
  • 其次,重构是为了把代码优化,前提是不能影响项目的使用。
  • 最后,重构的难度大小不一,可能只是稍微改动,可能难度比之前开发还要难。

基于上面的几点,需要大家去评估是否要进行重构。评估的指标,可以参考下面几点

  • 数量: 需要重构的代码是否过多。
  • 质量: 可读性,可维护性,代码逻辑复杂度,等问题,对代码的质量影响是否到了一个难以忍受的地步。
  • 时间: 是否有充裕的时间进行重构和测试。
  • 效果: 如果重构了代码,得到哪些改善,比如代码质量提高了,性能提升了,更好的支持后续功能等。

5.怎么重构

选定目标,针对性出击

怎么重构,这个就是具体情况,具体分析了。如同“为什么重构一样”。发现代码有什么问题就针对什么情况进行改进。

重构也是写代码,但是不止于写,更在于整理和优化。如果说写代码需要一个‘学习--了解-熟练’的过程,那么重构就需要一个‘学习-感悟-突破-熟练’的过程。

针对重构的情况,下面简单的用几个例子进行说明

5-1.函数无扩展性

如下面一个例子,在我一个库的其中一个 API

//检测字符串
//checkType('165226226326','mobile')
//result:false
let checkType=function(str, type) {
    switch (type) {
        case 'email':
            return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str);
        case 'mobile':
            return /^1[3|4|5|7|8][0-9]{9}$/.test(str);
        case 'tel':
            return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str);
        case 'number':
            return /^[0-9]$/.test(str);
        case 'english':
            return /^[a-zA-Z]+$/.test(str);
        case 'text':
            return /^\w+$/.test(str);
        case 'chinese':
            return /^[\u4E00-\u9FA5]+$/.test(str);
        case 'lower':
            return /^[a-z]+$/.test(str);
        case 'upper':
            return /^[A-Z]+$/.test(str);
        default:
            return true;
    }
}

这个 API 看着没什么毛病,能检测常用的一些数据。但是有以下两个问题。

1.但是如果想到添加其他规则的呢?就得在函数里面增加 case 。添加一个规则就修改一次!这样违反了开放-封闭原则(对扩展开放,对修改关闭)。而且这样也会导致整个 API 变得臃肿,难维护。

2.还有一个问题就是,比如A页面需要添加一个金额的校验,B页面需要一个日期的校验,但是金额的校验只在A页面需要,日期的校验只在B页面需要。如果一直添加 case 。就是导致A页面把只在B页面需要的校验规则也添加进去,造成不必要的开销。B页面也同理。

建议的方式是给这个 API 增加一个扩展的接口

let checkType=(function(){
    let rules={
        email(str){
            return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str);
        },
        mobile(str){
            return /^1[3|4|5|7|8][0-9]{9}$/.test(str);
        },
        tel(str){
            return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str);
        },
        number(str){
            return /^[0-9]$/.test(str);
        },
        english(str){
            return /^[a-zA-Z]+$/.test(str);
        },
        text(str){
            return /^\w+$/.test(str);
        },
        chinese(str){
            return /^[\u4E00-\u9FA5]+$/.test(str);
        },
        lower(str){
            return /^[a-z]+$/.test(str);
        },
        upper(str){
            return /^[A-Z]+$/.test(str);
        }
    };
    //暴露接口
    return {
        //校验
        check(str, type){
            return rules[type]?rules[type](str):false;
        },
        //添加规则
        addRule(type,fn){
            rules[type]=fn;
        }
    }
})();

//调用方式
//使用mobile校验规则
console.log(checkType.check('188170239','mobile'));
//添加金额校验规则
checkType.addRule('money',function (str) {
    return /^[0-9]+(.[0-9]{2})?$/.test(str)
});
//使用金额校验规则
console.log(checkType.check('18.36','money'));

上面的代码,是多了一些,但是理解起来也没怎么费劲,而且拓展性也有了。

上面这个改进其实是使用了策略模式(把一系列的算法进行封装,使算法代码和逻辑代码可以相互独立,并且不会影响算法的使用)进行改进的。策略模式的概念理解起来有点绕,但是大家看着代码,应该不绕。

这里展开讲一点,在功能上来说,通过重构,给函数增加扩展性,这里实现了。但是如果上面的 checkType 是一个开源项目的 API ,重构之前调用方式是: checkType('165226226326','phone') 。重构之后调用方式是: checkType.check('188170239','phone') ;或者 checkType.addRule() ;。如果开源项目的作者按照上面的方式重构,那么之前使用了开源项目的 checkType 这个 API 的开发者,就可能悲剧了,因为只要开发者一更新这个项目版本,就有问题。因为上面的重构没有做向下兼容。

如果要向下兼容,其实也不难。加一个判断而已。

let checkType=(function(){
    let rules={
        email(str){
            return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str);
        },
        mobile(str){
            return /^1[3|4|5|7|8][0-9]{9}$/.test(str);
        },
        tel(str){
            return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str);
        },
        number(str){
            return /^[0-9]$/.test(str);
        },
        english(str){
            return /^[a-zA-Z]+$/.test(str);
        },
        text(str){
            return /^\w+$/.test(str);
        },
        chinese(str){
            return /^[\u4E00-\u9FA5]+$/.test(str);
        },
        lower(str){
            return /^[a-z]+$/.test(str);
        },
        upper(str){
            return /^[A-Z]+$/.test(str);
        }
    };
    //暴露接口






请到「今天看啥」查看全文