《Java开发手册》 个人笔记

最新文档下载链接:https://github.com/alibaba/p3c
在这里插入图片描述
在这里插入图片描述



编程规约

命名风格

  1. 命名采用英文,不允许采用拼音与汉字。

  2. 类名采用UpperCameICase风格命名,但 DO / BO / DTO / VO / AO / PO / UID 例外。

  3. 方法名、参数名、成员变量、局部变量使用lowerCamelCase风格命名。

  4. 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不嫌名字长。

  5. 抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类命名使用:测试类的名称+Test 。

  6. 定义数组使用 "int[ ] xxx"形式替代"int xxx[ ]"形式

  7. POJO类中的任何布尔类型的变量,都不要加is前缀,否则部分框架解析会引起序列化错误
    在这里插入图片描述

  8. 包名统一使用小写,点分隔符间有且只有一个自然语义的英语单词。
    包名统一使用单数形式,但如果类名有复数含义,类名可以使用负数形式。
    例如:com.alibaba.ei.kunlun.aap.util,类名 MessageUtils

  9. 避免子父类的成员变量间,或不同代码块的局部变量采用完全相同的命名。

  10. 在常量与变量命名时,表示类型的名词放在词尾,提升辨识度。
    例如:startTime / workQueue / nameList / TERMINATED_THREAD_COUNT

  11. 如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式。
    例如:public class LoginProxy; public class ResourceObserver;

  12. 接口类中的方法和属性不添加任何修饰符(包括public),保持代码整洁,并加上Javadoc注释。

  13. 接口和对应实现类的命名
    对于Service和DAO,基于SOA理念,暴露出来的服务一定是接口,内部的实现类用Impl的后缀与接口区别。
    如果是形容能力的接口名称,取对应的形容词为接口名(通常是-able的形容词),例如:AbstractTranslator 实现 Translatable 接口。

  14. 枚举类名带上Enum后缀,枚举成员名称需全大写,单词间用下划线划开。

  15. 各层命名规约:
    Service / DAO 层方法命名规约:

    • 获取单个对象的方法用 get 做前缀
    • **获取多个对象的方法用 list 做前缀,复数结尾。**如: listObjects
    • 获取统计值的方法用 count 做前缀
    • 插入的方法用 save / insert 做前缀
    • 删除的方法用 remove / delete 做前缀
    • 修改的方法用 update 做前缀

    领域模型命名规约:

    • 数据对象:xxxDO,xxx即为数据表名
    • 数据传输对象:xxxDTO,xxx为业务领域相关的名称
    • 展示对象:xxxVO,xxx一般为网页名称
    • POJO是 DO/DTO/BO/VO的统称,禁止命名成 xxxPOJO

常量定义

  1. 不允许任何魔法值(未经预先定义的常量)直接出现在代码中。
    例如:String key=“Id#taobao_”+tradeId; 这个语句中"Id#taobao_"是魔法值,很容易导致数据出错。
  2. 在long或Long赋值时,数值后使用大写的L,不能使用小写l,避免与数字1混淆。
  3. 不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。
  4. 常量的复用层次有五层:跨应用共享常量、应用内共享常量、子工程内共享常量、包内共享常量、类内共享常量。
    在这里插入图片描述
  5. 如果变量值仅在一个固定范围内变化用enum类型来定义。

代码格式

  1. 如果大括号{}内为空,则保持{}即可,中间无需换行空格。
    如果是非空代码块,则:左大括号前不换行,左大括号后换行;右大括号前换行,右大括号后若有else等代码则不换行,表示终止的必须换行。
  2. 左小括号与右小括号与括号内的字符间不出现空格,而左大括号前需要空格。
    例如:if (a == b)
  3. if / for / while / switch /do 等保留字与括号之间都必须加空格
  4. 任何二目、三目云算法的左右两边都需要加一个空格。
  5. 采用4个空格缩进,禁止使用Tab字符
    在这里插入图片描述
  6. 注释的双斜线与注释内容之间有且仅有一个空格
  7. 在进行强制转换时,右括号与强制转换值之间不需要任何空格隔开。
  8. 单行字符数限制不超过120个,超出则换行,换行规则如下:
    在这里插入图片描述
  9. 方法参数在定义和传入时,多个参数逗号后面必须加空格
  10. IDE的text file encoding 设置为 UTF-8;IDE中文件的换行符使用Unix格式,不使用Windows格式。
  11. 单个方法的总行数不超过80行,除注释外的方法签名、左右大括号、方法内代码、空行、回车及任何不可见字符的总行数。
  12. 不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开能够提升可读性,但没必要多个。

