bo dto java vo_JAVA 开发手册


1 编程规约

1.1 命名风格

  • 代码中的命名不能以下划线或者美元符号开始,也不能以下划线或者美元符号结束(_name , $name , name_ , name$)
  • 代码中命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式(国际通用除外:hangzhou , guangzhou ,alibaba)
  • 类名使用UpperCamelClass(首字母大学驼峰命名法)风格,但DO/BO/DTO/VO/AO/PO等情形除外。
  • 方法名、参数名、成员变量、局部变量都统一使用lowerCameCase(首字母小写驼峰命名法)风格,必须遵从驼峰形式(localValue/getHttpMessage()/inputUserId)
  • 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌弃名字长度(MAX_STOCK_COUNT/CACHE_EXPIRED_TIME)
  • 抽象类命名使用Abstract或Base开头;异常类命名使用Exception结尾;测试类命名以它要测试的类名开始,以Test结尾
  • 类型与中括号之间无空格相连定义数组(定义数组:int[] arr 和 int arr[] ;建议使用int[] arr)
  • POJO 类中布尔类型的变量都不要加 is 前缀,否则部分框架解析会引起序列化错误。(需要在设置对应的映射关系),如基本数据类型Boolean isDeleted;方法名称也是isDeleted(),RPC框架在反向解析的时候,"误认为" 属性名称是deleted。导致属性值获取不到
  • 包名统一使用小写,点分隔符之间有且只有一个自然语义的英语单词。包名统一使用单数形式。类名可以使用复数(cn.pconline/MessageUtils)
  • 避免在字符类的成员变量之间或者不同代码块的局部变量之间采用完全相同的命名方式,使可读性降低

java public class ConfusingName{ public int age; } public class son extends ConfusingName{ public int age; }

  • 杜绝不规范的缩写,避免词不达义
  • 自定义任何编程元素在命名时,使用尽量完整的单词组合来表达其意。如:int a;
  • 在常量与变量的命名时,表示类型的名词放在词尾,以提升辨识度。如:startTime/workQueue/nameList/TERMINATED_THREAD_COUNT (不适用startAt/QueueOfWork/listName)
  • 如果模块、接口、类、方法使用了设计模式,应在命名时体现出具体模式(如:ResourceObserver , BeanFactory)
  • 接口类中的方法和属性不要任何修饰符号,保持代码的简洁性。尽量接口中也不要定义变量。
  • 接口和实现类的命名规则:1. 对于Service 和 DAO类,基于SOA 的理念,暴露出来的服务一定是接口,内部实现类用Impl后缀和接口区别。(如 CacheServiceImpl 实现 CacheService接口) 2. 对于形容能力的接口名称,取对应的形容词为接口名(如:AbstractTranslator 实现 Translatable)
  • 枚举类名带上Enum后缀,枚举成员需要全大写,单词间用下画线隔开(ProcessStatusEnum 成员名称:SUCCESS/UNKNOWN_REASON)
  • 各层级命名规范:1. Service/DAO层方法命名规约:1.1 获取单个对象的方法用get作为前缀。1.2 获取多个对象的方法用list作为前缀,复数结尾。1.3 获取统计值用count作为前缀。1.4 插入方法用save/insert 作为前缀。1.5 删除方法用remove/delete作为前缀。1.6 修改方法使用update作为前缀 。2. 模型命名规约:2.1 数据对象:xxxDO , xxx数据表名。2.2 数据传输对象:xxxDTO 。2.3 展示对象(页面名称):xxxVO。2.4 POJO是VO/DTO/BO/VO统称。

1.2 常量定义

  • 不允许任何未经预先定义的常量直接出现在代码中
  • 在long或Long赋值时,数值后使用大写L,不能使用小写l,避免与数值1混淆,造成误解
  • 不要使用一个常量类维护所有的常量,按常量所属模块功能进行分类,维护
  • 常量的复用层次:跨应用共享常量,应用内共享常量,子工程内共享常量,包内共享常量,类内共享常量。1放置在二方库中 ,2 放置在一方库中,3 放在子工程的constant目录中,4 当前包单独constant目录下,5 直接类内部private statis final 定义
  • 变量值仅在一个固定范围内变化用enum类型来定义

