java优化代码常见套路

程序员的痛点(烂代码)

每次做完项目之后,自己想重新回顾一下以前写的代码,整理出一些东西,却发现如同看天书一般,头晕眼花,完全感觉不像自己的写的代码,辣眼睛,犹如下图
在这里插入图片描述
所以为了爱护本人的眼睛,所以觉得很有必要整理一下一些优化代码的套路…

首先说一个最重要的优化原则:代码优化是你觉得你代码很繁琐、阅读性很差的时候一定要马上优化,立刻马上,不管你现在有多忙,每天优化才叫重构,每年优化那叫重写

这个原则为什么重要?因为很多程序员会在写代码的时候说「先不优化了,等不忙的时候再优化」,然后……就没有然后了,我也是这样,所以就导致了大量捞比代码的产生

该如何优化代码

1、逻辑复杂的业务代码一定要有注释(可能你写的是爽了,后面维护你代码的人可能会想往你头上暴扣)

2、首先是变量名、方法名这些,命名一定要规范,千万别出现aa、bb这种命名,然后我们可以对我们的一些状态变量进行集中管理
这个什么意思呢,比如我们在项目中一个订单的状态,0代码已下单、1代表已付款、2代表交易中等等…这一大堆的状态代表数据。
可能前期我们写的时候印象很深刻,万一后期你要改动,又或者需求有变动?你确定你的一堆状态数字还记得吗

所以我们在项目开始初期就可以写一个工具类,来专门管理我们状态结果
比如

package com.javaxl.miaosha_02.result;

public class CodeMsg {
	
	private int code;
	private String msg;
	
	//通用的错误码
	public static CodeMsg SUCCESS = new CodeMsg(0, "success");
	public static CodeMsg SERVER_ERROR = new CodeMsg(500100, "服务端异常");
	public static CodeMsg BIND_ERROR = new CodeMsg(500101, "参数校验异常:%s");
	//登录模块 5002XX
	public static CodeMsg SESSION_ERROR = new CodeMsg(500210, "Session不存在或者已经失效");
	public static CodeMsg PASSWORD_EMPTY = new CodeMsg(500211, "登录密码不能为空");
	public static CodeMsg MOBILE_EMPTY = new CodeMsg(500212, "手机号不能为空");
	public static CodeMsg MOBILE_ERROR = new CodeMsg(500213, "手机号格式错误");
	public static CodeMsg MOBILE_NOT_EXIST = new CodeMsg(500214, "手机号不存在");
	public static CodeMsg PASSWORD_ERROR = new CodeMsg(500215, "密码错误");
	
	//订单模块 5004XX
	public static CodeMsg ORDER_NOT_EXIST = new CodeMsg(500400, "订单不存在");
	
	//秒杀模块 5005XX
	public static CodeMsg MIAO_SHA_OVER = new CodeMsg(500500, "商品已经秒杀完毕");
	public static CodeMsg REPEATE_MIAOSHA = new CodeMsg(500501, "不能重复秒杀");
	
	
	private CodeMsg( ) {
	}
			
	private CodeMsg( int code,String msg ) {
		this.code = code;
		this.msg = msg;
	}
	
	public int getCode() {
		return code;
	}
	public void setCode(int code) {
		this.code = code;
	}
	public String getMsg() {
		return msg;
	}
	public void setMsg(String msg) {
		this.msg = msg;
	}
	
	public CodeMsg fillArgs(Object... args) {
		int code = this.code;
		String message = String.format(this.msg, args);
		return new CodeMsg(code, message);
	}

	@Override
	public String toString() {
		return "CodeMsg [code=" + code + ", msg=" + msg + "]";
	}
}

在实际开发中如果项目比较大,甚至可以分模块来管理,每一个模块都专门写一个工具类来管理你的状态代码

3、尽量避免重复代码
当你发现某些代码重复出现的次数一多,你就应该有想法把它们抽取出来进行优化了

