程式設計師是怎麼存檔並管理檔案版本的?

炭燒生蠔發表於2021-07-06

大家好哇~ 歡迎來到波波和阿菌神奇的“科普”頻道!

今天,我們為大家介紹程式設計師是如何怎麼存檔並管理檔案版本的。

大家要做好心理準備,今天的“科普”稍有點點硬核,阿菌想從需求分析,產品設計,程式碼實現等全方位角度為大家“科普”,綜合的東西較多,可能不太好看懂......

但內容應該還是有點點意思的,畢竟阿菌總是寫一些亂七八糟的東西,如果暫時感覺難以消化可以考慮先收藏吖~

在開始之前,我們先為大家介紹一個概念,叫:版本管理。

我們先從日常生活講起。

阿菌這壞小子有時會想回到自己的過去,比如說回到小學時候的自己,回到初中時的自己,回到高中時的自己,或者回到大學前的自己,重讀一次大學等等......

人生的每個階段,我們都可以看成自己的一個版本,比如說小學版的阿菌,初中版的阿菌,高中版的阿菌,大學版的阿菌......

要是老天真的給我們每個階段都存了檔,那我們就可以回到過去的版本,重新開始了!

從舊版本發展出新的人生,或許我們的人生可以擁有好幾條分支路線呢......

雖然目前看來不太現實,就算真的有得選,阿菌也絕不會回到過去的版本,因為阿菌不敢保證在另一條人生分支上,還能遇到波姐......

咳咳,打住!

雖然人生沒有存檔,但是電腦上的檔案可以存檔!

大家考慮以下場景:假設學院安排我們做一次畢業晚會宣傳活動,既要有 PPT,又要有文稿,還得有海報。

於是我們高高興興地把全學院的本科生資料編寫到了 PTT,文稿和海報裡。

這個時候,學院說:怎麼可以只有本科生的資料?研究生的相關資料也要加進去!

然後我們在現有的 PPT,文稿和海報裡,加入了研究生的資料。

接著學院說:臨時通知,研究生的晚會另外舉行!

大家可能就傻眼了:我們已經在 PPT,文稿和海報里加了研究生的資訊,而且已經和本科生的內容融合到了一起,這刪起來也太麻煩了吧!!!

要是我們提前把本科生的策劃資料儲存為一個版本就好了,這樣就能直接把本科生的版本交給學院,完成工作。

現在場景有了,痛點有了,接下來我們著手設計一款軟體產品來解決這個問題(有同學會說,阿菌你扯淡,我每個版本複製一份就行啦,搞個軟體出來幹嘛?呃呃,堅持看完就懂啦,它不只存檔這麼簡單哦。我們手動拷貝存檔容易出現各種各樣的問題,比如忘了存,忘了存在哪,存的順序搞亂了等等......試過就會有體會哦)。

現在我們有一個資料夾,資料夾目錄下有 PPT,文稿和海報,要不我們就在這裡建立多一個資料夾用於存檔吧!把名字起名為“.jun”就好啦!

現在我們建立好了一個“.jun”資料夾用於存放當前目錄的版本資訊,接下來我們要思考的是,該如何儲存各個檔案的版本?

在計算機領域,我們來到的環節應該叫設計底層資料結構,我們可以把“.jun”資料夾看成一個資料庫,這個資料庫會用來儲存當前資料夾下檔案的版本資料。

嗯,不如這樣吧!

我們就把這些.doc、.psd、.ppt檔案稱為 object 吧!(反正也不知道叫什麼好)我們在“.jun”資料夾下建立一個“objects”檔案用於存放各個 object 的資訊,這樣,我們就有了一個 object 資料庫了!

呃,聽起來好像很厲害,其實阿菌只是建立了兩個資料夾......

現在我們想想一個 object 該存些什麼東西比較好,究竟什麼東西才能精準定位一個檔案的版本資訊呢?

想來想去,不如這樣吧,我們一個 object 至少得包含三個資訊:

  1. 檔案的原本資訊,我們PPT,WORD文件中的內容就叫原本資訊,直接儲存原本資訊可能需要很大的空間(至少和這些檔案本身一樣大),我們可以先壓縮,再儲存。

  2. object 的型別,考慮到我們當前資料夾下除了有 PPT,文稿和海報之外,以後還可能放新的檔案或者新的資料夾,檔案和資料夾都應該叫 object,只不過可以用不同的型別區分他們。(大家可以在這留個心眼,這是這款版本控制管理軟體的精華部分,後面就知道啦)

  3. 一串字元數字,我們起個專業點的名字叫雜湊值,用於標識當前的 object,每個 object 都有獨一無二的雜湊值(其實就一串亂七八糟的數字字元,這樣不容易重複)。