1.3 代码格式

  • 大括号内为空,简洁写成{}既可,大括号中间无需换行和空格,如果非空代码块则 1 左大括号前不换行 2 左大括号后不换行 右大括号前换行,右大括号还有else等代码不换行;表示终止的右大括号后必须换行。
  • 左小括号和字符之间不出现空格,右小括号和字符之间不出现空格,而左大括号前需要空格
  • if/for/while/switch/do 等保留字与括号之间必须加空格
  • 任何二目,三目运算符的左右两边都需要加一个空格
  • 采用4个空格缩进,禁止使用tab字符
  • 注释的双斜线与注释内容之间有且只有一个空格
  • 在进行类型强制转化时,右括号与强制转换值之间不需要任何空格隔开
  • 单行字符数限制不超过120个,超出需要换行,换行需要 1 第二行相对第一行缩进4个空格,第三行开始不用,2 运算符与下文一起换行,3 方法调用的点符号与下文一起换行,4 方法调用中多个参数需要换行,在逗号后进行 5 在括号前不要换行
  • 方法参数在定义和传入时,多个参数逗号后边必须加空格
  • IDE的text file enconding 设置为UTF-8;IDE中文件的换行符使用UNIX格式,不使用windows格式
  • 单个方法总行数不超过80行
  • 没有必要增加若干空格来使变量赋值等号与上一行对应位置等号对齐
  • 不同逻辑,不同语义,不同业务的代码之间插入一个空行分隔提高可读性

1.4 oop规约

  • 避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接类名来访问即可
  • 所有的覆写方法,必须@Override 注解
  • 相同参数类型,相同业务含义,才可以使用java的可变参数,避免使用Object , 可变参数必须放置在参数列表的最后
  • 外部正在调用或者二方库依赖的接口,不允许修改方法签名,避免对接口调用产生影响。接口过时必须加@Deprecated注解,说明新接口或者新服务是什么
  • 不能使用过时的类和方法
  • Object 的 equals 方法容易抛空指针异常,应使用常量或者确定有值得对象来调用equals
  • 所有整型包装类对象之间值比较,全部使用equals方法比较
  • 浮点数之间的等值比较,基本数据类型不能用==来比较,包装数据类型不能equeals来判断
  • 定义数据对象DO类时,属性类型要与数据库字段类型相匹配
  • 为了精度丢失问题,禁止使用构造方法BigDecimal(Double) 的方式把double值转化为BigDecimal对象
  • 关于基本数据类型与包装数据类型的使用标准如下:1 所有的 POJO 类属性必须使用包装数据类型 2 RPC 方法的返回值和参数必须使用包装数据类型 3 所有局部变量使用基本数据类型
  • 定义DO/DTO/VO等POJO类时,不要设定任何属性默认值
  • 序列化类新增属性时,不用修改serialVersionUID字段,避免反序列化失败,完全不兼容升级,避免序列化混乱,请修改序列化号
  • 构造方法里面禁止加入任何业务逻辑,如有初始化逻辑,请放在init中
  • POJO 类必须写toString方法,如果继承了另一个POJO类,注意前面加一个super.toString
  • 禁止在POJO类中,同时存在对应属性xxx的isXxx() 和getXxx()方法
  • 使用索引访问用String 的split方法得到的数组时,需要最后一个分隔符后有无内容的检查,否则会有抛出IndexOutOfBoundException的风险
  • 当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起,便于阅读
  • 类中方法定义的顺序:公有方法/保护方法 --> 私有方法 --> getter/setter 方法
  • setter 方法中,参数名称与类成员变量名称一致,this.成员名 = 参数名 。在getter/setter方法中,不要增加业务逻辑,增加排查问题的难度
  • 循环体内,字符串的连接方式,使用StringBuilder的append方法进行扩展。
  • final 可以声明类,成员变量,方法,本地变量。 1 不允许被继承 , 2 不允许修改引用的域对象,3 不允许被覆盖的方法 4 不允许运行过程中重新赋值的局部变量 5 避免上下文重复使用一个变量,使用final可以强制重新定义一个变量
  • 慎用Object的clone方法拷贝对象
  • 类成员与方法访问控制从严 1 如果不允许外部直接通过new来创建对象,那么构造方法必须是private。 2 工具类不允许有public或default构造方法 。3 类非static成员变量并且与子类共享,必须是protected 。4 类非static成员变量并且仅在本类使用,必须是private 。5 类static成员变量如果仅在本类使用,必须是private。 6 若是static 成员变量,考虑是否final 7 类成员方法只供类内部调用,必须是private. 8 类成员方法只对继承类公共,那么限制为protected

