一、代码的可读性
1.1、命名
命名随处可见,给变量、函数、参数、类和封包命名。应遵循规范文档的命名规范,并且一旦发现有更好的名称,就换掉旧的。这么做,你和读你代码的人都会更开心。
1.2、格式
- 大括号与if, else, for, do, while语句一起使用,即使只有一条语句(或是空),也应该把大括号写上 ;
- 空的块状结构可以简写为一行,但如果他是多块状结构的一部分,无论如何也要换行;
//空块状结构
void do(){}
if() return ;
//多块状结构
if(expression){
}else if(otherExpression){
}else{
}
- 一行代码超过80个字符需要换行;
- 一个方法函数内的代码不超过100行。
1.3、异常
- 异常处理需要对java和spring的异常体系有所了解,并遵循对已知捕获、对未知抛出的原则,进行项目内异常体系的构建、优化;
- 长代码块的异常需要分别处理,不要在一个try catch中进行捕获;
- 反例:
public void test(){
try{
do smoething
do smoething
do smoething
}catch(Exception e){
}
}
- 正例:
public void test(){
try{
do smoething
}catch(Exception e){
}
try{
do smoething
}catch(Exception e){
}
try{
do smoething
}catch(Exception e){
}
}
- 循环体内不要使用try catch,需要时在循环外使用;
1.4、常量/魔术值
- 所有常量的修饰符必须含有static final;
- 常量的使用域需要限制在相应模块中,防止版本更迭后其他模块发生错误;
- 代码中涉及逻辑中断和条件判断不要出现不明的、未经释义的值,需要有明确命名魔术值;
- 反例:
public boolean hasAdminRole(List<Role> roles){
for(Role role : roles){
if(user.getId == 9527){
return true;
}
}
return false;
}
- 正例:
//key角色对应的id
private static final Integer KEY_ROLE_ID = 9527;
public boolean hasKeyRole(List<Role> roles){
for(Role role : roles){
if(user.getId == KEY_ROLE_ID){
return true;
}
}
return false;
}
1.5、函数
- 每一个方法函数在逻辑上应该包含明确的 输入(参数)和输出(返回值);
- 一个方法函数需要明确的主体逻辑和次要逻辑,对于复杂冗长的函数,尽可能的展现主体逻辑,拆分次要逻辑到其他函数中,进行模块化编程;
- 反例:
public LoginUser getUserInfo(){
//获取用户 token
ServletAttributs requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRquest();
String token = request.getHeader("access_token");
if(StringUtils.isEmpty(token)){
throw new EmptyLoginTokenException("用户登录token获取错误");
}
//根据token 获取用户信息
SysUser user = (SysUser)redisUtil.get(token);
//转换loginUser
LoginUser loginUser = new LoginUser();
loginUser.setId(user.getId());
loginUser.setAccount(user.getAccout());
loginUser.setName(user.getName());
...
//补充角色信息
List<SysRole> roles = new ArrayList<>();
roles = roleService.getRolesByUserId(loginUser.getId());
if(CollectionUtils.isNotEmpty(roles)){
Set<SysRole> roleSet = new HashSet(roles);
roles.setRoles(roleSet);
//补充权限信息
List<SysPermission> permissions = new ArrayList<>();
permissions = permissionService.getPermissionsByRoles(roles);
if(CollectionUtils.isNotEmpty(permissions)){
Set<SysRSysPermissionole> permissionSet = new HashSet(permissions);
roles.setPermissions(permissionSet);
...
}
}
return loginUser;
}
- 正例:
public LoginUser getUserInfo(){
//获取用户 token
String token = getToken();
//根据token 获取用户信息
SysUser user = (SysUser)redisUtil.get(token);
if(ObjectUtil.isNEmpty(user)){
return null;
}
//转换loginUser
LoginUser loginUser = convertLoginUser(user);
//补充角色信息
supplyLoginUser(loginUser);
return loginUser;
}
private String getToken(){
ServletAttributs requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRquest();
String token = request.getHeader("access_token");
if(StringUtils.isEmpty(token)){
throw new EmptyLoginTokenException("用户登录token获取错误");
}
return token;
}
private LoginUser convertLoginUser(SysUser user){
LoginUser loginUser = new LoginUser();
loginUser.setId(user.getId());
loginUser.setAccount(user.getAccout());
loginUser.setName(user.getName());
...
return loginUser;
}
private void supplyLoginUser(LoginUser loginUser){
List<SysRole> roles = new ArrayList<>();
roles = roleService.getRolesByUserId(loginUser.getId());
if(CollectionUtils.isNotEmpty(roles)){
Set<SysRole> roleSet = new HashSet(roles);
roles.setRoles(roleSet);
//补充权限信息
List<SysPermission> permissions = new ArrayList<>();
permissions = permissionService.getPermissionsByRoles(roles);
if(CollectionUtils.isNotEmpty(permissions)){
Set<SysRSysPermissionole> permissionSet = new HashSet(permissions);
roles.setPermissions(permissionSet);
...
}
}
}
1.6、嵌套
- 一个方法函数内的嵌套不要超过三层;
- 反例:
void render(List<User> users){
if(CollectinUtils.isNotEmpty(users)){
for(User user : users){
if(CollectinUtils.isNotEmpty(user.getRoles())){
for(Role role : user.getRoles()){
do something
}
}
}
}
}
- 正例:
void render(List<User> users){
if(CollectinUtils.isEmpty(users)){
return;
}
for(User user : users){
if(CollectinUtils.isEmpty(user.getRoles())){
continue;
}
for(Role role : user.getRoles()){
do something
}
}
}
1.7、条件
- 循环条件进行逻辑上的合并;
- 合并前:
List goodNames = new ArrayList<>();
if(bool){
for (String name: names) {
if (name.contains("bad")) {
continue;
}
goodNames.add(name);
...
}
}
- 合并后:
List goodNames = new ArrayList<>();
for (String name: names) {
if (!name.contains("bad")) {
goodNames.add(name);
...
}
}
- 多条件判断超过三个需要换行;
if(menus.contain(MenuEnums.HOME_PAGE)
&& menus.contain(MenuEnums.ERROR_PAGE)
&& menus.contain(MenuEnums.LOGIN_PAGE)){
}
- 赋值条件为2个时尽量使用三目运算符号;
Subject sub = new Subject();
sub.setDataScope(UserContext.hasAdminRole()?"all":user.getDataScope());
1.8、边界
项目管理中,项目由进度、成本、质量和边界构成,边界是指研发过程中应完成需求对应的功能,避免不必要的过度编码,由此引发的返工问题也会回过头来影响进度、成本与代码质量。
1.9、注释
- 遵循注释规范使用单行注释与多行注释,代码中不要使用尾注释;
- 优雅代码应追求即使没有注释仍能被看懂的原则,通过规范的命名和简介的功能描述减少冗余注释的编写;
二、代码的可维护性
- 编写时可维护性:是指在程序或系统上线后爆出 BUG,开发团队能够及时扑灭这个 BUG 且不会爆出其他 BUG。保持方法的原子性,提高代码内聚,能使某处修改的影响降到最低,这样某处方法出现 BUG,也不太会影响到其他模块的正常运作;
- 运行时的可维护性:是指在系统运行过程中(或无需再次编码发布、只需系统重启一次)修改系统的某项配置并使其生效,且不影响现在正在进行的业务和用户的操作。这要求软件工程师不能把代码写死。例如配置文件、数据库连接字符串、资源文件、日志等。
三、代码的可变更性
- 提高代码的复用:需要对整个系统的整体进行分析与合理规划,长期不断的对系统模型进行划分,对模型边界进行设定,保证每个功能被合理地划分到响应的模型的每个类中,这样可以很好地保证代码复用;
- 设计模式:是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。
四、技术评审
技术评审包含编码前的方案评审和编码后的代码review,是代码质量管理方式的一种,以下列举代码评审阶段排排查的典型案例:
- 功能错误
- 资源泄漏
- 空的异常处理、缺失日志
- 事务的使用
- 文档、类、方法、复杂算法注释
- 重复代码
- 过长的方法参数列表
- if/while/for等嵌套3层以上
- 无实际意义的类、方法、变量名称