比如我们在做前后端分离项目的时候,后端每一个方法都需要返回固定的Json格式,以前我们是这样干的,我们可能会封装一个JSON格式的工具类JsonData,里面有3个参数,第一个是返回码、第二个是消息提示、第三个是结果集
比如我下面的登录方法

        public JsonData login(Staff staff){
            JsonData jsonData = null;
            Staff login = staffService.login(staff);
            if(login != null){
                //登录成功
                jsonData = new JsonData(1,"欢迎管理员"+staff.getStaffName()+"",login);
            }
            else{
                jsonData = new JsonData(0,"用户名或密码错误",login);
            }
            return jsonData;
        }

然后我们发现我们每次都要重复写我们的状态码、消息提示这些东西,那么我们就可以想办法优化一下了,在固定的地方写好,我们调用就好了,我们用泛型T指定类型,成功就返回成功的类型,失败了返回失败的类型

package com.p2p.p2pstaff.config;

public class Result<T> {
	
	private int code;
	private String msg;
	private T data;
	
	/**
	 *  成功时候的调用
	 * */
	public static  <T> Result<T> success(T data){
		return new Result<T>(data);
	}
	
	/**
	 *  失败时候的调用
	 * */
	public static  <T> Result<T> error(CodeMsg codeMsg){
		return new Result<T>(codeMsg);
	}
	
	private Result(T data) {
		this.data = data;
	}
	
	private Result(int code, String msg) {
		this.code = code;
		this.msg = msg;
	}
	
	private Result(CodeMsg codeMsg) {
		if(codeMsg != null) {
			this.code = codeMsg.getCode();
			this.msg = codeMsg.getMsg();
		}
	}
	
	
	public int getCode() {
		return code;
	}
	public void setCode(int code) {
		this.code = code;
	}
	public String getMsg() {
		return msg;
	}
	public void setMsg(String msg) {
		this.msg = msg;
	}
	public T getData() {
		return data;
	}
	public void setData(T data) {
		this.data = data;
	}
}

我们就在全局状态管理类中添加我们的失败状态

	//staff
	public static  CodeMsg STAFF_FAIL = new CodeMsg(0,"登录失败");

然后最终优化后的代码

        public Result<Staff> login(Staff staff){
            Staff login = staffService.login(staff);
            if(login != null){
            	//登录成功
                return Result.success(login);
            } else{
                return Result.error(CodeMsg.STAFF_FAIL);
            }
        }

前台后台两次md5加盐加密

后台md5加密相比大家是耳孰能详,我们的shiro等很多权限框架都用到了这一点,而在后台加密依然可能存在密码被截取的可能性。

想象你的密码在被加密前就已经被抓取到了那么加密还有什么用呢?也就是截取我们表单提交的内容,这个是有很多办法能够实现的,比如我们利用抓包工具等等,所以说密码一样存在泄漏的可能。
所以我们就有了在前台就先加密一次然后再提交到后台,这样就算截取到了也是我们加密后的密码了

所以我们需要在登录前进行密码处理

	//获取我们输入的密码
	var inputPass = $("#password").val();
	/* var g_passsword_salt="1a2b3c4d" */
	var salt = g_passsword_salt;
	var str = ""+salt.charAt(0)+salt.charAt(2) + inputPass +salt.charAt(5) + salt.charAt(4);
	var password = md5(str);

前台加密完后进入后台,用我们加密过的密码进行二次加密,我们两次加密的密码都要存入数据库的,不然我们登录是无法验证的

后台取出我们需要认证的盐,然后用我们的shiro去认证密码,这个工具类就和我们以前shiro使用的验证的是一样的

    /**
     * 进行密码验证
     *
     * @param credentials        未加密的密码
     * @param salt               盐
     * @param encryptCredentials 加密后的密码
     * @return
     */
    public static boolean checkCredentials(String credentials, String salt, String encryptCredentials) {
        return encryptCredentials.equals(createCredentials(credentials, salt));
    }
