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

编写可复用可阅读的代码

作者: 王巨喜 | 来源:发表于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 }



相关文章

  • 编写可复用可阅读的代码

    避免面条式代码,提高代码的可阅读和可维护性. 避免散乱的ifelse 问题根据不同条件编写条件语句是开发中很常见的...

  • 编写可复用的代码

    在日常开发中,我们经常听到这样的话:“把这段代码提成一个单独的方法(类),这样就可以在被复用了”,然而,我们由于我...

  • 《编写可读代码的艺术》读书笔记之第1章 代码应当易于理解

    关键思想1:代码应当易于理解 编写可维护、可复用、可扩展的代码,是好几代程序员孜孜追求的目标。但如此“远大”的目标...

  • Swift - 泛型

    泛型 泛型代码让你能根据自定义的需求,编写出适用于任意类型的、灵活可复用的函数及类型。你可避免编写重复的代码,而是...

  • 23.泛型

    泛型代码让你能根据自定义的需求,编写出适用于任意类型的、灵活可复用的函数及类型。你可避免编写重复的代码,而是用一种...

  • Swift学习笔记--泛型

    泛型 原文链接 泛型代码让你能根据你所定义的要求写出可以用于任何类型的灵活的、可复用的函数。你可以编写出可复用、意...

  • Swift-泛型

    泛型代码让你能根据你所定义的要求写出可以用于任何类型的灵活的、可复用的函数。你可以编写出可复用、意图表达清晰、抽象...

  • Vue学习的第十一天

    #编写可复用组件 在编写组件时,最好考虑以后是否要进行复用,可复用组件应当定义一个清晰的公开接口,同时也不要对其使...

  • 自下向上的编写容易阅读的代码(上)

    我在 关于极简编程的思考 中曾提到要编写可阅读的代码。因为代码是编写一次,阅读多次。 阅读者包括代码编写者,以及后...

  • 自下向上的编写容易阅读的代码方法(下 )

    我在 关于极简编程的思考 中曾提到要编写可阅读的代码。因为代码是编写一次,阅读多次。 阅读者包括代码编写者,以及后...

网友评论

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

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