谈谈代码健壮性之极限值处理(防御性编程)

  我们知道,web开发的数据不断在数据库端、服务器端、客户端进行传递。

  我们为了防止脏数据,我们需要对每个数据项的极限值进行特殊的处理;或者,换个角度来讲,为了我们的代码更加的健壮,我们不得不考虑所有与业务相关的极限值的处理。

  这里的”极限值处理“的定义比较宽泛,如

    1> 处理空值(如null/undefined/''等)
    2> 处理数据类型
    3> 处理数据范围
    4> 其他与业务相关的特殊值或范围的处理

  在这里,我仅仅说说有关前端方面的极限值的处理情况。主要包含两点:

    1> 页面显示的极限值处理
    2> JavaScript函数参数的极限值处理

  下面我来分别介绍——

  

  一、页面显示的极限值处理

  举个例子,freemarker显示商品价格。或许一个马虎的同学会这样写:

<!-- 什么极限值都没有考虑 -->
<p class="price">
    <label>售价:</label>
    <span>${goods.price}</span>
</p>

  嗯,写完,感觉还不错,完成这个功能了。也没有其他考虑代码健壮性的意识,直到测试同学一次又一次的提示他纠正极限值的处理情况,于是乎,来来回回的更改。测试同学的提交bug流程、与程序同学的bug沟通过程,程序同学的代码逻辑重新阅读过程、修改过程,测试同学的回归测试过程。

  真是浪费了不少时间去处理这个极限值。然而,处理的可能不够彻底。及其可能它的处理完善过程如下——

<!-- 考虑了字段price的空值(默认值) -->
<p class="price">
    <label>售价:</label>
    <span>${goods.price!default("--")}</span>
</p>
<!-- 1.考虑了字段price的空值(默认值) -->
<!-- 2.考虑了对象goods的空值 -->
<p class="price">
    <label>售价:</label>
    <span>${goods?if_exists.price!default("--")}</span>
</p>
<!-- 1.考虑了字段price的空值(默认值) -->
<!-- 2.考虑了对象goods的空值 -->
<!-- 3.考虑了字段price的特殊(极限)值 -->
<p class="price">
    <label>售价:</label>
    <#if goods?if_exists.price?exists && goods?if_exists.price?int &gt; 0>
        <span>${goods.price}</span>
    <#else>
        <span class="no-data">--</span>
    </#if>
</p>

  对以上最后一个处理的比较全面的极限值的例子的解释:首先判断goods这个key是否存在(goods?if_exists),如果存在,再去判断goods对象下的price字段是否存在(goods?if_exists.price?exists),紧接着判断这里的price字段的是否大于0,如果大于0,则显示该价格。不符合以上任何条件者,显示“--”。

  一次又一次的更替,相信大家都不希望把时间浪费在一次又一次无谓的重复性的完善修改工作。根据业务需求,一次性考虑好极限情况,让我们的代码达到最佳健壮性,便会免去那些无谓的重复性工作。从用户浏览的角度来讲,能够避免错误异常代码的显示,给予更加好的提示。

  总结一下页面显示的极限值处理——

    1> 考虑好每一个字段(包括基本数据类型、对象及对象的属性)的极限值
    2> 这里的极限值处理可能包括:空值判断(是否赋予默认值)、数据类型判断、数据范围判断等
    3> 考虑好每一个极限值的显示方式。依旧拿上面的例子来说,可能会有这样的需求,如果price>1000,需要以“1,000”的方式进行显示,当然这里就不涉及freemarker的控制显示了,最好在程序后端进行处理。

  

  二、JavaScript函数参数的极限值处理

  这个话题也是一个大家常常忽视的问题,同样也会引来测试同学与程序同学来来回回重复性工作的处理流程。我们细细的看一下这个话题。

  而今,数据参数的提交大多基于JavaScript来触发,就连普普通通的表单提交,还要经过JavaScript的一轮校验处理,校验通过了,由JavaScript处理提交。

  既然如此,我们便需要考虑好JavaScript参数传递的极限值处理问题。先拿jQuery框架中的一个方法举例吧——

jQuery.fn = jQuery.prototype = {
    
    get: function( num ) {
        
        //判断是否为null,当然这里的undefined == null也是返回true
        return num == null ?
            
            // 如果为null,则返回所有符合的对应dom的数组对象
            // Return a 'clean' array
            this.toArray() :
            
            // 如果为非null,则再次判断其他特殊情况
            // Return just the object
            ( num < 0 ? this[ this.length + num ] : this[ num ] );
    }
    
};

  嗯,极限值处理的很不错,当num为负值时,考虑了使用逆序手法取出对应dom。

  我们拿几个函数调用来测试一下它的健壮性——

