Paper Writing
1、免费下载SCI文献的方法
2、硕士论文的书写方法
3、Obsidian与Zotero联动高效阅读文献
4、PicGo图床配置
5、文档写作技巧
6、代码规范
-
+
游客
注册
登录
代码规范
> 编程是一种艺术,越简洁约优雅。 记得曹彦涛先生说过:“普通人学习提升能力,高手学习改变思维。”编程之前先掌握代码开发的原则和规范,才能在正确的方向上不断积累,厚积薄发。 ## 开发原则 ### 自明性 > 代码要具有自明性,好的代码就是最好的文档。 **变量名** - 变量名应该是名词,能够正确地描述业务。如果一个变量名需要注释来补充说明,那么说明这个命名就有问题。 - 变量名中最重要的部分要放在最前面,限定词要放在最后面。 **函数名** - 函数名要具体,能够体现出做什么。 - CRUD 的函数名约定如<a href="#1">附录一</a>所示。 **注释** - 注释的目的应该是为了**阐述代码背后的意图**,而不是复述代码的功能。好的代码应该能够显性化地表达意图。**真正的高手是尽量不写注释**。**注释力求精简准确,表达到位**。 ### 单一职责原则 > 一个方法只做一件事情。 - 好的函数应该是清晰易懂的。一个方法只做一件事情。 - 函数最理想的参数数量是零,应尽量避免超过两个参数。如果参数过多,一些参数就应该考虑封装成类了。 ### 避免重复原则 > 多次遇到同样的问题,就应该抽象出一个共同的解决方法。 - 随意复制和粘贴代码,必然会导致代码的重复。在以后需要修改时,需要修改所有的副本,容易遗漏。 - **必要时抽取共性方法,或者抽象公共类,甚至是组件化**。 ### 保持简洁原则 > 好的目标不是越复杂越好,而是越简洁越好。 > 真正的简单,是在纷繁复杂中,把握问题的核心。 ### 开闭原则 > 对扩展开放,对修改关闭。 - 通过继承和多态来实现,即封装不变部分,对于需要变化的部分,通过接口继承实现的方式来实现“开放”。 - 很多设计模式都可以达到开闭原则,例如[装饰器模式](https://notebook.ricear.com/doc/859)、[策略模式](https://notebook.ricear.com/doc/858)、适配器模式、观察者模式。 ## 开发规范 无规矩不成方圆。团队中每个成员都按照统一的规范进行开发,代码的可维护性才会更高。本文主要从编程规约、异常日志、安全规约、MySQL 数据库和工程结构五个方面出发对代码开发的规范进行阐述。 ### 编程规约 #### 命名风格 - 常量要全部大写,单词间用下划线隔开。力求**语义表达完整清楚**,不要嫌名字长。 > ✅ MAX_STOCK_COUNT,可以清晰表达变量的含义为【最大库存数量】。 > > ❌ MAX_COUNT,含义有歧义,是什么的最大数量。 - 类型与中括号紧挨相连来表示数组。 > ✅ 定义整型数组 `int[] arrayDemo;` > > ❌ 在 `main` 参数中,使用 `String args[]` 来定义。 - 包名统一使用小写,点分隔符之间有且仅有一个自然语义的英文单词,统一使用**单数形式**;类名如果有复数含义,**类名可以使用复数形式**。 > ✅ 应用工具类包名为 `com.alibaba.ai.util`;类名为 `MessageUtils`。 - 杜绝完全不规范的缩写,避免望文不知义,降低代码的可读性。常见变量名的缩写如<a href="#2">附录二</a>所示。 > ❌ `AbstractClass` 缩写为 `AbsClass`;`condition` 缩写为 `condi`。 - 为了达到代码自解释的目标,类名和变量名应使用尽量完整的单词组合来表达其意。 > ✅ 在 JDK 中,表达原子更新的类名为 `AtomicReferenceFIeldUpdater`。 > > ❌ 变量 `int a` 的随意命名方式。 - 如果使用了设计模式,在命名时需体现出具体的设计模式。 > ✅ > > ```java > public class OrderFactory > public class LoginProxy > public class ResourceObserver > ``` - 接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的简洁性;尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是**整个应用的基础常量**。 > ✅ > > ```java > // 接口方法签名 > void commit(); > // 接口基础常量 > String COMPANY = "alibaba"; > ``` > > ❌ > > ```java > // 接口方法定义 > public abstract void f(); > ``` - 对于 Service 和 DAO 类,基于 [SOA](https://www.zhihu.com/question/42061683) 的理念,暴露出来的服务一定是接口,内部的实现类用 Impl 的后缀与接口区别;如果是形容能力的接口名称,取对应的形容词为接口名(通常用 -able 的形式)。 > ✅ `CacheServiceImpl` 实现 `CacheService` 接口;`AbstractTranslator` 实现 `Translatable` 接口。 #### 常量定义 - 不允许任何未经预先定义的常量直接出现在代码中。 > ❌ > > ```java > String key = "Id#taobao_" + tradeId; > cache.put(key, value); > ``` - 在 long 或者 Long 赋值时,数值后使用大写的 L,不能是小写的 l,小写容易跟数字 1 混淆,造成误解。 > ❌ `Long a = 2l`,写的是数字 21,还是 Long 型的 2? - 不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。因为大而全的常量类,杂乱无章,使用查找功能才能定位到修改的常量,不利于理解和维护。 > ✅ 系统相关常量放在类 CacheConsts 下;系统配置相关常量放在类 ConfigConsts 下。 - 如果变量值仅在一个一定范围内变化用 enum 类型来定义。 > ✅ > > ```java > public enum SeasonEnum { > SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4); > private int seq; > SeasonEnum(int seq) { > this.seq = seq; > } > } > ``` #### 代码格式 - 如果大括号内为空,则简洁地写成 {} 即可,不需要换行;如果是非空代码块,则左大括号前不换行,左大括号后换行右大括号前换行,右大括号后还有 else 等代码则不换行,表示终止的右大括号后必须换行。 - `if/for/while/switch/do` 等保留字与括号之间都必须加空格。 - **采用四个空格缩进,禁止使用 tab 字符**。如果使用 tab 缩进,必须设置 1 个 tab 为 4 个空格。IDEA 设置 tab 为 4 个空格时,请勿勾选 "use tab character"。 - IDE 的 "text file encoding" 设置为 UTF-8,换行符使用 Unix 格式,不要使用 Windows 格式。 - 单个方法总行数不超过 80 行。代码逻辑分清红花和绿叶、个性和共性,**绿叶逻辑单独出来成为额外方法,使主干代码更加清晰;共性逻辑抽取成共性方法,便于复用和维护**。不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开来以提升可读性。 #### OOP 规约 - Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals,推荐使用 java.util.Objects#equals(JDK7 引入的工具类)。 > ✅ `"test".equals(object);` > > ❌ `object.equals("test");` - 所有的 POJO 类属性必须使用包装数据类型。RPC 方法的返回值和参数必须使用包装数据类型。所有的局部变量使用基本数据类型。 > ✅ 数据库的查询结果可能是 null,因为自动拆箱,用基本数据类型接收有 NPE 风险。 > > ❌ 显示成交总额涨跌情况,即正负 x%,x 为基本数据类型。调用 RPC 服务,调用不成功时,返回的是默认值,页面显示为 0%,这是不合理的,应该显示为中划线。所以包装数据类型的 null 值,能够表示额外的信息,如远程调用失败、异常退出等。 - 构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中。 - POJO 类必须写 toString() 方法,可以使用 IDE 中的工具 "source > generate toString"。如果继承了另一个 POJO 类,注意在前面加一下 super.toString()。这样在方法执行抛出异常时,可以直接调用 POJO 的 toString() 方法打印其属性值,便于排查问题。 - 使用索引访问用 String 的 split 方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有抛 IndexOutOfBoundsException 的风险。 > ❌ > > ```java > String str = "a,b,c,,"; > String[] ary = str.split(","); > // 预期大于 3,结果是 3 > System.out.println(ary.length); > ``` - 当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起,便于阅读。类内方法定义的顺序依次是 “共有方法/保护方法 > 私有方法 > getter/setter 方法”。 #### 集合处理 - 只要重写 equals,就必须重写 hashCode。因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须重写这两个方法。如果自定义对象作为 Map 的键,那么必须重写 hashCode 和 equals。 - 使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一样的数组,大小就是 list.size(),最好将方法入参数组大小定义与集合元素个数一致。 > ✅ > > ```java > List<String> list = new ArrayList<String>(2); > list.add("guan"); > list.add("bao"); > String[] array = new String[list.size()]; > array = list.toArray(array); > ``` > > ❌ 直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[] 类,若强转其它类型数组将出现 ClassCastException 错误。 - 使用工具类 Arrays.asList() 把数组转换成集合时,不能使用其修改集合相关的方法,他的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。因为 asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList() 体现的是适配器模式,只是转换接口,后台的数据仍是数组。 > ❌ > > ```java > String[] str = new String[] { "you", "wu" }; > List list = Arrays.asList(str); > > list.add("yangguanbao"); // 运行时异常。 > str[0] = "gujin"; // list.get(0)也会随之修改 > ``` - 不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator,如果并发操作,需要对 Iterator 对象加锁。 > ✅ > > ```java > List<String> list = new ArrayList<>(); > list.add("1"); > list.add("2"); > Iterator<String> iterator = list.iterator(); > while (iterator.hasNext()) { > String item = iterator.next(); > if ("1".equals(item)) { > iterator.remove(); > } > } > ``` > > ❌ > > ```java > List<String> list = new ArrayList<>(); > list.add("1"); > list.add("2"); > for (String item : list) { > if ("1".equals(item)) { > list.remove(item); > } > } > ``` > > ```java > List<String> list = new ArrayList<>(); > list.add("1"); > list.add("2"); > for (String item : list) { > if ("2".equals(item)) { // ConcurrentModificationException > list.remove(item); > } > } > ``` - Map 类集合 K/V 是否可以存储 null 值的情况。 | 集合类 | Key | Value | Super | 说明 | | -------------------------------------------------------- | ---- | ----- | ----------- | ---------- | | Hashtable | ❌ | ❌ | Dictionary | 线程安全 | | [HashMap](https://notebook.ricear.com/doc/813) | ✅ | ✅ | AbstractMap | 线程不安全 | | [ConcurrentHashMap](https://notebook.ricear.com/doc/813) | ❌ | ❌ | AbstractMap | 线程安全 | | TreeMap | ❌ | ✅ | AbstractMap | 线程不安全 | - 利用 Set 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 List 的 contains 方法进行遍历、对比、去重操作。 #### 并发处理 - 创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。 > ✅ > > ```java > public class TimerTaskThread extends Thread { > public TimerTaskThread() { > super.setName("TimerTaskThread"); > ... > } > } > ``` - 线程资源必须通过[线程池](https://notebook.ricear.com/doc/818)提供,不允许在应用中自行显式创建线程。 - 线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式。让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 > ⚠️ Executors 返回的线程池对象具有如下弊端: > > - FixedThreadPool 和 SingleThreadPool 允许请求的队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。 > - CacheThreadPool 和 ScheduledThreadPool 允许创建的线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。 - SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量。如果定位为 static ,必须加锁,或者使用 [DateUtils](https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/time/DateUtils.html) 工具类。如果是 JDK8 的应用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat,官方给出的解释是 "simple beautiful strong immutable thread-safe"。 > ✅ > > ```java > private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() { > @Override > protected DateFormat initialValue() { > return new SimpleDateFormat("yyyy-MM-dd"); > } > }; > ``` - **尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用 RPC 方法**。 - 多线程 count++ 操作尽量使用原子类(例如 AtomicInteger),如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)。 > ✅ > > ```java > AtomicInteger count = new AtomicInteger(); > count.addAndGet(1); > ``` > > ```java > LongAdder longAdder = new LongAdder(); > longAdder.increment(); > ``` #### 控制语句 - 在一个 switch 块内,都必须包含一个 default 语句并且放在最后,及时是空代码。 - 在 if/else/for/while/do 语句中必须使用大括号,即使只有一行代码,避免单行的编码方式。 > ❌ > > ```java > if (condition) statements; > ``` - 在高并发场景中,避免使用【等于】判断作为中断或退出的条件。如果并发控制没有处理好,容易产生等值判断被“击穿”的情况,可以使用大于或小于的区间判断条件来代替。 > ❌ 判断剩余奖品数量等于 0 时,终止发放奖品,但因为并发处理错误导致奖品数量瞬间变成了负数,这样的话,活动无法终止。 - 表达异常的分支时,少用 if-else,如果非得使用 if()...else if()...else... 方式表达逻辑,为避免后续代码维护困难,请勿超过 3 层。超过 3 层的逻辑判断代码可以使用<a href="#3">卫语句</a>、策略模式、状态模式等来实现。 > ✅ > > ```java > public void today() { > if (isBusy()) { > System.out.println(“change time.”); > return; > } > > if (isFree()) { > System.out.println(“go to travel.”); > return; > } > > System.out.println(“stay at home to learn Alibaba Java Coding Guidelines.”); > return; > } > ``` - 不要在条件判断中执行其它复杂的语句,**将复杂逻辑判断的结果赋值给一个有意义的布尔变量名**,以提高可读性。 > ✅ > > ```java > final boolean existed = (file.open(fileName, "w") != null) && (...) || (...); > if (existed) { > ... > } > ``` > > ❌ > > ```java > if ((file.open(fileName, "w") != null) && (...) || (...)) { > ... > } > ``` - **循环体中的语句要考量性能**。以下操作尽量移至循环体外处理,如定义对象、变量、获取数据库连接、进行不必要的 try-catch 操作(这个 try-catch 是否可以移至循环体外)。 - **避免采用取反逻辑运算符**。 > ✅ 使用 if (x < 628) 来表达 x 小于 628。 > > ❌ 使用 if (!(x >= 628)) 来表达 x 小于 628。 #### 注释规约 - 类、类属性、类方法的注释必须使用 Javadoc 规范,使用 `/*` 内容格式,不得使用 `//` 方式。 - 所有的抽象方法(包括接口中的方法)必须使用 Javadoc 注释,除了返回值、参数、异常说明外,还必须指出该方法做什么事情、实现什么功能、对子类的实现要求、调用注意事项。 - 所有类都必须添加创建者和创建日期。 - 方法内部单行注释,在被注释语句上方另起一行,使用 `//` 注释;方法内部多行注释,使用 `/*` 注释,注意与代码对齐。 - 所有的枚举类型字段必须要有注释,说明每个数据项的用途。 - 用中文注释把问题说清楚,专有名词与关键字保持英文原文即可。 > ❌ "TCP 连接超时"解释成“传输控制协议连接超时”,理解反而费脑筋。 - **谨慎注释掉代码**。对于暂时被注释掉,后续可能恢复使用的代码,**在注释代码上方,统一规定使用 `///` 详细说明注释掉代码的理由**,而不是简单地注释掉。对于后续不再使用的代码,直接删除。 - **好的命名、代码结构是自解释的,注释力求精简准确、表达到位**。 > ❌ 方法名 put,加上两个有意义的变量名 elephant 和 fridge,已经说明了这是在干什么。语义清晰的代码不需要额外的注释。 > > ```java > // put elephant into fridge > put(elephant, fridge); > ``` - 特殊注释标记,请标明标记人与标记时间。注意及时处理这些标记,通过标记扫描,经常清理此类标记。 > 待办事项(TODO):(标记人,标记时间,[预计处理时间])。表示需要实现,但目前还未实现的功能。 > > 错误,不能工作(FIXME):(标记人,标记时间,[预计处理时间])。标记某代码是错误的,而且不能工作,需要及时纠正的情况。 #### 其它 - 及时清理不再使用的代码段或配置信息,避免程序过度臃肿,代码冗余。 ### 异常日志 #### 异常处理 - 捕获异常时尽可能的区分异常类型,再做对应的异常处理。 - 捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之。如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。 - 防止 NPE 是程序员的基本修养。 > NPE 产生的场景如下: > > - 返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE。 > > - 数据库的查询结果可能为 null。 > - 集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。 > - 远程调用返回对象时,一律要求进行空指针判断,防止 NPE。 > - 对于 Session 中获取的数据,建议 NPE 检查,避免空指针。 > - 级联调用 obj.getA().getB().getC(),容易产生 NPE,可以使用 JDK8 的 Optional 类来防止 NPE。 > ❌ > > ```java > // 如果为 null,自动拆箱抛 NPE > public int f() { return Integer 对象} > ``` - 公司外的 http/api 开放接口必须使用【错误码】;应用内部推荐异常抛出;跨应用间 RPC 调用优先考虑使用 Result 方式,封装 isSuccess() 方法、错误码、错误简短信息。 #### 日志规约 - 应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架 SLF4J 中的 API。使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。 > ✅ > > ```java > import org.slf4j.Logger; > import org.slf4j.LoggerFactory; > > private static final Logger logger = LoggerFactory.getLogger(Abc.class); > ``` - 日志文件至少保存 15 天,因为有些异常具备以周为频次发生的特点。 - 应用中的扩展日志(如打点、临时监控、访问日志等)命名方式为 appName_logType_logName.log。 > logType 表示日志类型,如 stats/monitor/access 等;logName 表示日志描述。这种命名方式的好处在于通过文件名就可以知道日志文件属于什么应用、什么类型、什么目的,也有利于归类查找。 > > 推荐对日志进行分类,如将错误日志和业务日志分开存放,便于开发人员查看,也便于通过日志对系统进行及时监控。 > ✅ mappserver 应用中单独监控时区转换异常,如 mappserver_monitor_timeZoneConvert.log。 - 对 trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方式。 > logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);对于日志级别是 warn,上述日志不会打印,但是会执行字符串拼接操作,如果 symbol 是对象,会执行 toString() 方法,浪费了系统资源,执行了上述操作,最终日志却没有打印。 > ✅ > > ```java > //(条件)建设采用如下方式 > if (logger.isDebugEnabled()) { logger.debug("Processing trade with id: " + id + " and symbol: " + symbol); } > //(占位符) > logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol); > ``` - 异常日志打印时应该包括两类信息,分别是【案发现场信息】和【异常堆栈信息】。如果不处理,那么向上抛出。 > ✅ > > ```java > logger.error(各类参数或者对象 toString() + "_" + e.getMessage(), e); > ``` - 避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 `additivity=false`。 > ✅ > > ```xml > <logger name="com.taobao.dubbo.config" additivity="false"> > ``` - **尽量用英文来描述日志信息**,如果日志中的错误信息用英文描述不清楚的话使用中文描述即可,否则容易产生歧义。 - **谨慎地记录日志**。 > 生产环境禁止输出 debug 日志。 > > 有选择地输出 info 日志。 > > 如果使用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志。 > > 大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。 > > 记录日志时思考“这些日志真的有人在看吗?看到这条日志你能做什么?能不能给问题排查带来好处?” ### 安全规约 - 隶属于用户个人的页面或者功能必须进行权限控制校验。 - 用户敏感数据禁止直接展示,必须对展示数据进行脱敏。 > ✅ 中国大陆个人手机号码显示为 158****9119,隐藏中间四位,防止隐私泄露。 - **用户输入的 SQL 参数严格使用参数绑定或者 METADATA 字段值限定,防止 SQL 注入,禁止字符串拼接 SQL 访问数据库**。 - 用户请求传入的任何参数必须做有效性验证。 ### MySQL 数据库 #### 建表规约 - 表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint(1 表示是,0 表示否) > ✅ 表达逻辑删除的字段名 is_deleted,1 表示删除,0 表示未删除。 - 表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字。 > ✅ aliyun_admin、rdc_config、level3_name。 > > ❌ AliyunAdmin、rdcConfig、level_3_name。 - **表名不使用复数名词**,表名应该仅仅表示表里面的实体内容,不应该表示实体数量,对应于类名也是单数形式,如何表达习惯。 - 禁用保留字,如 desc、range、match、delayed 等。 - 主键索引名为 pk\_字段名;唯一索引名为 uk\_字段名;普通索引名则为 idx\_字段名。 - **小数类型为 decimal,禁止使用 float 和 double**。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。 > ⚠️ float 和 double 在存储的时候存在精度损失的问题,很可能在值比较的时候得不到正确的结果。 - 如果存储的字符串长度几乎相等,使用 char 定长字符串类型。 - varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000。如果存储长度大于 5000,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。 - 一张表必备的三个字段,分别为 id、gmt_create、gmt_modified。 > id 类型为 bigint unsigned,单表时自增,步长为 1;gmt_create 和 gmt_modified 的类型均为 datetime 类型。 - 库名与应用名称尽量一致。 - 表名最好为“业务名称_表的作用”。 > ✅ alipay_task / force_project / trade_config。 - 如果修改字段含义或对字段表示的状态追加时,需要及时更新字段注释。 - 字段允许适当冗余,以提高查询性能,但必须考虑数据一致。 > ✅ 商品类目名称使用频率高,字段长度短,名称基本一成不变,可在相关联的表中冗余存储类目名称,避免关联查询。 - 单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。 #### 索引规约 - 业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。 - 超过三个表禁止 join;需要 join 的字段,数据类型必须绝对一致;多表关联查询时,需要保证被关联的字段有索引。 - 如果有 order by 的场景,请注意利用索引的有序性。order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort的情况,影响查询性能。 > ✅ > > ```sql > -- 索引:a_b_c > where a = ? and b = ? order by c; > ``` > > ❌ > > ```sql > -- 索引 a_b 无法排序 > where a > 10 order by b; > ``` - 建组合索引的时候,**区分度最高的在最左边**。 > **建索引时如果存在非等号和等号混合,请把等号条件的列前置**。如 `where c > ? and d = ?`,那么及时 `c` 的区分度更高,也必须把 `d` 放在索引的最前列,即索引 `idx_d_c`。 #### ORM 映射 - 在表查询中,一律不要使用 \* 作为查询的字段列表,需要哪些字段必须明确写明。 > 使用 \* 作为查询的字段列表会增加分析器解析成本;增减字段容易与 resultMap 配置不一致;无用字段增加网络消耗,尤其是 text 类型的字段。 - @Transaltional 事务不要滥用。事务会影响数据库的 QPS,另外使用事务的地方需要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。 #### 其它 > ✅ > > ```sql > -- 如果 a 列的值几乎接近于唯一值,那么只需要单建 idx_a 索引即可 > where a = ? and b = ?; > ``` - 不要使用 count(列名) 或 count(常量) 来替代 count(*)。 > count(\*) 是 SQL92 定义的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关;count(\*) 会统计值为 NULL 的行,而 count(列名) 不会统计值为 NULL 的行。 - count(distinct col) 会计算该列除 NULL 之外的不重复行数。 > 对于 count(distinct col1, col2),如果一列全为 NULL,那么即使另一列有不同的值,也返回 0。 - 使用 ISNULL 来判断是否为 NULL 值。 - 禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。 - 数据订正(特别是删除、修改记录操作)时,要先 select,避免出现误删除,确认无误才能执行更新语句。 - 注意字符统计函数的区别。 > ✅ > > ```sql > -- length 是汉字算三个字符,数字或字母算一个字符;中文标点符号算三个字符,英文标点符号算一个字符 > select length('轻松工作'); -- 返回为 12 > -- character_length 是汉字、数字或字母都算是一个字符,包括中英文标点符号也算一个字符 > select character_length('轻松工作'); -- 返回为 4 > ``` - truncate table 比 delete 速度快,且使用的系统和事务日志资源少,但 truncate 无事务且不触发 trigger,有可能造成事故,故不建议在开发代码中使用此语句。 ### 工程结构 #### 二方库依赖 - GroupID 格式为 **com.{公司/BU}.业务线[.子业务线]**,最多 4 级。 > {公司/BU} 例如 `alibaba/taobao/tmall/aliexpress` 等 BU,一级子业务线可选。 > ✅ `com.taobao.jstorm` 或 `com.alibaba.dubbo.register`。 - ArtifactID 格式为 **产品线名-模块名**,语义不重复不遗漏,需要先到中央仓库去查证一下。 > ✅ `dubbo-client/fastjson-api/jstorm-tool`。 - 版本号命名方式为 **主版本号.次版本号.修订号**。其中**主版本号为产品方向改变,或者大规模 API 不兼容,或者架构不兼容升级;次版本号为保持相对兼容性,增加主要功能特性,影响范围极小的 API 不兼容修改;修订号为保持完全兼容性,修复 BUG,新增次要功能特性等**。 > 起始版本号必须为 1.0.0,而不是 0.0.1。正式发布的类库必须先去中央仓库进行查证,使版本号有延续性,**正式版本号不允许覆盖升级**,如当前版本为 1.3.3,那么下一个合理的版本号只能为 1.3.4 或 1.4.0 或 2.0.0。 - 依赖于一个二方库群时,必须定义一个统一的版本变量,避免版本号不一致。 > 依赖 springframework-core/context/beans,他们都是同一个版本,可以定义一个变量 ${spring.version} 来保存版本,然后定义依赖的时候,引用该版本。 ## 参考文献 - [代码精进之路—从码农到工匠](https://weread.qq.com/web/reader/81132f5071cc7f7a81151c9)。 - [《阿里巴巴Java开发手册》v1.4.0(详尽版)](https://notebook.ricear.com/media/attachment/2023/11/%E9%98%BF%E9%87%8C%E5%B7%B4%E5%B7%B4Java%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8Cv1.4.0%E8%AF%A6%E5%B0%BD%E7%89%88.pdf)。 - [Java编码规范](https://github.com/wanfangdata/guide/blob/master/java%E5%BC%80%E5%8F%91%E8%A7%84%E8%8C%83/%E7%BC%96%E7%A0%81%E8%A7%84%E8%8C%83.md)。 - [Google Java 编程风格指南](https://pdai.tech/md/dev-spec/code-style/code-style-google.html)。 - [JAVA开发规范约定](https://caosg.gitbooks.io/java-devepment-rules/content)。 - [java-design-patterns](https://java-design-patterns.com)。 - [编程语言中常用的变量命名缩写](https://blog.csdn.net/qq_37851620/article/details/94731227)。 - [什么是卫语句](https://blog.csdn.net/wangpaiblog/article/details/114909737)。 - [4.5 以卫语句取代嵌套条件式](https://www.cnblogs.com/Braveliu/p/7364369.html)。 - [拥抱卫语句,别再用嵌套条件表达式了](https://juejin.cn/post/7083509863132692494)。 ## 附录 ### <a id="1">附录一:CRUD 函数名限定词约定</a> | CRUD 操作 | 函数名约定 | | ---------------- | ---------- | | 新增 | create | | 添加 | add | | 删除 | remove | | 修改 | update | | 查询(单个结果) | get | | 查询(多个结果) | list | | 分页查询 | page | | 统计 | count | ### <a id="2">附录二:常见变量名的缩写</a> | 全称 | 缩写 | 含义 | | -------------- | ---------- | ---------- | | calculate | calc | 计算 | | addition | add | 加 | | subtraction | sub | 减 | | multiplication | mul | 乘法 | | division | div | 除法 | | hexadecimal | hex | 十六进制 | | array | arr | 数组、集合 | | list | lst | 列表 | | Sequence | seq | 序列 | | Segment(s) | seg | 段 | | stack | stk | 栈 | | dictionary | dict | 字典 | | character | char | 字符 | | string | str | 字符串 | | text | txt | 文本 | | float | flt | 浮动、浮点 | | number | num | 数量、编号 | | image | img | 图像 | | bitmap | bmp | 位图 | | table | tbl | 表 | | link | lnk | 链接 | | lable | lbl | 标签 | | flag | flg | 标志 | | container | cntr | 容器 | | time stamp | ts | 时间戳 | | length | len | 长度 | | positive | pos | 正数 | | negative | neg | 负数 | | statistic | stat | 统计 | | summation | sum | 和 | | average | avg | 平均 | | maximum | max | 最大值 | | minimum | min | 最小值 | | middle | mid | 中值 | | increment | inc | 增加、增量 | | increase | inc | 增加 | | decrease | dec | 减少 | | different | diff | 不同的 | | frequency | freq | 频率 | | optimization | opt | 最优 | | total | tot | 全部的 | | vertical | vert | 垂直 | | horizontal | horz | 水平 | | row | row | 行 | | column | col | 列 | | positon | pos | 位置 | | point | pt | 点 | | pointer | ptr | 指针 | | index | idx / ndx | 索引、指示 | | value | val | 值 | | reference | ref | 引用 | | status | stat | 状态 | | original | orig | 原件 | | source | src | 源头 | | address | addr | 地址 | | coordinates | coord | 坐标 | | previous | pre | 前一个 | | current | cur | 当前的 | | initalize | init | 初始化 | | destination | dst/dest | 目的 | | iteration | itr/iter | 循环、迭代 | | count | cnt | 计数器 | | temporary | temp或tmp | 临时 | | source | src | 源头 | | resource | res | 资源 | | result | res | 结果 | | return | ret | 返回 | | return | rtn | 返回 | | answer | ans | 响应、回答 | | buffer | buf或buff | 缓冲区 | | database | db | 数据库 | | administrator | adm | 管理员 | | password | pwd | 密码 | | user | usr | 用户 | | directory | dir | 目录 | | document | doc | 文档 | | library | lib | 库 | | function | func | 函数 | | object | obj | 对象 | | argument | arg | 实参 | | instance | ins | 实例 | | variable | var | 变量 | | parameter | param | 参数(形参) | | encode | enc | 编码 | | print | prn | 打印 | | delete | del | 删除 | | insert | ins | 插入 | | error | err | 错误 | | break | brk | 间断 | | package | pkg | 打包 | | escape | esc | 退出 | | execute | exec | 执行 | | command | cmd | 命令 | | configuration | config | 配置 | | edit | edt | 编辑 | | display | disp | 显示 | | initialize | init | 初始化 | | trigger | trig | 触发 | | capture | cap或capt | 捕获 | | system | sys | 系统 | | environment | env | 环境 | | window | win(wnd) | 窗口 | | device | dev | 设备 | | message | msg | 消息 | | signal | sig | 信号 | | information | info | 信息 | | error | err | 错误 | ### <a id="3">附录三:卫语句</a> 卫语句是一种改善嵌套代码的优化代码。将经过嵌套的代码使用卫语句优化之后,代码嵌套层数可以降低,进而降低代码的复杂程度。卫语句通过对原条件进行逻辑分析,**将某些罕见或者不是本函数核心逻辑关心的条件提前判断,为真相时立刻返回**,从而简化程序的流程走向。 具体实例如下: 现在有一个函数需要计算支付给员工的工资,原始代码如下: ```java double getPayAmount() { double result; if (IsDead()) { result = DeadAmount(); } else { if (IsSeparated()) { result = SeparatedAmount(); } else { if (IsRetired()) { result = RetiredPayAmount(); } else { result = NormalPayAmount(); } } } return result; } ``` 函数中的条件逻辑使人难以看清正常的分支执行路径,下面采用卫语句让代码更清晰地阐述自己的意图。 ```java double getPayAmount() { if (isDead()) { return deadPayAmount(); } if (isSeparated()) { return separatedPayAmount(); } if (isRetired()) { return retiredPayAmount(); } return normalPayAmount(); } ```
ricear
2023年11月19日 15:45
©
BY-NC-ND(4.0)
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码