内容简介:代码结构及一些代码规范建议
一些感悟
- 代码结构和规范关系到项目的可持续维护以及维护的周期,非常重要,但真正重视并落地的很少
- 经典的MVC模式一般都能说出来,但真正落地到项目代码结构的时候,却缺少思考
- 当写代码和找代码让人感觉别扭的时候,就该考虑如何去优化了
- 一切皆对象,在规划代码结构的时候也需要有面向对象的思维方式
- 很多张口就是高并发、大数据、高流量等之类高大上词汇的人,缺很少注重代码的基础结构,写出的代码很难让人轻易上手
- 如果代码结构和规范做得好一点,一般项目有一两个顶梁柱再加一些新手就完全可以搞定。这样既可以节省人力成本,也可以快速培养新人,新加入的成员也能快速融入
以下是整理的一般类型的项目代码结构,仅供参考。部分模块是使用spring boot开发项目的命名,但总体结构思路是一样的,如果不使用spring boot开发项目,只是修改一下名字即可
建议的包结构及简单说明
-
itopener-parent:顶级maven parent,配置统一的maven插件、依赖包版本管理等
- itopener-utils:全局公用的 工具 类,如:加密操作、集合处理、字符串处理等等
- itopener-framework:基于框架的统一的封装,比如:拦截器、controller返回对象、BaseController等
-
itopener-spring-boot-starters-parent:自定义封装spring boot starter的parent,方便统一管理。此目录即公共组件库,存放各种封装好的组件,以便使用
-
itopener-lock-spring-boot-starter-parent:单个starter的parent,方便单个starter的管理,如:单独对此starter进行优化升级
- itopener-lock-spring-boot-starter:spring boot 的starter,使用的时候依赖此模块
- itopener-lock-spring-boot-autoconfigure:spring boot starter的自动配置模块
-
itopener-druid-spring-boot-starter-parent
- itopener-druid-spring-boot-starter
- itopener-druid-spring-boot-autoconfigure
-
itopener-lock-spring-boot-starter-parent:单个starter的parent,方便单个starter的管理,如:单独对此starter进行优化升级
-
itopener-demo-parent:应用的parent,方便项目的统一管理
-
itopener-demo-model:maven模块,存放与数据库表对应的域对象及其枚举定义、查询条件、mapper及操作等
- com.itopener.demo.model:po对象,与数据库表一一对应,可以增加关联对象的属性,方便返回数据,如订单表关联订单明细表的多条数据,则订单类里可以增加一个订单明细的List属性,但严禁增加查询条件,所有查询均使用conditions包对应的对象作为查询条件(只有一个查询条件的可以直接以条件作为参数,如select(long id)、select(UserCondition condition)
- com.itopener.demo.dao:mybatis对mapper的操作,建议使用sqlid的调用方式,更灵活,公用性更强
- com.itopener.demo.mapper:mybatis的映射文件,与数据库表一一对应
- com.itopener.demo.conditions:查询条件,建议每个表都对应一个查询条件,包含分页、 排序 、like条件、between条件等属性
- com.itopener.demo.enums:model对象中属性对应的枚举,属于model的属性,而并非全局公用,很多人会放到整个项目的公共模块,我认为有点过度设计
-
itopener-demo-common:应用的公共模块,主要存放公共的service操作,方便web、work、api等模块公用
- com.itopener.demo.common.service:公共的service
-
itopener-demo-web:web模块,提供web服务(针对有页面的web服务端)
- com.itopener.demo.Application:应用启动类
- com.itopener.demo.config:存放应用的配置
- com.itopener.demo.controller:控制器
- com.itopener.demo.service:应用的service,通过调用dao或公共的service组合成controller需要的业务
- com.itopener.demo.vo:请求参数和返回值对象
- com.itopener.demo.vo.converter:vo与model之间的转换器
- com.itopener.demo.enums:返回值相关的枚举定义(根据实际需要定义,非必要)
-
itopener-demo-api:api模块,对外提供http协议的服务(供其他系统调用,无页面)
- com.itopener.demo.Application:应用启动类
- com.itopener.demo.config:存放应用的配置
- com.itopener.demo.controller:控制器
- com.itopener.demo.service:应用的service,通过调用dao或公共的service组合成controller需要的业务
- com.itopener.demo.vo:请求参数和返回值对象
- com.itopener.demo.vo.converter:vo与model之间的转换器
- com.itopener.demo.enums:返回值相关的枚举定义(根据实际需要定义,非必要)
- itopener-demo-sdk:sdk模块,主要存放对外提供的接口的辅助类,如入参出参、调用方式等。接口调用方和提供方公用的模块
-
itopener-demo-work:work模块,应用所需的自动任务
- com.itopener.demo.Application:应用启动类
- com.itopener.demo.config:应用的配置
- com.itopener.demo.service:应用的service,通过调用dao或公共的service组合成work所需的业务
- com.itopener.demo.work:调度任务
-
itopener-demo-views:views模块,纯前端静态资源,方便前后端完全分离
- static:静态资源,如:js、css、jpg、html等
- templates:前端模板页面,如:html、ftl等(如果使用纯html则不需要此包)
-
itopener-demo-gateway:应用网关,用于鉴权、配置路由、负载均衡、重试等
- com.itopener.demo.Application:应用启动类
-
itopener-demo-model:maven模块,存放与数据库表对应的域对象及其枚举定义、查询条件、mapper及操作等
-
itopener-demo2-parent:另一个系统,与itopener-demo-parent一致
- itopener-demo2-model
- itopener-demo2-common
- itopener-demo2-web
- itopener-demo2-api
- itopener-demo2-work
- itopener-demo2-views
说明
-
此代码结构是使用spring boot、spring cloud作为主要开发技术。如果不使用spring boot + spring cloud,大体结构类似,比如:maven模块的划分、model/view/controller对应包的划分
-
model模块可以使用工具根据数据库表结构自动生成对应的文件
-
代码结构中的接口是http形式的接口,如果是rpc方式,api模块做对应调整即可
代码规范和结构的几点建议
-
service不需要接口,直接写 java 对象,区别在于spring会使用cglib作为代理的生成方式(如果是写接口和实现类,会使用jdk代理)。理由:在应用内部的各层次(controller、service、dao)之间进行调用,每次修改都是在应用内修改,不存在接口的调用方和实现方单独升级的情况,如果同时去修改接口和实现类,显得很多余;如果是应用之间的调用,必须定义接口。(个人认为原因可能是所谓的面向接口编程被过度理解和使用。应用内如果有一些复杂的业务逻辑,可适当考虑使用接口+多实现的方式,如遇这些情况,多参考 设计模式 的一些思想)
-
如果service需要写接口,接口以I开头,实现类以接口名去掉I命名。有的实现类会以impl结尾,个人认为没有必要。理由:impl表示的是类的层次结构,这个在包名上已经体现出来,并且类名应该主要体现实现的业务(见名知义),再有service的类名已经以service结尾,体现出了分层的意义,没有必要在命名上重复体现
-
mybatis的mapper建议使用sqlid的方式调用,这样能提高mapper.xml内代码的重用性;如果用定义接口与mapper.xml的id对应的方式,只能一一对应,不方便公用。比如User对象的查询功能:
<sql id="Where"> <where> <if test="id > 0"> and id = #{id,jdbcType=BIGINT} </if> <if test="status > 0"> and status = #{status,jdbcType=TINYINT} </if> <if test="username != null and username != ''"> and username = #{username,jdbcType=VARCHAR} </if> <if test="password != null and password != ''"> and password = #{password,jdbcType=VARCHAR} </if> </where> </sql> <select id="select" parameterType="com.itopener.demo.conditions.UserCondition" resultMap="BaseResultMap"> select <include refid="Column_List" /> from t_user <include refid="Where" /> </select>
使用sqlid调用方式的话,dao中的查询可以公用同一个sqlid:
public List<User> selectList(UserCondition condition) { return baseDao.selectList(NAMESPACE + "select", condition); } public List<User> selectPage(UserCondition condition) { return baseDao.selectPage(NAMESPACE + "select", condition); } public User selectOne(UserCondition condition) { return baseDao.selectOne(NAMESPACE + "select", condition); }
如果使用写接口与mapper对应的方式,mapper.xml则需要写三个select,或者通过传入参数进行判断处理,返回值也根据使用情况需要处理
-
关于使用eureka注册中心,个人认为只是在原有web应用之前加了一层控制,而不应该因此将原web应用的controller写成service,因为原web应用虽然是相当于提供服务给eureka调用,但毕竟也是通过一般web应用的http方式调用的,两者是独立存在的,不应该因此把类名称耦合起来,更不应该在两者之间定义接口来将两者耦合在一起
-
减少事务开启时间,建议尽量将接收到的参数的判断、对象的初始化、vo与po的转换等放到事务开启之前,因为service只是调用不同dao或公用service对数据的操作来组合成controller需要的业务,并做事务控制,所以service应该只接收自己需要的并且正确的数据对象
-
方法名去掉冗余的部分,java定义方法签名是方法名+参数,只要根据方法签名能够知道其意思即可,如:
User selectById(long id); User selectByName(String name);
可以改为:
User select(long id); User select(String name);
- 写工具类或封装的时候考虑级联操作,即对对象属性的操作方法返回当前对象,如下是自己封装的controller统一使用的返回对象:
import org.springframework.ui.ModelMap; /** * Created by fuwei.deng on 2017年5月5日. */ public class ResultMap extends ModelMap { /** */ private static final long serialVersionUID = 5898506914945717989L; public static final String CODE_SUCCESS = "ok"; public static final String CODE_FIALED = "failed"; private final String FLAG_KEY = "code"; private final String MSG_KEY = "msg"; public ResultMap(){ put(FLAG_KEY, CODE_SUCCESS); } public ResultMap(String msg) { put(FLAG_KEY, CODE_FIALED); put(MSG_KEY, msg); } public ResultMap(String code, String msg){ put(FLAG_KEY, code); put(MSG_KEY, msg); } public ResultMap setCode(String code) { put(FLAG_KEY, code); return this; } public ResultMap setMsg(String msg) { put(MSG_KEY, msg); return this; } public ResultMap put(String key, Object value){ super.put(key, value); return this; } public static ResultMap buildSuccess(){ return new ResultMap(); } public static ResultMap buildFailed(String msg){ return new ResultMap(msg); } }
使用的时候可以这样:
public ResultMap query(Condition condition){ long count = ... List<User> list = ... return ResultMap.buildSuccess().put("count", count).put("list", list); }
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 清晰的代码结构
- Python模块文件结构代码详解
- 你可以这样优化if-else代码结构
- 数据结构常见的八大排序算法及代码实现图解
- 使用结构赋值与扩展运算符,让你的代码更优雅
- 数据结构和算法必知必会的50个代码实现
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
数据结构(C语言版)
严蔚敏、吴伟民 / 清华大学出版社 / 2012-5 / 29.00元
《数据结构》(C语言版)是为“数据结构”课程编写的教材,也可作为学习数据结构及其算法的C程序设计的参数教材。 本书的前半部分从抽象数据类型的角度讨论各种基本类型的数据结构及其应用;后半部分主要讨论查找和排序的各种实现方法及其综合分析比较。其内容和章节编排1992年4月出版的《数据结构》(第二版)基本一致,但在本书中更突出了抽象数据类型的概念。全书采用类C语言作为数据结构和算法的描述语言。 ......一起来看看 《数据结构(C语言版)》 这本书的介绍吧!