OOP规约

  1. 避免使用一个类的对象引用访问该类的静态变量或静态方法,直接使用类名来访问。
  2. 所有覆写方法,必须加@Override注解
  3. 尽量不使用可变参数编程。
  4. 外部正在调用或者二方库依赖的接口,不允许修改方法签名,避免对接口调用放产生影响。接口过时必须加@Deprecated注解,并清晰地说明采用的新接口或者新服务是什么。
  5. 不能使用过时的类或方法。
  6. Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals。
  7. 所有整型包装类对象之间值的比较,全部使用equals方法比较。
    在这里插入图片描述
  8. 任何货比金额,均以最小货比单位且整型类型来进行存储。
  9. 浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用equals来判断。
    在这里插入图片描述
  10. BigDecimal的等值比较应该使用compareTo()方法,而不是equals()方法(equals()方法会比较值和精度,1.0与1.00会返回false,而compareTo()则会忽略精度
  11. 定义数据对象DO类时,属性类型要与数据库字段类型相匹配。
    例如:数据库字段的 bigint 必须与属性的 Long 类型相对应。
  12. 禁止使用构造方法BigDecimal(double)方式把double值转化为BigDecimal对象,因为BigDecimal存在精度损失。
    在这里插入图片描述
  13. 所有的POJO类属性必须使用包装数据类型;RPC方法的返回值和参数必须使用包装数据类型;所有的局部变量使用基本数据类型。
  14. **定义 DO / DTO / VO 等POJO类时,不要设定任何属性的默认值。**例如:createTime设置默认值为new Date()
  15. 构造方法里面进制加入任何业务逻辑,如果有初始化逻辑,请放在init方法中。
  16. 禁止在 POJO 类中,同时存在对应属性 xxx 的 isXxx() 和 getXxx() 方法。
  17. 类内方法定义的顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter方法。
  18. final可以声明类、成员变量、方法、以及本地变量,下列情况使用final关键字:
    (1)不允许被继承的类,如:String类
    (2)不允许修改引用的域对象,如:POJO类的域变量
    (3)不允许被覆写的方法,如:POJO类的setter方法
    (4)不允许运行过程中重新赋值的局部变量
    (5)避免上下文重复使用一个变量,使用final关键词可以强制重新定义一个变量,方便更好地进行重构。
  19. 慎用Object的clone方法来拷贝对象,因为默认是浅拷贝,如果想实现深拷贝,需要重写clone方法。
  20. 类成员与方法访问控制从严:
    (1)如果不允许外部直接通过new来创建对象,那么构造方法必须是private。
    (2)工具类不允许有 public 或 default 构造方法。
    (3)若是staic成员变量,考虑是否为final。

日期时间

  1. 一般表示日期的格式:yyyy-MM-dd HH:mm:ss (H表示24小时制,h表示12小时制)
  2. 获取当前毫秒数:System.currentTimeMillis()
  3. 不允许在程序任何地方使用:可能会有异常出现。
    (1)java.sql.Date
    (2)java.sql.Time
    (3)java.sql.Timestamp
  4. 不要在程序中写死一年为365天。使用LocalDate.now().lengthOfYear();获取今年的天数。
  5. 避免公历闰年2月问题。闰年的2月份有29天,一年后不存在2月29日。
  6. 使用枚举值来指代月份。如果使用数字,注意 Date,Calendar 等日期相关类的月份month取值在0-11之间。

集合处理

  1. 关于 hashCode 和 equals 的处理,遵循如下规则:
    (1)只要覆写 equals,就必须覆写 hashCode。
    (2)因为 Set 存储的是不可重复对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须覆写这两种方法。
    (3)如果自定义对象作为 Map 键,那么必须覆写 hashCode 和 equals 。
    注:String类因为覆写了 hashCode 和 equals 方法,所以可以愉快地使用String对象作为可以key来使用。
  2. 判断所有集合内部的元素是否为空,使用 isEmpty() 方法替代 size() == 0 的方式。因为在某些集合中,使用 isEmpty() 方法时间复杂度为O(1)。
  3. ArrayList 的 subList 结果不可强转成 ArrayList,否则会抛出ClassCastException 异 常:java.util.RandomAccessSubList cannot be cast to java.util.ArrayList。subList()返回的是 ArrayList 的内部类 SubList,并不是 ArrayList 本身,而是 ArrayList 的一个视图,对于 SubList 的所有操作最终会反映到原列表上
  4. 使用 Map 的方法 keySet()/values()/entrySet()返回集合对象时,不可以对其进行添加元素操作,否则会抛出 UnsupportedOperationException 异常。
  5. 在 subList 场景中,高度注意对父集合元素的增加或删除,均会导致子列表的遍历、增加、删除产生 ConcurrentModificationException 异常。
  6. 使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。
    在这里插入图片描述
  7. 使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一致、长度为 0 的空数组。
    在这里插入图片描述
  8. 在使用 Collection 接口任何实现类的 addAll()方法时,都要对输入的集合参数进行 NPE 判断。因为在 ArrayList#addAll 方法的第一行代码即Object[] a = c.toArray(); 其中 c 为输入集合参数,如果为 null,则直接抛出异常。
  9. 泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用 add 方法, 而<? super T>不能使用 get 方法,两者在接口调用赋值的场景中容易出错。
    扩展:PECS(Producer Extends Consumer Super)原则:第一、频繁往外读取内容的,适合用<? extends T>。第二、经常往里插入的,适合用<? super T>
  10. 在无泛型限制定义的集合赋值给泛型限制的集合时,在使用集合元素时,需要进行instanceof 判断,避免抛出 ClassCastException 异常.
  11. 不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。
  12. 集合初始化时,指定集合初始值大小,如果暂时无法确定初始值大小,设置默认值为16。尽早确定初始值大小可以降低扩容所带来的的性能成本。
  13. 使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。
    在这里插入图片描述
  14. Map类集合K/V存储null的情况如下:
    在这里插入图片描述
  15. 利用 Set 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 List 的contains()进行遍历去重或者判断包含操作。

并发处理

  1. 获取单例对象需要保证线程安全,其中的方法也要保证线程安全。
  2. 创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
  3. 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
  4. 线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
    在这里插入图片描述
  5. SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为 static,必须加锁,或者使用 DateUtils 工具类。
    在这里插入图片描述
  6. 必须回收自定义的 ThreadLocal 变量,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代理中使用 try-finally 块进行回收。
  7. 尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用 RPC 方法。
  8. 对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。
  9. 在使用阻塞等待获取锁的方式中,必须在 try 代码块之外,并且在加锁方法与 try 代码块之间没有任何可能抛出异常的方法调用。
  10. 在使用尝试机制来获取锁的方式中,进入业务代码块之前,必须先判断当前线程是否持有锁。锁的释放规则与锁的阻塞等待方式相同。
  11. 如果每次访问冲突概率小于 20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于3 次。
  12. 资金相关的金融敏感信息,使用悲观锁策略。
  13. 避免 Random 实例被多线程使用,在JDK7前,应该保证每个线程持有一个单独的Random实例;在JDK7之后,可以直接使用 API ThreadLocalRandom。
  14. volatile 解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。volatile不保证操作的原子性。
  15. HashMap 在容量不够进行 resize 时由于高并发可能出现死链,导致 CPU 飙升,在开发过程中注意规避此风险。HashMap是线程不安全的,在涉及多线程操作的时候应该选择线程安全的集合。
  16. ThreadLocal 对象使用 static 修饰,ThreadLocal 无法解决共享对象的更新问题。

控制语句

  1. 在一个 switch 块内,每个 case 要么通过 continue/break/return 等来终止,要么注释说明程序将继续执行到哪一个 case 为止;在一个 switch 块内,都必须包含一个 default语句并且放在最后,即使它什么代码也没有。
  2. 当 switch 括号内的变量类型为 String 并且此变量为外部参数时,必须先进行 null判断。
  3. 在 if/else/for/while/do 语句中必须使用大括号。
  4. 三目运算符 condition? 表达式 1 : 表达式 2 中,高度注意表达式 1 和 2 在类型对齐时,可能抛出因自动拆箱导致的 NPE 异常。
    在这里插入图片描述
  5. 在高并发场景中,避免使用”等于”判断作为中断或退出的条件。
    在这里插入图片描述
  6. 除常用方法(如 getXxx/isXxx)等外,不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。
    在这里插入图片描述
  7. 循环体中的语句要考量性能,以下操作尽量移至循环体外处理,如定义对象、变量、获取数据库连接,进行不必要的 try-catch 操作
  8. 避免采用取反逻辑运算符。

注释规约

  1. 类、类属性、类方法的注释必须使用 Javadoc 规范,使用/*内容/格式,不得使用// xxx 方式
  2. 所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释、除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。
  3. 所有的类都必须添加创建者和创建日期。
    添加方法:
    在这里插入图片描述
    在这里插入图片描述
  4. 方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释,注意与代码对齐。
  5. 所有的枚举类型字段必须要有注释,说明每个数据项的用途。
  6. **谨慎注释掉代码。在上方详细说明,而不是简单地注释掉。**如果无用,则删除。
  7. 避免出现注释的一个极端:过多过滥的注释。
  8. 特殊注释标记,请注明标记人与标记时间。注意及时处理这些标记,通过标记扫描,经常清理此类标记。线上故障有时候就是来源于这些标记处的代码。
    在这里插入图片描述

前后端规约

  1. 前后端交互的 API,需要明确协议、域名、路径、请求方法、请求内容、状态码、响应体。
    在这里插入图片描述
  2. 前后端数据列表相关的接口返回,如果为空,则返回空数组[]或空集合{}
  3. 服务端发生错误时,返回给前端的响应信息必须包含 HTTP 状态码,errorCode、errorMessage、用户提示信息四个部分。
    在这里插入图片描述
  4. 在前后端交互的 JSON 格式数据中,所有的 key 必须为小写字母开始的lowerCamelCase 风格。
  5. errorMessage 是前后端错误追踪机制的体现,可以在前端输出到 type="hidden"文字类控件中,或者用户端的日志中,帮助我们快速地定位出问题。
  6. 对于需要使用超大整数的场景,服务端一律使用 String 字符串类型返回,禁止使用Long 类型。Java 服务端如果直接返回 Long 整型数据给前端,JS 会自动转换为 Number 类型,而两种类型可能因精度损失造成不一致。
  7. HTTP 请求通过 URL 传递参数时,不能超过 2048 字节(2048字节是取所有浏览器的最小值)
  8. HTTP 请求通过 body 传递内容时,必须控制长度,超出最大长度后,后端解析会出错。
  9. 服务器内部重定向必须使用 forward;外部重定向地址必须使用 URL 统一代理模块生成,否则会因线上采用 HTTPS 协议而导致浏览器提示“不安全”,并且还会带来 URL 维护不一致的问题。
  10. 服务器返回信息必须被标记是否可以缓存,如果缓存,客户端可能会重用之前的请求结果,减少交互次数。
    在这里插入图片描述
  11. 前后端的时间格式统一为"yyyy-MM-dd HH:mm:ss",统一为 GMT。
  12. 在接口路径中不要加入版本号,版本控制在 HTTP 头信息中体现,有利于向前兼容。

其他

  1. 在使用正则表达式时,利用好其预编译功能(不要在方法体内定义),可以有效加快正则匹配速度。
  2. 避免用 Apache Beanutils 进行属性的 copy,可以使用其他方案比如 Spring BeanUtils, Cglib BeanCopier,注意均是浅拷贝。
  3. 后台输送给页面的变量必须加 ! v a r — — 中 间 的 感 叹 号 。 如 果 v a r 等 于 n u l l 或 者 不 存 在 , 那 么 !{var}——中间的感叹号。如果 var 等于 null 或者不存在,那么 !varvarnull{var}会直接显示在页面上。
  4. 注意 Math.random() 这个方法返回是 double 类型,注意取值的范围 0≤x<1(能够取到零值,注意除零异常),如果想获取整数类型的随机数,不要将 x 放大 10 的若干倍然后取整,直接使用 Random 对象的 nextInt 或者 nextLong 方法
  5. 任何数据结构的构造或初始化,都应指定大小,避免数据结构无限增长吃光内存。
  6. 对于暂时被注释掉,后续可能恢复使用的代码片断,在注释代码上方,统一规定使用**三个斜杠(///)**来说明注释掉代码的理由。

异常日志

错误码

  1. 错误码的制定原则:快速溯源、沟通标准化。错误码回答的问题是谁的错?错在哪?
  2. 错误码不体现版本号和错误等级信息。:错误码以不断追加的方式进行兼容。错误等级由日志和错误码本身的释义来决定。
  3. 全部正常,但不得不填充错误码时返回五个零:00000。
  4. 错误码为字符串类型,共 5 位,分成两个部分:错误产生来源+四位数字编号。
    在这里插入图片描述
  5. 编号不与公司业务架构,更不与组织架构挂钩,以先到先得的原则在统一平台上进行, 审批生效,编号即被永久固定。
  6. 错误码使用者避免随意定义新的错误码。尽可能在原有错误码附表中找到语义相同或者相近的错误码在代码中使用即可。
  7. 错误码之外的业务独特信息由 error_message 来承载,而不是让错误码本身涵盖过多具体业务属性。
  8. 在获取第三方服务错误码时,向上抛出允许本系统转义,由 C 转为 B,并且在错误信息上带上原有的第三方错误码。
  9. 错误码的后三位编号与 HTTP 状态码没有任何关系。

异常处理

  1. Java 类库中定义的可以通过预检查方式规避的 RuntimeException 异常不应该通过catch 的方式来处理,比如:NullPointerException,IndexOutOfBoundsException 等等。
  2. 异常捕获后不要用来做流程控制,条件控制。
  3. catch 时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码,不应该放在try块中。对于非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理。
  4. 捕获异常是为了处理它,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。
  5. 事务场景中,抛出异常被 catch 后,如果需要回滚,一定要注意手动回滚事务。
  6. 方法的返回值可以为 null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回 null 值。
  7. 防止 NPE,是程序员的基本修养,注意 NPE 产生的场景:
    在这里插入图片描述
  8. 定义时区分 unchecked / checked 异常,避免直接抛出 new RuntimeException(),更不允许抛出 Exception 或者 Throwable,应使用有业务含义的自定义异常。推荐业界已定义过的自定义异常,如:DAOException / ServiceException 等。
  9. 对于公司外的 http/api 开放接口必须使用 errorCode;而应用内部推荐异常抛出;跨应用间 RPC 调用优先考虑使用 Result 方式,封装 isSuccess()方法、errorCode、errorMessage;而应用内部直接抛出异常即可。

日志规约

  1. 应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架(SLF4J、JCL–Jakarta Commons Logging)中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
  2. 所有日志文件至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。对于当天日志,以“应用名.log”来保存,保存在/home/admin/应用名/logs/目录下,过往日志格式为: {logname}.log.{保存日期},日期格式:yyyy-MM-dd
  3. 根据国家法律,网络运行状态、网络安全事件、个人敏感信息操作等相关记录,留存的日志不少于六个月,并且进行网络多机备份。
  4. 应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:appName_logType_logName.log。logType:日志类型,如 stats/monitor/access 等;logName:日志描述。
  5. 在日志输出时,字符串变量之间的拼接使用占位符的方式,降低性能损耗。
  6. 对于 trace/debug/info 级别的日志输出,必须进行日志级别的开关判断。
  7. 避免重复打印日志,浪费磁盘空间,务必在日志配置文件中设置 additivity=false。
  8. 生产环境禁止直接使用 System.out 或 System.err 输出日志或使用e.printStackTrace()打印异常堆栈。
  9. 日志打印时禁止直接用 JSON 工具将对象转换成 String。打印日志时仅打印出业务相关属性值或者调用其对象的 toString()方法。
  10. 注意日志输出的级别,error 级别只记录系统逻辑出错、异常或者重要的错误信息。

单元测试

  1. 好的单元测试必须遵守 AIR 原则。Automatic(自动化);Independent(独立性);Repeatable(可重复)
  2. 单元测试应该是全自动执行的,并且非交互式的。单元测试中不准使用 System.out 来进行人肉验证,必须使用 assert 来验证。
  3. 单元测试用例之间决不能互相调用,也不能依赖执行的先后次序。
  4. 单元测试是可以重复执行的,不能受到外界环境的影响。
  5. 对于单元测试,要保证测试粒度足够小,有助于精确定位问题。单测粒度至多是类级别,一般是方法级别。
  6. 单元测试代码必须写在如下工程目录:src/test/java
  7. 编写单元测试代码遵守 BCDE 原则,以保证被测试模块的交付质量。Border,边界值测试;Correct,正确的输入;Design,与设计文档相结合,来编写单元测试;Error,强制错误信息输入。
  8. 和数据库相关的单元测试,可以设定自动回滚机制,不给数据库造成脏数据。或者对单元测试产生的数据有明确的前后缀标识。

安全规约

  1. 用户敏感数据禁止直接展示,必须对展示数据进行脱敏。
  2. 用户输入的 SQL 参数严格使用参数绑定或者 METADATA 字段值限定,防止 SQL 注入,禁止字符串拼接 SQL 访问数据库
  3. 用户请求传入的任何参数必须做有效性验证。
    注:Java 代码用正则来验证客户端的输入,有些正则写法验证普通用户输入没有问题,但是如果攻击人员使用的是特殊构造的字符串来验证,有可能导致死循环的结果。
  4. 禁止向 HTML 页面输出未经安全过滤或未正确转义的用户数据。
  5. 表单、AJAX 提交必须执行 CSRF 安全验证。:CSRF(Cross-site request forgery)跨站请求伪造是一类常见编程漏洞。对于存在 CSRF 漏洞的应用/网站,攻击者可以事先构造好 URL,只要受害者用户一访问,后台便在用户不知情的情况下对数据库中用户参数进行相应修改
  6. URL 外部重定向传入的目标地址必须执行白名单过滤。
  7. 在使用平台资源,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放的机制,如数量限制、疲劳度控制、验证码校验,避免被滥刷而导致资损。
  8. 发贴、评论、发送即时消息等用户生成内容的场景必须实现防刷、文本内容违禁词过滤等风控策略。

MySQL数据库

建表规约

  1. 表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint(1 表示是,0 表示否)。任何字段如果为非负数,必须是 unsigned,数据库表示是与否的值,使用 tinyint 类型。
  2. 表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字。MySQL 在 Windows 下不区分大小写,但在 Linux 下默认是区分大小写。因此,数据库名、表名、字段名,都不允许出现任何大写字母。
  3. 表名不使用复数名词
  4. 主键索引名为 pk_字段名;唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。
  5. 小数类型为 decimal,禁止使用 float 和 double。
  6. varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于此值,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。
  7. 表必备三字段:id, create_time, update_time。
    其中 id 必为主键,类型为 bigint unsigned、单表时自增、步长为 1。create_time,update_time的类型均为 datetime 类型,前者现在时表示主动式创建,后者过去分词表示被动式更新
  8. 库名与应用名称尽量一致。
  9. 字段允许适当冗余,以提高查询性能,但必须考虑数据一致。冗余字段应遵循:
    (1)不是频繁修改的字段。(2)不是唯一索引的字段。(3) 不是 varchar 超长字段,更不能是 text 字段。
  10. 单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。

索引规约

  1. 业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。
  2. 超过三个表禁止 join。需要 join 的字段,数据类型保持绝对一致;多表关联查询时,保证被关联的字段需要有索引。
  3. 在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度。
  4. 页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。
    说明:索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。
  5. 如果有 order by 的场景,请注意利用索引的有序性。order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。索引如果存在范围查询,那么索引有序性无法利用。
  6. 索引如果存在范围查询,那么索引有序性无法利用,能够建立索引的种类分为主键索引、唯一索引、普通索引三种,而覆盖索引只是一种查询的一种效果,用 explain (sql语句分析的命令)的结果,extra 列会出现:using index(使用了索引)。
  7. SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts最好。
  8. 建组合索引的时候,区分度最高的在最左边
  9. 防止因字段类型不同造成的隐式转换,导致索引失效。(无论类型自动或是强制转换,都会造成索引失效)

SQL语句

  1. 不要使用 count(列名)或 count(常量)来替代 count(*),它是 SQL92 定义的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。
  2. count(distinct col) 计算该列除 NULL 之外的不重复行数,注意 count(distinct col1, col2) 如果其中一列全为 NULL,那么即使另一列有不同的值,也返回为 0。
  3. 当某一列的值全是 NULL 时,count(col)的返回结果为 0,但 sum(col)的返回结果为NULL,因此使用 sum()时需注意 NPE 问题
    可以使用如下方式来避免 sum 的 NPE 问题:SELECT IFNULL(SUM(column), 0) FROM table;
  4. 使用 ISNULL()来判断是否为 NULL 值。因为NULL 与任何值的直接比较(NULL<>NULL、NULL=NULL、NULL<>1)都为 NULL
  5. 代码中写分页查询逻辑时,若 count 为 0 应直接返回,避免执行后面的分页语句。
  6. 禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。
  7. 数据订正(特别是删除或修改记录操作)时,要先 select,避免出现误删除,确认无误才能执行更新语句。
  8. 对于数据库中表记录的查询和变更,只要涉及多个表,都需要在列名前加表的别名(或表名)进行限定。(从可维护性考虑,即使目前某字段不存在多表中,也要添加表的别名,因为未来可能会增加同名字段)
  9. SQL 语句中表的别名前加 as,别名可以是表的简称,也可以以 t1、t2、t3、…的顺序依次命名。
  10. in 操作能避免则避免,若实在避免不了,需要仔细评估 in 后边的集合元素数量,控制在 1000 个之内。
  11. 因国际化需要,所有的字符存储与表示,均采用 utf8 字符集,那么字符计数方法需要注意。
    在这里插入图片描述
  12. TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少,但TRUNCATE无事务且不触发 trigger,有可能造成事故,故不建议在开发代码中使用此语句。

ORM映射

  1. 在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。
  2. POJO 类的布尔属性不能加 is,而数据库字段必须加 is_,要求在 resultMap 中进行字段与属性之间的映射。
  3. 每一个表也必然有一个<resultMap>与之对应。
  4. sql.xml 配置参数使用:#{},#param# 不要使用${} 此种方式容易出现 SQL 注入
  5. iBATIS 自带的 queryForList(String statementName,int start,int size)不推荐使用。
    在这里插入图片描述
  6. 不允许直接拿 HashMap 与 Hashtable 作为查询结果集的输出。
  7. 更新数据表记录时,必须同时更新记录对应的 update_time 字段值为当前时间。
  8. 执行 SQL 时,不要更新无改动的字段
  9. @Transactional 事务不要滥用。事务会影响数据库的 QPS,另外使用事务的地方需要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。
  10. <isEqual>中的 compareValue 是与属性值对比的常量,一般是数字,表示相等时带上此条件;<isNotEmpty>表示不为空且不为 null 时执行;<isNotNull>表示不为 null 值时执行。

工程结构

应用分层

  1. 根据业务架构实践,结合业界分层规范与流行技术框架分析,推荐分层结构如图所示:
    在这里插入图片描述
  2. (分层异常处理规约)在 DAO 层,产生的异常类型有很多,无法用细粒度的异常进行 catch,使用 catch(Exception e)方式,并 throw new DAOException(e),不需要打印日志,因为日志在 Manager/Service 层一定需要捕获并打印到日志文件中去,如果同台服务器再打日志,浪费性能和存储。在 Service 层出现异常时,必须记录出错日志到磁盘,尽可能带上参数信息,相当于保护案发现场。Manager 层与 Service 同机部署,日志方式与 DAO 层处理一致,如果是单独部署,则采用与 Service 一致的处理方式。Web 层绝不应该继续往上抛异常,因为已经处于顶层,如果意识到这个异常将导致页面无法正常渲染,那么就应该直接跳转到友好错误页面,尽量加上友好的错误提示信息。开放接口层要将异常处理成错误码和错误信息方式返回。
  3. 分层领域模型规约:
    在这里插入图片描述

二方库依赖

二方库:公司内部其他项目的依赖

  1. 定义 GAV 遵从以下规则:
    在这里插入图片描述
  2. 二方库版本号命名方式:主版本号.次版本号.修订号。起始版本号必须为:1.0.0。
  3. 线上应用不要依赖 SNAPSHOT 版本(安全包除外),即尚处于开发阶段的版本;正式发布的类库必须先去中央仓库进行查证,使 RELEASE 版本号有延续性,且版本号不允许覆盖升级。
  4. 二方库里可以定义枚举类型,参数可以使用枚举类型,但是接口返回值不允许使用枚举类型或者包含枚举类型的 POJO 对象。
  5. 依赖于一个二方库群时,必须定义一个统一的版本变量,避免版本号不一致。
  6. 所有 pom 文件中的依赖声明放在<dependencies>语句块中,所有版本仲裁放在 <dependencyManagement>语句块中。子项目的version 和 scope 都读取自父 pom。
  7. 为避免应用二方库的依赖冲突问题,二方库发布者应当遵循以下原则:
    (1)精简可控原则。移除一切不必要的 API 和依赖。
    (2)稳定可追溯原则。每个版本的变化应该被记录。除非用户主动升级版本,否则公共二方库的行为不应该发生变化。

服务器

  1. 高并发服务器建议调小 TCP 协议的 time_wait 超时时间。操作系统默认 240 秒后,才会关闭处于 time_wait 状态的连接,在高并发访问下,服务器端会因为处于 time_wait 的连接数太多,可能无法建立新的连接,所以需要在服务器上调小此等待值。
  2. 调大服务器所支持的最大文件句柄数(File Descriptor,简写为 fd)
  3. 给 JVM 环境参数设置-XX:+HeapDumpOnOutOfMemoryError 参数,让 JVM 碰到 OOM场景时输出 dump 信息。
  4. 服务器内部重定向必须使用 forward;外部重定向地址必须使用 URL Broker 生成,否则因线上采用 HTTPS 协议而导致浏览器提示“不安全“。此外,还会带来 URL 维护不一致的问题。

设计规约

  1. 在需求分析阶段,如果与系统交互的 User 超过一类并且相关的 User Case 超过 5 个,使用用例图来表达更加清晰的结构化需求。
  2. 如果某个业务对象的状态超过 3 个,使用状态图来表达并且明确状态变化的各个触发条件。状态图的核心是对象状态,首先明确对象有多少种状态,然后明确两两状态之间是否存在直接转换关系,再明确触发状态转换的条件是什么。
  3. 如果系统中某个功能的调用链路上的涉及对象超过 3 个,使用时序图来表达并且明确各调用环节的输入与输出。
  4. 如果系统中模型类超过 5 个,并且存在复杂的依赖关系,使用类图来表达并且明确类之间的关系。
  5. 如果系统中超过 2 个对象之间存在协作关系,并且需要表示复杂的处理流程,使用活动图来表示。
  6. 系统架构设计时明确以下目标:
    在这里插入图片描述
  7. 需求分析与系统设计在考虑主干功能的同时,需要充分评估异常流程与业务边界。
  8. 类在设计与实现时要符合单一原则。
  9. 谨慎使用继承的方式来进行扩展,优先使用聚合/组合的方式来实现
  10. 系统设计阶段,根据依赖倒置原则,尽量依赖抽象类与接口,有利于扩展与维护。
  11. 系统设计阶段,注意对扩展开放,对修改闭合。(开闭原则)
  12. 系统设计阶段,共性业务或公共行为抽取出来公共模块、公共配置、公共类、公共方法等,在系统中不出现重复代码的情况,即 DRY 原则(Don’t Repeat Yourself)。
  13. 敏捷开发是快速交付迭代可用的系统,省略多余的设计方案,摒弃传统的审批流程,但核心关键点上的必要设计和文档沉淀是需要的。
  14. 设计文档的作用是明确需求、理顺逻辑、后期维护,次要目的用于指导编码
  15. 可扩展性的本质是找到系统的变化点,并隔离变化点。
  16. 代码即文档的观点是错误的,清晰的代码只是文档的某个片断,而不是全部。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值