程序員構建老是出問題,怎麼辦?

​構建這一問題,究竟是哪一個環節出了 Bug?程序員

我老是聽到程序員談論構建的問題:「構建出錯了」,「我把構建搞壞了」等等。然而,真正的問題在於構建這個概念自己就有問題。每次修改應用程序都須要從頭從新構建的想法從根本上就有缺陷。編程

這個概念的實際問題在於,構建會致使開發過程當中的反饋循環漫長而痛苦。有些系統的應對手段是經過極快的速度,他們的觀點是,哪怕你每次進行修改都編譯整個系統,也不是問題,由於在你反應過來以前構建就會結束。緩存

問題在於,系統的規模會一每天擴大,終有一天這個法子再也行不通。markdown

更深層次的問題是,你會發現每次修改代碼後,即便當即從新構建,仍是須要從新啓動應用程序,並且每當你發現一個問題、修改代碼、通過從新編譯後,問題仍是沒有消失。換句話說,構建與實時編程是對立的。構建的反饋循環永遠都那麼漫長。機器學習

從根本上講,每次修改代碼的時候,不必從新編譯整個系統。就好像你想換個燈泡也不必把整座摩天大樓都拆了重建吧。編程語言

不管怎麼優化,構建都沒法真正成爲實時。函數

所以,諸如make之類的工具永遠也沒法解決問題。並且這些工具自己還有許多其餘問題。例如,咱們必須複製代碼中嵌入的依賴項信息:import、include、use或 extern 聲明之類的語句爲咱們提供的文件和模塊信息與咱們手動輸入並沒有二樣。這些複製工做乏味且容易出錯。並且這種複製太粗糙,粒度僅限於文件。編譯器能夠更精確地管理這些依賴關係,例如跟蹤何處使用了哪些函數。工具

注意:有些工具(如GN)能夠由編譯器提供依賴關係。雖然仍然很粗糙。oop

另外,這些工具提供的語言所具有的抽象機制和工具支持都不好。通常你們對make的不盡是其引入了其餘相似性質的工具層,例如Cmake。性能

一個更好的解決方案是,爲構建生成更好的DSL。基於某種真正的編程語言的內部DSL是改善問題的一種方法。例如 rake 和 scons,分別使用了 Ruby 和 Python。這些工具簡化了構建工做,但它們仍與構建有着千絲萬縷的聯繫,這纔是我最關心的根本問題。

話雖如此,若是咱們不打算使用傳統的構建系統來管理咱們的依賴項,那麼應該怎麼辦?

首先,咱們須要認識到咱們的許多依賴關係不是基礎的東西,例如可執行文件、共享庫、目標文件和任意類型的二進制文件等。咱們真正須要「構建」的只有源代碼。畢竟,若是使用解釋器,那麼你只需建立最基本的源代碼,而後逐步編輯和發展源代碼。

使用解釋器能夠避免構建二進制文件的問題。

成本是性能。編譯是一種優化,儘管是很重要的優化,一般是必不可少的。編譯須要比解釋器更全面的分析,並且還會執行預先計算,以免咱們在執行過程當中重複工做。從某種意義上說,編譯器的做用是記住解釋器的部分工做。

許多動態JIT正是實現了這一點,但從根本上講,靜態編譯也是如此——你只需提早記住便可。

從這個角度看,構建是分階段執行的一種形式,而咱們不斷構建的二進制文件只是緩存。

咱們能夠經過結合解釋與編譯,解決解釋器的性能難題。許多使用JIT編譯器的系統正是這樣作的。其優勢之一在於,咱們沒必要在啓動應用程序以前等待優化。還有咱們能夠修改代碼,並經過從新解釋和從新優化當即反應修改的結果。

然而,並不是全部的JIT都會這樣作,可是這種作法已經延續了數十年,例如Smalltalk VM等。Smalltalk 有不少優勢,其中之一即是你不多會遇到構建的麻煩。

然而,即便假設你的JIT引擎在發展的過程當中對代碼進行了增量優化,你與實時開發之間仍有障礙,這個障礙就是你仍然須要構建。

  • 類型。若是你的代碼因爲類型錯誤而出現問題,該怎麼辦?再次重申,咱們不須要經過構建來檢測到這一點。增量式類型檢查器可以在保存代碼時發現問題。固然,以往咱們不多使用增量式類型檢查器。實時系統的開發從來採用動態類型語言並不是巧合。可是,沒有根本性的緣由可以說明爲何咱們不能使用靜態類語言支持增量開發。這些技術能夠追溯到Cecil。有一篇有關Scala.js的文章https://2016.splashcon.org/de...
  • 測試。不少時候,構建過程包含測試,而構建失敗也是因爲測試發現了應用程序中的邏輯錯誤。可是,測試自己並非構建的一部分,也沒必要依賴於構建,畢竟構建只是更新應用程序的一種方法。在實時系統中,源代碼會當即反映到更新後的應用程序中。在這種環境中,測試能夠針對每次更新展開,可是開發人員無需等待測試完成。
  • 資源。應用程序能夠結合各類資源:媒體、文檔、各類數據(源文件或二進制文件、表或機器學習模型等)。其中一些資源可能須要本身計算,例如利用TeX或markdown之類的文檔生成PDF或HTML,並且不多包含實時或增量的階段。

即使咱們的資源可供隨時使用,過分依賴文件系統結構也會引起問題。資源一般會以文件的形式呈現。部署的結構可能與源代碼庫不一樣。在源代碼庫中編輯組件不會改變構建結構。

糾正這些問題並不是易事,一般軟件工程師甚至都不肯意嘗試。相反,他們會愈來愈依賴構建過程。這真的沒有必要。

咱們能夠將資源視爲緩存的對象,並根據須要生成它們。部署應用程序時,咱們須要確保全部資源都通過了預先計算,並且緩存在應用程序的固定位置上,若是在開發過程當中這個緩存丟失,那麼應用程序還能將它們放回同一個位置。軟件知道這些位置,所以它可以找出緩存在應用程序固定位置上的資源。

當經過應用程序邏輯訪問資源時,上述推理過程就很合理。那麼那些沒有被應用程序使用,而是提供給用戶使用的資源呢?在有些狀況下,文檔、示例代碼以及附帶的資源都屬於這種狀況。這類資源的處理不是應用程序自己的一部分,所以,它不是構建問題,而是部署問題。也就是說,部署只是將合適的對象放到既定的位置上。

  • 處理多種語言。在處理多種語言時,因爲某些語言不支持增量開發,因此咱們可能會被迫使用構建系統。假設應用程序的核心是用實時語言編寫的,那麼咱們應該將其餘語言視爲資源;它們的二進制文件是開發過程當中動態計算並緩存的資源。

總結

  • 構建和實時是針鋒相對的。
  • 編譯結果是緩存資源的一種形式,它是分階段執行的結果。
  • 爲了在工業環境中實現實時,咱們須要經過構建開發環境,確保每一個階段都通過了嚴格的優化。
  • 當底層的緩存值過時時,應自動緩存分段結果並沒有效化舊的緩存值。
  • 不管分階段保存的值是資源、共享庫/二進制文件仍是其餘內容,這種方法通通適用。
  • 對於計算緩存值和肯定緩存有效性來講,必要的數據必須保存在應用程序的固定位置。

讓咱們建設一個全新、勇敢、沒有構建的世界。

相關文章
相關標籤/搜索