go语言是一门开源的语言,我这里说开源,并不是指go的编译器等是开源,而是指go在机制上决定了当我们引入一个类库的时候,实质上是引入类库的源码。

纯go实现的类库、模块,基本是无法以编译后二进制的形式提供给第三方调用;要提供,就需要以源码的形式直接提供,与第三方程序一起编译。

这样的开源限制,确保了模块使用者the right to tinker,即所谓捣腾的权力。模块在使用的过程中遇到小问题,使用者是随时可以直接去调整模块的源码,不会因为“没有源码”而连简单的问题也无法自己解决。

Java的类库分发机制则非常不同,一般上,Java的类库是以二进制bytecode的Jar包的进行分发。我们的程序在引入Java类库的时候,编译器并不会将类库的源码与我们的程序一起编译。

显然,go的程序相对于Java需要做更多的编译,但因为go语言在设计之初便把编译速度作为设计目标之一来对待,一般go语言的程序编译速度都是十分之快的。

包分发机制的不同,对我们的开发范式显然也是会有影响的。

对于一般go语言程序来说,重新编译就像是吃饭喝水,普通得不能再普通;因此,“不用重新编译”对于go来说,并不是什么需要在乎的事情。

go程序使用了第三方模块,接口要替换实现,那么就修改一下代码,重新编译一下发布版本,这对于go来说,是非常常见同时也是非常流畅的流程。

相对于与Spring通过修改配置来替换实现,所不同的go语言的代码修改是强类型,有编译器来确保其正确性,而xml的配置修改,则更多要依赖于人肉或者是IDE来确保正确性。

代码也好,配置文件也好,都是文本;而我们的程序会依赖于这些“文本”的正确性来确保运行。

我并不会认为“只需要修改配置”对于开发者来说是一种优势;但它对于通过使用二进制分发的模块使用者来说,会是一种优势。

复杂度是守恒的,若是一个模块提供了切换接口实现的能力,这种能力要么通过配置来暴露,要么通过调用代码指定来暴露。

  • 通过配置来切换的话,那么程序无需重新编译,然后程序会在运行时检测正确性。
  • 通过代码指定切换的话,那么程序要重新编译,而编译器会在编译时检测正确性。

Java基于其虚拟机特性、反射等实现的模块可以非常之动态,它甚至可以做到程序在运行的时候,不重启来切换配置,在线上直接切换模块实现。

且不管我们在实际业务中是否存在这样需要“热切换”的场景,这至少是一种能力

那么,go这样动不动就需要重新编译的程序是否就没有热切换的可能呢?

其实也未必,如之前所述,依赖注入是使用于高度复杂、多团队开发的软件业务,而当我们需要处理的是如果庞大的复杂业务时,我们的程序也一般不可能会是一个monolitch单体应用,而会是走为服务的架构。

那么,在微服务架构里面,假设服务A依赖于服务B,B服务器切换了实现,重新上线了B2实例,然后,我们切换服务器注册,将原先B服务的流量都转向了B2。

这肯定也是属于“通过修改配置来切换实现”吧?

现在的微服务架构,也经常支持灰度切,AB测试切,流量复制切,灾被自动切等等。

因此,在服务器端领域“热切换”现在是可以不同的实现方式的;未必需要依赖于单一应用内依赖注入框架的能力。

在安卓上,同样是Java,同样是依赖注入,我们可以看到主流的依赖注入框架就不是Spring那样基于反射,而是基于代码生成无反射的Dagger。

因为客户端资源少,不希望承担反射的额外损耗;而且,客户端程序希望修改行为的时候,显然也是需要重新编译、用户更新安装的。

还是那句话,复杂度是守恒的,同样的问题,或者说需求会是以不同的形式出现;而随技术的演变,可以有不同的解决方式。理解各种不同解决方式出现的背景、适用场景、局限等等细节,才能够更好的应用技术。