最近在写框架的测试代码的时候,有需求要对 Log4j 的输出进行测试(依赖 Log4j 的输出来进行测试,这一点本身可能得深思一下),之前也有对 stdout 和 stderr 进行测试,用了一个叫做 system-rule 的包:

1
2
3
4
5
<dependency>
    <groupId>com.github.stefanbirkner</groupId>
    <artifactId>system-rules</artifactId>
    <version>1.5.0</version>
</dependency>

利用这个包中类,只需要在测试用例中加上一个 JUnit Rule,就可以获取到 stdout 和 stderr 中的内容,然后对其进行测试。现在我也想对 Log4j 的输出采用类似的方式进行测试,于是扩展了 JUnit 的 Rule,就有了以下这一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
 * 此 Rule 用于对 Log4j 进行测试
 *
 * @author khotyn 8/14/14 9:18 PM
 */
public class Log4jRule extends ExternalResource {
    private String       logName;
    private List<String> loggerMessages = new ArrayList<String>();

    public Log4jRule(String loggerName) {
        this.logName = loggerName;
    }

    public Log4jRule(Class className) {
        this.logName = className.getName();
    }

    public List<String> getLoggerMessages() {
        return loggerMessages;
    }

    public String getLogMessageAsString() {
        String result = "";

        for (String loggerMessage : loggerMessages) {
            result += loggerMessage;
            result += "\n";
        }

        return result;
    }

    @Override
    protected void before() throws Throwable {
        Logger logger = LogManager.getLogger(logName);
        logger.addAppender(new AppenderSkeleton() {
            @Override
            protected void append(LoggingEvent event) {
                loggerMessages.add(event.getMessage().toString());
            }

            @Override
            public void close() {

            }

            @Override
            public boolean requiresLayout() {
                return false;
            }
        });
    }
}

整段代码非常简单,继承了 JUnit 的 ExternalResource 类,然后在 before 方法中,给对应的 Logger 加上了一个 Appender,在 Appender 中,将日志内容收集到一个 List 中,然后拿到这个 List 就可以拿到日志的输出了。

使用的时候非常简单:

1
2
3
4
5
6
7
8
9
10
11
public class SampleTest {
    @Rule
    public Log4jRule log4jRule = new Log4jRule(SampleTest.class);

    @Test
    public void test() {
      	// .........
        Assert.assertTrue(log4jRule.getLogMessageAsString().contains(
            "Hello, world"));
    }
}

通过

1
log4jRule.getLogMessageAsString()

可以拿到一个 String 格式的日志输出,或者通过:

1
log4jRule.getLoggerMessages()

来获得一个日志输出的内容的 List,List 中的没一行就是日志中的一行

最近在开发公司的集成测试框架,有一些关于单元测试的体会,写一个博客记录一下想法。

为什么要写单元测试?

这个问题,已经有无数的关于技术的书、文章去阐述了,不断地强调单元测试的重要性。比如单元测试可以让你在软件开发的早期阶段发现 Bug,而不必到集成测试的时候才发现等等。不过,对于我来说,在切实地戳到我的痛点之前,我一直都没有去重视这些关于单元测试的忠告(虽然在心中记着,但是实际上并不是很在意)。

在写公司的集成测试框架的时候,有那么好几次,在调整了现有的功能,或者修复了某个 Bug 之后,因为懒惰,也因为跑一次完整的单元测试所需要的耗时较长,我侥幸地认为这些修改应该没有问题,直接打包交付。结果是,墨菲定律出现了,果然,没有经过测试的修改引发了新的 Bug,我不得不重新修改代码,然后厚着脸皮让用户重新试一次。

人总是不靠谱的,我们懒惰,我们存在侥幸心理,坏事儿总是在我们最不希望发生的时候发生。写单元测试不能防止我们懒惰,防止我们存在侥幸心理。但是一次成本低廉的单元测试会让我们觉得:“反正运行一次但单元测试不会耗费很多时间,不如跑一次吧”,它在一定程度上降低我们犯错的几率。

单元测试对于重构的意义也非常重大。很多有意思的程序员都有洁癖,会想着去修改某一段「恶心」的代码。我有过这样几次经历,在把一段「恶心」的代码修改地赏心悦目后,最后上线后发现引入了一个 Bug,心里暗骂一句 WTF,然后一脸黑线地把 Bug 修改了,想想如果当时有单元测试,那么会给我多大的勇气,让我可以肆无忌惮地去重构代码,这是多么爽的一件事情。