1.5 集合处理

  • 关于hashCode和equals的处理 1 只要覆写equals,就必须覆写hashCode 2 set存储的不重复的对象,依据hashcode 和 equals进行判断的,所以set存储对象需要重写。3 同理set , map键对象也需要重写
  • ArrayList 的 subList结果不可强转成ArrayList
  • 使用Map方法keyset() / values() /entrySet() 返回对象时,不可以对其进行添加元素操作,否则会抛出异常
  • Collections 类返回对象,不可对其进行添加或者删除元素操作
  • 在subList场景中,高度注意对原集合元素的增加和或删除,均会导致子列表的遍历,增加,删除产生ConcurrentModificationException异常
  • 使用集合转数组的方法,必须使用集合的toArray(T[] array) , 传入的是类型安全一致,长度为0的空数组。
  • 在使用Collection接口实现类的addAll()方法时,都要对输入的集合参数进行NPE判断
  • 使用工具类Arrays.asList() 把数组转换成集合时,不能使用其修改集合的相关方法,add/remove/clear 方法会抛出UnsupportedOperationException
  • 泛型通配符<? extends T> 来接收返回的数据,此写法的泛型集合不能使用add方法,而<? supper T> 不能用get 方法,作为接口调用赋值时易出错
  • 在无泛型限制定义的集合赋值给泛型限制的集合时,在使用集合元素时,需要进行instanceof判断,避免抛出ClassCastException
  • 不要在foreach循环里对元素进行remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁
  • 在JDK7版本及以上,comparator实现类要满足如下三个条件,不然Arrays.sort , Collections.sort会抛出IllegalArgumentException异常。1 x,y的比较结果与y,x比较结果相反 2 x>y , y>z 则x>z 3 x=y 则x,z比较结果与y,z比较结果相同
  • 集合泛型定义时,在JDK7及以上,使用diamond语法或者全省略
  • 集合初始化时,指定集合初始值大小
  • 使用entrySet遍历Map类集合kv,而不是keySet方式进行遍历
  • Map类集合k/v能不能存储null值得情况

6b68a4526c2aaaec337658874dac08d2.png
  • 合理利用好集合的有序性sort和稳定性order 避免集合的无序性和不稳定性带来的负面影响
  • 利用set元素唯一的特性,可以快速对一个集合进行去重操作,避免使用list的contains方法进行遍历,对比,去重操作。

1.6 并发处理

  • 获取单例对象需要保证线程安全,其中方法也要保证线程安全
  • 创建线程或线程池时,请指定有意义的线程名称,方便出错时回溯

