前言
没有开发规约造成的问题:
数据库的表结构和设计索引设计缺陷可能导致软件上的架构缺陷或性能风险
工程结构比较混乱,后期运维困难
没有鉴权的代码容易被黑客攻击
一个好的开发规约可以带来:
便于协同开发
便于高质量开发
提升系统稳定性
手册涉及的维度:
编程规约
异常日志
单元测试
安全规约
MySQL数据库
工程结构
设计规约
手册的规约等级:
强制
推荐
参考
手册的延伸部分:
说明:适当扩展和解释 规约内容
正例:提倡的编码和实现方式
反例:需要提防的雷区和真实的错误案例
手册涉及的知识点:
计算机基础
面向对象思想
JVM
数据结构与集合
并发与多线程
单元测试
编程规约
命名风格:
命名元素:不可以_ $开始命名/结束命名 一般
命名以字母开头 ,变量名可包括数字 _ 字母
命名元素:不可使用中文命名,不可拼音和英文混合使用,可纯拼音/
纯英文 ,当然要尽量避免使用纯拼音(除非一些国际通用的名称例如alibaba taobao youku hangzhou等可视为英文
类名格式:
类名 要使用大驼峰式写法
UpperCamelCase ,
每个单词首字母均大写 ,例如UserController。但DO/BO/DTO/VO/AO/PO/UID例外,例如UserVO(VO:View Object属于两个单词,首字母都得大写)
方法/参数/变量名格式:
方法名/参数名/成员变量/局部变量 使用小驼峰式写法
lowerCamelCase ,
除了首个单词其余单词首字母均大写 ,例如getUserName(int objectId)
常量名格式:
常量名全部大写 ,
单词间使用下划线 ,语义必须表达清楚完整,不要嫌名字长(不怕长就怕别人看不懂)例如CURRENT_USER
抽象类命名:Abstract/Base开头,例如BaseService
异常类命名:Exception结尾,例如ServerException
测试类命名:以需要测试的类名开始,以Test结尾,例如ApplicationTest
测试方法命名:以需要测试的方法名称开始,以Test结尾,例如saveUserTest(User user)
数组:类型[],例如int[] arrayDemo = new int[10];
POJO类中的Boolean类型变量都不能加is前缀 ,否则部分框架解析会引起序列化错误
包名:
均小写,单数形式 ;如果类名有复数含义则类名使用复数形式。例如com.practice.springmvc.util,类名为MessageUtils
杜绝不规范的缩写,避免别人看不懂
命名一定要意义完整,例如AtomicReferenceFieldUpdater原子更新的类名
常量与变量混合命名的时候,表示类型的名词放在词尾以提高辨识度,例如startTime/workQueue/THREAD_COUNT
设计模式:如果模块/接口/类/方法使用了设计模式,命名时需要体现具体的设计模式,利于阅读者快速理解架构设计理念。例如LoginProxy利用了代理模式
接口:接口类中的方法和属性不需要加修饰符,保持代码的简洁性。可加上有效的
Javadoc注释(一般使用javadoc xxx.java命令将程序中类、方法、属性等的注释抽取出来形成文档) 。尽量不要在接口中定义变量,除非要和接口方法有关且时整个应用的基础常量。
JDK8接口允许默认实现
接口Service/Dao,实现类为ServiceImpl/DaoImpl
接口为表示能力的,实现类以tor结尾,例如AbstractTranslator实现Translatable接口
枚举类 加上
Enum后缀 ,
成员名全大写,单词间使用下划线隔开
父子类之间避免使用相同命名,如果是一个含义子类直接调用父类即可,如果不是一个含义没必要使用相同命名
各层命名规约:controller/service:get list(用复数形式结尾) count save update delete;mapper:select check insert update delete
getxxxList == listxxxs getxxxCount == countxxx
POJO:Plain Ordinary Java Object 简单Java对象,即普通JavaBean
Entity(DO):实体类,一张数据表对应一个实体类
BO:Business Object 业务对象,将业务逻辑封装为一个对象(service层)
VO:Value Object 值对象,体现在视图层,用于前端展示以及视图层和控制层的数据传输封装
DTO:Data Transfer Object 数据传输对象,目标是数据访问对象从数据库中检索数据
PO:Persistant Object 持久层对象,一个PO就是数据库中的一条记录
DAO:Data Access Object 数据访问对象,负责持久层操作,封装对数据的访问(并非对数据库的访问)
Controller:控制层,业务层和视图层的中间层,负责传输VO对象和调用BO层(业务不复杂的话就是单纯service调用entity)的业务方法
View:视图层,即前端
实际项目中,一般应用:controller,service(bo),entity(po),dao,vo,view
JavaBean与POJO的区别:
POJO是简单的JavaBean类,具有属性和getter/setter方法
JavaBean是一种规范:
《Java开发手册-嵩山版补充》代码和注释中避免任何语言的种族歧视性词语
常量定义:需要提前声明、分类声明、enum
不允许没定义的常量直接出现在代码中 ,即使是一个文件路径分割符都得在本类中/const类中进行声明,便于后续运维改动变量值(不需要多处改动);避免编写代码时出现
编写错误导致常量使用错误(想用"abc",结果写成"ab") 极力避免硬编码 +(字符串拼接)
Long类型数据:数据默认以L结尾,不要以小写l结尾,以防和数字1混淆;Float类型数据以F结尾,Double类型数据以D结尾
不要使用一个常量类来维护所有常量,可按照功能进行分类,分开维护。适用于常量多且杂的项目,多用几个常量类来定义声明不同类型的常量(
可以在常量类中定义interface和enum来分类某个模块的常量 )
若变量值仅在一个固定范围内变化,使用enum定义
常量的复用层次:跨应用共享常量、应用内共享常量、子工程内共享常量、包内共享常量。类内共享常量
代码格式:
大括号中为空,直接{},无需加空格或换行
大括号中不为空,左大括号前不换行,左大括号后换行;右大括号前换行,后有else不换行,后表终止必须换行
public void aMetnod() {} //大括号中为空
public void bMethod() {
//...
} else {
//...
}
...
小括号左右和字符间无空格;左大括号前要有空格
if/for/while/switch/do等保留字/关键字与括号间要有空格
保留字:Reserve Word,在Java现有版本中无特殊含义,不能作为变量名称/类名称
const goto
关键字:Key Word,对Java编译器有特殊意义,用来表示一种数据类型或表示程序的结构
51个
访问修饰符:public private protected 用于修饰类/接口/抽象类,方法,属性。构造方法,常量,主方法
类,接口,抽象类:9个
class interface abstact(定义)
extends(继承类) implements(实现接口)
new(新建一个对象)super(调用父类方法/成员变量) this(指代当前对象)
instanceof(a instanceof b 判断a对象是否是其类型的子类b的实例)
数据类型:13个
void(无返回值)
byte short int long(整形数据)
float double(浮点型数据)
char(字符型数据)
boolean(判断型数据)
enum(枚举)
null true false(值类型)
线程:2个
synchronized 线程同步,修饰方法,代码块表示方法,代码块的同步
volatile 修饰属性表示属性的同步
异常:5个
throw 抛出异常,在方法中间使用 throw new xxxException();
throws 抛出异常给调用者,在方法名后使用 xxxxMehod() throws xxxException {}
try 捕获{}中的异常
catch 处理try捕获的异常
finally 不管是否有异常都会执行的代码块
返回:
return
循环,条件:if else switch case break default continue while do for
包:package import(导包到某类中)
瞬时的:transient 只能修饰变量,不能修饰方法和类,不需要serilizable的属性可加transient表征生命周期仅存在于调用者的内存而非磁盘中持久化
断言:assert
不可变的:final
静态:static
二目、三目运算符的左右两边都得加一个空格(赋值运算、逻辑运算、普通加减乘除运算)
int a = 3;
// 三目运算举例(注释符号与注释间有且只有一个空格
)
int a == 3 ? a = 1 : a = 2;
采用4个空格缩进,进制tab字符缩进(除非规定一个tab字符缩进数为4个空格)
双斜杠注释与注释内容间有且仅有一个空格
IDEA中的text file encoding必须设置为UTF-8,换行符为Unix格式 Windows目录\\,Linux目录/
每个方法除开注释的行数不超过80行,把高可用的/相对独立的单独抽取出来成为一个方法
不需要加很多空格保持多个变量赋值中的=在相同位置,保证=左右各有一个空格即可
private int a = 3;
private double b = 3.0D;
private float c = 4.1F;
private Long d = 1L;
不同业务代码可使用空行分隔开提升可读性,没必要插入多个空行。例如一个方法的多个模块,多个方法之间
《Java开发手册-泰山版补充》 类型强制转换的时候,右括号与强制转换值之间不需要空格
long first = 10.000L;
int second = (int)first + 1;
《Java开发手册-泰山版补充》 方法如果有多个参数,那么在定义和传入的时候,参数逗号后必须要加一个空格 method(arg1, arg2, ...)
OOP规约:
避免通过一个类的对象引用(new一个类对象)访问此类的静态变量/静态方法,徒增编译器解析成本,直接
类名.变量/类名.方法访问 即可
所有覆写方法均加@Override注解,
子类覆写父类的方法要加@Override注解(父类修改子类未修改,编译会立马报错,降低纠错成本;可以判断子类是否覆写父类成功,如果没有@Override注解,那么getObject可能写成get0bject也不报错(后面是数字0),实际上并没有覆写)
可变参数必须放在方法参数列表的最后,最好是不要使用可变参数编程,可变参数是指相同参数类型/业务含义,但是不确定其数量时可用
public List listUsers(String type, Long... ids) {...}
如果写的项目正在被其他项目调用,绝对不能修改方法签名(方法修饰符、返回值类型、方法名、参数列表),可以使用@Deprecated注解表示方法过时(但是该方法依旧可以被调用即旧服务依旧提供),并清楚说明提供的新方法是什么
调用方不可使用已经过时的方法,有义务去考证过时方法的新实现是什么
是否相同比较:使用常量/确定有值的对象来调用equals方法(否则容易抛出空指针异常),推荐使用JDK7引入的java.util.Objects类的静态方法equals(Object a, Object b)进行比较
如果是纯字符串比较,可使用StringUtils类的equals方法
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));// 值相等/为null可使用a == b; 引用相同可使用a != null && a.equals(b);
}
所有
整型包装类对象的值比较,使用equals方法
浮点数的等值判断,不管是基本数据类型和包装数据类型,都不可直接使用equals判断(由于丢失精度会导致结果为false)
可指定一个极小的误差范围,保证二者的差值在这个范围内即认为二者相等
使用
BigDecima l定义值(调用的构造方法参数为String类型才可),再进行浮点数的运算操作
创建BigDecimal对象,可使用new BigDecimal(String str);
也可使用BigDecimal.valueOf(Double d); 底层会使用Double.toString(d)按double的实际可表达的精度与尾数进行截断
不可直接使用参数为Double类型的构造方法创建BigDecimal对象,会造成精度损失
《Java开发手册-嵩山版补充》
BigDecimal比较两个值是否相同,使用compareTo可忽略精度,如果是equals方法会比较值和精度(那么1.0和1.00返回结果为false,精度不同,前者为0.1 后者为0.01)
double v1 = 1.11D;
double v2 = 1.11D;
// BigDecimal b = BigDecimal.valueOf(v1); // 底层valueOf方法: return new BigDecimal(Double.toString(val));
BigDecimal b1 = new BigDecimal(Double.toString(v1)); //BigDecimal转double: 使用doubleValue()方法
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.equals(b2); // true
}
定义DO类(Entity/Domian)的时候,属性类型必须要和数据库表的字段类型匹配
数据库的bigint对应DO属性的Long类型(如果使用Integer可能unsigned bigint的id过大的话映射导致超出Integer的表示范围而造成溢出成为负数)
数据库的BLOB对应DO属性的String类型(转为二进制字节码流进行交互)
数据库的VARCHAR对应DO属性的String类型
数据库的datetime对应DO属性的Date类型
数据库的tinyint对应DO属性的boolean类型
数据库的int对应DO属性的Integer类型(一般自增id都使用bigint以防后续数据超多)
基本数据类型:所有的局部变量推荐使用基本数据类型(short byte int long double float char boolean)
包装数据类型:所有的
POJO类属性、RPC方法的返回值和参数必须使用包装数据类型 (Short Byte Integer Long Double Float Character Boolean String)
定义POJO类(DO/DTO/VO/BO等):
不设定属性默认值(如果本身有默认值,创建的时候就会赋默认值,插入到数据库,但是需求是不需要插入这个默认值,出现冲突)
POJO类必须要toString方法,方便使用类的toString方法打印属性值排查问题
POJO类的属性如果是Boolean属性,使用setXXX和getXXX方法;如果是boolean属性,使用setXXX和isXXX方法
且getter和setter方法中最好不要加业务逻辑避免增加排查难度(XXX和属性名保持一致)推荐使用Boolean包装数据类型
POJO类属性必须是包装数据类型
序列化类(类 implements Serializable)即使新增属性,也不需要修改serialVersionUID字段,避免反序列化失败;当然如果不改无法反序列化那么就请修改这个值(避免因为serialVersionUID不一致导致抛出序列化运行时异常)
构造方法中禁止加入业务逻辑,如果需要创建一个init方法,在方法中通过调用构造方法new一个对象,然后添加业务逻辑
类中的方法:
多个同名方法/构造方法,按顺序放置在一起,方便阅读
方法定义顺序:
public方法 > protected方法 > private方法 > getter/setter方法
说明:
公有方法是类的调用者和维护者最关心的方法,首屏展示最好;保护方法虽然只是子类关心,也可
能是“模板设计模式”下的核心方法;而
私有方法外部一般不需要特别关心,是一个黑盒实现
;因为承载
的信息价值较低,所有 Service 和 DAO 的 getter/setter 方法放在类体最后。
循环体中推荐使用StringBuilder的append方法来拼接字符串(如果使用String +,会每次都重新创建一个String对象,赋值后由JVM销毁之前的String对象,过程复杂且资源浪费)
如果是多线程并发执行使用有安全机制(方法和代码块加synchronized关键字,即有一个线程执行其他线程堵塞直到这个线程结束释放同步锁)的StringBuffer,单线程多拼接使用StringBuilder,单线程拼接少的话使用String
final关键字使用:可声明类、成员变量、方法、本地变量
不可被继承的类 如String
不可被修改引用的域对象
不可被覆写的方法 如POJO中的setter方法
不可在运行过程中重新赋值的局部变量
为避免上下文同时使用一个变量,可通过final强制重新定义一个变量(感觉很少用到,目前还没接触过)
谨慎使用Object的clone方法实现对象拷贝,clone默认为浅拷贝,如果需要深拷贝可以考虑重写clone方法,或者遍历对象进行赋值(对于应用对象使用重新new一个对象,然后再赋值)
拷贝:将对象A中的成员属性值赋值给对象B
浅拷贝:按位拷贝,基本数据类型直接值拷贝;包装数据类型拷贝
地址引用(而非引用对象) 即AB中的这一属性指向同一内存空间(并且如果B中的包装数据发生改变,会引发A发生改变) 底层是直接调用父类的clone方法
3. 深拷贝:为引用类型的数据成员另辟空间,实现真正内容上的拷贝。B中引用类型的改变不再影响A中对应的引用类型 底层是对引用类型调用类型的clone方法
类成员与方法控制一定要严格:
若不允许外部直接通过new创建对象,这个对象类的构造方法一定是private,并提供静态方法供其他类调用,这个方法中可new创建类对象
工具类中方法不允许有public default构造方法,多为public static方法
类中非static且与子类共享的成员变量,必须为protected
类中非static且只在本类使用的,必须private
类中static且只在本类使用,必须private(一次创建多次使用,其他处可重新赋值;若多次使用无需重新赋值,添加final关键字)
类中static属性若项目中唯一值不可变,添加final关键字 public/private static final xxx...;
类方法只本类调用,必须private
类方法只本类/继承类调用,必须protected
任何类、方法、参数、变量,严控访问范围。
过于宽泛的访问范围,不利于模块解耦
。思考:如果
是一个 private 的方法,想删除就删除,可是一个 public 的 service 成员方法或成员变量,删除一下,不
得手心冒点汗吗?(可能会其他地方有所调用)变量像自己的小孩,尽量在自己的视线内,变量作用域太大,无限制的到处跑,那么你
会担心的。
《Java开发手册-泰山版补充》
任何货币金额,均以最小货币单位且整型类型进行存储 例如人民币用分来存储(常用最小单位为分)
《Java开发手册-泰山版补充》
日期时间:
日期格式化,yyyy-MM-dd HH:mm:ss(24小时制) 大写的YYYY表示week in which year
yyyy-MM-dd hh:mm:ss(12小时制)
不允许在程序的任何地方使用java.sql.Date java.sql.Time java.sql.Timestamp
不要在程序中写死一年为365天,避免在公历闰年时出现日期转换错误或程序逻辑错误
// 获取今年的天数
int daysOfThisYear = LocalDate.now().lengthOfYear();
// 获取指定某年的天数
LocalDate.of(2011, 1, 1).lengthOfYear();
避免公历闰年2月问题。
闰年的 2 月份有 29 天,一年后的那一天不可能是 2 月 29
日
使用枚举值来指代月份,注意如果使用Date Canlendar的话月份的取值范围是0-11 例如Canlendar.JANUARY Calendar.MARCH
集合处理:
hashCode和equals:如果一个对象需要覆写equals方法,那么必须覆写hashCode(例如引用类型的Set集合覆写;Key为引用类型的Map集合覆写) String本身已覆写直接用即可
Collections类返回的对象如emptyList()/singletonList()都是不可变的(immutable list),不可对其进行添加/删除元素的操作,否则会报UnsupportedOperationException异常
集合转数组:使用集合的toArray(T[] array)方法,方法参数为同类型长度为0的空数组
List list = Lists.newArrayList();
String[] array = new String[0];
list.toArray(array); // String[] array = list.toArray(new String[0]);
使用集合的addAll方法时,需要对输入的集合参数进行NPE判断 NPE:NullPointerException空指针异常
数组转集合:使用Arrays.asList()
foreach循环中不可进行remove/add操作,
remove元素的话使用迭代器Iterator方式 ,如果有并发必须对Iterator对象加锁
List list = new ArrayList<>();
list.add("1");
list.add("2");
for (String item : list) {
if ("2".equals(item)) { //使用此法集合的倒数第二个参数不会报错,主要是因为remove这个元素后,iterator的hasNext方法找不到下一个元素,直接返回false就不会进入到next方法中,所以就不会检查并发修改是否异常 游标++ == remove前的size-1 hasNext方法返回false
list.remove(item); //如果remove掉2之后,list的大小-1,游标++不等于size进入next方法 游标==2 size==1
}
} // 结果报错:原因是foreach底层是创建了一个Iterator对象,使用iterator对象的hasNext判断是否还有元素,cursor游标++,next方法取下一个元素。如果使用list的remove方法会导致iterator对象的modCount++从而和iterator自己方法操作递增的expectedModCount值不同而抛出ConcurrentModificationException并发修改异常,如果使用iterator的remove方法可以保证modCount和expectedModCount始终相同,所以不会报错
// Exception in thread "main" java.util.ConcurrentModificationException
// at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
// at java.util.ArrayList$Itr.next(ArrayList.java:851)
JDK7及以上,集合定义可使用diamond语法或全省略
Map userMap = new HashMap<>();
List userList = new ArrayList(); // 或List userList = Lists.newArrayList(); 二者在使用上无区别
集合初始化的时候最好指定集合初始值的大小,避免后续多次扩容。如果暂时无法确定默认16(一般为需要存储的元素个数/负载因子默认0.75)+ 1
推荐使用Map类的entrySet遍历,而非keySet 主
要是后者需要遍历两次,先是转为iterator对象获取所有的key,在遍历key获取key对应的value;后者直接遍历一次就将key和value放在entry中。 JDK8的话直接使用Map对象的forEach方法即可
Map maps = Maps.newHashMap();
maps.put(1,"aaa");
maps.put(2,"bbb");
maps.forEach((key,value) -> {
System.out.println(key +":" +value);
});
利用 Set 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 List 的
contains 方法进行遍历、对比、去重操作
集合的有序性sort/unsort:遍历结果按照某种比较规则顺序依次排列的 ArrayList无序 HashMap无序 TreeSet有序
集合的稳定性order/unorder:集合每次遍历的元素次序是一定的 ArrayList稳定 HashMap不稳定 TreeSet稳定
《Java开发手册-泰山版补充》判断集合是否为空,使用isEmpty,而非size() == 0,使用isEmpty时间复杂度为O(1) 也可使用apache.commons.collections4.CollectionUtils的isEmpty方法,底层用的也是isEmpty
并发处理:
获取单例对象需要保证线程安全,其中的方法也需要线程安全 资源驱动类(数据库连接池等)、工具类、单例工厂类都需要注意
创建线程/线程池的时候使用有意义的线程名称,方便出错时回溯 例如TabelMaintainRunnableThread表示TableMaintainRunnable线程类的一个线程
线程资源必须由线程池提供,不可显式创建
线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资
源的开销,解决资源不足的问
题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
SimpleDateFormat线程不安全,一般不要定义为static变量,如果需要定义为static那么得加锁,或者使用DateTime类
DateTimeFormat替代
// Str转Date String-DateTime-Date
private static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";
// 定义格式化对象
DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(STANDARD_FORMAT);
//将字符串解析为DateTime对象
DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr);
// 将DateTime转为Date
Date date = dateTime.toDate();
// Date转Str
if(date == null) {
return StringUtils.EMPTY; // 返回""空字符串,而非null对象(可能会引起其他地方报错)
}
// 将Date转为DateTime Date-DateTime-String
DateTime dateTime = new DateTime(date);
// 格式化为string
String dateStr = dateTime.toString(STANDARD_FORMAT);
自定义的ThreadLocal变量必须回收,尤其在线程池场景下,由于线程经常会被复用,如果不清理的话可能会影响后续业务逻辑和造成内存泄漏,尽量在try-finally中进行回收
高并发同步调用的时候需要考虑到锁的性能损耗:
尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用 RPC 方法
能用无锁数据结构,就不要用锁
能锁区块,就不要锁整个方法体
能用对象锁,就不要用类锁
OOM:Out Of Memory内存溢出,原因可能是分配太少;用得太多;用完没释放
对多个资源、数据库表、对象同时加锁的时候,需要保持加锁顺序一致,否则可能会造成死锁。 例如线程一需要对表A B C依次全部加锁才可进行更新,那么线程二加锁顺序也得A B C
AQS:Abstact Queued Synchronized,提供一种实现阻塞锁和一系列依赖FIFO等待队列的同步器框架
进入业务代码块之前,必须先判断当前线程是否持有锁。锁的释放规则与锁的阻塞等待方式相同(如果有锁则释放;如果锁被占用则等待)
并发修改同一记录的时候,为避免更新丢失需要加锁,加锁位置可在应用层、缓存、数据库层,
推荐使用version作为更新依据
如果每次访问冲突概率小于 20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于
3 次
。
乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,不会上锁。更新的时候会判断一下在此期间别人有没有更新这个数据(使用版本号机制和CAS算法实现) 乐观锁适用于多读少写的应用类型,可提高吞吐量
悲观锁:总是假设最坏的情况,每次拿数据都会加锁 Java中的synchronized体现悲观锁思想
控制语句:
switch块中,每个case要么通过continue/break/return终止,要么注释说明程序需要执行到哪个case终止。必须包含default放在最后即使default中啥都没有
说明:
注意
break
是退出
switch
语句
块,而
return
是退出方法体
。
switch括号中为String的时候,在执行switch-case前需要做null判断,否则不执行switch-case且报错NPE "null" != null
if/else/for/while/do即使是循环体中只有一行代码,也要使用大括号且避免采用单行编码方式如if (condition) statements;
在高并发场景中,不要使用“等于”作为中断/退出的条件,可
使用大于/小于区间来代替
。因为如果并发控制没有做好的话可能会产生等值判断被“击穿”的情况,例如
判断剩余奖品数量等于 0 时,终止发放奖品,但因为并发处理错误导致奖品数量瞬间变成了负数,
这样的话,活动无法终止
if-elseif请勿超过3层,如果需要的话使用卫语句、策略模式、状态模式等实现
卫语句:多条if语句,每条使用return返回
if (man.isUgly()) {
System.out.println("本姑娘是外貌协会的资深会员");
return;
}
if (man.isPoor()) {
System.out.println("贫贱夫妻百事哀");
return;
}
2. 策略模式:把对象本身和运算规则区分开,设计策略接口,符合这个策略的方法实现这个策略接口即可
3. 状态模式: 一个对象的行为取决于一个或多个动态变化的属性 允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象( Objects for States),状态模式是对象行为型模
式
不要在其他表达式中插入赋值语句,容易让人忽略本身变量的变量值已经改变
循环体需要考虑性能,定义对象、定义变量、获取数据库连接、不必要可移至循环体外的的try-catch都尽量移至循环体外,避免每次循环都消耗一次资源
避免采用 取反逻辑运算符,不利于快速理解(除非取反处理更简单,例如DateTime的after方法取反可以判断当前时间是否小于等于被比较时间)
if (!(new DateTime(dateStr).isAfterNow())) { // dataStr是否不晚于当前时间,即是否小于登录当前时间
// ...
}
需要进行参数校验:对外提供RPC/API/HTTP开放接口;敏感权限入口;需要极高稳定性和可用性的方法;执行期间开销很大的方法;调用频次低的方法
不需要进行参数校验:被循环调用的方法;底层调用频度较高的方法(例如DAO层,因为参数错误不太可能到底层才暴露);被声明为private供自己调用的,在调用之前已经进行校验那就无需在这个private方法中二次校验
注释规约:
类、类属性、类方法注释必须使用
/**内容*/格式
即Javadoc规范
所有的
抽象方法必须要加注释,返回值、参数、异常说明、方法功能都得详细说明
所有类必须添加创建者
@Auth
o
r
和创建日期
@Date
方法中的单行注释需要
在被注释的语句上方另起一行,使用//注释
方法中的
多行注释使用/* */
,注意与代码对齐
英文不ok的话倒不如中文讲的明明白白,当然专有名词与关键字该英文的还得英文,总的来讲就是得便于阅读和理解
代码修改的同时要保证注释的同步修改(尤其参数、返回值、异常、核心逻辑等)
注释相当于导航功能
注释代码需谨慎,并且在上方详细说明注释的理由;如果无用的话直接删除(自己觉得多实现方法可以自己备份项目在本地加上,项目中的代码要保证简洁性)
注释的要求:
可准确反映设计思想和代码逻辑
能描述业务含义,使别的程序员可迅速了解代码背后的信息,主要是好久没看也可清晰理解当时的思路;也方便代码给继任者看便于其快速接替工作
好的命名、代码结构本身有自解释能力,注释力求简洁准确,表达到位
特殊注释需要注明标记人和标记时间并及时处理(通过标记扫描)
TODO:待办事宜,表示需要实现但尚未实现的功能,只能应用于类、接口和方法
FIXME:错误,表名某段代码是错误的需要纠正但还未纠正
《Java开发手册
-嵩山版补充
》
前后端规约:
前后端交互的API,需要明确协议、域名、路径、请求方法、请求内容、状态码、响应体
协议:生产环境必须使用HTTPS
路径:每个API对应一个路径表示API具体的请求地址
代表一种资源,只能为名词,推荐使用复数,不能为动词,请求方法已表达动作含义
url不可大写
禁止携带表示请求内容类型的后缀,如.json .xml 这些通过accept表达即可
请求方法:对资源具体操作的定义
GET:从服务器取资源
POST: 在服务器新建资源
PUT:在服务器更新资源
DELETE: 从服务器删除资源
请求内容:URL带的参数必须无敏感信息,符合安全要求,body中带参数的话必须设置Content-Type,否则前后端可能无法绑定数据
响应体:可放置多种数据类型,有Content-Type头确定
前后端数据列表相关的接口返回,如果为空直接返回空数组[]或者空集合{},有利于数据层面上的协作更高效,减少前端琐碎的null判断
服务端发生错误,返回前端的响应信息必须包含HTTP状态码(浏览器),errorCode(前端开发),errorMessage(错误排查人员),用户提示信息(用户)四个部分
前后端交互的JSON格式数据中,所有key遵从小驼峰lowerCamelCase风格
对于超大整数场景,服务端一律使用String字符串类型返回,禁止使用Long类型
HTTP请求通过URL传递参数时,不能超过2048字节
说明:
不同浏览器对于 URL 的最大长度限制略有不同,并且对超出最大长度的处理逻辑也有差异,2048
字节是取所有浏览器的最小值。
其他:
正则表达式使用的时候,利用其预编译功能,可有效加快正则匹配速度。简单来讲就是
在方法体外执行Pattern.complie("regex")创建正则表达式对象,避免放在方法体中多次调用方法多次预编译降低性能
Math.random()方法随机值区间为[0,1),返回值为double类型;如果需要获取整数类型的随机数,直接使用Random.nextInt()/Random.nextLong()方法,方法取值从0开始(包括0)
// 取(0,1)的随机值
double value1 = Math.random();
// 当value1 == 0的时候重新取值,直到value1 != 0为止跳出while循环
while (value1 == 0) {
value1 = Math.random();
}
// 取[10,20]的随机值
/* Random的取值范围[0,n),当左值不为0的时候,可以上限-下限范围降为Random支持的区间,取完随机值再加一个下限即可; 如果需要包括上限,则取值 +1
nextInt(n + 1) == [0,n+1) = [0,n]
int value2 = Random.nextInt(20 - 10 + 1) + 10;
获取系统当前毫秒数:System.currentTimeMills()
日期格式化:yyyy-MM-dd HH:mm:ss(12小时制hh:mm:ss)
及时清理不再使用的代码段或配置信息
《Java开发手册-泰山版补充》避免使用Apache BeanUtils进行属性copy,推荐使用Spring BeanUtils拷贝,注意都是浅拷贝(对于对象拷贝的是引用地址并非引用对象值)
Apache BeanUtils 力求做得完美 , 在代码中增加了非常多的校
验、兼容、日志打印等代码,过度的包装导致性能下降严重
异常日志
《Java开发手册-泰山版补充》
错误码:
错误码执行规则:快速溯源、简单易记、沟通标准化
异常处理:
Java类库中定义的可通过预检查方式规避的RuntimeException异常不应该通过catch方式处理,例如NullPointerException IndexOutOfBoundsException等,即提前进行校验例如判断对象是否为null,如果不是则写业务逻辑
异常不用来做流程控制,条件控制,异常的效率远低于条件判断
捕获异常是为了处理它,而非啥都不干直接抛弃;如果不想处理那就直接throw new xxxException抛给其调用者,最外层的业务使用者必须处理异常并将其转为用户可理解的内容
如果try中有事务,catch处理异常后要手动rollback回滚事务
finally中需要对资源对象、刘对象关闭,如果有异常也得加try-catch JDK7以后支持try-with-resources(省略finally)
try (InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dst)) { // 将创建对象放在try中,try-catch结束后自动将这些资源释放(编译的时候底层其实还是try-catch-finally 此乃语法糖的魅力所在简化代码并保证功能)
byte[] buff = new byte[1024];
int n;
while ((n = in.read(buff)) != 0) {
out.write(buff, 0, n);
}
} catch (IOException e) {
e.printStackTrace();
}
finally中不能加return,否则会覆盖掉try中的return(因为try执行完return之后还得执行finally块)
抛出的异常必须和捕获的异常匹配,或者捕获异常是抛出异常的父类
方法返回值可以为null,必须添加注释说明什么情况下返回null值;防止NPE是调用者的责任,
即使被调用方法返回空集合或者空对象,对调用者来说,也
并非高枕无忧,必须考虑到远程调用失败、序列化失败、运行时异常等场景返回 null 的情况
RPC方法返回封装的Result对象,Result对象封装错误码、isSuccess方法、错误信息
对于多个方法都需要进行相同参数的校验操作,抽出来做成一个private boolean checkParam方法(抽取公共方法/公共类/组件化)
日志规约:
日志依赖日志框架SLF4J的API,使用门面模式的日志框架,利于日志维护和统一各个类的日志处理
门面模式:
提供一个
统一的接口去访问多个子系统的多个不同的接口
,它为子系统中的一组接口提供一个统一的高层接口。使得子系统更容易使用 例如客户通过调用包工头接口中的不同方法去调用和泥、砌墙等类中的方法(客户只用和包工头交互,具体的盖房实现由包工头找人即和泥的,砌墙的,贴砖的等人实现)
缺点:不符合开闭模式
优点:松耦合,使用简单
与 代理模式的区别:相同点是都引入了中介起到了代理的功能;但是代理模式是代理一个类,且代理类和原类实现的是同一个功能;而门面类接口代理的是一系列类,实现的不同类可有不同的功能
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Logger logger = LoggerFactory.getLogger(DepartmentServiceImpl.class);
所有的日志文件至少保存15天;
网络
运行状态、安全相关信息、系统监测、管理后台操作、用户敏感操作需要留存相关的网络日志不少于 6 个月,且进行网络多机备份。
应用中的例如打点、临时监控、访问日志等扩展日志的命名方式:
appName_logType_logName.log
logType为日志类型(stats/monitor/access),logName为日志描述 这样的话就可通过文件名知道日志文件属于哪个应用,什么类型,什么目的,也利于归类查找
例如force_web_timeZoneConvert.log表示force-web应用中单独监控时区转换异常
日志输出时,字符串拼接使用
占位符
而非append/+硬拼接(有性能损耗),使用占位符仅仅是替换动作
logger.info("{} : 账户信息已更新", username); //username替换info中的{}
《Java开发手册-泰山版补充》生产环境中禁止直接使用System.out System.err输出日志或使用e.printStackTrance()打印异常堆栈
说明:
标准日志输出与标准错误输出文件每次 Jboss 重启时才滚动,如果大量输出送往这两个文件,容易
造成文件大小超过操作系统大小限制。
单元测试 Unit Test
AIR:
A:Automatic自动化
I:Independent独立性
R:Repeatable可重复
测试代码遵守BCDE原则以保证被测试模块的交付质量
B:Border边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等
C:Corrent正确输入,并得到预期结果
D:Desing 与设计文档结合编写单元测试
E:Error强制错误信息输入,包括非法数据、异常流程等并得到预期处理结果
单元测试为全自动执行的,测试方法中
不允许使用System.out进行人肉检测,必须使用assert断言来验证
保证单元测试的独立性,每个测试方法前@Test注解,每个测试方法可独立运行
保证单元测试可重复执行
保证测试粒度足够小,利于精准定位问题,顶多是类界别,一般都是方法测试 xxxMethodTest
核心业务、核心应用、核心模块的增量代码确保单元测试通过。
说明:
新增代码及时补充单元测试,如果新增代码影响了原有单元测试,请及时修正
单元测试代码目录:/src/test/java下,不可写在业务代码目录下(/src/main/java)因为源码编译会跳过test目录
在工程规约的应用分层中提到的 DAO 层,Manager 层,可重用度高的 Service,都应该进行单元
测试。
单元测试的基本目标:语句覆盖率达到
70%
;核心模块的语句覆盖率和分支覆盖率
都要达到 100%
和数据库相关的单元测试,可以设定自动回滚机制,不给数据库造成脏数据。或者
对单元测试产生的数据有明确的前后缀标识。
单元测试最好覆盖所有的测试用例
单元测试也需要进行维护
安全规约:
隶属于用户个人的页面/功能必须进行权限校验,防止横向越权
横向越权:同级用户访问/修改其他用户的数据
避免方法:读操作和写操作添加用户的验证(落实到数据库读写操作中)
对于用户敏感数据禁止直接展示,必须进行脱敏操作 禁止向HTML页面输入未经安全过滤/未正确转义的用户数据
/**
* 手机号中间四位的正则表达式
*/
private static final String MOBILE_PATTEN = "(\\d{3})\\d{4}(\\d{4})"; // 从0开始,第3位即第四个数字,中间4个数字,结尾留4个数字
/**
* 替换中间四位的正则表达式
*/
private static final String MOBILE_REPLACE_PATTEN = "$1****$2";
// 密码置""
user.setPassword(StringUtils.EMPTY);
// 手机号中间四位置*
user.setMobile(user.getMobile().replaceAll(MOBILE_PATTEN,MOBILE_REPLACE_PATTEN));
密码:使用org.apache.commons.lang3的StringUtils的EMPTY将密码置空""
手机号:中国大陆的个人手机号码显示隐藏中间4位,防止隐私泄露
用户输入的SQL参数严格使用参数绑定/METADATA字段值限定,防止SQL注入,防止字符串拼接SQL访问数据库
用户请求传入的任何参数都必须进行有效性验证
表单、AJAX提交必须执行CSRF安全验证
CSRF:Cross-site request forgery跨站请求伪造,
对于存在 CSRF 漏洞的应用
/网站,攻击者可以事先构造好 URL,只要受害者用户一访问,后台便在用户不知情的情况下对数据库中
用户参数进行相应修改
发贴、评论、发送即时消息等用户生成内容的场景必须实现防刷、文本内容违禁词
过滤等风控策略
MySQL数据库
建表规约:
表达是否的字段使用is_xxx命名,字段类型为unsigned tinyint(1为是,0为否)
表名、字段名必须使用小写字母/数字,开头只能小写字母,禁止两个下划线中间只出现数字。由于数据库字段名无法进行预发布修改代价很大(涉及代码中映射的DO类属性联调修改),所以字段名称需要慎重考虑
表名不用复数名词
禁用保留字 order user等
主键索引名:pk_字段名
唯一索引名:uk_字段名
普通索引名:idx_字段名
小数类型的字段使用decimal并注明小数位,例如decimal(6,2)表明总共6位数,小数点后2位即xxxx.xx(禁止使用float和double,存在进度丢失问题)
如
果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数并分开存储
若存储的字符串长度每条数据都几乎相同,使用char定长字符串类型
如果不确定长度,使用varchar,不预先分配存储空间;如
果存储长度大于5000(varchar的存储长度),字段类型改为text且独立出一张表用主键对应,避免影响其他字段索引效率(如果有BLOB类型字段同text类型字段,独立出来和主键单独组成一张表)
一张表的必备字段:
bigint unsigned类型的自增id,步长为1
datetime类型的create_time
datetime类型的update_time
表名:业务名_表的作用,例如alipay_task system_config
字段、索引、表都得加注释
字段允许适当冗余以提高查询性能,但必须考虑数据一致性
如果一张表行数超出500万行/容量超出2GB,推荐分库分表
索引规约:
表中
具有唯一特性的字段/字段组合,必须建成唯一索引
唯一索引的查询速度非常明显,相较之下对insert的速度损耗可以忽略
即使在应用层做了非常完善的校验控制,根据墨菲定律只要没有唯一索引必然会产生脏数据
禁止超过三张表的join操作,需要join操作的字段类型必须一致 ;多表关联查询的时候需要保证被关联的字段有索引
页面搜索严禁全模糊搜索/左搜索,如果需要直接去搜索引擎即可
左模糊:%xxx,
索引的最左匹配原则不能被打破,所以无法使用
利用覆盖索引来查询操作,避免回表
回表:执行一条sql语句,需要从多个b+索引中取数据
覆盖索引:
能够建立索引的种类分为主键索引、唯一索引、普通索引三种,而覆盖索引只是一种查询的一种效
果,用 explain 的结果,extra 列会出现:using index
回表的避免方法:select目标
利用延迟关联/子查询优化多分页场景
SQL语句:
不要用count(1) count(字段名)替代count(*),count(*)是SQL92定义的标准统计行数的语法,与数据库无关,跟是否为NULL无关即
count(*)会统计值为NULL的行,但是count(字段名)不会统计此列为NULL值的行 统计行数使用count(*)/count(1)
count(1)和count(*)没有什么区别在MySQL中,但是无法确定在其他数据库中是否无区别,况且SQL92标准用法,并且已经优化的很好了,所以统计行数直接使用count(*)即可
count(distinct col)可计算该列除了NULL之外的不重复行数(值为NULL的行不记录) ,count(distinct col1, col2)若其中一列为全为null,结果返回0(即col1 col2都不记录任意一个值为NULL的行)
如果某一列全为NULL,count(col)结果为0,但是需要注意的是sum(col)结果为null,所以在使用sum的时候要特别注意NPE问题,可使用
IFNUL
L 判断,如果是NULL的话将sum结果置为0
SELECT IFNULL(SUM(column), 0) FROM table; // 如果sum(column)结果为null,则IFNULL整体结果为0;如果不为null则IFNULL整体结果为sum(column),保证结果不为NULL从而避免NPE问题
代码中的分页查询逻辑中,如果count为0直接返回,避免执行后续的分页语句(即先查查数据库有无数据即检查查询的结果集合是否为null,有则分页,无则直接返回null/空集合即可)
数据表设计中不可用外键,外键概念应该在应用层解决,极力避免数据库本身的级联更新(级联更新易引起数据库更新风暴即短时间内很多更新操作;外键影响数据库插入速度)
禁止使用存储过程 ,难以调试和扩展,更没有移植性
存储过程:Stored Procedure,一种在数据库中存储复杂程序以便外部程序调用的一种数据库对象,简单来讲就是数据库SQL语言层面上的代码封装与重用
目的:完成特定功能的SQL语句集,经编译创建并保存在数据库中,用户可通过指定存储过程的名字并给定参数来调用执行
优点:
可封装,隐藏复杂的商业逻辑
可回传值,可接受参数
无法使用select命令来执行
可用于数据检验,强制实行商业逻辑
缺点:
往往定制化于特定的数据库上,因为支持的编程语言不同。当切换到其他厂商的数据库系统时,需要重写原有的存储过程
性能调校与撰写,受限于各种数据库系统
数据写操作尤其是修改和删除操作,要先select避免出现误删除,确认无误后再执行修改/删除语句
in操作要避免,如果避免不了控制in后的集合元素数量在1000个以内
select
from product
where status = 1
and product.name like #{productName}
and category_id in
#{item}
数据库字符集统一使用utf-8编码,
utf8_general_ci;如果存储表情,则使用utf8mb4
truncate table速度比delete块,且使用的系统和事务日志资源少,但是其无事务且不触发trigger可能会造成事故,所以在开发环境中不使用,一般使用的都是delete而且基本都要求加where条件(不加where条件就是全表数据删除)
drop table table_name; 删除某张表
truncate table table_name;清空某张表,只留下表的字段和类型(相当于刚刚创建好这个表)
delete from table_name where condition;删除表中满足condition条件的数据,如果没有condition相当于truncate操作
《Java开发手册-泰山版补充》SQL语句中如果涉及多个表,使用别名as,并以t1,t2...顺序依次命名
ORM映射:
表查询中禁止使用*作为查询的字段列表,必须明确写明需要查询的字段 (MyBatis中如果是全查询的话可以提前声明一个Base_Column_List,然后select中调用即可,封装避免后续增减字段引发多处修改)
id, category_id, name, sub_title, main_image, sub_images, detail, price, stock, status,
create_time, update_time
select
from product
where id = #{id,jdbcType=BIGINT}
使用*增加查询分析器解析成本
无用字段例如 text增加网络消耗
POJO类的Boolean属性(不是包装类属性
)不加is,数据库字段必须加is_,由于属性和字段名不一致,所以resultMap中必须要加映射,否则会报错
数据库配置文件、映射文件中参数使用#{},不要使用${}避免SQL注入
#{}调用的是prepareStatement,底层变量使用占位符覆盖,
#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。
即使是敏感SQL也会被自动转为str类型不再具有特殊含义
${}调用的是statement,底层变量使用的是字符串+硬拼接方式,
$将传入的数据直接显示生成在sql中。
如果拼接的SQL有特殊含义不会进行转义处理即会发生SQL注入
SQL注入:
针对程序员编写时的疏忽,通过SQL语句,实现无账号登录,甚至篡改数据库。
SQL注入的基本思路:寻找可注入的位置;判断服务器类型和后台数据库类型;SQL注入攻击
预防的措施:检查变量数据类型和格式;过滤特殊符号;使用#{}绑定变量使用预编译语句
预编译语句:将语句中的值使用占位符替代,由于sql语句可能多次执行只是值不同,可以先把这条sql语句编译了后续直接赋值即可,避免多次编译,提高性能(优势:一次编译,多次运行;可防止SQL注入)
一般sql执行的过程:词法和语义解析;优化sql语句执行执行计划;执行并返回结果
String sql = "select * from user_table where username=' "+userName+" ' and password=' "+password+" '";
--当输入用户名为or 1 =1 --,上面的SQL语句变成:SELECT * FROM user_table WHERE username='’or 1 = 1 -- and password='’
"""
--分析SQL语句:
--条件后面username=”or 1=1 用户名等于 ” 或1=1 那么这个条件一定会成功;
--然后后面加两个-,这意味着注释,它将后面的语句注释,让他们不起作用,这样语句永远都--能正确执行,用户轻易骗过系统,获取合法身份 。
--这还是比较温柔的,如果是执行SELECT * FROM user_table WHERE
username='' ;DROP DATABASE (DB Name) --' and password=''
--其后果可想而知…
不可将HashMap,HashTable直接作为查询结果集的输出,虽然会置入字段名和属性值但是值的类型不可控
创建数据记录,必须create_time = now()
更新数据记录,必须update_time = now() 直接在sql语句中写,无需在应用中写
工程结构
应用分层:
web-service-dao-db
分层异常处理:
DAO层使用try-catch,并将异常抛出throw new DAOException(e),无需打印日志(service/manager层肯定可以捕获并打印日志,此处无需再多余打印操作)
Service层,异常必须记录日志到磁盘且尽可能带上参数信息(保护案发现场)
Manager层如果单独部署同Service层;如果与Service层同机部署,日志方式同DAO层
Web层不可抛出异常,必须处理异常且将异常封装为前端可以识别的对象(错误提示信息)传给前端,一般是错误编码和错误信息方式
分层领域模型规约:
DO:与数据库表结构一一对应,通过DAO层向上传输数据源对象
DTO:数据传输对象,Service/Manager层向外传输的对象
BO:业务对象,Service层封装的业务逻辑对象
AO:Application Object应用对象,在Web层与Service间抽象的复用对象模型(目前还未接触到)
VO:与前端交互的数据封装对象
Query:数据查询对象,
注意
超过 2 个参数的查询封装
,禁止使用 Map 类
来传输。
二方库依赖:
GAV定义规则:Maven坐标,唯一标识jar包
g
roupId格式:com.{公司}.业务线.[子业务线],最多4级 例如com.alibaba.dubbo.register
a
rtifactId格式:产品线名-模块名 例如fasjson-api
v
ersion:主版本号.次版本号.修订号
主版本号:
产品方向改变,或者大规模 API 不兼容,或者架构不兼容升级
次版本号:保持相对兼容性,增加主要功能特性,影响范围极小的 API 不兼容修改
修订号:保持完全兼容性,修复 BUG、新增次要功能特性等
线上应用不要依赖SNAPSHOT版本,可保证应用发布的幂等性,也可加快编译时的打包构建
幂等性:多次请求统一资源,对资源的影响相同,不会因为多次点击而产生副作用。
声明为幂等的服务会认为调用方调用失败是常态,是正常业务,并且允许在调用失败后必然会有重试的
如果依赖一个二方库群,必须定义一个统一的版本变量,避免版本号不一致 可在pom.xml中使用版本锁定统一声明库群的版本
1.8
1.8
5.0.2.RELEASE
org.springframework
${spring.version}
org.springframework
${spring.version}
org.springframework
${spring.version}
项目的pom.xml中禁止出现相同groupId artifactId但不同的version
《Java开发手册-泰山版补充》不要使用不稳定的工具包或Utils类 不稳定是指无法做到向下兼容;编译期正常运行期异常
服务器:
Xms:
设定程序启动时占用内存大小。一般来讲,大点,程序会启动的快一点,但是也可能会导致机器暂时间变慢。
Xmx:
设定程序运行期间最大可占用的内存大小。如果程序运行需要占用更多的内存,超出了这个设置值,就会抛出OutOfMemory异常。
Xss:
设定每个线程的堆栈大小。这个就要依据你的程序,看一个线程大约需要占用多少内存,可能会有多少线程同时运行等
默认Byte为单位
设计规约:
存储方案和底层数据结构 需要一致通过并生成文档
用啥数据库存储
表结构设计是否满足性能要求、技术方案、业务功能
表结构中的字段名、字段类型、索引
需求分析如果user case超出5个就得使用用例图
如果某个业务对象的状态超过3个,使用状态图表达并明确状态变化的各个触发条件
若系统某个功能的调用链路上的涉及对象超过3个,使用时序图表达并明确各调用环节的输入与输出
时序图反映了一系列对象间的交互与协作关系,清晰立体地反映系统的调用纵深链路。
若系统中模型类超出5个,并存在复杂依赖关系,使用类图表达并明确类间关系
若系统中超出2个对象间存在协作关系且有复杂的处理流程,使用活动图表示
谨慎使用继承,优先使用聚合/组合方式实现 继承被滥用的话会产生很多难以维护的代码 使用继承无非是想使用复用性,同样具有复用性的还有聚合和组合
聚合:空心菱形,A聚合到B 指B由A组成,部分与整体依赖不强,生命周期不同,B没了A还在,如员工A与部门B
2. 组合:实心菱形,A组合到B 指B由A组成,部分与整体依赖强,生命周期同,B没了A就没了,如部门A与公司B
系统设计遵守开闭原则(对扩展开放,对修改闭合)