public String login(HttpServletResponse response, LoginVo loginVo) {
		if(loginVo == null) {
			throw new GlobalException(CodeMsg.SERVER_ERROR);
		}
		String mobile = loginVo.getMobile();
		String formPass = loginVo.getPassword();
		//判断手机号是否存在
		MiaoshaUser user = getById(Long.parseLong(mobile));
		if(user == null) {
			throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST);
		}
		//验证密码
		String dbPass = user.getPassword();
		String saltDB = user.getSalt();
		if(!PasswordHelper.checkCredentials(formPass, saltDB, dbPass)) {
			throw new GlobalException(CodeMsg.PASSWORD_ERROR);
		}
		//生成cookie
		String token	 = UUIDUtil.uuid();
		addCookie(response, token, user);
		return token;
	}

就这样两次加密就完成了

JSR303和全局异常处理

全局异常处理
如果系统发生了异常,不做统一异常处理,前端会给用户展示一大片看不懂的文字。做统一异常处理后当异常发生后可以给用户一个温馨的提示,不至于使用户满头雾水,所以一方面是为了更好的用户体验 如果不统一全局异常,服务端和前端在遇到异常的时候处理起来杂乱无章非常费力。所以另一方面是为了制定规范提高工作效率

我们这里也就是通过写一个全局异常处理类,来处理我们的运行异常,并给与相对应的提示,而不是返回500错误i西南西

package com.javaxl.miaosha_02.exception;

import java.util.List;

import javax.servlet.http.HttpServletRequest;

import com.javaxl.miaosha_02.result.CodeMsg;
import com.javaxl.miaosha_02.result.Result;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;


@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
	@ExceptionHandler(value=Exception.class)
	public Result<String> exceptionHandler(HttpServletRequest request, Exception e){
		e.printStackTrace();
		if(e instanceof GlobalException) {
			GlobalException ex = (GlobalException)e;
			return Result.error(ex.getCm());
		} else if(e instanceof BindException) {
			BindException ex = (BindException)e;
			List<ObjectError> errors = ex.getAllErrors();
			ObjectError error = errors.get(0);
			String msg = error.getDefaultMessage();
			return Result.error(CodeMsg.BIND_ERROR.fillArgs(msg));
		}else {
			return Result.error(CodeMsg.SERVER_ERROR);
		}
	}
}

我们的信息提示类,也就是前面接收的封装全局信息的类,去继承我们的全局异常处理类,来返回错误提示信息

package com.javaxl.miaosha_02.exception;


import com.javaxl.miaosha_02.result.CodeMsg;

public class GlobalException extends RuntimeException{

	private static final long serialVersionUID = 1L;
	
	private CodeMsg cm;
	
	public GlobalException(CodeMsg cm) {
		super(cm.toString());
		this.cm = cm;
	}

	public CodeMsg getCm() {
		return cm;
	}

}

JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation,官方参考实现是Hibernate Validator。

此实现与 Hibernate ORM 没有任何关系。 JSR 303 用于对 Java Bean 中的字段的值进行验证。
Spring MVC 3.x 之中也大力支持 JSR-303,可以在控制器中对表单提交的数据方便地验证。

我们大部分前台项目都是做了JS正则判断的代码,那么如果别人知道了你的请求地址,它是不是就能跳过你的js验证,直接去访问你的数据库的某一个方法呢?这当然是可以的,所以我们就需要用JSR303来处理这种请求

比如我们在注册的时候信息必须满足格式才能插入数据库,而果然跳过js验证,那么数据库就会多很多垃圾数据,这样肯定是不行的,所以我们就加了验证在后台

    public Result<String> doLogin(HttpServletResponse response, @Valid LoginVo loginVo) {
    	log.info(loginVo.toString());
    	//登录
    	String token = userService.login(response, loginVo);
    	return Result.success(token);
    }