```java public class UserThreadFactory implements ThreadFactory{ private final String namePrefix; private final AtomicInteger nextId = new AtomicInteger(1); UserThreadFactory(String whatFeaturOfGroup){ namePrefix = "" //线程名称前缀 }

@Override
  public Thread newThread(Runnable task){
      String name = namePrefix + nexId.getAndIncrement();
      Thread thread = new Thread(name); //设置名称,省略其他参数
      return thread;
  }

} ```

  • 线程资源必须通过线程池提供,不允许在应用中自行显示创建线程
  • 线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险
  • SimpleDateFormat 是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类。jdk8应用可以:1 使用Instant代替Date 2 LocalDateTime 代替 Calender 3 DateTimeFormatter 代替SimpleDateFormat。
  • 必须回收自定义的ThreadLocal变量,尤其在线程池场景下,线程经常被复用,如果不清理自定义的ThreadLocal变量,可能会影响后续的业务逻辑和造成内存泄漏问题,尽量在代理中使用try-finally块进行回收
  • 高并发时,同步调用应该去考量锁的性能损耗。能无锁数据结构就不用锁,能锁区快,就不要锁整个方法体,能锁对象就不用类锁
  • 对多个资源,数据库表,对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。
  • 在使用阻塞等待获取锁的方式中,必须在try代码块之外,并且在加锁方法与try代码块之间没有任何可能抛出异常的方法调用,避免在加锁成功后,在finally中无法解锁 1 如果在lock方法与try代码块之间的方法调用抛出异常,那么无法解锁,造成其他线程无法成功获取锁。2 如果lock方法在try代码块之外,可能由于其他地方抛出异常,导致在finally代码块中,unlock对未加锁的对象解锁,它会调用AQS的tryRelease的方法,抛出IllegalMonitorStateException异常 3 在lock对象的lock方法实现中可能抛出unchecked异常。
  • 在使用尝试机制来获取锁的方式中,进入业务代码块之前,必须先判断当前线程是否持有锁。锁的释放规则与锁阻塞等待方式相同
  • 并发修改统一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用version作为更新依据(每次访问冲突概率小于20% ,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于3次)
  • 多线程并行处理定时任务时,Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其他任务便会自动终止运行。如果在处理定时任务时使用ScheduledExecutorService就没有这个问题
  • 资金相关的金融敏感信息,使用悲观锁策略
  • 使用CountDownLatch进行异步转同步操作,每个线程退出之前必须调用countDown方法,线程执行代码注意catch异常,确保countDown方法被执行到,避免主线程无法执行到await方法,知道超时才返回结果
  • 避免Random实力被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一个seed导致性能下降
  • 在并发场景下,通过双重检查锁(double-checked locking)实现延迟初始化的优化问题隐患
  • volatile解决多线程内存不可见问题,对与一写多读,可以解决变量同步问题,但是多写,同样无法解决线程安全问题
  • HashMap在容量不够下进行resize时由于高并发可能出现死链,导致cpu飙升,在开发过程中可以使用其他数据结构或加锁来规避风险
  • ThreadLocal对象使用static修饰,ThreadLocal无法解决共享对象的更新问题

1.7 控制语句

  • 在一个switch块内,每个case要么通过continue/break/return等来终止,要么注释说明程序将继续执行到哪一个case为止;在一个swith块内,都必须包含一个default语句并且放在最后,即时它什么代码也没有
  • 当switch括号内的变量类型为String并且此变量为外部参数时,必须先进行null判断
  • 在if/else/for/while/do语句中必须使用大括号
  • 在高并发场景中,避免使用等于判断作为中断或退出的条件
  • 表达异常的分支时,少用if-else方式(不超过三层),这种方式可以改写成:使用卫语句,策略模式,状态模式等来实现。卫语句即代码逻辑先考虑失败,异常,中断,退出直接返回的情况。
  • 除常用方法等外,不要在条件判读中执行其他复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。
  • 不要在其他表达式中,插入赋值语句
  • 循环体中的语句要考虑性能,以下操作尽量移至循环体外处理,定义对象,变量,获取数据库连接,进行不必要的try-catch操作
  • 避免采取反逻辑运算符
  • 接口入参保护,常见用于批量操作的接口
  • 需要进行参数校验的:1 调用频次低的方法 2 执行时间开销很大的方法 3 需要稳定性和可用性的方法 4 对外提供开放的接口 5 敏感权限入口
  • 以下情形,不需要进行参数校验:1 极有可能被循环使用的方法 2 底层调用频率比较高的方法 3 被声明private只会被自己代码所调用。

1.8 注释规约

  • 类、类属性、类方法的注释必须使用Javadoc规范,使用/*内容*/,不得使用//
  • 所有抽象方法必须使用javadoc注释,除了返回值,参数,异常说明外,还必须指出该方法做什么事情,实现什么功能
  • 所有类都必须添加创建者和创建日期
  • 方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/**/注释,注意与代码对齐
  • 所有的枚举类型字段必须要有注释,说明没有数据项的用途
  • 与其“半吊子”英文来注释,不如用中文注释把问题说清楚。专有名词与关键字保持英文原文即可
  • 代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑等修改
  • 谨慎注释掉代码,在上方详细说明,没有用的删除掉
  • 对于注释的要求:1 能够准确的放映设计思想和代码逻辑,2 能够描述业务含义
  • 要求精简准确表达到位,避免注释滥用。
  • 特殊注释标记,标记好标记人,时间。TODO 待办(标记人,标记时间,标记事件) FIXME 错误(标记人,标记时间)

