Spring 项目中把大量的 SQL 分散在 Java 代码中,无 Here Doc 的情况下用加号来连接写着实在是不爽,于是之前思考这个 Spring 项目中把 SQL 语句写在 .sql 文件中 -- 把它们写在 *.sql 文件中,但是这个 *.sql 需要特定的格式来标识属性 Key--!select.user
而且还需要一个额外的类 SqlPropertySourceFactory 来解析上面的 *.sql 文件, 识别出 select.user 是 Key, 紧接着后面的块是相应的属性值,用注解引用它时还有点额外的 factory 属性来配置,如
select id, firstname, lastname, address --!update.user
update ........@PropertySource(value = "classpath:sql/queries.sql", factory = SqlPropertySourceFactory.class)
所以一直在思考是否能够再简单些,是否能用一个自定义的注解,如@SqlPropertySource("classpath:sql/queries.sql")
捉摸了很久,似乎有点难度,不过再不断发掘的过程中找到了这个类org.springframework.core.io.support.PropertiesLoaderUtils, 有下面的代码片断 Read More第八章:边界
本章关于如何学习使用第三方组件
- 第三组件或框架追求普适性,而使用者则想要集中满足特定的需求
- 学习第三方组件首当其冲当然还是文档,其次重要的是它的单元测试,我甚至是把单元测试当作文档的一部分来看待。其实更重要的方法是学习性测试(learning tests), 通过测试来学习才是切实的体验,是一种精确的试验,我们甚至可以用自己的测试来验证第三方组件的新版本
- 边界上会发生有趣的事,这就要求我们对它清晰的分割和定义,以避免我们的代码过多地了解第三方组件中的特定信息,依靠你能控制的东西好过依靠你控制不了的东西,免得日后受它控制。简而言之就是尽可能隔离边界,在边界改动时只需要修改一下适配器,对的适配
第九章:单元测试
这一章我自认为是很重要的,特别是正在采用测试驱动开发/设计(TDD) 的程序员来讲。充分,良好的单元测试是保证我对生产代码大刀阔斧的关键,这也就是 TDD 的最后一个 D 又能理解了 Design 的原由
Read More
三个月前写过一篇 Mockito 中捕获 mock 对象方法的调用参数,一般项目中 Mockito 不决求助于 JMockit, 同样的在 JMockit 也需对捕获被 Mock 的方法调用参数。当我们用new Expectations(){{}}打桩并在后面断言了返回值,那就无需捕获参数来断言,匹配到了方法调用即证明传入的参数也是对的,如下面的代码所示1public class UserServiceTest { 2 3 @Mocked 4 private UserDao userDao; 5 6 @Test 7 public void couldCallUserDaoToAddUser() { 8 new Expectations(){{ 9 userDao.findById(123); 10 result = "Yanbin"; 11 }}; 12 13 UserService userService = new UserService(userDao); 14 String user = userService.findBy(123); 15 16 assertThat(user).isEqualTo("Yanbin"); //这里断言成功也就证明了 userDao.findById(123) 方法被调用,参数必须是 123 17 } 18}
但如果是未打桩的方法,或打桩是用的模糊参数(withInstanceOf(String.class)), 或是无返回值的方法就要事后对是否调用了某个方法以及传入什么参数的情况进行断言。 Read More- 对于同步方法的测试很简单,调用完后可立马检查执行状态; 而异步方法,由于我们无法确切的知道何时结束,因此以往的办法是用
Thread.sleep(500)来预估一个执行时间。然后通常我们估计的要长于实际的时间,这就很浪费,况且偶然的超过预估的等待时间也并不意味着代码有问题。还有sleep方法还抛出一个检测异常InterruptedException, 一般会要对Thread.sleep(500)作下简单包装。
于是今天要介绍的 Awaitility 就应运而生了,专门针对异步方法的测试。它的官方文档在 https://github.com/awaitility/awaitility/wiki/Usage。本文主要关注在 Java 8 环境下用 Lambda 的代码书写方式。Awaitlity 实际运行是以某种轮询的方式来检查是否达到某个运行状态,可设定最多,最少等待时间,或永久等待,或自定义轮询策略,之后就开始进行需要的断言,所以它可以尽可能的节省测试异步方法所需的时长。而不像Thread.sleep(500)一路等到黑,并且没有回头路。
通常我会在项目中给 JUnit 配上三个最佳伴侣,它们是(按mvn dependency:tree中的显示方式):- org.awaitility:awaitility:2.0.0:test
- org.assertj:assertj-core: version: 3.8.0:test
- org.mockito:mockito-core:2.7.22:test
当然如果项目中没有异步调用自然是不需要 Awaitility, 在我的项目中是基本不可能的。以上三种都追求 DSL,以流畅的方式进行愉快的测试。
现在来尝试下 Awaitility 的几种基本的用法,先假定有下面的代码UserServiceRead More 
第四章:注释
- 别给糟糕的代码加注释 -- 重新写吧
- 什么也不会比乱七八糟的注释更有本事搞乱一个模块。什么也不会比陈旧,提供错误信息的注释更有破坏性
- 若编程语方足够有表达力,或者我们长于用这些语方来表达意图,就不那么需要注释 -- 也许根本不需要
- 注释的恰当用法是弥补我们在用代码表达意图时遭遇的失败。注释总是一种失败
- 不准确的注释要比没注释坏得多。它们满口胡言。代码,只有代码能忠实的告诉你它做的事
- 好注释不多,如法律信息,警示等。我都不觉得
TODO是多好的东西 - 大多数注释都是坏注释,甚至一些 IDE 自动生成的代码也加一堆的废话注释,分散阅读注意力。我也写过那种日志式注释,这本是版本控制系统干的事。
- 很奇怪 IntelliJ IDEA 还在自动为新建的类加下创建者,代码是团队共有,时间长了根本就与作者无半毛关系。我经常碰到这种写有作者的代码哭笑不得,直接把原作者改了或删了也不太好,还不如把文件删了,新建一个没有作者注释的类
- 直接把代码注释掉是种讨厌的做法,自己可能不会再启用,其他人以为很重要更不敢删。不用了最好果断删掉,版本控制系统会帮你记住历史。
- 个人认为,注释会胡说八道,产品代码和测试代码不会撒谎。有功夫写注释还不如写好测试用例,测试需要维护,它本身就是最真实的文档。
修改私有属性来 Mock 可能不是一种很好的测试方式, 因为属性名是动态的,但有时不得已而为了,例如下面的代码:1public class UserService { 2 private ExternalApi external = ExternalApi.default(); 3 private UserDao userDao; 4 5 public UserService(UserDao userDao) { 6 this.userDao = userDao; 7 } 8 9 public User findUserById(int id) { 10 return userDao.findById(external.convertId(id)); 11 } 12}
测试时欲隔离对 ExternalApi 的外部依赖, 当然可以把它也作为构造函数的一个参数,这样创建 UserService 实例时就可以 Mock external 属性。不过 external 经常是不变的,所以作为方法参数的必要性也不大。这就希望能在构造出 UserService 之后对 external 私有属性进行 Mock 处理。
在 Mockito 1.x 和 2.x 下要使用不同的方式,分别使用到 Whitebox 和 FieldSetter 类,它们都来自于mockito.internal.util.reflection包,可见 Mockito 打心底不推荐直接使用它们,但谁叫它们是 public 的呢。还有一种方式是使用 PowerMock + Mockito, 这是后话。 Read More
一句话概要:对 Lambda 环境变量的任何改动都会引起一次 Lambda 的冷启动,大可放心在 handleRequest(...) 方法外使用环境变量。
从 AWS 上 Java Lambda 应用记要 中,我学到了 Lambda 的实例是跨请求共享的,所以为使用 Lambda 配置的环境变量时曾写出了下面复杂而多余的 AWS Lambda 代码:这段代码看起来很在理,既然 Lambda 实例是共享的,那么在必变环境变量之后就可能不会重新初始始化实例,所以在每次的请求方法中对比如果环境变量值改动了就重新用最新的配置值来初始化线程池。然而上面的代码结结实实是多余的,真是把 Lambda 想得太简单了,如果是很多环境变量岂不是逐一判断。 Read More1public class Handler implements RequestHandler<SNSEvent, String> { 2 3 private int threadPoolSize = getThreadPoolSizeFromEnv(); 4 private ExecutorService threadPool = Executors.newFixedThreadPool(threadPoolSize); 5 6 @Override 7 public String handleRequest(SNSEvent snsEvent, Context context) { 8 int configuredThreadPoolSize = getThreadPoolSizeFromEnv(); 9 if(configuredThreadPoolSize != threadPoolSize) { 10 threadPoolSize = configuredThreadPoolSize; 11 threadPool = Executors.newFixedThreadPool(threadPoolSize); 12 } 13 14 return "Hello Lambda"; 15 } 16 17 private int getThreadPoolSizeFromEnv() { 18 return Integer.parseInt(System.getenv().getOrDefault("threadpool_size", "50")); 19 } 20}
直接一句话:去掉 Log4J 的依赖,把 Slf4J, Logback, 和 log4j-over-slf4j 依赖加进来就行了,配置文件换成 logback.xml,这就完了,不要往下看了,都是些废话。当我们用 Serverless 命令sls create -t aws-java-maven -p hello-lambda创建的示例项目中直接用的是 Log4J 日志组件,而且也没用像 Slf4j, 或 Apache Common Logging 更上一层的通用日志框架。查看了几个 AWS 本身的组件 S3, SNS, 和 Kinesis 的 SDK, 它们内部是用的 Apache Common Logging 声明的日志变量import org.apache.commons.logging.LogFactory;
而我们自己的组件中通用日志组件是 Slf4j, 底层实现为 Logback, 所以我们希望在 Lambda 中使用 Logback 来写日志。
import org.apache.commons.logging.Logger;
private static final Log log = LogFactory.getLog(AmazonKinesis.class)
选用一个通用日志框架总是明智之举,因为一个项目经常杂糅了多种日志实现,使用 Slf4J 或 Apache Common Logging 可以把它们(Log4J, Logback, 或更多)输出到共同的目的地,并且有统一的日志输出接口。而我们认为通用日志框架还是 Slf4J 要先进些,所以我们在 Java Lambda 中的日志方案是 Slf4J + Logback,还需要把 Log4J 的日志桥接到 Slf4J 上来,再经由 Logback 输出。
回到前面创建的hello-lambda项目,看其中怎么用的 Log4J,先瞧瞧pom.xml文件怎么引入的 Log4J, 它是间接通过一个 AWS 定义的 Log4J Appender 引入的 Read More
AWS 的 Lambda 给了那些不想自己管理 EC2 服务器和配置负载人员很大的便利,所以 Lambda 被描述为 Serverless。真正的只关注业务就行,怎么调度,同时有多少个实例运行交给亚马逊去处理就是了。运行 Lambda 的环境也是亚马逊内部的 EC2 服务器,镜像是 Amazon Linux, 所以如果想运行系统命令,那是 Linux 的。Lambda 支持多种语言 Node.js, Python, C#(.net core), 还有 Java 8,我们就选择了 Java 8, 一开始还担心它与别的语言比起来会多大劣势,其实不然。而且所谓的 Java 8, 并非单指 Java 语言,而是指 JVM 平台,所以也可以用 Scala, Clojure, Groovy, Kotlin 来写。
Java 与脚本语言如 Node.js, Python 相比给人一个明显的感觉是启动慢,还有人用统计数据来比划 AWS Lambda cold start(pseudeo-)benchmark. 不过真不用担心,人家说的是冷启动,也就发生在部署后第一次执行启动会比较慢。要是我们的 Lambda 经常被调用,或每天触发比较集中,Lambda 在任务到来之前处理待续状态,就不会有冷启动的耗时过程。或者是每次任务要执行 3 分钟左右,又何必在乎毫秒级的冷启动时间。
说到底就是别理会下面的数据20ms startup time for Python ~ $0.04167540ms startup time for Node.js ~ $0.0833580ms startup time for Java ~ $0.1667
Lambda 实例重用
Java 的 Lambda 就是一个微服务,在首次触发时微服务冷启动有些慢,但一旦启动之后就可以用这个微服务实例接受后续的请求,只有在比较长的一段时间内未被触发 AWS 才会把这个微服务杀掉。 Read More
第一章:整洁代码
- 代码即设计
- 童子军军规:把露营地清理得比来时还干净,签入代码前是否已做重构
- 你湎自行实践,且体验自己失败。你须观察他人的实践与失败
- 勒布朗(LeBlanc) 法则:稍后等于永不 (Later equals never)
- 赶上期限的唯一方法--做得快的唯一方法--就是始终尽可能保持代码整洁
- 缺乏“代码感”的程序员,看混乱是混乱,无处着手。有“代码感”的程序员能从混乱中看出其他的可能与变化
- 不好的代码引诱别人做没规矩的优化,搞出一堆混乱来
- 整洁的代码只做好一件事,糟糕的代码想做太多事,它意图混乱,目的含混
- 整洁的代码从不隐藏设计者的意图
- 整洁的代码只提供一种而非多种做一件事的途径
- 整洁的代码总是看起来像是某位特别在意它的人写的
- 回放我们编码过程显示,多多数时间都是在滚动屏幕,浏览其他模块
- 读与写花费时间的比例超过 10:1, 写新代码时,我们一直在读旧代码
- 编写代码的难度,取决于读周边代码的难度