我在外面登录的方法中加了自定义注解,验证格式是否正确,只有通过了验证才能访问方法,否则就进入异常处理
在这里插入图片描述
自定义注解的代码

package com.javaxl.miaosha_02.validator;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {IsMobileValidator.class })
public @interface  IsMobile {
	
	boolean required() default true;
	
	String message() default "手机号码格式错误";

	Class<?>[] groups() default { };

	Class<? extends Payload>[] payload() default { };
}

验证格式是否符合我们的要求

package com.javaxl.miaosha_02.validator;
import  javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import com.javaxl.miaosha_02.util.ValidatorUtil;
import org.apache.commons.lang3.StringUtils;


public class IsMobileValidator implements ConstraintValidator<IsMobile, String> {

	private boolean required = false;
	public void initialize(IsMobile constraintAnnotation) {
		required = constraintAnnotation.required();
	}
	public boolean isValid(String value, ConstraintValidatorContext context) {
		if(required) {
			return ValidatorUtil.isMobile(value);
		}else {
			if(StringUtils.isEmpty(value)) {
				return true;
			}else {
				return ValidatorUtil.isMobile(value);
			}
		}
	}
}

所以只要加了这个注解的就都会先进入验证才能访问数据库
所以我们最后直接通过错误格式并访问不了,而是进了我们的错误处理页面
在这里插入图片描述

Redis通用的key生成策略和通用的RedisService方法

通用key生成是个什么概念呢,也就相当于分组了,我们在项目中需要用到redis的地方肯定不止一个模块,肯定很多模块都需要用到redis,所以我们在存储的时候生成一个文件夹,然后每一个key的名字我们以固定的格式给它拼接上,就如下图效果
在这里插入图片描述
BasePrefix
我们通过放射获取类名,然后拼接上我们的prefix

package com.javaxl.miaosha_02.redis;

public abstract class BasePrefix implements KeyPrefix{
	
	private int expireSeconds;
	
	private String prefix;
	
	public BasePrefix(String prefix) {//0代表永不过期
		this(0, prefix);
	}
	
	public BasePrefix( int expireSeconds, String prefix) {
		this.expireSeconds = expireSeconds;
		this.prefix = prefix;
	}
	
	public int expireSeconds() {//默认0代表永不过期
		return expireSeconds;
	}

	public String getPrefix() {
		String className = getClass().getSimpleName();
		return className+":" + prefix;
	}
}

MiaoshaUserKey生成策略
这也就是生成我们的prefix和规定我们的过期时间的类
最终我们生成看到了就是我们的ClassName+prefix所生成的key

package com.javaxl.miaosha_02.redis;

public class MiaoshaUserKey extends BasePrefix{

	public static final int TOKEN_EXPIRE = 3600*24 * 2;
	private MiaoshaUserKey(int expireSeconds, String prefix) {
		super(expireSeconds, prefix);
	}
	public static MiaoshaUserKey token = new MiaoshaUserKey(TOKEN_EXPIRE, "tk");
	public static MiaoshaUserKey getById = new MiaoshaUserKey(0, "id");
}

通用的Redis操作类
高并发redis做缓存是很通用的手段
当数据量过大时操作redis就有可能出现重复的现象

然后我们通过泛型封装一个通用的存值、取值、自增、自减等操纵的方法,尽量来避免这些问题

package com.javaxl.miaosha_02.redis;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSON;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

@Service
public class RedisService {
	
	@Autowired
	JedisPool jedisPool;
	
	/**
	 * 获取当个对象
	 * */
	public <T> T get(KeyPrefix prefix, String key,  Class<T> clazz) {
		 Jedis jedis = null;
		 try {
			 jedis =  jedisPool.getResource();
			 //生成真正的key
			 String realKey  = prefix.getPrefix() + key;
			 String  str = jedis.get(realKey);
			 T t =  stringToBean(str, clazz);
			 return t;
		 }finally {
			  returnToPool(jedis);
		 }
	}
	
