[探索]在開發中儘可能提升代碼的複用性

ctrl+c 和 ctrl+v 給咱們帶來了不少的便利,可是也使咱們變得懶惰,不肯思考。

1.前言

相信不少人和我同樣,在開發項目的時候,由於項目趕,或者一時沒想到等緣由。頻繁使用 ctrl+c 和 ctrl+v ,致使代碼不少都是重複的。這幾天,也看了本身之前寫的代碼,簡單的探索了一下,挑選幾個實例,分享下如何在特定場景下,保證代碼質量前提下,提升代碼複用性。css

提升代碼的複用性,應該是不一樣場景,不一樣解決方案的。同時也要保證代碼質量。不建議強制提升代碼複用性,若是提升代碼複用性會大大的下降代碼的可讀性,維護性,可能會得不償失。

2.HTML+CSS

在作項目的時候,相信頁面上總會有不少類似的按鈕,好比下圖,項目上幾個類似的按鈕。面對這樣的需求,以前是直接寫三個按鈕html

圖片描述

<button type="button" class='u-btn-yes-samll'>肯定</button>
<button type="button" class='u-btn-yes'>肯定</button>
<button type="button" class='u-btn-yes-big'>肯定</button>

css前端

button{
    border:none;
    font-family:'微軟雅黑'; 
}
.u-btn-yes{
    width:80px;
    height:36px;
    font-size:14px;
    color:#fff;
    border-radius:10px;
    background:#09f;
}
.u-btn-yes-samll{
    width:60px;
    height:30px;
    font-size:12px;
    color:#fff;
    border-radius:10px;
    background:#09f;
}
.u-btn-yes-big{
    width:100px;
    height:40px;
    font-size:16px;
    color:#fff;
    border-radius:10px;
    background:#09f;
}

這樣至關於每增長一種按鈕,就增長了幾行的 css 代碼,實際上改變的只有 width ,height ,font-size 這三個屬性。數組

實際上能夠根據大小進行代碼複用。微信

.u-btn-yes{
    width:80px;
    height:36px;
    font-size:14px;
    color:#fff;
    border-radius:10px;
    background:#09f;
}
.u-btn-yes.u-btn-samll{
    width:60px;
    height:30px;
    font-size:12px;
}
.u-btn-yes.u-btn-big{
    width:100px;
    height:40px;
    font-size:16px;
}

頁面調用編輯器

<button type="button" class='u-btn-yes u-btn-samll'>肯定</button>
<button type="button" class='u-btn-yes'>肯定</button>
<button type="button" class='u-btn-yes u-btn-big'>肯定</button>

頁面上可能還有不少按鈕相似的,可是不一樣顏色的(以下圖),也能夠靈活處理,這樣還能夠自由組合。這個也是社區上不少 UI 庫所使用的方式。函數

圖片描述

.u-btn{
    width:80px;
    height:36px;
    font-size:14px;
    color:#fff;
    border-radius:10px;
    background:#09f;
}
.u-btn-samll{
    width:60px;
    height:30px;
    font-size:12px;
}
.u-btn-big{
    width:100px;
    height:40px;
    font-size:16px;
}
.u-btn-red{
    background:#f33;
}
.u-btn-yellow{
    background:#f90;
}

html優化

<button type="button" class='u-btn u-btn-samll'>肯定</button>
<button type="button" class='u-btn u-btn-red'>肯定</button>
<button type="button" class='u-btn u-btn-big u-btn-yellow'>肯定</button>
對於這些按鈕,不建議設置 margin ,positon 等樣式,由於不一樣的按鈕在不一樣的地方,上面這幾個屬性基本不會同樣。

3.JavaScript

關於提升代碼複用性的好處,在上面 HTML+CSS的實例裏面並無很明顯的優點,但在 JS 裏面提升代碼的複用性優點就比較明顯了,下面簡單列舉幾個例子。this

3-1.封裝經常使用函數

在上家公司的項目裏面有這樣代碼,目的也很明顯,就是用戶填寫表單的時候,尚未填寫完整就提交,前端這裏就須要給一個簡單的提示。spa

//當信息沒填寫完整的時候彈出提示
layer.alert('請檢查信息是否填寫完整',{
    title:'提示',
    icon:2
})