1.9 其他

  • 使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度
  • velocity 调用POJO类的属性时,直接使用属性名取值即可
  • 后台输送给页面的变量必须加$!{var} -- 中间加上感叹号
  • 注意Math.random() 这个方法返回是double类型
  • 获取当前时间毫秒值 System.currentTimeMills();
  • 日期格式化,pattern 表示年份统一使用小写y
  • 不要在视图模块加入任何复杂的逻辑
  • 任何数据结构的构造或初始化,都指定大小,避免数据结构无限增长吃光内存
  • 及时清理不再使用的代码或配置信息

2 异常日志

2.1 异常处理

  • java类库中定义的可以通过预检查方式规避的RuntimeException异常不应该通过catch的方式来处理
  • 异常不要用来流程控制,条件控制
  • catch时分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码,对于非稳定代码的catch尽可能进行区分异常类型,再做对应的异常处理
  • 捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容
  • try块放在事务代码中,catch异常后,如果需要回滚事务,一定要注意手动回滚事务
  • finally块必须对资源对象,流对象进行关闭,有异常的还要做try-catch
  • 不要再finally块中使用return
  • 捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类
  • 在调用RPC,二方包,动态生成类的相关方法时,捕捉异常必须用Throwable类进行拦截
  • 方法的放回值可以为null,不强制返回空集合,或者空对象,必须添加注释充分说明什么情况下返回null
  • 防止产生NullPointException。1 返回基本数据类型,return包装数据类型对象时,自动拆箱有产生NPE,2 数据库查询结果为null 3 集合的元素不为空,取出也可能为null 4 调用第三方服务是 5 对于session中获取的数据时,一律采用空指针判断 6 级联调用
  • 自定义异常返回,有对应的状态码与异常类型
  • 避免重复性代码

2.2 日志规约

  • 应用中不可直接使用日志系统(Log4j,Logback)中的API,而应依赖使用日志框架的SLF4J中的api,使用门面模式的日志框架,有利于 维护和各类的日志处理方式统一
  • 所以日志文件至少保存15日,有些以周为频次的任务。网络运行状态,安全相关信息,系统检测,管理后台操作,用户敏感操作需要留存相关的网络日志不少于六个月
  • 应用中的扩展日志命名方式:appName_logtype_logName.log。如state/monitor/access
  • 在日志输出时,字符串变量之间的拼接使用占位符的方式
  • 对于trace/debug/info级别的日志输出,必须进行日志级别的开关判断
  • 避免重复打印日志,浪费磁盘空间,需要在log4j.xml中设置additivity=false
  • 异常信息应该包括两类信息,案发现场信息和异常堆栈信息,如果不处理,那么通过关键字throws往上抛出
  • 谨慎的记录日志,生产环境严禁输出debug日志,有选择的输出info日志。注意日志输出量问题,避免服务器磁盘被撑爆,及时删除观察日志
  • 可以使用warn日志级别来记录用户输入错误的情况,避免用户投诉时,无从入手
  • 尽量用英文描述日志错误信息

3 单元测试

  • 好的单元测试必须遵守AIR原则 自动化,独立性,可重复
  • 单元测试应该是全自动执行的,并且非交互式的。测试用例通常是被定期执行的,执行过程必须完全自动化才有意义。单元测试不准使用System.out进行人工验证。必须使用assert验证
  • 保持单元测试的独立性。为了保证单元测试稳定可靠便于维护,单元测试用例之间不能互相调用,也不能依赖执行的先后顺序
  • 单元测试是可以重复执行的,不能收到外界环境的影响。
  • 对于单元测试,要保证测试粒度足够小,有助于精确定位问题,一般为方法级别
  • 核心业务,核心应用,核心模块的增量代码确保单元测试通过
  • 单元测试代码必须写在如下工程目录下:/src/test/java 不允许写在业务代码目录下
  • 单元测试的基本目标:语句覆盖达到70% 核心模块的语句覆盖和分支覆盖要达到100%
  • 编写单元测试代码要遵循BCDE原则,保证交付质量。border 边界测试循环边界,特殊取值,特殊时间点,数据顺序。correct正确的输入,并得到预期的结果。design 与设计文档相结合,编写单元测试。error强制错误信息输入,并得到预期的结果
  • 对于数据库相关的查询,更新,删除等操作,不能假设数据库里数据是存在的,或者直接操作数据库把数据插入进去,请使用程序插入或者导入数据方式准备数据
  • 和数据库相关的单元测试,可以设定自动回滚机制,不给数据库造成脏数据,或者对单元测试产生数据有明确的前后缀标识
  • 对于不可测试代码适当的时机做必要的重构,使代码变得可测,避免为了达到测试要求而书写不规范的测试代码
  • 在设计评审阶段,开发人员和测试人员一起确定单元测试范围,单元测试最好覆盖所有的测试用例
  • 为了方便进行单元测试,业务代码应避免以下情况 1 构造方法中做的事情过多,2 存在过多的全局变量和静态方法 3 存在过多的外部依赖 4 存在过多的条件语句
  • 不要对单元测试存在如下误解 1 那是测试同学干的事,2 单元测试代码是多余的 3 单元测试代码不需要维护 单元测试与线上故障没有辩证关系