好的单元测试应该是怎样的?

一个好的单元测试,我觉得最重要的一点就是运行成本得低,也就是说一个单元测试越快越好。运行一次单元测试的成本越低,你才会越愿意去运行单元测试。如果运行一次单元测试得 10 分钟 20 分钟,那么我想很多人的侥幸心理又会出来了。

我是怎样写单元测试的?

我一般是这样写单元测试的,先想清楚模块的边界,有哪几种可能的输入,这些输入对应的可能输出是什么,然后以最快的速度堆积代码把功能先实现出来,接着写单元测试,把测试用例全部跑过。接下来马上着手重构之前写的代码,不断重构,不断地跑单元测试,知道重构后的代码让自己满意为止。

背景

Tomcat 作为 Servlet 规范的实现者,它在应用启动的时候会扫描 Jar 包里面的 .tld 文件,加载里面定义的标签库,但是,我们在开发的时候很多都不是采用 JSP 作为 Web 页面的模板的,很多都是使用 Velocity 之类的模板引擎,自然而然,为了加快应用的启动速度,我们可以把 Tomcat 里面的这个功能给关掉。

方法

看 Tomcat 的配置文档,关于 Context 的设置这一块,看到了 processTlds 这个属性可以设置,看下这个属性的说明:

Whether the context should process TLDs on startup. The default is true. The false setting is intended for special cases that know in advance TLDs are not part of the webapp.

只要在 Context 中把这个属性设置成 false,那么我们就可以关闭 Tomcat 的 TLD 扫描功能了,为了让所有的应用都可以关闭这个功能,我们可以将 Tomcat 目录下的 conf/context.xml 修改成如下这样:

1
2
3
4
<?xml version='1.0' encoding='utf-8'?>
<Context processTlds="false">
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
</Context>

但是,在 Tomcat 6 中测试的时候,发现这个功能没有生效,无奈只能 Debug Tomcat 的源码,发现 StandardContext 的 init 方法下有如下代码:

1
2
3
4
5
6
7
8
if (processTlds) {
    this.addLifecycleListener(new TldConfig());
}

super.init();

// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(INIT_EVENT, null);

这里需要说明的一点是,我们的默认的 context 配置是在 lifecycle.fireLifecycleEvent(INIT_EVENT, null); 这行代码中被处理的,而在这行代码之前,Tomcat 就已经使用了 processTlds,我们的配置完全没有生效。

Workaround

那么,这么解决呢?在 context 中,我们还可以配置一个 JarScanner,这个 JarScanner 会被用来扫描 Jar 包中的 tld 文件,我们可以在默认的 context.xml 中配置一个空的 JarScanner,像下面这样:

1
2
3
4
<?xml version='1.0' encoding='utf-8'?>
<Context processTlds="false">
    <JarScanner className="com.alipay.sofa.runtime.test.patch.tomcat.NullJarScanner"/>
</Context>

NullJarScanner 的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.alipay.sofa.runtime.test.patch.tomcat;

import org.apache.tomcat.JarScanner;
import org.apache.tomcat.JarScannerCallback;

import javax.servlet.ServletContext;
import java.util.Set;

/**
 * @author khotyn 14-1-21 下午4:37
 */
public class NullJarScanner implements JarScanner {
    @Override
    public void scan(ServletContext context, ClassLoader classloader, JarScannerCallback callback, Set<String> jarsToSkip) {
        // Do nothing at all.
    }
}

需要注意的是,Tomcat 7 不会出现上述的问题,你只要在配置中把 processTlds 设置成 false 即可。

如果进度正常,新版本的 Java,Java 8 将在三月份发布,Java 开发人员期待已久的 lambda 也将在 Java 8 中得到支持。目前,Java 8 的早期版本已经可以在 Java 的网站上下载到了,Intellij IDEA 也已经在其最新的版本支持了 Java 8。所以,最近花了点时间了解了一下 Java 8 中新增加的一些特性。

由于 lambda 的引入,Java 8 对原来的集合类做了大幅的更新,让集合操作可以支持 lambda 表达式。在看新的的集合类的代码的时候,发现了 java 8 似乎增加了一个新的方法描述符,比如在 java.lang.Iterable 里面就新加入了下面这个方法:

1
2
3
4
5
6
default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

在方法的最前面,是一个 default 描述符。等等,Iterable 不是个接口吗,怎么有具体的实现代码了?