基於 layer 這個開源庫,代碼看得特別的簡單。可是隨着項目的開發,用戶填寫表單的地方有多個,那麼上面的代碼就會被複制到多個地方,這樣不免會有有點多餘。

另外,這樣作最大的一個問題就是:若是上面的代碼在項目上有20個地方在用,有一天需求變了,title 這個屬性值要從‘提示’變成‘警告’。那就麻煩了,要找20個地方,即便編輯器有全局替換的功能,這樣的改動出問題的機率也比較大。面對這樣的狀況。以前處理的方法是對這個彈窗進行簡單粗暴的封裝,方便複用。

function openTips(){
    //當信息沒填寫完整的時候彈出提示
    layer.alert('請檢查信息是否填寫完整',{
        title:'提示',
        icon:2
    });
}

在須要的地方,須要的時候進行調用就好,這樣能夠寫少不少代碼!修改起來,也只須要修改 openTips 這一個地方就完事了。

openTips();

3-2.使用策略模式代替 switch

下面再看一個實例。借用下以前羣友的發的代碼。

模擬數據

let listWarnConf = [
    {
        warnType: 1,
        warnCondition: 220,
    },
    {
        warnType: 2,
        warnCondition: 36,
    },
    {
        warnType: 3,
        warnCondition: 45,
    },
    {
        warnType: 4,
        warnCondition: 110,
    },
    {
        warnType: 5,
        warnCondition: 380,
    }
]

業務邏輯代碼

listWarnConf.forEach(item => {
    switch(item.warnType) {
        case 1:
            item.warnTypeText = '超壓';
            item.warnConditionText = `電壓高於${item.warnCondition}V`
            break;
        case 2:
            item.warnTypeText = '欠壓';
            item.warnConditionText = `電壓低於${item.warnCondition}V`
            break
        case 3:
            item.warnTypeText = '超載';
            item.warnConditionText = `電流高於${item.warnCondition}A`
            break
        case 4:
            item.warnTypeText = '電壓不平衡';
            item.warnConditionText = `電壓不平衡高於${item.warnCondition}%`
            break
        case 5:
            item.warnTypeText = '電流不平衡';
            item.warnConditionText = `電流不平衡${item.warnCondition}%`
            break
    }
})

圖片描述

這樣看着結果是沒問題的,可是看着那麼多 case 執行的都是賦值操做。並且最大的問題和上面同樣,若是多個地方使用,需求變了,那麼仍是要修改這麼多的地方,下面優化下,讓代碼的複用性提升下。

//設置配置數據
let warnConfig={
    1:{
        warnTypeText:'超壓',
        warnConditionText:'電壓高於replaceTextV'
    },
    2:{
        warnTypeText:'欠壓',
        warnConditionText:'電壓低於replaceTextV'
    },
    3:{
        warnTypeText:'超載',
        warnConditionText:'電流高於replaceTextV'
    },
    4:{
        warnTypeText:'電壓不平衡',
        warnConditionText:'電壓不平衡高於replaceText%'
    },
    5:{
        warnTypeText:'電流不平衡',
        warnConditionText:'電流不平衡高於replaceText%'
    }
}
//業務邏輯--根據配置數據設置warnTypeText和warnConditionText
listWarnConf.forEach(item => {
    item.warnTypeText=warnConfig[item.warnType].warnTypeText;
    item.warnConditionText=warnConfig[item.warnType].warnConditionText.replace('replaceText',item.warnCondition);
})

這樣改代碼量沒減小,可讀性比 switch 差,但能讀懂。可是這樣作就是重複的代碼少了,配置數據和業務邏輯分離了,若是之後要修改配置數據或者業務邏輯,就修改其中一項便可,互相不影響。把配置數據抽出來公用,那麼在須要修改的時候,直接修改就好。

關於提升代碼的複用性,或者說減小重複的代碼,我的覺能夠往如下目標努力--當需求發生改變,須要修改代碼的時候,一樣的代碼不要修改兩次。

3-3.保持函數單一職責,靈活組合

保持函數的單一職責,保證一個函數只執行一個動做,每一個動做互不影響,能夠自由組合,就能夠提升代碼的複用性。

好比下面的代碼,從服務端請求回來的訂單數據以下,須要進行如下處理
1.根據 status 進行對應值得顯示(0-進行中,1-已完成,2-訂單異常)
2.把 startTime 由時間戳顯示成 yyyy-mm-dd
3.若是字段值爲空字符串 ,設置字段值爲 ‘--’

