美文网首页
编写可复用可阅读的代码

编写可复用可阅读的代码

作者: 王巨喜 | 来源:发表于2020-05-15 18:29 被阅读0次

    避免面条式代码,提高代码的可阅读和可维护性.

    避免散乱的ifelse

    • 问题
      根据不同条件编写条件语句是开发中很常见的情景, 很容易写出类似如下代码:
    if (element.getType().equals(JOUR)) {
        // return buildJourney()
    } 
    if (isTraffic(element))) {
        // return buildTraffic()
    } 
    if (isPOI(element)) {
        // return buildPOI()
    }
    // more conditions...
    
    1. 上述面条式代码很难维护
    2. 复用性不强, if场景判断在不同的场景中需要重复编写
    • 解决方案
      用枚举实现来代替散乱的ifelse<br />
    • 代码示例
     public class JourneyBuilder {
        enum JourType {
            JOUR {
                @Override
                boolean math(JourneyPlanCardIndex cardInfo, JourneyPlanIndex planInfo) {
                    return planInfo != null;
                }
    
                @Override
                SourceElemDTO build(JourneyPlanCardIndex cardInfo, JourneyPlanIndex planInfo) {
                    SourceElemDTO result = // build result
                    return result;
                }
            },
            POI {
                @Override
                boolean math(JourneyPlanCardIndex cardInfo, JourneyPlanIndex planInfo) {
                    // match poi/hotel/scenic..
                }
    
                @Override
                SourceElemDTO build(JourneyPlanCardIndex cardInfo, JourneyPlanIndex planInfo) {
                     SourceElemDTO result = // build result
                    return result;
                }
            },
         
            TRAFFIC {
                @Override
                boolean math(JourneyPlanCardIndex cardInfo, JourneyPlanIndex planInfo) {
                    // match bus train flight..
                }
    
                @Override
                SourceElemDTO build(JourneyPlanCardIndex cardInfo, JourneyPlanIndex planInfo) {
                    SourceElemDTO result = // build result
                    return result;
                }
            };
    
            abstract boolean math(JourneyPlanCardIndex JourneyPlanCardIndex, JourneyPlanIndex planInfo);
    
            abstract SourceElemDTO build(JourneyPlanCardIndex JourneyPlanCardIndex, JourneyPlanIndex planInfo);
        }
     }
    ​
    ​
    // 使用
     targetTypes.stream().filter(jt -> jt.math(cardInfo, planInfo))
                .map(jt -> jt.build(cardInfo, planInfo))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
       
     在不同场景中, 只要把targetTypes替换下就行
    

    可复用的业务逻辑校验

    • 问题

      业务中有很多参数/规则需要校验, 判断和校验规则散乱在代码中严重影响可维护性.比如:
    // scenenA:
    if(model.getTable().startsWith("__test__")){
        //do action
    }
    ​
    // sceneB:
    if(model.getAge() >= 18){
        if(now.getDayOfWeek() == DayOfWeek.WEDNESDAY){
            // do action
        }else{
            // do others
        }
    }
    ​
    // sceneC:
    if(now.getDayOfWeek() != DayOfWeek.WEDNESDAY){
        // throw IllegalArgumentException
    }
    

    • 解决思路
      将各个判断逻辑抽离成函数式断言, 使用的时候根据场景组合这些断言.<br />
    • 代码示例
    // 规则定义
    Predicate<Model> modelChecker = m -> whiteList.contains(m);
    Predicate<String> testTableChecker = tmp -> tmp.startsWith("__test__");
    Predicate<Integer> adultAgeChecker = age -> age >= 18;
    Predicate<LocalDate> closingDayChecker = date -> date.getDayOfWeek() == DayOfWeek.WEDNESDAY;
    ​
    // 校验器定义
    public class Checker<T> {
        private final T obj;
        private Predicate<T> predictor = T -> true;
    ​
        private Checker(T obj) { this.obj = obj; }
    ​
        public boolean check() {  return predictor.test(obj); }
        
        public static <T> Checker<T> of(T t) { return new Checker<>(Objects.requireNonNull(t));}
    ​
        public <R> Checker<T> append(Function<T, R> projection, Predicate<R> prediction) {
            predictor = predictor.and(projection.andThen(prediction::test)::apply);
            return this;
        }
    }
    ​
    ​
    // 使用, 根据场景在append中自由组合条件即可
     boolean check = Checker.of(model)
                .append(Function.identity(), modelChecker)
                .append(Model::getAge, adultAgeChecker)
                .append(Model::getClosingDay, closingDayChecker)
                .check();
    ​
    ​
    

    可复用的数据抽取

    • 场景
      将外部非结构化数据专程业务处理需要的类型数据(如将map转为class), 是开发中很常见的情形, 比如从数据库查询、静态数据导入、第三方接口导入等.<br />常见实现:
    List<Map<String, Object>> questions = // data queried from somewhere
        for (Map<String, Object> qaMap : questions) {
            Long questionId = Long.parseLong(Objects.toString(qaMap.get("id")));
            ScenesSummary ssItem = new ScenesSummary();
            ssItem.setTitle(Objects.toString(qaMap.get("title"), StringUtils.EMPTY).trim());
            ssItem.setReadCnt(NumberUtils.toLong(Objects.toString(qaMap.get("readCount")), 0L));
            ssItem.setQuestionId(questionId);
            ....
        
    


    • 问题
    1. 代码逻辑不清晰
    2. 没有复用性, 比如下次业务需要组装个有发布时间、发布人的对象, 类似代码又得重写一遍
    3. 变更不友好, 由于代码杂糅在一起, 改一处可能产生更多问题


    • 解决思路

      抽离出属性接口, 数据类根据需要实现相关接口, 即可按需组装出实体类<br />
    • 示例
    1. 接口定义(根据属性)

    public interface TitleField extends PropertyField {
    ​
        default String getTitle() {
            Object value = get("title");
            // title中替换某些变量等
            return Objects.toString(value, StringUtils.EMPTY).trim();
        }
    }
    ​
    public interface ReadCountField extends PropertyField {
    ​
        default Long getReadCnt() {
            Object value = get("readCount");
            // 对阅读数做处理
            return NumberUtils.toLong(Objects.toString(value), 0L);
        }
    }
    ​
    public interface QuestionIdField extends PropertyField {
    ​
        default Long getQuestionId() {
            Object value = get("id");
            return Long.parseLong(Objects.toString(value));
        }
    }
    ​
    public class Answer extends AbstractField implements TitleField, LikeCntField {
    ​
        protected Answer(Map<String, Object> properties) {
            super(properties);
        }
    }
    ​
    public interface AnswerField extends PropertyField {
    ​
        default List<Answer> getAnswerList() {
            return listFields("answers", Answer::new);
        }
    }
    ​
    ​
    public interface LikeCntField extends PropertyField {
    ​
        default int getLikeCnt() {
            Object value = get("id");
            return Integer.parseInt(Objects.toString(value), 0);
        }
    }
    

    1. 组装类
    public class ScenesSummary extends AbstractField
            implements TitleField, QuestionIdField, ReadCountField {
    ​
        public ScenesSummary(Map<String, Object> properties) {
            super(properties);
        }
    }
    ​
    // 使用
    Map<String, Object> data = //data from bizql query
    ScenesSummary ss = new ScenesSummary(data);
    ss.getTitle();
    

    1. 如何复用<br />以上“title中替换某些变量” 、 “对阅读数做处理” 的逻辑在这种实现方式下可以有效复用, 有更多场景需求时, 可根据场景需求快速搭出所需结构, 如透出答案列表:
    public class Scenes2  extends AbstractField
            implements TitleField, AnswerField {
    ​
        public ScenesSummary(Map<String, Object> properties) {
            super(properties);
        }
    }
    

    1. 支持组件
    public interface PropertyField {
        /**
         * 设置属性
         */
        void set(String key, Object value);
    ​
        /**
         * 获取属性
         */
        Object get(String key);
    ​
        /**
         * 复杂对象字段
         */
        <T> T objectField(String key, Function<Map<String, Object>, T> constructor);
    ​
        /**
         * 复杂对象字段列表
         */
        <T> List<T> listFields(String key, Function<Map<String, Object>, T> constructor);
    }
    ​
    ​
    ​
    public class AbstractField implements PropertyField {
        private final Map<String, Object> properties;
    ​
        protected AbstractField(Map<String, Object> properties) {
            Objects.requireNonNull(properties);
            this.properties = properties;
        }
    ​
        @Override
        public void set(String key, Object value) {
            this.properties.put(key, value);
        }
    ​
        @Override
        public Object get(String key) {
            return properties.get(key);
        }
    ​
        @Override
        public <T> T objectField(String key, Function<Map<String, Object>, T> constructor) {
            Optional<T> opt = Optional.ofNullable(properties.get(key))
                .filter(Objects::nonNull)
                .map(sub -> (Map<String, Object>)sub)
                .map(constructor);
            return opt.orElse(null);
        }
    ​
        @Override
        public <T> List<T> listFields(String key, Function<Map<String, Object>, T> constructor) {
            List<Map<String, Object>> sublist = (List<Map<String, Object>>)properties.get(key);
            if (CollectionUtils.isEmpty(sublist)) {
                return null;
            }
    ​
            return sublist.stream()
                .map(item -> constructor.apply(item))
                .collect(Collectors.toList());
        }
    }
    


    <br />

    继承结构和范型

    • 问题
      在基类中需要处理获取范型的实际类型, 而范型定义(T)本身不能获得实际类型(T.getClass())
    • 解决思路
      范型是编译时特性, 因此在运行时完全可以确定类型(通过getActualTypeArguments获取)
    • 代码示例
    public abstract class OrderProcessor<T> {
        // 子类的具体类型
        private Class<T> snapType;
    ​
        public JingweiProcessor() {
            this.snapType = (Class<T>)((ParameterizedType)getClass()
                         .getGenericSuperclass()).getActualTypeArguments()[0];
        }
        
        protected T toObject(Map<String, Serializable> rowDataMap) {
            String jsonStr = JSON.toJSONString(rowDataMap);
            // 这里需要用到范型的具体类型
            return JSONObject.parseObject(jsonStr, snapType);
        }
        
        protected void process() {
            T order = toObject(rowDataMap)
            // do process
        }
    }
    


    可复用的分页遍历

    • 问题
      在业务处理中耦合数据库遍历, 不同的逻辑要写相同的查询-foreach代码<br />
    • 解决思路
      将数据库分页遍历从逻辑代码里抽离出来.<br />
    • 示例
    public abstract class PagedIterator<T> implements Iterator<T> {
        protected long minId = 0;
        // 批量结果
        private List<T> batchResult;
        private Iterator<T> innerIterator;
        
        @Override
        public boolean hasNext() {
            boolean init = false;
            if (batchResult == null) {
                init = true;
                batchResult = batchQuery();
                innerIterator = batchResult.iterator();
            }
    ​
            if (innerIterator.hasNext()) {
                return true;
            } else if (!init) {
                // 寻找下一批
                batchResult = batchQuery();
                innerIterator = batchResult.iterator();
                return innerIterator.hasNext();
            }
            return false;
        }
    ​
        /**
         * 批量查询
         */
        protected abstract List<T> batchQuery();
    ​
        /**
         * 查询对象的id
         */
        protected abstract long getId(T obj);
    ​
        @Override
        public T next() {
            T item = innerIterator.next();
            minId = Math.max(minId, getId(item));
            return item;
        }
    }
    ​
    // 使用
     SourcePageIterator srcIterable = new SourcePageIterator(tableName, indexSourceDao, earliestEnd);
     for (IndexSourceDO source : srcIterable) {  // do action }
    



    相关文章

      网友评论

          本文标题:编写可复用可阅读的代码

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