Pattern : Expectation miss record

模式:记录预期未命中

问题

在我们的日常维护线上项目中,分析log是我们的主要任务之一。时常,我们会遇到这样的场景:线上的项目出了问题,我们抓到了log, 我们通过log找到了出问题的地方,但是为什么这里会出问题我们还是不清楚,有时出问题的地方并不是第一现场,我们还得沿着时间点向上一步一步仔细地查找,有时可能问题的根因离现场很远,我们找了半天还是没有找到,有时也可能因为缺少响应的log,使我们的分析无法继续下,从而不得不自己线下复现问题,或者添加log发测试版本给客户,让客户帮重现。

比如下面这个例子:

Example

class DataInfo
{
   // ..... 
}
 
DataInfo getDataInfoFromOther(Others o)
{
    if (o != null && someConditionIsTrue(o))
    {
        return new DataInfo(o);
    }
 
    return null;
}
 
DataInfo2 getDataInfo2(DataInfo dataInfo)
{
    if(dataInfo != null && someConditionIsTrue(dataInfo))
    {
        return new DataInfo2(dataInfo);
    }
 
    return null;
}
 
boolean handleOthers2(Others2 o2)
{
    if(o2 != null)
    {
        //handle with o2
 
        return true;
    }
 
    return false;
}
 
void doAction(Others o, Others2 o2)
{
    DataInfo2 dataInfo2= getDataInfo2(getDataInfoFromOther(o));
 
    if(dataInfo2 != null && handleOthers2(o2))
    {
        showDialogWith(dataInfo2);
    }
    
}

如何上面的dialog没有如预期一样展示出来,那么根因是什么呢?我们可以从代码逻辑上找出问题所在么?o为null, 或者o不满足条件检查?有或是 dataInfo 不满足条件检查?还是Others 为null ? 等等。

解决方案一

已经有一些现理论或模式讨论过或解决类似的问题,其中之一就是防御式编程或者Fail fast。上面的代码可以这样写:

class DataInfo
{
   // ....
}
 
DataInfo getDataInfoFromOther(Others o)
{
    checkNotNull(o);
 
    if (someConditionIsTrue(o))
    {
        return new DataInfo(o);
    }
    else
    {
        Log("some condition is not true of o");
    }
 
    return null;
}
 
DataInfo2 getDataInfo2(DataInfo dataInfo)
{
    checkNotNull(dataInfo);
 
    if(someConditionIsTrue(dataInfo))
    {
        return new DataInfo2(dataInfo);
    }
    else
    {
        Log("some condition is not true of dataInfo");
    }
 
    return null;
}
 
boolean handleOhters2(Others2 o2)
{
    checkNotNull(o2);
     
    // handle something
 
    return true;
}
 
void doAction(Others o, Others2 o2)
{
    DataInfo2 dataInfo2= getDataInfo2(getDataInfoFromOther(o));
    checkNotNull(dataInfo2);
    if(handleOthers2(o2))
    {
        showDialogWith(dataInfo2);
    }
}

checkNotNull 方法来自于google Guava 库的Condition类,它的作用是如果参数为null, 就抛出NPE。 现在,如果我们基于以上的代码再来分析为什么dialog没有显示出来,我们就会获得足够多的信息来分析。首先,没有crash,说明以上的checkNotNull 检查都通过了,然后我们在根据log, 来找出是哪里没有满足条件,这样我们就很容易定位到问题所在,不需要在复现或者是加log发版本了。

但是,NPE 对于上线的产品来说太粗暴了,有时我们仅仅想记录异常,而不是crash。 我们也可以像这样改造上面的代码:

解决方案二

class DataInfo
{
    // ....
}
 
DataInfo getDataInfoFromOther(Others o)
{
    if(o == null)
    {
        Log("o is null in getDataInfoFromOther");
        return null;
    }
 
    if (someConditionIsTrue(o))
    {
        return new DataInfo(o);
    }
    else
    {
        Log("some condition is not true of o");
    }
 
    return null;
}
 
DataInfo2 getDataInfo2(DataInfo dataInfo)
{
    if(dataInfo == null)
    {
        Log("dataInfo is null in getDataInfo2");
        return null
    }
 
    if(someConditionIsTrue(dataInfo))
    {
        return new DataInfo2(dataInfo);
    }
    else
    {
        Log("some condition is not true of dataInfo");
    }
 
    return null;
}
 
boolean handleOhters2(Others2 o2)
{
    if(o2 == null)
    {
        Log("o2 is null in handleOthers2");
        return false;
    }
  
    // handle something
 
    return true;
}
 
void doAction(Others o, Others2 o2)
{
    DataInfo2 dataInfo2= getDataInfo2(getDataInfoFromOther(o));
    if(dataInfo2 == null)
    {
        Log("dataInfo2 is null in doAction");
        return;
    }
 
    if(handleOthers2(o2))
    {
        showDialogWith(dataInfo2);
    }
}

上面的代码也可以工作,但是显得比较啰嗦,也降低了代码的流畅性和可读性,而且,所有的程序员都是懒惰的,很少有程序员能坚持这样添加log,但是,我们可以让我们自己的生活更美好一点,不是吗?

记录预期未命中模式

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static indi.arrowyi.emr.Emr.*;
import static org.junit.jupiter.api.Assertions.*;

class DataInfo
{
    // ....
}

DataInfo getDataInfoFromOther(Others o)
{
    if (notNULL(o) && beTrue(someConditionCheck(o)))
    {
        return new DataInfo(o);
    }
    
    return null;
}
    
DataInfo2 getDataInfo2(DataInfo dataInfo)
{
    if(notNull(dataInfo) 
            && beTrue(someConditionIsTrue(dataInfo)))
    {
        return new DataInfo2(dataInfo);
    }
    
    return null;
}

boolean handleOhters2(Others2 o2)
{
    if(notNull(o2)){
        // handle something
        return true;
    }
    
    return false;
}

void doAction(Others o, Others2 o2)
{
    if(notNull(getDataInfo2(getDataInfoFromOther(o)))
    && handleOthers2(o2)) 
    {
        showDialogWith(dataInfo2);
    }
}

看!上面的代码又恢复了流畅性和可读性,而且在每一个分支判断上,如果不符合我们的预期,都会有log记录下来。
打印出的log像下面这样:

should not be null
Expectation miss record : at EmrTest.java
indi.arrowyi.emr.EmrTest  
testBeTrue  53

有文件名,类名,方法名,行号,通过这些信息我们很容易找到哪里不符合我们的流程预期,从而做跟进一步的调查。

现有库

好了, 以上就是我这里介绍的记录预期未命中 模式,这里有一个现成的Java库可以直接使用:传送门

Kotlin版本 EmrKotlin

希望这个模式 可以让你的生活更美好一些 。

注意

这个模式只是在,***“预期”***未达到时才适用,一般的判断,比如正反分支都有意义的情况不要乱用这个模式,应为这个模式作为一道防线,资源消耗是比较大的,只是用在正常流程下不会发生的情况的一个保护,这样的情况应该很少出现的,这样对于资源消耗来说也才就不是问题。

你可能感兴趣的:(Android,DesignPattern,java,android)