let orderList=[
    {
        id:1,
        status:0,
        startTime:1538323200000,
    },
    {
        id:2,
        status:2,
        startTime:1538523200000,
    },
    {
        id:3,
        status:1,
        startTime:1538723200000,
    },
    {
        id:4,
        status:'',
        startTime:'',
    },
];

需求彷佛很簡單,代碼也少

let _status={
    0:'進行中',
    1:'已完成',
    2:'訂單異常'
}
orderList.forEach(item=>{
    //設置狀態
    item.status=item.status.toString()?_status[item.status]:'';
    //設置時間
    item.startTime=item.startTime.toString()?new Date(item.startTime).toLocaleDateString().replace(/\//g,'-'):'';
    //設置--
    for(let key in item){
        if(item[key]===''){
            item[key]='--';
        }
    }
})

圖片描述

運行結果也正常,可是這樣寫代碼重複性會不少,好比下面,另外一組重服務端請求回來的用戶數據,用戶數據沒有 status,startTime,兩個字段,並且須要根據 type 對應顯示用戶的身份(0-普通用戶,1-vip,2-超級vip)。

let userList=[
    {
        id:1,
        name:'守候',
        type:0
    },
    {
        id:2,
        name:'浪跡天涯',
        type:1
    },
    {
        id:3,
        name:'曾經',
        type:2
    }
]

出現這樣的需求,以前寫的代碼沒法重用,只能複製過來,再修改下。

let _type={
    0:'普通用戶',
    1:'vip',
    2:'超級vip'
}
userList.forEach(item=>{
    //設置type
    item.type=item.type.toString()?_type[item.type]:'';
    //設置--
    for(let key in item){
        if(item[key]===''){
            item[key]='--';
        }
    }
})

圖片描述

結果正常,想必你們已經發現問題了,代碼有點多餘。下面就使用單一職責的原則改造下操做函數,設置 status,startTime,type,-- 。這裏拆分紅四個函數。

let handleFn={
    setStatus(list){
        let _status={
            0:'進行中',
            1:'已完成',
            2:'訂單異常'
        }
        list.forEach(item=>{
            item.status=item.status.toString()?_status[item.status]:'';
        })
        return list
    },
    setStartTime(list){
        list.forEach(item=>{
            item.startTime=item.startTime.toString()?new Date(item.startTime).toLocaleDateString().replace(/\//g,'-'):'';
        })
        return list;
    },
    setInfo(list){
        list.forEach(item=>{
            for(let key in item){
                if(item[key]===''){
                    item[key]='--';
                }
            }
        })
        return list;
    },
    setType(list){
        let _type={
            0:'普通用戶',
            1:'vip',
            2:'超級vip'
        }
        list.forEach(item=>{
            item.type=item.type.toString()?_type[item.type]:'';
        })
        return list;
    }
}

下面直接調用函數就好

//處理訂單數據
orderList=handleFn.setStatus(orderList);
orderList=handleFn.setStartTime(orderList);
orderList=handleFn.setInfo(orderList);
console.log(orderList);
//處理用戶數據
userList=handleFn.setType(userList);
userList=handleFn.setInfo(userList);
console.log(userList);

運行結果也正常

圖片描述

若是嫌棄連續賦值麻煩,能夠借用 jQuery 的那個思想,進行鏈式調用。

let ec=(function () {
    let handle=function (obj) {
        //深拷貝對象
        this.obj=JSON.parse(JSON.stringify(obj));
    };
    handle.prototype={
        /**
         * @description 設置保密信息
         */
        setInfo(){
            this.obj.map(item=>{
                for(let key in item){
                    if(item[key]===''){
                        item[key]='--';
                    }
                }
            });
            return this;
        },
        /**
         * @description 設置狀態
         */
           setStatus(){
               let _status={
                   0:'進行中',
                   1:'已完成',
                   2:'訂單異常'
               }
               this.obj.forEach(item=>{
                item.status=item.status.toString()?_status[item.status]:''
            });
            return this;
           },
           /**
         * @description 設置時間
         */
           setStartTime(){
               this.obj.forEach(item=>{
                item.startTime=item.startTime.toString()?new Date(item.startTime).toLocaleDateString().replace(/\//g,'-'):'';
            });
            return this;
           },
           /**
         * @description 設置type
         */
           setType(){
            let _type={
                0:'普通用戶',
                1:'vip',
                2:'超級vip'
            }
            this.obj.forEach(item=>{
                item.type=item.type.toString()?_type[item.type]:'';
            })
            return this;
        },
        /**
         * @description 返回處理結果
         * @return {Array|*}
         */
        end(){
            return this.obj;
        }
    }
    //暴露構造函數接口
    return function (obj) {
        return new handle(obj);
    }
})();

這樣就能夠鏈式調用了

//處理訂單數據
orderList=ec(orderList).setStatus().setStartTime().setInfo().end();
console.log(orderList);
//處理用戶數據
userList=ec(userList).setType().end();
console.log(userList);

事情到這裏了,相信你們發現一個很嚴重的問題就是循環的次數增長了。沒優化以前,只須要循環一次,就能夠把設置狀態,設置時間,設置--這些步驟都完成,可是如今 setStatus().setStartTime().setInfo() 這裏的代碼,每執行一個函數,都遍歷了一次數組,這個就得優化下。處理的方式就是在每個函數裏面,只記錄要處理什麼,可是不進行處理,等到執行到 end 的時候再統一處理,以及返回。

let ec=(function () {
    let handle=function (obj) {
        //深拷貝對象
        this.obj=JSON.parse(JSON.stringify(obj));
        //記錄要處理的步驟
        this.handleFnList=[];
    };
    handle.prototype={
        /**
         * @description 設置保密信息
         */
        handleSetInfo(item){
            for(let key in item){
                if(item[key]===''){
                    item[key]='--';
                }
            }
            return this;
        },
        setInfo(){
            this.handleFnList.push('handleSetInfo');
            return this;
        },
        /**
         * @description 設置狀態
         */
           handleSetStatus(item){
               let _status={
                   0:'進行中',
                   1:'已完成',
                   2:'訂單異常'
               }
            item.status=item.status.toString()?_status[item.status]:''
            return item;
           },
           setStatus(){
            this.handleFnList.push('handleSetStatus');
            return this;
        },
           /**
         * @description 設置時間
         */
           handleSetStartTime(item){
            item.startTime=item.startTime.toString()?new Date(item.startTime).toLocaleDateString().replace(/\//g,'-'):'';
            return item;
           },
           setStartTime(){
            this.handleFnList.push('handleSetStartTime');
            return this;
        },
           /**
         * @description 設置type
         */
           handleSetType(item){
            let _type={
                0:'普通用戶',
                1:'vip',
                2:'超級vip'
            }
            item.type=item.type.toString()?_type[item.type]:'';
            return item;
        },
        setType(){
            this.handleFnList.push('handleSetType');
            return this;
        },
        /**
         * @description 返回處理結果
         * @return {Array|*}
         */
        end(){
            //統一處理操做
            this.obj.forEach(item=>{
                this.handleFnList.forEach(fn=>{
                    item=this[fn](item);
                })
            })
            return this.obj;
        }
    }
    //暴露構造函數接口
    return function (obj) {
        return new handle(obj);
    }
})();

這樣改,以前的調用方式不須要改變,而後結果也是正確的

圖片描述

可能你們會以爲很簡單一個需求,卻搞得這麼複雜。若是這樣想是正確的,由於這個的確搞複雜了,可讀性也差了,但想到項目遇到的處理數據不止這一些,還有好比金額的格式顯示,其它數據的各類狀態碼解析顯示,銀行卡號每隔4位分割,電話號碼的顯示等等。因此就先封裝一下,之後用的時候,直接使用。不知道算不算是先苦後甜?若是需求比較簡單,可能真的不必這麼樣封裝。

4.小結

假期看代碼,提升代碼複用性的總結,差很少就是這些了,固然還有一些實例,可是在以前已經寫過了,和該文章說起的實例也是大同小異,就再也不重複說起。提升代碼的複用性是一個很大的話題,若是你們有什麼好的建議,實例,歡迎分享。

-------------------------華麗的分割線--------------------

想了解更多,和我交流,內推職位,請添加我微信。或者關注個人微信公衆號:守候書閣

圖片描述圖片描述

相關文章
相關標籤/搜索