<!-- 测试使用的html代码 -->
<div class="test">1</div>
<div class="test">2</div>
<div class="test">3</div>
<div class="test">4</div>

<script src="lib/jquery-1.9.1.js"></script>

<script>
    
    //没有参数 - 罗列满足选择条件的所有符合的dom对象并组成数组
    console.log($('.test').get());    //[div.test, div.test, div.test, div.test]

    //传递参数 - 数字1 - 选取满足条件的第二个dom对象
    console.log($('.test').get(1));    //<div class="test">2</div>

    //传递参数 - 数字5 - 超出dom数组对象最大长度4,取值为undefined - 源于数组本身特性
    console.log($('.test').get(5));    //undefined
    
    //传递参数 - 字符串'1' - 选取满足条件的第二个dom对象
    console.log($('.test').get('1'));    //<div class="test">2</div>
    
    //传递参数 - 非数字字符串'xxx' - 为undefined - 源于数组本身特性
    console.log($('.test').get('xxx'));    //undefined
    
</script>

  以上测试均没有报错,似乎已经满足了我们的功能需要。

  但是,我们读它的源码,似乎依旧可以提高代码的健壮性。如——

    1> 判断参数num是否为数字
    2> 判断参数num的绝对值是否小于数组长度

  对于该段源码的健壮性改造如下——

jQuery.prototype.get = function ( num ) {
    
    //判断是否为null
    if ( num == null ) {
        return this.toArray();
    }
    
    //判断是否为数字
    if ( typeof num === 'number' ) {
        
        //判断参数绝对值是否小于数组长度
        if ( Math.abs(num) < this.length ) {
            
            if ( num > 0 ) {    //为正数
                return this[ num ];
            } else {    //非正数
                return this[ this.length + num ];
            }
            
        }
        
    }
    
};

  这样,不仅增强了代码的健壮性,更加增强了其功能性代码的可读性。

  再举一个例子吧——

 1 (function ($) {
 2     
 3     var events = {
 4         show: function (opt) {
 5             
 6             //这里对opt判空,如果为空,赋予一个默认值空对象{}
 7             opt = opt || {};
 8             
 9             //接下访问opt对象里的属性,就无须判断opt是否为空了
10             if (opt.xxx) {    //替换 opt && opt.xxx
11                 //todo something
12             }
13             
14             if (opt.yyy) {    //替换 opt && opt.yyy
15                 //todo something
16             }
17             
18         },
19         close: function (opt) {
20             //...
21         },
22         lock: function (opt) {
23             //...
24         },
25         top: function (opt) {
26             //...
27         }
28     };
29     
30     $('.m-dialog').die('m-dialog').live('m-dialog',function(events){
31         
32         return function(opt){
33             
34             //一层一层做参数校验
35             opt
36             && opt.event
37             && opt.event in events
38             && typeof events[opt.event] === 'function'
39             && events[opt.event].apply(this, arguments);
40             
41             return false;
42         }
43         
44     }(events));
45     
46 })(jQuery);

  以上两个例子,更多的是涉及到的基础判空的处理情况,并没有涉到相关的业务方面的内容。可能这个部分是比较通用的,也是要常常需要去做处理的。当涉及到业务时,需要根据根据业务再去做对应极限值处理。

  在JavaScript的极限值处理过程中,绝大部分的处理均源于对函数参数的极限值处理,因此,我们不单单要关注每一个函数的功能单一性,同事我们需要培养对每一个函数参数做极限值处理的敏感思维,这样,回头我们才会觉得,我们的代码是健壮的、是经得起考验的。更加重要的是,免去了测试同学与程序同学来来回回的bug提交与修复的重复性工作,提高工作效率。

  

  三、总结

  这里,我仅仅对前端的两个方面做了极限值处理的介绍,如下——

    1> 页面显示的极限值处理
    2> JavaScript函数参数的极限值处理

  当然,还有一个比较明显的部分便是JavaScript控制传递参数到后端的极限值处理情况,也就是参数校验了。有关这部分,就请看我的谈谈代码健壮性之前端校验这篇文章吧。我想这部分更多的极限值处理,便是赋予其传参默认值了。

  

  四、扩展

  这里仅仅介绍了前端方面的极限值处理,不妨联想一下后端的极限值处理,相信同样如是。提高代码健壮性,这些极限值便不得不多去思考。

  再扩展来说,无非是说对数据流的极限值的处理,我们需要对数据流的存储、流向、更改的任何步骤都需要考虑每一个数据项的极限值,并采用合理方式处理之,方能提高代码健壮性。

 

  欢迎各位博友批评指教,对处理极值有什么好的点子,不妨提供上来,一起讨论、一起学习。

 

posted @ 2013-05-27 16:05  金广国  阅读(2407)  评论(4编辑  收藏  举报