依賴——軟體工程師的痛

Richard_Winters發表於2021-01-21

為什麼各個語言都會有這麼多的依賴問題?

軟體包的分發規模產生了巨大的變化

大部分主流程式語言都誕生於上個世紀,程式碼包的分發範圍在當時僅限於小規模的團體,例如公司內部或者單個軟體專案內部,這種分發規模 只要內部有良好的程式碼約定就不會導致模組依賴衝突,但今天我們已經廣泛運用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這樣的版本衝突導致的依賴缺失問題,而且這些問題大多時候很難被發現,只能等到執行時,例如你在程式碼中使用模組A的高版本才有的python方法,但是你執行時import了一個低版本的模組A,這個低版本的模組A裡面剛好缺失了模組A高版本才有的python方法,而恰好這個程式碼並不是熱點程式碼(在伺服器上老半年不一定會跑到的地方),那就等著這顆雷線上上隨時爆炸吧。

微服務下完全失控的依賴管理

在微服務下,以我個人的觀察,大部分公司的微服務介面,可能只採用了簡單的人肉管理,而且微服務大多都是跨部門維護介面的情況,這個時候如果溝通不暢,更容易出現問題。當然跨服務的RPC呼叫大多都是熱點程式碼,如果出現問題,大多很容易被暴露出來。

  • Java中使用了feign 通過靜態編譯約束大概率上解決了 介面引數的問題, 但如果你的feign模組是依賴方的低版本,而對方在生產釋出流程中部署了高版本的服務,照舊會出現問題,當然這個時候需要強有力的跨部門跨技術組的協調方案,不然依舊會暴露依賴衝突這個問題。

相關文章