提交 1714fbbd 编写于 作者: S star

代码整洁之道

上级 8b2c4afe
......@@ -220,6 +220,32 @@ Spring,SpringBoot 与 SpringCloud:
SpringBoot 自定义 Starter:
需要用到的注解:
1. @Configuration:指定配置类
2. @ConditionalOnXxx:在指定条件成立的情况下自动配置类生效
3. @AutoConfigureAfter:指定自动配置类的顺序
4. @Bean:给容器中添加组件
5. @ConfigurationPropertie:结合相关 xxxProperties 类来绑定相关的配置
6. @EnableConfigurationProperties:让 xxxProperties 生效加入到容器中
此外,还要将需要启动就加载的自动配置类,配置在 META‐INF/spring.factories 中。
启动器设计规约:
1. 通常来说,SpringBoot Starter 启动器应该只用来做依赖导入,它是一空的 jar 包,用来提供辅助性依赖管理,不应该存在任何的 java 代码。
2. 命名:
1. 官方启动器:spring-boot-starter-xxx,如:spring-boot-starter-web
2. 自定义启动器:xxx-spring-boot-starter,如:druid-spring-boot-starter
---
### 4.SpringBoot AutoConfiguration
......@@ -1610,19 +1636,141 @@ public FilterRegistrationBean<StatViewFilter> statViewFilter(){
**结合 MyBatis 配置多数据源**
1. 搭建项目:创建 SpringBoot 项目,引入需要的项目依赖(不同数据库时需要引入不同的数据库 Driver)。
2. 编写项目配置:
~~~properties
# 服务端口
server.port=8080
# MySQL 数据源连接相关信息
spring.datasource.mysql.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.mysql.url=jdbc:mysql://192.168.253.128:3306/common?serverTimezone=UTC
spring.datasource.mysql.username=root
spring.datasource.mysql.password=TinyStar0920
# Oracle 数据源连接相关信息
spring.datasource.oracle.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource.oracle.url=jdbc:oracle:thin:@192.168.253.128:1521:ORCLCDB
spring.datasource.oracle.username=C##STAR
spring.datasource.oracle.password=123456
# Druid 相关配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.druid.initial-size=5
# 配置 Druid 监控
spring.datasource.druid.filter.commons-log.connection-logger-name=stat,wall,log4j
spring.datasource.druid.stat-view-servlet.enabled=true
# 日志相关配置
logging.level.com.star.md=debug
~~~
3. 添加作用在 mapper 上的注解用来使用不同的数据源:
~~~java
@Documented
@Repository
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MysqlRepository {
@AliasFor(annotation = Repository.class)
String value() default "";
}
~~~
同理,添加 OracleRepository 注解。
4. 使用 MysqlProperties 封装 Mysql 配置属性:
~~~java
@Data
@Component
@ConfigurationProperties(prefix = "spring.datasource.mysql")
public class MysqlProperties {
private String url;
private String username;
private String password;
private String driverClassName;
}
~~~
同理,使用 OracleProperties 封装 Oracle 配置属性。
5. 配置 DataSource,SessionFactory,SessionTemplate 组件:
~~~java
@Data
@Configuration
@MapperScan(basePackages = "com.star.md.dao", annotationClass = MysqlRepository.class,
sqlSessionFactoryRef = "mysqlSessionFactory", sqlSessionTemplateRef = "mysqlSessionTemplate")
@MapperScan(basePackages = "com.star.md.dao", annotationClass = OracleRepository.class,
sqlSessionFactoryRef = "oracleSessionFactory", sqlSessionTemplateRef = "oracleSessionTemplate")
public class DataSourceConfig {
@Bean
@Primary
public DataSource mysqlDataSource(MysqlProperties properties) {
DruidDataSource mysqlDataSource = DruidDataSourceBuilder.create().build();
mysqlDataSource.setDriverClassName(properties.getDriverClassName());
mysqlDataSource.setUrl(properties.getUrl());
mysqlDataSource.setUsername(properties.getUsername());
mysqlDataSource.setPassword(properties.getPassword());
return mysqlDataSource;
}
@Bean
public SqlSessionFactory mysqlSessionFactory(@Qualifier("mysqlDataSource") DataSource dataSource) throws Exception {
final SqlSessionFactoryBean mysqlSessionFactoryBean = new SqlSessionFactoryBean();
//配置数据源
mysqlSessionFactoryBean.setDataSource(dataSource);
//配置 mysql mapper 文件位置
mysqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:/mapper/mysql/*Mapper.xml"));
SqlSessionFactory sessionFactory = mysqlSessionFactoryBean.getObject();
assert sessionFactory != null;
sessionFactory.getConfiguration().setMapUnderscoreToCamelCase(true);
return sessionFactory;
}
@Bean
public SqlSessionTemplate mysqlSessionTemplate(@Qualifier("mysqlSessionFactory") SqlSessionFactory sessionFactory) {
return new SqlSessionTemplate(sessionFactory);
}
@Bean
public DataSource oracleDataSource(OracleProperties properties) {
DruidDataSource oracleDataSource = DruidDataSourceBuilder.create().build();
oracleDataSource.setDriverClassName(properties.getDriverClassName());
oracleDataSource.setUrl(properties.getUrl());
oracleDataSource.setUsername(properties.getUsername());
oracleDataSource.setPassword(properties.getPassword());
return oracleDataSource;
}
@Bean
public SqlSessionFactory oracleSessionFactory(@Qualifier("oracleDataSource") DataSource dataSource) throws Exception {
final SqlSessionFactoryBean oracleSessionFactoryBean = new SqlSessionFactoryBean();
//配置数据源
oracleSessionFactoryBean.setDataSource(dataSource);
//配置 oracle mapper 位置
oracleSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:/mapper/oracle/*Mapper.xml"));
SqlSessionFactory sessionFactory = oracleSessionFactoryBean.getObject();
assert sessionFactory != null;
org.apache.ibatis.session.Configuration sessionConfiguration = sessionFactory.getConfiguration();
sessionConfiguration.setMapUnderscoreToCamelCase(true);
return sessionFactory;
}
@Bean
public SqlSessionTemplate oracleSessionTemplate(@Qualifier("oracleSessionFactory") SqlSessionFactory sessionFactory) {
return new SqlSessionTemplate(sessionFactory);
}
}
~~~
1. 向容器中添加了 2 个数据源, 2 个 SqlSessionFactory,2 个 SqlSessionTemplate。
2. 使用 **@MapperScan** 标注了要扫描的 mapper 组件,由于容器中存在多个 SqlSessionFactory 和多个 SqlSessionTemplate 组件,所以还要额外为扫描到的 mapper 指定要使用的 SqlSessionFactory 和 SqlSessionTemplate。
......
此差异已折叠。
:bulb: ***Later equals never.***
#### 1.有意义的命名
软件中随处可见命名,我们给变量、函数、类和包命名,下面是起一个好名字应该遵从的规则。
==名副其实==
选个好名字要花时间,但是省下来的时间比花掉的多,注意命名,而且一旦发现有更好的命名就换掉旧的,这样做,读代码的人都会更开心。
1. 指明计量单位和计量单位的名称,如:int elapsedTimeInDays、int fileAgeInDays 等。
2. 选择体现业务本意的名称,而不是字母(如:d、e),类型(如:theList、list1)等名称。
3. 利用类代替复杂的数据结构。
例如:应用中使用 int\[]\[] 数组表示坐标,如果在代码中大量使用这个二维数组,代码会变得极其繁杂,我们可以定义一个类
~~~java
public class Position {
private int value;
private int x;
private int y;
}
~~~
4. 避免使用魔术变量(未经定义的常量),例如:
~~~java
if(age == 5){
// do something
}
~~~
此时,我们应当将 5 这个魔术数放到文件前面并定义一个数字,如:`private static final int MIN_AGE = 5;`
==避免误导==
程序员必须避免留下掩藏代码本意的错误线索,避免使用与本意相悖的词。
1. 避免使用某些专有名称作为变量名,例如:aix、sco、bat 等,虽然看起来像是不错的缩写,但是极易引起误导。
2. 不要使用 accountList 来表示一组账号,除非它真的是 List 类型,否则会引起错误的判断,此时使用 accountGroup 或 accounts 都是更好的选择。
3. 不要使用外形相似度较高的名称,例如 XYZControllerForEfficientHandlingOfStrings 和 XYZControllerForEfficientStorageOfStrings,更有甚者使用小写的字母 l 和大写的字母 O,他们看起来完全像常量「壹」和「零」。
==做有意义的区分==
在代码中,同一作用范围的两样不同的东西不能重名,如果我们只是为满足编译器或解释器的需要而修改代码,那么将会带来无尽麻烦。
1. 只是在命名后添加数字远远不够,每个不同的命名都应该能表达出不同的意义,如 a1、a2、a3、a4... 等名称纯属误导,完全没有提供正确的信息,例:
~~~java
public void copyChars(char[] c1, char[] c2) {
for (i = 0; i< c1.length; i++) {
c2[i] = c1[i];
}
}
~~~
此时,两个参数名 c1 和 c2 完全没有任何意义,如果将其替换为 source 和 destination,函数就会好看许多。
2. 避免使用废话来区分命名,假设有一个 Product 类,还有一个 ProductData 和 ProductInfo 类,那么它们虽然名称不同,但是都是意义含混的废话;当我们面对 getActiveAccount、getActiveAccounts 和 getActiveAccountInfo 这 3 个函数时,也无法快速的分辨到底该调用哪一个函数。
3. 避免冗余的命名,例如 variable 永远不要出现在变量名中,table 永远不要出现在数据库表名中,nameString 也不会比 name 更好。
==使用读得出来的名称==
人类擅长于记忆和使用单词,如果名称读不出来,在讨论的时候就会变得十分尴尬。
1. 不要使用字母组合命名,例如程序里面有一个 genymdhms 函数(生成年月日时分秒),这样的函数名完全无法读出来也无法一眼看出它的含义,如果换成 genTimestamp 这样的命名就会使函数读起来更像人话。
2. 不要使用不恰当的缩写,例如:DtaRcrd、Cstm、Acct 并不会比 DataRecord、Customer、Account 这些完整的单词更好更简洁。
==使用可搜索的名称==
对于单字母名称和数字常量,有一个问题就是很难再一大段代码中搜索出来。
1. 搜索 MAX_CLASSES_PER_STUDENT 很容易,但是查找数字 7 就比较麻烦了,同时字母 e、s 等也不是一个便于搜索的好名称,我们应该尽量避免使用单字母名称和数字常量。
2. 名称长短应该与其作用域大小相对应,如果变量或常量可能在代码中多处使用,则应该赋予它们便于搜索的名称。
==避免使用标记==
现代编程语言有丰富的类型系统,并且强制使用类型,所以把类型和作用域编进名称里面,只会突然增加负担。
1. 不必使用 m_ 来标记成员变量,我们要学会无视前缀或后缀,只看到名称中有意义的部分。
2. 在创建接口时,不要使用前导字母 I,比如 IShapeFactory 对于 ShapeFactory 来说,前面的 I 字母根本就是一句废话。
==类名和方法名==
1. 类名和对象名应该是名称和名词短语,例如 Customer、Account 等,此外还要避免使用 Data、Info、Manager 这样语义广泛的名称。
2. 方法名应当是动词或动词短语,如 postPayment、deletePage 等。
==一词一义==
1. 每个概念值对应一个词,假如一堆代码中的控制器有 controller、manager 和 driver,name就会让人产生困惑,我们的命名应当一以贯之。
2. 避免将同一单词用于不同目的(双关),例如:使用 add 用于向集合中添加元素,而在另外的地方用来拼接字符串,这种做法是强烈禁止的。
==使用解决方案领域名称和问题领域名称==
1. 只有程序员才会读你的代码,所以我们可以尽情的使用那些计算机科学术语,算法名,模式名,没有程序员不知道 AccoutVisitor 的含义,同样,大多数程序员也都了解 JobQueue 的含义。
2. 如果不能使用程序员熟悉的术语进行命名,那么可以考虑从所涉及的问题的领域获取命名,与软件的实际功能更为贴近。
==为代码添加语境==
很少有名称是能够自我说明的,如果不能自我说明,我们还剩最后一招,给名称添加前缀语境。
1. 当我们看见一堆单词如 state、city、street、name、houseNumber 时,我们很容易推断这是一个地址的描述,但是如果我们只看见一个孤零零的 name 变量呢,我们无法进行推断,但是我们可以添加前缀进行说明:addrName,这样我们就可以明白这个变量是更大结构的一部分。
2. 当需要使用到一堆相关联的变量时,可以使用一个类封装它们,类也是语境的一部分。
3. 不要添加无意义的语境,假如有一个名为 Gas Station Deluxe(加油站豪华版)的应用,如果给其中的每一个类都加上 GSD 前缀就不是什么好点子了;只要名称足够清楚,短名称就比长名称好。
---
#### 2.函数
在编程的早期岁月,系统由程序和子程序组成,后来到了 Fortran 和 PL/1 的年代,系统由程序、子程序和函数组成,如今只有函数保留了下来,函数是所有程序中的第一组代码。
==短小==
函数的第一条规则是短小,第二条规则还是要短小。
1. 函数尽量不要写得过长,**20** 行封顶最佳。
> :gear: 个人不倾向于这这么短的函数,频繁的函数调用不仅不会降低代码编写难度,还会提高难度。一般情况下,**50-60** 行左右封顶为最佳。
2. if、else、while 等语句,其中的代码应该只占一行,该行一般为一个函数调用语句,这样能够很好的保持函数大小。
3. 函数中的代码嵌套层级(if、while 等嵌套)不宜过多,否则不易于阅读和理解,最好是一层或两层,不可超过三层。
==只做一件事==
函数应该只做一件事,做好这件事,只做一件事。
如何判断函数只做了一件事,还是做了几件事?例:
~~~java
public void buy() {
// 1.走进商店
// 2.选好商品
// 3.掏出钱包
// 4.付钱
// 5.放回钱包
// 6.走出商店
}
~~~
在这个函数中,模拟了一个买东西的情节,其中 1、2、6 步骤都是买东西过程下的抽象层中的一件事,但是 3、4、5 却是在买东西这个动作下的更细分的步骤,很明显不属于函数名下的抽象层,此时 3、4、5 就合一被拆出一个新函数「结账」。
> :yellow_heart: 向下原则:让每个函数后面跟着位于下一抽象层及的函数,在阅读时就能遵循抽象层级向下阅读。
==使用具有描述性的名称==
函数起一个具有描述性的名称,函数的功能越集中,就越便于起一个好名字。
要害怕长名称,长名称要比短而费解的短名称好。
同一个模块中,函数的命名方式要保持一致,使用与模块名一脉相承的短语、名词和动词给函数命名。
==函数参数==
理想的函数参数是 0,其次是 1,再次是 2,应当尽量避免 3 参数的函数(除非万不得已)。
1. 一般来说,信息通过参数输入函数,然后通过返回值进行输出,所以尽量不要在参数中输出函数结果。
2. 不要使用标识参数:向函数输入 boolean 值是一种不优雅的做法,相当于大声宣布本函数不只做一件事,在任何情况下我们都不要这样做;此时我们应该将这种情况拆分为 2 个函数 doXxxForXxx 和 doXxxForNonXxx。
3. 双参数函数:
1. 两个参数最好是某个值的有序组成部分,如:`new Point(0, 0);`。
2. 使用两个同类型的参数时,应额外注意其先后顺序,如:`assertEquals(expected, actual);`中一般约定期望值在前,实际值在后。
3. 尽量的转化双参数函数为单参数函数,可以使用诸如添加成员变量、新建一个类等方法。
4. 三参数函数:如果一个函数看起来需要 2 个、3 个及 3 个以上的参数,说明一些参数应该封装成类了,例:
~~~java
Circle makeCircle(double x, double y, double radius);
Circle makeCircle(Point center, double radius);
~~~
5. 可变参数:在 Java 中,可以使用 ... 向函数传入可变长度的参数。
6. 函数和参数应当形成一种非常良好的动词/名词的对应关系,例如: `writeField(name);`。
==分割指令和询问==
1. 函数要么做什么事,要么回答什么事,两者不可得兼,例:
~~~java
boolean isSuccess = setAttribute("username", "unclebob");
---------------------------------------------------------
if(attributeExists("username")) {
setAttribute("username", "unclebob");
}
~~~
此两种代码明显后者更优。
2. 使用异常替代返回错误码,将询问以异常的形式抛出,能够很好的分割指令和询问。
1. 将 try/catch 代码块单独抽离乘一个函数,使之从主代码块中分离出来,代码就会变得更简洁。
2. 错误处理就是一件事,处理错误的函数不应该做其他的事,如果在抛出错误的时候需要做另外的一件事,那么应该好好考虑此处是否应该抛出错误。
3. 尽量使用异常代替错误码,返回错误码一般是一个枚举类,其他许多类都要依赖并使用它,使得在修改这个枚举类时造成了很大的负面压力,所以,尽量使用异常派生类。
---
#### 3.注释
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册