看到這裡,大家可能會有疑惑:你們為啥一直在說怎麼設計,我們更想知道的是,為什麼這麼設計?

下面揭示謎底:

大家先看第一張圖,當前資料夾下的 PPT,文稿和海報,我們可以分別用三個 object 表示:

由於他們放在同一個目錄下,於是我們可以用一個大的 object 來標記他們,我們把這個大 object 的型別定義為 tree(樹幹的感覺),這個 tree object 對等的就不是一個個檔案了,而是一個資料夾:

細心的讀者朋友會發現:咦?這個 tree object 貌似已經包含了當前資料夾下的所有檔案資訊,也就是說,這個 tree object 貌似已經可以記錄當前資料夾的版本資訊了!

我們可以通過這個 tree object 找到PPT,文稿和海報對應的 object 們,這些 object 儲存有PPT,文稿和海報某個時間點的原資訊,我們只要把這些資訊解壓出來,就能把資料夾恢復到曾經存檔時候的樣子了。

接下來的問題是:我們不會只保留一份存檔,我們會儲存很多份存檔,如何才能把一系列存檔組織起來呢?

接下來我們引入一個新的 object 型別,我們叫起名叫 commit 型別好了,就是一個提交的意思。想要把存檔串聯起來,我們得加上一項引數,指明上一個 commit object 是誰,或許我們還可以加上時間等資訊,這樣一來,每一次提交就是一個版本:

上面這個圖由於位置不夠,畫得不夠直觀,我們再畫一個圖,大家明白每個 commit object 都指向一個 tree object 就行了。也就是說:commit object 只是 tree object 的一層封裝,雖然還原出一個資料夾過去的存檔我們只需要 tree object,但封裝成了 commit object 後,通過“father”這個屬性,就能把一系列的存檔連起來,而且還能蓋上時間戳,這樣整個存檔記錄就很清晰了。

講到這裡不知道大家會不會冒出一些奇怪的想法,假設學院佈下了兩個任務:我們本科生,既要和研究生搞一次畢業聯誼,又要和博士生搞一次畢業聯誼,我們這套版本管理系統還能用上嗎?(也只有阿菌才能想出這樣奇奇怪怪的活動)

當然可以啦,請看下圖:

我們可以建立兩個 commit object(兩個新的存檔),分別指向最開始儲存了本科生資料的存檔,然後我們就能分別在兩個新的存檔上幹活啦,而且兩個存檔互不影響,可以繼續在兩個存檔之後建立新的存檔,就像下面這樣:

我們可以給上面的功能起個好聽的名字,叫“分支”。

分支的功能還可以這樣用,我們考慮下面這種場景:

學院不再搞花樣了,只要做好本科生的畢業活動策劃就行。但是學院規定的時間很短,阿菌一個人做不完,他找來了他的同學阿叉和阿勾一起做,三個人分別負責PPT,文稿和海報。

為了不影響最開始的版本,他們三個每人拉出來一條分支進行工作,每個工作階段的內容照樣進行版本管理:

等各自的工作完成後,合併出最新的版本:

這樣一來,大家協同工作起來就方便多啦。

可能有同學會問:阿菌,你設計的 commit object 不是有一個父指標麼?每個父指標指向上一個 commit object,在上面的圖裡,最終版的父指標指向誰呀?貌似一共有三個父節點?

呃,這個確實是阿菌疏忽了,圖沒有畫好......

是這樣的,有時候我們資料結構設計好了,最好就不要改了,遇到現有資料結構不能掌控的場景,我們就要設計處理流程,這就是所謂的“演算法”吧......(畢竟增刪改查某種程度也算是演算法......)

我們總是說:軟體 = 資料結構 + 演算法,下面阿菌帶大家還原這個處理流程的設計。

因為我們的 commit object 只能指向一個父節點,於是我們設計的合併流程是這樣子的:最終的合併,交給一個人處理(站在一條分支上處理,假設叫它主分支)。

