避免面条式代码,提高代码的可阅读和可维护性.
避免散乱的ifelse
- 问题
根据不同条件编写条件语句是开发中很常见的情景, 很容易写出类似如下代码:
if (element.getType().equals(JOUR)) {
// return buildJourney()
}
if (isTraffic(element))) {
// return buildTraffic()
}
if (isPOI(element)) {
// return buildPOI()
}
// more conditions...
- 上述面条式代码很难维护
- 复用性不强, 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);
....
- 问题
- 代码逻辑不清晰
- 没有复用性, 比如下次业务需要组装个有发布时间、发布人的对象, 类似代码又得重写一遍
- 变更不友好, 由于代码杂糅在一起, 改一处可能产生更多问题
- 解决思路
抽离出属性接口, 数据类根据需要实现相关接口, 即可按需组装出实体类<br />
- 示例
- 接口定义(根据属性)
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);
}
}
- 组装类
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();
- 如何复用<br />以上“title中替换某些变量” 、 “对阅读数做处理” 的逻辑在这种实现方式下可以有效复用, 有更多场景需求时, 可根据场景需求快速搭出所需结构, 如透出答案列表:
public class Scenes2 extends AbstractField
implements TitleField, AnswerField {
public ScenesSummary(Map<String, Object> properties) {
super(properties);
}
}
- 支持组件
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 }
网友评论