4 安全规约

  • 隶属于用户个人的页面或者功能必须进行权限控制校验
  • 用户敏感数据禁止直接展示,必须对展示数据进行脱敏
  • 用户输入的sql参数,严格使用参数绑定或者metadata字段值限制,防止sql注入,禁止字符串拼接sql访问数据库
  • 用户请求传入任何参数必须做有效性验证 pagesize 过大的内存溢出,恶意order by 导致数据库慢查询 任意重定向 sql注入 反序列化注入 正则输入源串拒绝服务ReDoS
  • 禁止向HTML页面输入未经安全过滤或未正确转义的用户数据
  • 表单,AJAX提交必须执行CSRF安全验证
  • 在使用平台资源,短信,邮件,电话、下单、支付必须实现正确的防重放的机制,如数量限制,疲劳度控制,验证码校验,避免被滥刷而导致资源损耗
  • 发帖,评论,发生即时消息等用户生成的场景必须实现防刷,文本内容违禁词过滤等风控策略

5 MYSQL数据库

5.1 建表规约

  • 表达是与否概念的字段,必须使用is_xxx方式命名,数据类型是unsigned tinyint(1 是 0 否) 任何字段如果非负数,必须是unsigned
  • 表名,字段名必须使用小写字母或数字,禁止数字开头,禁止两个下划线中间出现数字。
  • 表名不使用复数名词
  • 禁用保留字
  • 主键索引名为pk_字段名,唯一索引名为uk_字段名。普通索引为idx_字段名
  • 小数类型为decimal 禁止使用float和double
  • 如果储存的字符串长度几乎相等,使用char定字符串类型
  • varchar是可变长字符串,不预先分配存储空间,长度不超过5000 ,如果储存长度超过,定义类型为text ,独立出来一张表。用主键来对应,避免影响其他字段索引效率
  • 表必备三字段:id create_time update_time
  • 表命名遵循"业务名称_表作用"
  • 库名与应用名称尽量一致
  • 修改字段含义对字段表示状态追加时,需要及时更新字段注释
  • 字段允许适当冗余,以提高查询性能,但必须考虑数据一致,冗余字段应遵循:1 不是频繁修改字段,2 不是varchar超长字段,更不能是text,不是唯一索引字段
  • 单表行数据超过500w行或者单表容量超过2GB才推荐进行分库分表
  • 合适的字符存储长度,不但节约数据库表空间,节约索引存储,提升检索速度

5.2 索引规约

  • 业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。
  • 超过三个表禁止join 。需要join的字段,数据类型必须保持一致。多表关联查询时,保证被关联的字段需要有索引
  • 在varchar字段上建立索引,必须制定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度即可
  • 页面搜索严禁左模糊或者全模糊,需要的话走搜索引擎来解决。
  • 如果有order by的场景,请注意利用索引的有序性。order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现file_sort的情况,影响查询性能。存在范围查询的话,索引无效
  • 利用覆盖索引来进行查询操作,避免回表
  • 利用延迟关联或者子查询优化超多分页场景
  • SQL性能优化目标:至少要达到range级别,要求是ref级别,如果可以是consts最好 consts 单表中最多只有一个匹配行,在优化阶段可以读取导数据,ref使用普通索引,range对索引进行范围检索
  • 建组合索引的时候,区分度最高的在最左边
  • 防止因字段类型不同造成的隐式装换,导致索引失效
  • 创建索引时避免有如下极端误解:宁滥勿缺,宁缺勿滥,抵制唯一索引