这个 default 就是在 java 8 中新引入的,它可以让你的接口有一个默认的实现,接口的实现类可以不用去实现 default method,比如,下面这段代码,是可以正常编译通过的:

1
2
3
4
5
6
7
8
9
class Impl implements A {

}

interface A {
    default String foo() {
        return "A";
    }
}

引入 default 的带来的一个好处就是在现有的接口上增加方法而不用让其实现修改代码,通过这种机制,Java 8 可以通过平滑的方式在原有的 Java 的 API 上引入 lambda 的支持。

那么,如果一个类实现了两个接口,这两个接口里面有方法签名相同的 default method,那运行的时候到底会选择哪一个?答案是编译不通过,如果出现这种情况,实现类必须实现 default method,以消除歧义,比如下面这样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class MultiImpl implements A, B {

    /**
     * 由于 A,B 中都有 String foo() 接口,不知道要调用哪个,所以实现类必须实现一下
     *
     * @return
     */
    @Override
    public String foo() {
        return "C";
    }
}

interface A {
    default String foo() {
        return "A";
    }
}

interface B {
    default String foo() {
        return "B";
    }
}

当然,在的实现类中,也可以直接调用某个接口的 default method:

1
2
3
4
5
6
class MultiImplInvokeSuper implements A, B {
    @Override
    public String foo() {
        return B.super.foo();
    }
}

已经出售,谢谢

转让自用的 13 寸 MacBook Pro,2009 年年中的款,型号 MB990,入手时间 2010 年初,使用时间三年不到,除了电池的续航能力有所下降,其他没有出现任何问题,内存我自己升级到了 8 G,平时的开发也都是用这台开发(你知道开个 Eclipse 或者 Intellij IDEA 是很耗资源的),也比较顺畅,价格方面是 2500,当然可刀~,暂时只接受杭州的当面交易。

有意向的可以直接在这篇博客下面回复,或者给我发邮件联系我,hting1#gmail.com

关于这个型号的 MBP 的其他参数,可以直接看下面这张图:

image

上几张本本的照片:

合上后,有些许划痕

image

image

Google 的 Guava 库是一个 Java 程序员必须了解的库,它提供了一些非常强大的功能,比如函数式风格的集合操作,Cache Builder 等等的功能,另外 Google Guava 还提供了一个非常方便的观察者模式的实现:EventBus。这篇文章就来介绍一下 EventBus 的使用。

EventBus 对象

在举例说明 EventBus 的使用方式之前,我们先来看一下 EventBus 对象,EventBus 对象整个负责了观察者模式监听者的注册,事件的分发,所以,在使用 EventBus 的时候,你就省去了非常多的工作,你只要去使用 EventBus 就可以了,不用再去自己实现一个 Publisher 的类,使用 EventBus 的第一步就是你需要一个 EventBus 的实例:

1
EventBus eventBus = new EventBus();

注册监听者

使用 EventBus 监听事件,只需要在你的处理事件的方法上添加一个 @Subscribe 注解就可以:

1
2
3
4
5
6
static class Subscriber {
    @Subscribe
    public void subscribe(Event event) {
        System.out.println(event.getWord());
    }
}

这里的事件对象 Event 可以是任何的对象,可以是 Object,但是也可以是任何你自定义的消息对象。

建立一个类以后,就可以往 EventBus 中注册 Subscriber:

1
eventBus.register(new Subscriber());

分发事件

在注册完事件后,就可以去分发事件了,分发的代码非常简单:

1
eventBus.post(new Event("Hello world"));

这样,所有的注册在 EventBus 中的监听者,只要它的监听方法的参数是 Event 或者 Event 的超类,那么都会收到事件。

结论

EventBus 作为一个 In-JVM 的观察者模式的实现,非常使用,使用起来非常简单,可以减少不少的工作,建议在项目中可以多多使用。

前一段时间特别喜欢上知乎,觉得还是一个挺有意思的网站,同类的国外网站有 Quora,不过我英语不太好,所以平时还是喜欢逛逛知乎,但是最近逛知乎的时候发现一个问题,里面的有一些的问题的高票答案其实不是在回答问题,而是在发泄自己的情感,而这些答案往往还能够得到高票(历史上的煽动家大概也是这样的,让自己的话迎合大众的情感,以此得到他们的支持),比如下面这个:

image