從上圖可以看到,我們以阿叉製作PPT的分支作為主分支,把阿勾的分支內容合併到了阿叉的分支上,這樣阿叉的分支就多出來一個合併節點,這個合併節點指向的是阿叉分支上的前一個節點,這樣一來,PPT 和文稿內容就合併到主分支上了。

滴滴滴~ 下面我們把阿菌那壞小子製作的海報也合併上去:

這樣一來,我們既沒有破壞原來的資料結構,也沒有破壞軟體的設計:一個存檔版本管理軟體,我們可以在阿叉這條主分支上找到所有的內容。

當然阿菌這樣的設計不太好,我們其實可以設計得更好,比如說單獨抽出來一條主分支,阿叉,阿勾,阿菌製作各自內容的時候單獨拉分支(一共四條分支),每個人都製作好了,再合併回主分支。這樣主分支就會非常乾淨利落,而不是像現在這樣,在主分支上,還能看到阿叉的各個版本......

呃呃,軟體開發就是這樣的啦,在摸索中不斷總結最佳實踐!

估計大家看著看著就能看出來啦:阿菌,你這講的不是 Git 麼?

沒錯,阿菌今天介紹的就是一款叫 Git 的分散式版本控制軟體-版本控制部分的底層設計原理,現在大多數程式設計師都是基於 Git 進行協同開發的。和上面例子不同的是,程式設計師寫的是程式碼檔案,而不是文稿和PPT。有時候一個功能往往會有好幾個程式設計師開發,大家可以理解為分組開發。常見主流的協作流程會是這樣的:

簡單介紹一下:

一、master 分支儲存了正式釋出的歷史版本,是一個功能完整且隨時可以釋出到線上進行部署的可用分支。

二、Hotfix 分支是用來修復線上bug,快速打補丁的。

三、Release 是一個釋出分支。

四、Develop 一般作為功能的整合分支。

五、 Feature 分支則是功能分支。

至於每個分支具體的用法,大家可以到網上搜尋,根據阿菌的經驗,其實看了也沒啥用。只有真正到公司裡參與到開發專案中,才能真正領會到各個分支的意義。

今天介紹的內容,是 Git 這款軟體最基本的原理,在搞清楚了這個的基礎上使用 Git,會輕鬆很多。Git 的另一個重要的特色是:分散式。也就是說,它是用於多人(公司或團隊)協同進行存檔和版本控制的。

有位大神看了我們的文章後認為如果我們能講講分散式,那這篇文章會更加加分,那肯定沒問題。

我們現代的程式設計師,寫程式碼的時候上來就是用 Git,理所應當以為版本管理系統都是現在分散式的樣子。殊不知,以前的版本管理系統都是集中式的。

我們先簡單介紹一下什麼叫集中式,還是用本文的案例:

像上圖這樣,有一個集中的地方管理所有檔案,每個人開發只要拉取特定檔案進行開發,這叫集中式開發。

集中式開發的弊端大致有以下兩點:

效率。中央倉庫出了問題,所有人都無法正常工作了,因為大家都依賴他拉取和推送檔案。
穩定性。中央倉庫掛了,存檔就沒了。

針對以上弊端,我們能想到的,最直接的處理方式是:每個人都儲存一份倉庫的拷貝。如下圖所示:

有同學會問,欸,阿菌,那我還有必要學 SVN 那些集中式版本管理軟體麼?

當然不用,Git 的出現,已經完全顛覆了過去的集中式版本管理系統。SVN 的版本管理策略和 Git 還是有很大區別的,阿菌沒有說 SVN 不好,大家要知道,這裡涉及的原因很多。這是時代變化所引起的變革,以及大環境所需導致的變遷,不存在孰好孰壞的問題。

我們嘗試開闢一個角度想(不一定對):在以前的年代,記憶體,磁碟,計算資源都是很寶貴的,當時的機器根本就不適宜支援我們在每臺機器上,儲存完整的工程檔案存檔,也不可能採用 Git 這種壓縮儲存整個檔案原資訊的策略(增量儲存能省很多空間,代價是犧牲效能)。所以,使用 SVN 這樣集中式的版本管理系統,或許是個很好的選擇。

現在機器越來越好了,磁碟大,網路快,直接就能在每個人的機器上儲存完整副本。更重要的是,Git 本身的設計非常優良,它站在 Linux 作業系統肩上發家(最初是因為想分散式開發 Linux 而創造的 Git),後來還發展出了 Github 這樣的開源社群。慢慢地,大家都願意遷移到 Git 上開發了。