5.3 SQL语句

  • 不要使用count(列名)或count(常量) 替代count(),count(*)是sql92定义的标准统计行数的语法,跟数据库无关,跟NULL和非NULL无关
  • count(distinct col) 计算该列除NULL之外的不重复行数
  • 使用ISNULL() 来判断是否为NULL值,null与任何值比较结果都是null
  • 代码中写分页查询逻辑时,若count为0直接返回,避免执行后面的分页语句
  • 不得使用外键与级联,一切外键概念必须在应用层解决
  • 禁止使用储存过程,储存过程难以调试和扩展,更没有移植性
  • 数据订正时,要先select,避免出现误删除,确认无误后才能执行更新语句
  • in操作能避免则避免,若避免不了,需要评估in的数据量,保持1000以内
  • 如果有国际化需要,所有字符存储与表示,均以utf-8编码,注意字符统计函数的区别
  • TRUNCATE TABLE 比DELECT快,且使用的系统和事务日志资源少,但TRUNCATE无事务且不触发trigger,可能造成事故

5.4 ORM映射

  • 在表查询中,一律不要使用*作为查询的字段列表,需要哪些字段必须明确写明 1 增加查询分析器解析成本,2 增减字段容易与resultMap配置一致 3 无用字段增加网络消耗,尤其是text类型字段
  • POJO 类的布尔属性不能加is,而数据库字段必须加is_,要求在resultMap中进行字段与属性之间的映射
  • 不要用resultClass 当返回参数,即时所有类属性名称与数据库字段一一对应,也需要定义,每个表都需要一个pojo类与之对应
  • sql.xml配置参数使用:#{} #param# 不要使用${} 这种方式容易出现sql注入
  • mybatis自带的queryForList(String statementName , int start , int size) 不推荐使用(实现方式在数据库渠道statementName 对应sql语句的所有记录,再通过subList取start size的子集合)
  • 不允许直接拿HashMap与HashTable作为结果集的输出(值类型不可控制)
  • 更新数据表记录,必须同时更新对应的gmt_modified字段值为当前时间
  • 不要写大而全的数据更新接口。传入pojo类,不管是不是自己目标更新的字段,都update全部,1 是易出错,2 是效率低,3 binlog存储
  • @Transactional 事务不能滥用。事务会影响数据库的QPS , 另外使用事务的地方需要考虑各方面的回滚方案,包括缓存回滚,搜索引擎回滚,消息补偿,统计修正
  • 中的compareValue是与属性值对比的常量,一般是数字,表示相等时带上此条件。 表示不为空且不为null执行, 表示不为null值时执行。

6 工程结构

6.1 应用分层

  • 默认上层依赖于下层,箭头关系表示可直接依赖。

​ 开发接口层:可直接封装Service方法暴露RPC接口,通过Web封装成http接口,进行网关安全控制,流量控制;

​ 终端显示层:各个端的模板渲染并执行显示的层。当前主要是velocity渲染,js渲染,jsp渲染,移动端展示

​ web层:对访问控制进行转发,参数基本校验,不复用业务简单处理

​ service层:相对具体的业务逻辑服务层

​ manager层:对第三方平台封装层,预处理返回结果转化异常信息,对service层通用能力下沉,缓存方案,中间件通用处理,与DAO层交互,对多个DAO组合复用

​ DAO层:数据访问层,与底层MYSQL ,ORACLE ,HBase 进行数据交互

​ 外部接口或第三方平台:包括其他部门RPC开发接口,基础平台,其他公司的HTTP接口

  • 异常处理:DAO层的异常可以直接抛出到Service层,service层出现异常,必须打印日志到磁盘,尽可能带上参数。如果manager与service同机部署,日志方式与DAO层处理一致,单独部署采取与service一直处理方式。web层不应该继续上抛出异常,出于顶层,应该转到错误友好界面。
  • 分层领域模型规约
    • DO (Data Object) : 此对象与数据库表结构一一对应,通过DAO层向上传输数据源对象
    • DTO( Data Transfer Object) : 数据传输对象,server或manager向外传输对象
    • BO(Business Object) : 业务对象,由service层输出封装业务逻辑的对象
    • AO(Application Object):应用对象,在web层与service层之间抽象的复用对象模型
    • VO(View Object) : 显示层对象,通常是web向模板渲染引擎层传输对象
    • Query:数据查询对象,各层接受上层的查询请求,注意超过2个参数查询封装,禁止使用map类传输

