业务发开中,其实大部分的代码用的技术都不是很深,大部分呢都是基本的CRUD的开发,但是很矛盾的是为什么大家还是苦逼的加班,熬夜。一方面工作不复杂,一方面却累成狗,问题到底出在哪里,时间都花到哪里去了?
其实大部分人的大部分时间都是在 定位问题 + 改代码,真正开发的时间并不多。定位问题包括开发转测试的时候发现问题和上线后发现问题,改代码的包括改bug和因为需求变动修改代码。
其实,对于个人来说,技术很重要,但是对于工作来说,编码的习惯比技术更加主要。工作中你面试的大部分技术都不需要用到的。工作中,因为你的编码习惯不好,写的代码质量差,代码冗余重复多,很多无关的代码和业务代码搅在一起,导致了你疲于奔命应付各种问题。
后面我会把我们系统中大家编码的问题一个一个写出来,并把我的解决办法分享出来。
第一篇 关于Controller
Controller也就是API接口,工作中少不了要定义接口,系统间的、前后台的。 但是经常会看到很多不规范的接口定义:
返回的格式不统一
同一个接口,有时候返回数组,有时候返回单个;成功的时候返回对象,失败的时候返回错误信息字符 串。工作中有个系统集成就是这样定义的接口,真是辣眼睛。这个对应代码上,返回的类型是map,json, object,都是不应该的。实际工作中,应该定义一个统一的格式,就是ResultBean
错误范例:
//返回map可读性不好,尽量不要
@PostMapping("/delete")
public Map<String, Object> delete(long id, String lang) {
}
// 成功返回boolean,失败返回string,大忌
@PostMapping("/delete")
public Object delete(long id, String lang) {
try {
boolean result = configService.delete(id, local);
return result;
} catch (Exception e) {
log.error(e);
return e.toString();
}
}
没有考虑失败情况
一开始只考虑成功场景,等后面测试发现有错误情况,怎么办,改接口呗,前后台都改,劳民伤财无用功。
错误范例:
//不返回任何数据,没有考虑失败场景,容易返工
@PostMapping("/update")
public void update(long id, xxx) {
}
出现和业务无关的输入参数
当前用户信息不应该出现参数里面,应该从当前会话里面获取。后面讲ThreadLocal会说到怎么样去掉。除了代码可读性不好问题外,尤其是参数出现当前用户信息的,这是个严重问题。 错误范例:
//(当前用户删除数据)参数出现lang和userid,尤其是userid,大忌
@PostMapping("/delete")
public Map<String, Object> delete(long id, String lang, String userId) {
}
出现复杂的输入参数
一般情况下,不允许出现例如json字符串这样的参数,这种参数可读性极差。应该定义对应的bean。
错误范例:
// 参数出现json格式,可读性不好,代码也难看
@PostMapping("/update")
public Map<String, Object> update(long id, String jsonStr) {
}
没有返回应该返回的数据
例如,新增接口一般情况下应该返回新对象的id标识。别人要不要是别人的事情,你该返回的还是应该返回。
错误范例:
// 约定俗成,新建应该返回新对象的信息,只返回boolean容易导致返工
@PostMapping("/add")
public boolean add(xxx) {
//xxx
return configService.add();
}
如何解决呢
定义一个通用的返回bean:
/**
* 接口通用的返回bean
*
* @author BENJAMIN
*
* @param <T>
*/
public class ResultBean<T> implements Serializable {
/**
*
* @author Administrator TODO description
* @date 2017年4月27日
*/
public ResultBean() {
super();
}
/**
*
*/
private static final long serialVersionUID = -1092646848721671618L;
/**
* 通用API调用成功
*/
public final static Integer success = 200;
/**
* 通用的验证参数失败
*/
public final static Integer validFaild = 400;
private long ElapsedMilliseconds;
public long getElapsedMilliseconds() {
return ElapsedMilliseconds;
}
public void setElapsedMilliseconds(long elapsedMilliseconds) {
ElapsedMilliseconds = elapsedMilliseconds;
}
public final static Integer errorUnknown = 900;
public final static Integer errorDB = 901;
private Integer resultCode;
public Integer getResultCode() {
return resultCode;
}
public void setResultCode(Integer resultCode) {
this.resultCode = resultCode;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public String getErrMsg() {
return errMsg;
}
public void setErrMsg(String errMsg) {
this.errMsg = errMsg;
}
private T data;
private String errMsg;
/**
* 失败
*
* @param resultCode
* @param errMsg
*/
public ResultBean(Integer resultCode, String errMsg) {
super();
// 创建日期对象
Date d = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date = sdf.format(d);
this.resultCode = resultCode;
this.errMsg = errMsg;
this.isSuccess = false;
this.time = date;
}
/**
* 成功
*
* @param resultCode
* @param data
* @param errMsg
*/
public ResultBean(Integer resultCode, T data, String errMsg) {
super();
// 创建日期对象
Date d = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date = sdf.format(d);
this.resultCode = resultCode;
this.data = data;
this.errMsg = errMsg;
this.isSuccess = true;
this.time = date;
}
private boolean isSuccess;
private String time;
/**
* @return the time
*/
public String getTime() {
return time;
}
/**
* @param time
* the time to set
*/
public void setTime(String time) {
this.time = time;
}
public boolean isSuccess() {
return isSuccess;
}
public void setSuccess(boolean isSuccess) {
this.isSuccess = isSuccess;
}
}
通过一个AOP来环绕所有的API
@Component
@Aspect
@Order(5)
public class ApiAspect {
private Logger logger = LoggerFactory.getLogger(ApiAspect.class);
@Around("@annotation(com.yonyou.cloud.common.annotation.YcApi)")
public Object logServiceAccess(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
String className = pjp.getTarget().getClass().getName();
String methodName = pjp.getSignature().getName();
String fullMethodName = className + "." + methodName;
boolean needLog = false;
logger.info(fullMethodName + "将被调用");
Object result = null;
try {
result = pjp.proceed();
if (result instanceof ResultBean<?>) {
((ResultBean<?>) result).setSuccess(true);
}
} catch (Throwable e) {
if (result != null && result instanceof ResultBean) {
ResultBean<?> errorResult = (ResultBean<?>) result;
if (NumberUtil.isBlankChar(errorResult.getResultCode())) {
errorResult.setResultCode(ResultBean.errorUnknown);
}
if (StrUtil.isEmpty(errorResult.getErrMsg())) {
errorResult.setErrMsg(e.getLocalizedMessage());
}
} else {
if (e instanceof BizException) {
result = new ResultBean<Object>(((BizException) e).getCode(), e.getMessage());
} else {
result = new ResultBean<Object>(ResultBean.errorUnknown, e.getMessage());
}
}
logger.error(fullMethodName + "执行出错,详情:", e);
}
long end = System.currentTimeMillis();
long elapsedMilliseconds = end - start;
if (needLog) {
logger.info(fullMethodName + "执行耗时:" + elapsedMilliseconds + " 毫秒");
}
if (result != null && result instanceof ResultBean) {
((ResultBean<?>) result).setElapsedMilliseconds(elapsedMilliseconds);
}
try {
if (result != null && result instanceof ResultBean) {
ResultBean<?> e = (ResultBean<?>) result;
Object data = e.getData();
logger.info("返回值:" + data.toString());
}
} catch (Exception e) {
logger.error("error", e);
}
return result;
}
这样我们的Controller 就简化成:
@GetMapping("/all")
public ResultBean<Collection<Config>> getAll() {
return new ResultBean<Collection<Config>>(configService.getAll());
}
@PostMapping("/add")
public ResultBean<Long> add(Config config) {
return new ResultBean<Long>(configService.add(config));
}
@PostMapping("/delete")
public ResultBean<Boolean> delete(long id) {
return new ResultBean<Boolean>(configService.delete(id));
}
总结
Controller中的总结:
1 所有函数返回统一的ResultBean格式
没有统一格式,AOP无法玩。
2 ResultBean是controller专用的,不允许往后传!
3 Controller做参数格式的转换,不允许把json,map这类对象传到services去,也不允许services返回json、map。
一般情况下!写过代码都知道,map,json这种格式灵活,但是可读性差,如果放业务数据,每次阅读起来都比较困难。定义一个bean看着工作量多了,但代码清晰多了。
4 参数中一般情况不允许出现Request,Response这些对象
主要是可读性问题。一般情况下。
5 不需要打印日志
日志在AOP里面会打印,而且我的建议是大部分日志在Services这层打印。
下一篇我们来说说如何打印日志
关于规范的代码类库会放在这个git库中: https://github.com/ambluse/common-elegance