再換一個角度:大家想想,如果一個工程真的非常非常龐大,單臺機器不能拉取整個工程進行開發,集中式的版本控制無疑是更好的選擇。

但是現在業界流行微服務,系統的拆分解耦是大勢所趨,這也註定了大工程會被拆分成小工程。而體量小的工程,恰好非常適合使用分散式版本控制。

存在即合理,任何一項技術,我們在評價它時,都不能脫離時代背景和現實需求。

我們這篇文章不教操作,關於 Git 操作的文章,網上一抓一大把,各種奇技淫巧,應有盡有。

各位如果想自己玩出奇技淫巧,那就跟著阿菌一起深入資料結構探索原理吧,那些只教奇技淫巧的部落格,通常都不怎麼說原理,懂了原理才能更好地發掘奇技淫巧吖!

有同學可能會問:阿菌,原理懂了,但是不太能和操作對應起來,我們平時用 Git,就是一條 pull 和一條 push,兩條指令走天下。分支我是懂了,但這些分散式操作我還不太懂,不太能聯絡起來。。。

阿菌實習時的導師,曾教給阿菌一個非常重要的學習方法:當我們看到一個沒接觸過的東西的時候,我們首先要想 —— 如果這個東西交給你來做,你會怎麼實現?

阿菌看過一點點 Git 的原始碼,也翻過官方文件,但是不可能在這裡把這些東西全倒出來,我們嘗試引導大家一點點思考。(最終的深入精確學習,還是建議大家看原始碼文件,想要深入學一個東西,這苦是不得不吃的)

我們沿用這個圖進行講解,在看之前,大家務必先搞清楚 object 的底層資料結構,以及怎樣把 object 串聯起來形成分支。我們先回顧一下當時的總覽圖:

之前我們提到以阿叉作為主分支,是一個不太好的設計,但是不影響我們講解,我們假設阿叉已經開發完了,遠端倉庫就是阿叉的倉庫。

現在阿勾已經開發完了,阿勾會把他的本地倉庫推送到遠端倉庫,阿勾的倉庫是這樣子的:

阿菌能猜到一些小夥伴看到這懵逼了,千萬不能懵逼,一定要全文串聯回憶,我們的版本管理軟體,就是一條連結串列加樹,這裡的每個圈圈都是一個 commit object,他們是對應的,阿菌只是用一個小圈圈代替,以節省位置:

明白了這個之後,我們來看一下合併的流程,首先阿勾會把自己的本地倉庫推送到遠端倉庫(主分支):

現在,大家明白,為什麼合併分支會多出來一個提交了吧?

還有一些常用的指令,什麼 rebase 吖,不就是調整連結串列麼?不懂的話可以去刷刷演算法題,打打基礎。

不過,多人一起辦公難免會遇到一些問題,比如說大家同時修改了一個檔案,發生了衝突。但懂了原理後這都很好解決,大家溝通一下用誰修改的版本就好了,協商出一個不衝突的版本,然後合併就行。合併的原理上文已經提到啦,一通百通。

也正由於是多人協同辦公,註定我們的軟體要具備聯網的能力,涉及許多網路互動,這是分散式軟體必不可少滴內容。但大家要相信,最底層原理懂了,具體如何使用,如何與他人在網路中合作,那都是很簡單的內容,學一個軟體關鍵是要學透它的底層資料結構,學明白後,上層操作都會引刃而解的。

前兩天我們釋出了一篇文章,能幫助大家進一步深入學習並掌握 Git 的底層原理:Git原理學習捷徑:實現一個簡易的Git

大家可以抽一個上午的時間跟著阿菌簡單操作一下,畢竟 Talk is easy,想學會點東西,還是得動手實踐。

阿菌是用 Go 語言寫的,寫得比較爛,有興趣的同學用自己熟悉的語言寫就好啦,畢竟寫程式碼只是實現想法,擼起袖子加油幹就好~

想了一下,下一篇技術類的科普或許可以給大家介紹一下爬蟲,因為爬蟲這個技能點容易延申到單機,叢集,分散式這些聽起來高大上的東西,我們會盡最大的努力帶有興趣的小夥伴入個門兒,有趣的理論加實踐,學起來可帶勁兒啦!

相關文章