正常的答案应该是列出邹孟睿的经历,但是得票第一的回答其实是在表达自己对「来往」这个产品的不满,或者是觉得「来往」并没有什么未来,当然,他自己似乎也有点不好意思,所以用了「匿名」来回答。

知乎上的这种答案不止这里一个,还有很多讨巧的答案,作为一个问答网站,其用户对于问题的回答应该尽量保持中立,这样的答案毫无疑问是无异于网站的形象,所以现在知乎对我的吸引力也越来越少了。

既然提到了快感,顺便说一句:“阅读新闻也是一种快感”,这是浪费时间的快感,不看新闻,你也不会错过任何重要的消息,所以,不如把手机上的新闻客户端给删掉,它不能给你带来任何东西。

哦,对了,有一个新闻客户端,素以评论见长,它提供了另一种形式的快感发泄,是的,我说的就是「网易新闻」。

简介

jvmtop 是一个分析工具,顾名思义,它是一个针对 jvm 的 工具,展示的方式和 unix 的 top 命令相似。

jvmtop 的项目地址是:jvmtop,安装 jvmtop 除了项目地址上的方式以外,还可以通过 jenv 安装:jenv install jvmtop

jvmtop 提供了两个视图,一个是概览视图,可以展示出当前机器的所有的 JVM 的情况,命令是

1
jvmtop.sh

显示出的信息类似下面这样:

image

其中,各个字段的意义分别如下:

  • PID:进程 ID
  • MAIN-CLASS:main 类的名字
  • HPCUR:当前被使用的 heap 的大小
  • HPMAX:最大可用的 heap 的大小
  • NHCUR:当前被使用的非 heap 大小(比如:perm gen)
  • NHMAX:最大可用的非 heap 大小
  • CPU:CPU 的使用情况
  • GC:消耗在 GC 上的时间比例
  • VM:JVM 的提供者,大版本号,小版本号,图中的意思是 Apple 提供的 JDK 6U51 版本。
  • USERNAME:当前的用户名
  • #T:线程数量
  • DL:是否有现成发生死锁

还有一个视图是详情视图,展示一个 JVM 的详细情况,使用的命令如下:

1
jvmtop.sh <pid>

显示的信息类似下面这样:

image

其中,各个字段的意义如下:

  • TID:线程 ID
  • NAME:线程名
  • STATE:线程状态
  • CPU:线程当前的 CPU 占用情况
  • TOTALCPU:从线程被创建开始总体的 CPU 占用情况
  • BLOCKBY:阻塞这个线程的线程 ID

更加详细的用法大家可以用下面的用 jvmtop.sh -h 来查看。

实现

jvmtop 的实现相对来说还是比较简单的,整个 jvmtop 才 14 个类。

image

其中 JvmTop.java 是入口类。

jvmtop 在启动后,会首先用 sun.jvmstat.monitor.* 下面的类以及 com.sun.tools.attach.VirtualMachine 获取到当前机器的所有的 JVM,然后通过 attachment api 将 management-agent.jar 这个 agent 加载到目标 JVM 上,这样,通过 JMX,就可以拿到当前的 JVM 的各种信息了,具体各个信息需要用什么样的 MBean 去拿,大家可以看对应的源代码。

其实,如果需要一个 JVM 的静态的信息,比如,PID,MAIN-CLASS,JVM-ARGS 等等静态信息,直接用 sun.jvmstat.monitor.* 下的 API 就可以,只有需要动态信息的时候,我们才需要通过 attachment api 把 JMX 的功能打开,通过各种 MBean 去获取这些信息。如果后续需要实现类似的功能,也可以通过这样的思路去做。

image

今天和朋友聊天,说起眼见的问题,以前在业务团队的时候大家都奔着业务而去,完成业务上的需求,最多了解一下所用到的框架,鲜有关注业界的开源软件的动态。

而到了中间件团队,所有人都得规划、实施、推广自己的产品,如果只是做一些平庸的事情,那实在对不起你所在的团队,所以只能硬着头皮去花时间关注业界的开源软件的动态,慢慢地,也就开阔了自己的眼见,碰到一个问题以后,发现还可以用这样的方法解决,这是之前从来没有想到的。

最近其实一直在反思,当时这么快下决定转岗到现在的部门到底对不对?诚然,当时转岗的决定,有其他的非常私人的考虑,在新的岗位,各种不习惯,但是这里有很多新鲜的事情,可以作为学习的对象;有一些很强的人,可以作为学习的榜样。当你进入了一个更大的世界,你又重新回到了婴儿的时代,疯狂快速地吸收着周围的一切。

