当前位置 博文首页 > Richard_Winters:依赖——软件工程师的痛

    Richard_Winters:依赖——软件工程师的痛

    作者:Richard_Winters 时间:2021-01-21 22:04

    为什么各个语言都会有这么多的依赖问题?

    软件包的分发规模产生了巨大的变化

    大部分主流编程语言都诞生于上个世纪,代码包的分发范围在当时仅限于小规模的团体,
    例如公司内部或者单个软件项目内部,这种分发规模 只要内部有良好的代码约定就不会导致模块依赖冲突,
    但今天我们已经广泛运用github社区来分发软件代码包,分发的规模已经跨越国界跟种族以及不同的语言,这在当时是无法想象的。

    • 例如现如今依然非常流行的C语言,就存在经典的函数命名冲突的问题,在小规模团体内部可以通过约定函数命名前缀来避免编译时的冲突。
      C++在后续引入了命名空间解决了这一问题

    • C++编译器版本众多,又有很多公司或者团体采用二进制闭源发布代码,又导致了二进制ABI依赖问题, 在今天你依旧可以看到golang swift
      这些新兴的编译型的语言都是依赖源码编译的,因为它们大多各自语言的各个版本之间的并不是二进制兼容的,例如X86 就有 fastcall stdcall 等等
      函数调用约定,之前我翻了一下golang的内部实现,发现go语言在内部又搞了自己一套汇编调用约定,在go使用标准的C函数库的时候还需要做一些转换。

    动态语言在运行时链接函数

    动态语言天生就有的毛病,所有的调用都是在运行时才能确认被调用的模块的位置以及代码,
    因为在动态语言中,并不像C/C++那样在编译期有大量的静态检查,当然你要是喜欢用C/C++的void * ,上帝也没法拯救你。

    • Java通过虚拟机字节码兼容性约定以及命名空间,在字节码层面使用操作数栈屏蔽了编译型语言的二进制调用约定问题, 但其动态链接,
      依旧没有避免运行时依赖缺失的问题,例如包A依赖了包B的v1版本,但是在项目中引入的包C的依赖了包B的v2版本,在maven的仲裁机制下,编译后只导入了
      包B的v1版本,当程序跑进包C里面的代码就会因为包B的v1版本缺失了一些v2版本的特性而报错,而且这些因为maven仲裁机制导致的依赖缺失的问题并不会在编译期被发现,
      大多只能等到线上运行的时候才会被发现,如果包C的代码并不是热点代码,大部分时候程序并不会跑进包C,很有可能你会在深夜因为报错日志而被领导催促起床来解决版本依赖问题。
      解决这个问题的办法只能依靠版本语义管理,在每次发布前都要检测maven版本仲裁是否存在版本号不兼容的情况。例如 aa.bb.cc 这种版本号,cc代表bugfix的版本,
      大多时候出现这种依赖冲突,人工仲裁选择使用高版本即可,而bb代表大的改动,可能存在接口不兼容的情况,这个时候就要对依赖进行代码检查,确保Java动态链接调用没有问题才能上线使用

    • 在Python里面同样存在类似Java这样的版本冲突导致的依赖缺失问题,而且这些问题大多时候很难被发现,只能等到运行时,例如你import了一个低版本的模块A,里面缺失了模块A更高版本才有的Python方法,
      而恰好这个代码并不是热点代码,那就等着这颗雷在线上随时爆炸吧。