6.2 二方库依赖

  • 定义GAV遵从以下规则 GroupID:com.{公司/BU}.业务线[.子业务线] ArtifactID:产品线名-模块名。语义不重复不遗漏,先到中央仓库去查证一下
  • 二方库版本号命名方式:主版本.次版本.修订号 主版本号:产品方向改变或者大规模API不兼容,或者架构不兼容升级。次版本号:保持相对兼容性,增加主要功能特征,影响极小的API不兼容修改。修订号:保持完全兼容性,修复BUG,新增次要功能特性等
  • 线上应用不要依赖SHAPSHOT版本,安全包除外
  • 二方库的新增和升级,保持除功能点外的其他jar包仲裁结果不变,如有改变必须明确评估和验证
  • 二方库可以定义枚举类型,参数可以使用枚举类型,但是接口返回值不允许使用枚举类型或者包含枚举类型的pojo对象
  • 依赖于一个二方库群时,必须定义一个统一的版本变量,避免版本号不一致
  • 禁止在子项目中pom依赖中出现相同的GroupId , 相同的ArtifactId,不同Version
  • 底层基础技术框架,核心数据管理平台,或近硬件端系统谨慎引入第三方实现
  • 所有pom文件的依赖声明放在 语句块中,所有版本仲裁放在语句中
  • 二方库不要有配置项,最低限度不要再增加匹配项
  • 避免应用二方库依赖冲突问题,1 精简可控原则,稳定可追溯原则

6.3 服务器

  • 高并发服务器建议调小TCP协议的time_wait超时时间
  • 调大服务器所支持的最大文件句柄数
  • 给JVM环境参数设置-xx:+HeapDumpOnOutOfMemoryError参数,让JVM碰到OOM场景时输出dump信息
  • 在线上生产环境,jvm的xms和xmx设置一样大小的内存容量,避免在GC后调整堆大小带来的压力
  • 服务器内部重定向使用forward,外部重定向地址使用url拼装工具类生成,否则可能带来URL维护不一致的问题和潜在的安全风险

7 设计规范

  • 存储方法和底层数据结构的设计获得评审一直通过,并沉淀成为文档
  • 在需求分析阶段,如果与系统交互的User超过一类并且相关User Case超过5个,使用用例图来表达更加清晰的结构化需求
  • 如果业务对象状态超过3个,使用状态图来表达并且明确状态变化的各个触发条件
  • 如果系统中某个功能调用链路设计对象超过三个,使用时序图来表达并且明确各个调用环节的输入与输出
  • 如果系统中模型类超过5个,并且存在复杂的依赖关系,使用类图来表达并且明确类之间的关系
  • 如果系统中超过两个对象之间存在协作关系,并且需要表示复杂的处理流程,使用活动图来表示
  • 需求分析与系统设计考虑主干功能的同时,需要充分评估异常流程和业务边界
  • 类在设计与现实是要符合单一原则
  • 谨慎使用继承的方式进行扩展,优先使用聚合/组合的方式实现
  • 系统设计,根据依赖倒置原则,尽量依赖抽象类和接口,有利于扩展与维护
  • 系统设计,注意对扩展开发,对修改闭合
  • 系统设计,共性业务或公共行为抽取出来公共模块,公共配置,公共类,公共方法。避免重复代码,重复配置
  • 避免如下误解:敏捷开发 = 讲故事 + 编码 + 发布
  • 系统设计主要目的:明确需求,理顺逻辑,后期维护,次要目的用于指导编码
  • 设计本质是识别和表达系统难点,找到系统变化点,并隔离变化点
  • 系统架构设计的目的:确定系统边界,确定系统在技术层面上的做于不做。确定系统内模块之间关系。确定模块之间的依赖关系以及模块的宏观输入及输出。确定指导后续设计与演化原则。使后续的子系统或模块设计在规定的框架内继续演化。确定非功能性需求,非功能性需求说的是安全性,可用性,可扩展性。
  • 无障碍产品设计,所有可交互的控件元素必须能被tab聚焦,并且焦点顺序符合自然操作逻辑,用户登陆校验和请求拦截的验证码需提供图形验证意外的其他方式
  • 自定义的控件需明确交互方式
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值