晚上跑步的时候突然想起了 Clyde 三年前和我说的他的梦想:“等我老了的时候,我希望成为一个讲故事的人”,当时我并不明白他到底想表达什么,只能随便应付几句。现在,我似乎有点想明白了,那些有着很多精彩的故事的人,无非就是旅行家,船员之类的,想要成为一个讲故事的人,必须有足够的人生经历,和这个世界有更大的交集。最近在看德田秋声的「霉」,永远待在一个地方,生活大概就像「霉」中的男女主人公过的那样,平淡,烦躁,偶尔有点阳光,能够讲述的大概也就是些平淡无奇的故事。

一直很佩服身边的一些朋友和同事,他们能够把一件事情坚持两三年,有些人跑地并不快,但是他们总能比你更加坚持,跑得更远,和他们相比,我的生活是不是太过肤浅,似乎从来没有坚持一件事情超过一年,对于那个超过一年的世界,我陌生无比,这大概是因为我一直都比较缺乏意志力和专注力。

旧世界纵然舒适无比,但是前方永远有一个更吸引人,更宽广,更深邃的世界,多年以后,希望在那个世界遇见更好的自己。

今天的工作基本上就是在重构实习生写的 JS 代码,改的昏天暗地,他们基本把整个工程的 JS 代码都放到了一个文件中,代码中一会儿以空格缩进,一会儿以 tab 缩进,到处都是不必要的变量,我的代码洁癖又犯了,忍不住直接进行了重构,对 JS 进行模块化的拆分,每一个模块都有相对独立的功能,改了之后舒畅了很多,就像拿掉了卡在喉咙中鱼刺。

工作这几年以来,我也遇到过几个和我一样,有着或者或少代码洁癖的人,看到 IDE 中出现的警告信息就会感到不舒服(从 Eclipse 转到 Intellij IDEA 以后,洁癖的程度越来越严重了,Intellij IDEA 会对你的代码风格做更彻底的检查),看到以 tab 来做缩进简直就像看到异教徒一样,看到代码中已经被注释掉的废弃代码那肯定得马上删掉。

可惜的是,工作遇到的更多的人,在这方面要随意的多,他们更多追求的是把工作完成,让功能能够跑起来,其实一定程度上来说,他们是对的,他们是更加实用主义的程序员,实用主义能够帮助我们更快地达到自己的目标,而不会被和目标无关的东西所牵绊。但是,我觉得,一个程序员在追求程序的实用主义的时候,也应该去追求程序本身的美感。

或许有人会问,什么是程序的美感,关于这一点,我们可以从很多的侧面去描述:

  • 更人性化的用户体验
  • 更简洁的架构
  • 程序内一致的代码风格
  • 更易读的代码

但是,“美”本身是用语言是无法完整描述的,你可以说娜塔丽•波特曼很美,你也可以说加西莫多很多,但是他们之间的差距是如此之大,除了他们都是人之外你都找不到他们的共同点。所以“美”就是“美”,只有你身在其中的时候你才能够感受到。

或许对于很多人而言,“代码美”根本就是个扯淡的东西。其实写代码的过程和做木工的过程是很像的,说白了,都是设计出一样东西,然后拿来用而已。看看下面的无印良品木制家居,不得不承认,它比我们平时从市面上看到的普通的木制家居要舒服地多。同样一段代码也可以比另一段代码看起来更加“舒服”,更加“美”,只是每一个人对这种“美”的感受能力不同而已,有些能够感受到,有些不能感受到(在这一点上,不得不承认,我对“代码美”的感受能力是比较差的)。

image

想要去培养这种对“程序美”的感受能力,一个很好的方式就是去阅读优秀的开源代码,比如 Tomcat,Dubbo 等等,有时候,当你看到一段不错的代码,一个简洁的有用的架构设计的时候,你会感到来自内心深处的愉悦,这就是“美”的一个侧面。我相信,培养这样的感受能力,是非常有益于身心健康的,并且我也相信那些透露出“美”的代码在某种程度上也是更加优秀的代码(内王而外圣)。

这个世界已经够丑陋了,如果我们已经解决了温饱问题,又有了这么多的自由时间,不如做点“漂亮”点的东西出来,写一些更有用,更“美”的代码出来~