	/**
	 * 设置对象
	 * */
	public <T> boolean set(KeyPrefix prefix, String key,  T value) {
		 Jedis jedis = null;
		 try {
			 jedis =  jedisPool.getResource();
			 String str = beanToString(value);
			 if(str == null || str.length() <= 0) {
				 return false;
			 }
			//生成真正的key
			 String realKey  = prefix.getPrefix() + key;
			 int seconds =  prefix.expireSeconds();
			 if(seconds <= 0) {
				 jedis.set(realKey, str);
			 }else {
				 jedis.setex(realKey, seconds, str);
			 }
			 return true;
		 }finally {
			  returnToPool(jedis);
		 }
	}
	
	/**
	 * 判断key是否存在
	 * */
	public <T> boolean exists(KeyPrefix prefix, String key) {
		 Jedis jedis = null;
		 try {
			 jedis =  jedisPool.getResource();
			//生成真正的key
			 String realKey  = prefix.getPrefix() + key;
			return  jedis.exists(realKey);
		 }finally {
			  returnToPool(jedis);
		 }
	}
	
	/**
	 * 删除
	 * */
	public boolean delete(KeyPrefix prefix, String key) {
		 Jedis jedis = null;
		 try {
			 jedis =  jedisPool.getResource();
			//生成真正的key
			String realKey  = prefix.getPrefix() + key;
			long ret =  jedis.del(key);
			return ret > 0;
		 }finally {
			  returnToPool(jedis);
		 }
	}
	
	/**
	 * 增加值
	 * */
	public <T> Long incr(KeyPrefix prefix, String key) {
		 Jedis jedis = null;
		 try {
			 jedis =  jedisPool.getResource();
			//生成真正的key
			 String realKey  = prefix.getPrefix() + key;
			return  jedis.incr(realKey);
		 }finally {
			  returnToPool(jedis);
		 }
	}
	
	/**
	 * 减少值
	 * */
	public <T> Long decr(KeyPrefix prefix, String key) {
		 Jedis jedis = null;
		 try {
			 jedis =  jedisPool.getResource();
			//生成真正的key
			 String realKey  = prefix.getPrefix() + key;
			return  jedis.decr(realKey);
		 }finally {
			  returnToPool(jedis);
		 }
	}
	
	private <T> String beanToString(T value) {
		if(value == null) {
			return null;
		}
		Class<?> clazz = value.getClass();
		if(clazz == int.class || clazz == Integer.class) {
			 return ""+value;
		}else if(clazz == String.class) {
			 return (String)value;
		}else if(clazz == long.class || clazz == Long.class) {
			return ""+value;
		}else {
			return JSON.toJSONString(value);
		}
	}

	@SuppressWarnings("unchecked")
	private <T> T stringToBean(String str, Class<T> clazz) {
		if(str == null || str.length() <= 0 || clazz == null) {
			 return null;
		}
		if(clazz == int.class || clazz == Integer.class) {
			 return (T)Integer.valueOf(str);
		}else if(clazz == String.class) {
			 return (T)str;
		}else if(clazz == long.class || clazz == Long.class) {
			return  (T)Long.valueOf(str);
		}else {
			return JSON.toJavaObject(JSON.parseObject(str), clazz);
		}
	}

	private void returnToPool(Jedis jedis) {
		 if(jedis != null) {
			 jedis.close();
		 }
	}

}

程序猿的必读书籍

第一阶段:

《C语言程序与设计》
《c++进阶宝典》
《Java数据结构和算法》

第二阶段:

《教你怎么不生气》
《老子》
《沉默的愤怒》

第三阶段:

《颈椎病康复指南》
《腰椎间盘突出日常护理》
《强迫症的自我恢复》

第四阶段:

《活着》
在这里插入图片描述
end…

评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不愿秃头的阳某

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值