曲突徙薪亡恩泽,焦头烂额为上客?
数据库设计准则
- 表名用统一的前缀,不要在表名中使用定制项目的甲方名字
- 字段名不要求统一前缀,尽量做到准确表达意思,不要有太多的分段(下划线分割)
- 数据库各个实体的定义,不允许使用拼音,包括并不限于:表名,字段名,索引名等
- 数据库脚本所有字段和表必须写comment
- 合理的命名外键,和对应的表要一目了然
- 表字段可以存在适当冗余,但要注意更新数据的难度
- 表字段尽量添加不为空属性(not null),int型必须不为空。这样一来可以简化操作(insert,update),二来可以减少对null的检查。不为空属性尽量配置default值,int型根据业务配置0或1等
- 能用数字类型,不用varchar类型,varchar类型的宽度要尽量考虑合理
- 如果能确定字符串宽度(例如货币/国家缩写),要使用固定宽度的char
- 数字类型要区分tinyint, smallint, bigint(java的long型),并在设计时给出合理的宽度(括号内的数字只是一种显示时的宽度,可以结合预期的实际进行填写)
- 在java端映射到boolean值的字段,如果肯定是T/F,要使用tinyint(1)
- 产品设计给出的字段缺省值,要用default进行定义,例如启用与否,如果缺省是启用,则要置1
- 数据库字段只存原始数据,不存计算类数据
- 由于精度问题,字段类型尽量不使用浮点数
- 所有业务表包含以下通用信息(一般主键和多租户在最前,其余几项在最后),可用于在Java模型层面和各种接口做对应。包括并不限于
- t_uid(主键)
- org_code(多租户)
- version(乐观锁),int(11) not null default 0
- 审计信息(创建人,创建时间,最后更新人,最后更新时间),not null,确保第一次创建即有值
- deleted(软删除标记,出于性能考虑,不要使用delete_time是否为空判断),tinyint(1) not null default 0
Java实体对象设计准则
数据库映射对象(Entity)
- 实体类名为某种意思表达+entity后缀,和数据库表进行对应
- 实体类必须继承自AbstractEntity,保证可以接入通用信息。JPA的方式未来应该不会用,如果用的话,可能要改为实现多个接口的方式(例如
IBaseEntity, IAuditable, IOrgCodeable, ISoftDeleteable
) - 属性名和数据库字段一一对应,用驼峰命名法,属性名必须要写JavaDoc(依据阿里规约插件提示)
- varchar,char字段对应String类型;除了bigint对应long以外,其他均对应int类型;not null类型的字段,必须映射为基本类型的属性
- 该实体使用的类枚举类型(例如状态,类型等),如果不需要定义枚举的话,必须用final int或final string提前定义,在引用时禁止使用magic number
- 实体类对外提供各种getInstance方法重载,原则上不允许业务代码(service层)进行new对象,这样可以更好的控制通用属性,降低产生bug的风险
- 实体类尽量提供内部计算/判断方法,对外部隐藏实现,充分实现封装。
接口参数及返回值(Request & Response)
暂略
代码实现准则
基础
- 安装阿里规约插件,充分重视idea高亮背景提示和插件提示,并尽量按要求修改
- 原则上不允许使用拼音;禁止任何单词拼写错误,关注idea的绿色波浪线;变量定义要有意义,不能使用诸如list,map,set等通用词汇,ijk只能用于for循环;如果用缩写必须符合一般认知,例如不能用add作为address的缩写,要使用addr
- 非javaDoc的注释,目的只能是3个,一是对比较复杂的算法进行描述,二是对非常规操作(注意并不一定是不合理)进行解释,三是TODO。注释原则上用中文
- 接口必须用javaDoc描述干什么的和怎么用,实现原则上要用javaDoc描写具体实现,特别是复杂的算法
- 日志不反对用中文,但鼓励用英文,因为有些终端对中文支持不好;返回给前端的提示,包括异常提示,全部要使用中文
- 不使用的代码,或者删除,或者进行详细注释
- 明确有改进或未完成的代码,必须使用TODO标记注释(idea用亮黄色以示突出)
- 最大化的限制magic number
- 变量定义并有所指代后,在其生命周期内,如未发生变化,不能再使用其指代的那段代码/数据
- 能用基本类型,不要使用包装类型,有效降低NPE风险
- 类枚举字段多代表type,status等含义,无法穷举的字符串多使用name,code等,要合理命名;当使用枚举概念时,必须选择以下两个方案:一是在实体类中用final变量指代其所有含义(适用于较简单的概念),二是使用枚举值(适用于较复杂的概念)
- 包装类和对象要使用equals比较,而不是==;基本类型和枚举类可以安全的使用==
- 无特殊情况,变量定义应紧贴使用的地方
- 避免无意义的continue,break等控制语句,或空程序体,函数体,或永真,永假的判断,特别注意idea的提示(非错误)。如需保留,用注释进行明确说明
- 理解“卫语句”的概念。能提前退出方法时,要先实现,避免天梯似的代码缩进
- if/else代码块只能是有区分的部分,原则上不允许将公共部分写入,如有需要,请注释理由
优雅的或更强的替代品
- 用
Date.from(Instant.now())
替换new Date()
- 空字符串用
StringUtils.EMPTY
代替;一个空格的字符串用StringUtils.SPACE
代替 - 用
StringUtils.subString
替换String本身的subString方法,后者不够安全,容易NPE -
SimpleDateFormat
线程不安全,使用String.format
获取字符串的转换 - 用
String.valueOf
做字符串转换,而不是拼接一个空字符串;用Integer.parseInt
做数字转换 - 要对预知为数字类型的变量进行cast,最好使用Number这个类,然后在用其intValue, doubleValue做转换,防止castException
- 字符串判空要使用
StringUtils.isBlank
,可实现trim,isEmpty不具备该功能 - 利用optional的orElse和orElseGet降低NPE风险。前者适用于固定数据,后者适用于方法
- 利用stream替换老式的循环用以转换数据结构的方式
- equals的常量翻转使用
- 集合类型要使用
CollectionUtils.isEmpty()
判空,以防止NPE
集合类型
- 确定为单个对象,不要使用集合类型进行操作
关注性能
- For循环的字符串拼接性能较差,改用StringBuilder或StringUtils的join方法,前者把joiner加在最前面,输出字符串只需要subString(1)即可;后者不用处理多余的joiner
- 变量定义的初始值不能随便new一个对象,更不能马上又赋值给另外一个对象。那么这个new出来的对象只能等待垃圾回收
- 慎用findAll,一方面是否考虑过多租户下的数据泄露,另一方面要确保不会获取极其大量的数据导致oom
- 尽量合并多个for循环,通过合理的设计,避免或减少o(n)复杂度的运算
- List等集合类型,在其外部实现for循环时会造成o(n^2)的复杂度,要提前用Map等结构进行转换
- 数据检查要先内存(例如参数本身),再类内存环境(缓存,Redis等),最后是数据库或文件IO。在较理想情况下可有效的降低耗费
- 如不使用集合类型下标,尽量使用增强型for循环
- 不要在集合类型或数组中做无谓的转换,浪费性能。要明确各个集合类型的特点及适用场景
- 绝大多数情况下禁止循环访问数据库,要特别注意方法中没有,但循环调用了方法的情况
代码结构
- 写代码前一定要脑子清楚,状态不好就休息,不要生产混乱的代码
- 保证每个代码块的逻辑单一性,不要穿插着写代码
- 对第三方http接口进行封装,禁止直接使用httpClient或restTemplate
- 资源访问也进行封装,例如ftp,fastdfs,文件系统等
- 一个方法中逻辑判断尽量保持标准的一致,例如判断某种状态,尽管有两个字段或数据结构都可以判断,也要使用其一,尽管两者可能等价
- 同样的业务逻辑,即使在不同的类和方法中进行实现,标准或算法要保持一致,甚至要抽象成一个方法。保证在需要变化的时候可有效的降低修改量和bug产生概率
- 每个方法要有明确的目的,如果步骤较多,要使用注释STEP n来标记步骤;同一个方法要完成两件事,则要统筹考虑代码执行顺序,不要两件事混着向前推进。如果无关则拆分,如果有关,要注意顺序耦合的问题
- 正确区分增删改等操作型方法,和查的只读型方法,不同的操作,对方法返回值的定义也不同
- 随着业务拓展,对核心表要做垂直拆分,在代码层体现为新的entity,不要频繁修改核心表及其对应业务逻辑
- 多个if/else,要使用策略模式,方法模板模式,工厂模式进行重构
网友评论