美文网首页
如何避免使用continue和break

如何避免使用continue和break

作者: yiltoncent | 来源:发表于2017-03-07 23:48 被阅读249次

    大神王垠说避免使用continue和break。他说:

    出现continue或者break的原因,往往是对循环的逻辑没有想清楚。如果你考虑周全了,应该是几乎不需要continue或者break的。如果你的循环里出现了continue或者break,你就应该考虑改写这个循环。改写循环的办法有多种:

    1.如果出现了continue,你往往只需要把continue的条件反向,就可以消除continue。
    2.如果出现了break,你往往可以把break的条件,合并到循环头部的终止条件里,从而去掉break。
    3.有时候你可以把break替换成return,从而去掉break。
    4.如果以上都失败了,你也许可以把循环里面复杂的部分提取出来,做成函数调用,之后continue或者break就可以去掉了。

    现在我有这样一段代码:代码要处理的就是检查name中有没有非规范的字符。代码功能是没有问题的。

    int is_valid_name(char *name, uint16_t len)
    {
        char regularChrs[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
        char tmp[sizeof(uint16_t)]={0};
        uint16_t regLen,i,j; 
        if(len <= 0)
            return 1;   
        memcpy(tmp, name, len);
        
        regLen = strlen(regularChrs);
        for(i=0; i<len; i++)
        {
            for(j=0; j<regLen; j++)
            {
                if(tmp[i] != regularChrs[j])
                    continue;
                else
                    break;
            }
            if(j == regLen)
            {
                return 1;
            }
        }
        return 0;
    }
    

    如何改写呢?

    这里要注意,我们的核心逻辑里面包含了continebreak,其控制流程如下图所示:

    QQ图片20151207160802.jpg-1034kBQQ图片20151207160802.jpg-1034kB

    但仔细分析一下,我们可以发现continue语句是无效的语句,因为即使没有它,控制流程也能返回。那么实际的逻辑语句是:

            ...
            for(j=0; j<regLen; j++)
            {
                if(tmp[i] == regularChrs[j])
                    break;
            }
            ...
    

    改写步骤

    现在情况简单明了,只需要将break语句消除掉即可,方法是:如果出现了break,你往往可以把break的条件,合并到循环头部的终止条件里,从而去掉break。并将中间循环提取出来形成一个单独函数,用于判断一个字符是否是规范字符,以使表达更简洁;另外对于返回值,要根据函数语义进行修改。

    const char regularChrs[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
    uint16_t regLen = strlen(regularChrs);
    int is_valid_letter(char ch)
    {
        uint16_t index = 0;
        while(index < regLen && ch != regluarChrs[index])
        {
            index += 1;
        }
        //退出循环后可以判断是否遍历完regularChrs。
        //如果遍历完,说明ch不等于规范字符串中的任何一个。invalid
        if(j == regLen)
        {
            return 0; //invalid
        }else
        {
            return 1; //valid
        }
    }
    

    然后将原来代码中相关的逻辑处理用这个函数代替。

    int is_valid_name(char *name, uint16_t len)
    {
        uint16_t i = 0;
        
        if(len <= 0)
            return 0;
        
        while(i < len && is_valid_letter(name[i]) )
        {
            i += 1;
        }
        //遍历完整个name发现都是valid letter,即name也是valid    
        if(i == len)
        {
            return 1; //valid
        }else
        {
            return 0; //invalid
        }
    }
    

    仔细观察上面两个函数,它们代码结构是相似的,不同在于两个函数的判断标准是相反的,因此返回值正好相反,这是为了保证函数语义的一致性
    这样的两个不同的逻辑判断如果在一个函数里面实现的时候,就会因为逻辑嵌套且上层与下层的逻辑实现不同而造成背离感,这种背离感是这种复杂逻辑所固有的。在我们修改版本中体现的更好。修改版本中,如果你保证上层与下层函数语义的一致性,那么就会失去实现逻辑的一致性;如果保证实现的一致性,那么就会失去函数语义的一致性。
    我们可以想象用is_invalid_letter函数来重新实现我们的功能。代码如下:

    int is_invalid_letter(char ch)
    {
        uint16_t index = 0;
        while(index < regLen && ch != regluarChrs[index])
        {
            index += 1;
        }
        //退出循环后可以判断是否遍历完regularChrs。
        //如果遍历完,说明ch不等于规范字符串中的任何一个。invalid
        if(j == regLen)
        {
            return 1; //invalid
        }else
        {
            return 0; //valid
       }
    }
    
    int is_valid_name(char *name, uint16_t len)
    {
        uint16_t i = 0;
        
        if(len <= 0)
            return 0;
        
        while(i < len && !is_invalid_letter(name[i]) )
        {
            i += 1;
        }
        //遍历完整个name发现都是valid letter,说明name也是valid   
        if(i == len)
        {
            return 1; //valid
        }else
        {
            return 0; //invalid
        }
    }
    

    此时大家会发现,两个函数实现逻辑已经是相同的了,但是函数语义已经背反。

    总结

    原始代码里面,看起来代码量比较少,但是语义不清,理解起来要困难很多,而且尤其是continuebreak在一起使用时,使整个代码逻辑上跟纠缠的面条一样,增加阅读难度。经过整理之后的代码,代码行数虽然增加,但是语义清晰,容易理解,而且同构的代码在逻辑层次上也能捋清了。
    大神诚不欺我矣。

    再进一步,这样的一个功能,还可以优化一下,函数接口参数增加对规范字符的支持。函数原型是:

    int is_valid_name(char *name, uint16_t len, char *regular, uint16_t reglen)
    

    至于实现,相信大家!

    相关文章

      网友评论

          本文标题:如何避免使用continue和break

          本文链接:https://www.haomeiwen.com/subject/ewvvgttx.html