IoC for Golang ?

使用Golang已经好几年了,开发了好多项目,有大有小。当然大的也大不到哪去,毕竟没有一个项目做得长久。之前从Java转到Go,好多思维也跟着过来,其中一个就是IoC。如果在Java没使用过IoC,基本上可以说没使用过Java。

IoC的好处街知巷闻,但在Go的世界里,IoC可有可无,至少我使用的第三方库中,几乎没看到IoC的影子。

不用New?

Java使用IoC的其中一个好处是:如果要更换一个接口实现了,依赖此接口的对象的初始化由IoC解决,不用你去手动修改代码。这里的代码主要是构造函数的参数,或者SetXXX的参数。

背后的魔法是大量的发射以及可能的配置(XML,annotation)。程序启动到初始化完成不会很快。所以Java程序往往给人一直很慢的错觉。

Go较弱的annotation和反射功能,无法达到Java那种IoC的高度。但Go绝大多数情况编译出来是一个可执行的文件。编译器从main.go出发,逐个遍历依赖的代码,最后都打包到可执行程序中。没有被import的代码,最终编译后的文件是不包含这段代码的,这个时候反什么射啊。

Go程序来说,无法避免手动new。即使使用了IoC库,用法也方便不到哪去。但是New其实不繁琐,编译器会通知你需要修改参数。如果你的对象是singleton,其实往往只需要改一处地方。带来的好处是启动速度快得惊人。

Constructor or Property

Java的Spring支持对象的构造函数参数注入和Property(SetXXX)注入。在xml时代,property有个优势:书写顺序随意,显而易懂。到了Annotation时代,就只有劣势了。

我个人比较喜欢构造函数方式,构造函数天然就有定义对象依赖的语义。不用看具体实现,看函数声明就知道这个对象在依赖什么。同时发现问题更快速:参数数量和类型对应不上,在启动阶段就抛异常。

Property就很困扰,因为并不是所有的SetXXX都是required,有些甚至是接口的功能,不是依赖。同时少注入依赖又不会在启动阶段发现问题,直到运行到调用该依赖的方法时才抛出空指针异常,有时带来相当大的灾难。这是多么崩溃的事情,annoation在一定程度上减少这种灾难的频率,前提是你要标注上。

在Go中,我都是NewXXX函数,归编译器的事情就应该推给编译器。只可惜Go没有optional类型,interface可以传nil啊,写if xx == nil是多么累和丑。

.NET HttpClient缺陷感想

InfoQ翻译转载《.NET HttpClient的缺陷和文档错误让开发人员倍感沮丧》,里面提到了HttpClient一些出人意料的行为。

那篇文章中除了最后一个DNS bug,其他问题都可以归结为设计缺陷。HttpClient的功能没问题,能够很好工作,只是使用方式有些特别。

HttpClient的这个名字给人感觉就是这个类实例只负责一个Http请求,Http请求处理完毕就可以丢掉。再加上HttpClient实现了IDispoable接口,更加深了这个印象。好在MSDN文档的Remark部分说明了HttpClient的内部行为,还提供了例子代码。撞坑的人也许以后都提醒自己MSDN的Remark部分不能忽略,而且要仔细读。

HttpClient设计与.NET程序员长期的使用习惯不一致,导致使用方式不对,造成资源泄露。其实我也在Java出名的异步Http库async-http-client 掉坑了。这个库的文档更加少,GitHub的Wiki只有一两个链接,主要的信息都在主页的README.md上。官方提供的例子都是new一个DefaultAsyncHttpClient,然后用它进行HTTP请求。页面和Javadoc文档都没有提及DefaultAsyncHttpClient应该跟HttpClient一样,一个实例重复使用,它相当于ServiceProvider或者Manager的角色。

DefaultAsyncHttpClient内部维护一个线程池用于异步操作,令我惊讶的是这些线程都不是Daemon线程,我的命令行Main函数都退出了,Java进程还在跑,GitHub的这个issue描述了这个行为。

直到撞坑,我才知道DefaultAsyncHttpClient的正确用法,以及程序在退出前,必须显示关闭DefaultAsyncHttpClient实例。