美文网首页Android开发Android开发经验谈Android开发
关于入参校验,有比用if else判断的更好方式

关于入参校验,有比用if else判断的更好方式

作者: 生活简单些 | 来源:发表于2018-11-20 08:24 被阅读30次
    validation.jpg

      很多时候面对业务性质地项目,功能实现容易,但是维护起来不容易,当代码量达到某个级别,如果没有一些基本地策略很难维护好现有地代码质量。其中,参数乱传导致运行期间某名奇怪的错误和崩溃会让你头疼。其实,不管任何项目都会涉及到传参行为,然后会涉及到参数校验问题,很多时候是写个方法统一if else 判断各个参数是否合法,甚至有人在代码内部在使用入参的时候才开始判断是否为空等工作,效率之低,问题之大,面对此现象如何化解呢?
      Android项目当然也逃不过,早期本人跟同事们也是这么干的,类似如下:

    public static void startActivity(Context context, String name, String email, String phone, int age){
        if (ValidateUtils.isEmpty(name) 
            && ValidateUtils.validateEmail(email)
            && ValidateUtils.validatePhone(phone)
            && ValidateUtils.isLargeThanZero(age)) {
        
        Intent intent = new Intent(context, NextActivitity.class);
        intent.putExtra(EXTRA_NAME, name);
        intent.putExtra(EXTRA_EMAIL, email);
        intent.putExtra(EXTRA_PHONE, phone);
        intent.putExtra(EXTRA_AGE, age);
        context.startActivity(intent);
    }
    
    // 举一个validate方法的例子
    public static boolean validatePhone(String phoneNumber) {
        return TextUtils.isEmpty(phoneNumber) && phoneNumber.length() != 11;
    }
    

    看起来还可以,参数问题也的确能帮助排查问题,就是累人,而且还不可以自定义错误提示信息。

      其实,Java早就有Bean Validation 2.0的标准,在Hibernate项目中得到了充分利用,其中定义了很多常见的Annotation,如@NotNull,@Min, @Max, @Size 等等。但是,在Android中想要使用他们,需要写注解解析器。所以,今天我们就是来写注解解释器的。
    首先,让我们看看使用Demo:

    class People {
        @NotNull
        @NotEmpty
        List<Child> children = new ArrayList<>();
    
        @NotNull
        Child[] array;
    }
    
    class Child {
        @NotNull
        @Len(len = 4)
        @NotBlank
        String name;
    
        @Min(min = 10)
        @Range(min = 1, max = 20)
        int age;
    
        @Max(min = 200)
        int height = 190;
    
        Child(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
    
    // 通过注解校验器校验java bean
    People people = new People();
    people.array = new Child[]{new Child("hell", 18)};
    people.children.add(new Child("hhee", 10));
    boolean valid = Validator.validate(v.getContext(), people);
    if (valid){
        // do something like: start new Activity, new Service or call some api
        Toast.makeText(MainActivity.this, "params are valid", Toast.LENGTH_SHORT).show();
    }
    

    显而易见,以上Demo使用了七个Annotation:


    1. @NotNull:不能为null;
    2. @Len:长度必须为指定的;
    3. @NotBlank:字符串不能为空白;
    4. @Min:最小不能小于指定数字;
    5. @Max:最大不能大于指定数字;
    6. @Range:数字范围必须在指定的最小和指定的最大之间;
    7. 其实,可以根据业务自由扩充,比如@Email,@Phone,@IPV4等等

    关于如何实现,以下举几个例子:

    1. 首先,定义Annotation,每个Annotation内部都提供了message()用于定义校验不通过的提示文案:
    // @NotNull
    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface NotNull {
        String message() default "%s不能为null";
    }
    
    // @Range
    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Range {
        int min();
        int max();
        String message() default "%s不在范围%d和%d之间";
    }
    

    message()方法默认返回模版错误提示内容,但具体场景可以自定义,即:返回一个固定场景的提示语,如:“用户名不能为空”。

    1. 对Annotation的解析器进行抽象:
    public abstract class ConstraintValidator<A extends Annotation> {
        protected A annotation;
        protected String fieldName;
    
        public abstract boolean isValid(Object value);
    
        ConstraintValidator(A annotation, String fieldName){
            this.annotation = annotation;
            this.fieldName = fieldName;
        }
    
        public abstract String getMessage();
    }
    
    1. 定义Annotation解析器:
    class NotNullValidator extends ConstraintValidator<NotNull> {
        NotNullValidator(NotNull annotation, String fieldName) {
            super(annotation, fieldName);
        }
      
        /**
         * 先尝试取默认内容(根据是否包含%),否则取用户自定义内容
         */
        @Override
        public String getMessage() {
            String message = annotation.message();
            if (message.contains("%")) {
                return String.format(annotation.message(), fieldName);
            } else {
                return message;
            }
        }
    
        @Override
        public boolean isValid(Object value) {
            return value != null;
        }
    }
    

    每个Annotation对应一个解析器,同样类似的还有LenValidator、MaxValidator、MinValidator等等,一共七个。

    1. 对外Annotation校验器, 尝试所有支持的Annotation对指定的对象进行校验,如果校验通过则返回true,否则返回false并弹Toast,Toast内容为getMessage()返回内容:
    public class Validator {
        private static final String TAG = "Validator";
    
        /**
         * Validate fields under object.
         */
        public static boolean validate(Context context, Object object) {
            if (object == null) {
                return false;
            }
    
            // ignore String and Number
            if (object instanceof String || object instanceof Number) {
                throw new IllegalArgumentException("String or Number instance is not supported");
            }
    
            // check fields in java.util.List
            if (object instanceof List) {
                List list = (List) object;
                for (Object item : list) {
                    boolean valid = validate(context, item);
                    if (!valid) {
                        return false;
                    }
                }
    
                return true;
            }
    
            // check fields in array
            if (object.getClass().isArray()) {
                Object[] array = (Object[]) object;
                for (Object item : array) {
                    boolean valid = validate(context, item);
                    if (!valid) {
                        return false;
                    }
                }
    
                return true;
            }
    
            // check fields of object
            Field[] fields = object.getClass().getDeclaredFields();
            for (Field field : fields) {
                // ignore static and final
                int modifiers = field.getModifiers();
                boolean isStatic = Modifier.isStatic(modifiers);
                boolean isFinal = Modifier.isFinal(modifiers);
                if (isStatic || isFinal) {
                    continue;
                }
    
                try {
                    field.setAccessible(true);
                    Object value = field.get(object);
                    Annotation[] annotations = field.getAnnotations();
                    if (annotations.length > 0) {
                        for (Annotation annotation : annotations) {
                            // validate object its self
                            boolean valid = validateWithAnnotation(context, annotation, value, field.getName());
                            if (!valid) {
                                return false;
                            }
                        }
                    }
    
                    // validate fields of object, but make sure it's not String or Number
                    if (!(value instanceof String) && !(value instanceof Number)) {
                        boolean valid = validate(context, value);
                        if (!valid) {
                            return false;
                        }
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                    return false;
                }
            }
    
            return true;
        }
    
        /**
         * Validate value with annotations that we support.
         *
         * @param context    Android context
         * @param annotation annotation to validate with
         * @param object     object to validate
         * @return return true when the object passes validation
         * or the annotation we don't support
         */
        private static <A extends Annotation> boolean validateWithAnnotation(Context context, A annotation, Object object, String fieldName) {
            ConstraintValidator validator = null;
            if (annotation instanceof NotNull) {
                validator = new NotNullValidator((NotNull) annotation, fieldName);
            } else if (annotation instanceof NotEmpty) {
                validator = new NotEmptyValidator((NotEmpty) annotation, fieldName);
            } else if (annotation instanceof Min) {
                validator = new MinValidator((Min) annotation, fieldName);
            } else if (annotation instanceof Max) {
                validator = new MaxValidator((Max) annotation, fieldName);
            } else if (annotation instanceof Range) {
                validator = new RangeValidator((Range) annotation, fieldName);
            } else if (annotation instanceof Len){
                validator = new LenValidator((Len) annotation, fieldName);
            } else if (annotation instanceof NotBlank){
                validator = new NotBlankValidator((NotBlank) annotation, fieldName);
            }
    
            if (validator != null) {
                boolean valid = validator.isValid(object);
                if (!valid) {
                    Log.e(TAG, validator.getMessage());
                    Toast.makeText(context, validator.getMessage(), Toast.LENGTH_SHORT).show();
                    return false;
                }
            }
    
            return true;
        }
    }
    

    当然,对外入口API只有一个:
    boolean valid = Validator.validate(context, object);

      在随后的开发工作中,它将适用于团队合作的代码中,如每个页面或者Service的入参校验,每个自定义View的初始化入参校验,甚至每个封装的library对外公开的api入参校验等等。
      首先,请求参数类对象打开就能看到每个字段的各自要求(不能为空,长度限制等),然后自己就有意识地传正确地参数给对方;
      其次,即便不小心传错参数,Validator会校验提醒你错在哪边,无需对方开发同事停下手头工作帮你看你的问题,是不是这种工作方式也算增加了工作流的并发呢?
    关于实现你可以参考这里

    相关文章

      网友评论

        本文标题:关于入参校验,有比用if else判断的更好方式

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