在課程裡,我其實早就學過 Git 的 merge conflict,也練習過怎麼解。
但那時候的衝突,是為了練習而刻意製造出來的。
所以就算畫面跳出 conflict,我也大概知道:
這裡會發生衝突
↓
找到衝突的位置
↓
確認要保留的內容
↓
處理完成
真正到了 PawPal,感覺完全不一樣。
我印象很深的是,有一次 PR 開出去之後,組員在 PR 頁面提醒我:
這邊要解衝突。
看到的當下,我其實知道 merge conflict 是什麼。
但我第一個想到的不是:
指令要怎麼下?
而是:
我要留哪一邊?
如果我解錯了,會不會把組員的程式刪掉?
原本正常的功能會不會被我弄壞?
那也是我第一次真的感受到:
merge conflict 最麻煩的地方,不是 Git 跳出警告,而是它把「最後要留下什麼」這個決定交給了我。
課堂上的 conflict,通常是我們自己刻意做出來的。
例如兩個 branch 都去修改同一段文字,再讓它們合併。
所以看到衝突時,其實心裡已經知道:
好,這就是今天要練習的地方。
但真實專案不是這樣。
PawPal 是多人一起開發,每個人都在不同的 branch 上做自己的功能。
等到大家的程式開始整合時,發生衝突的兩邊,很可能都不是「錯的」。
一邊可能是我正在做的新功能。
另一邊也可能是組員剛完成,而且已經正常運作的功能。
所以不能單純想:
這是我的
所以留我的
也不能看到另一邊來自 dev,就直接全部留下。
真正要確認的是:
兩邊到底各自在做什麼?
哪些內容需要留下?
有沒有可能兩邊其實都需要?
這才是我覺得真實專案裡,解 conflict 最難的地方。
以前看到 conflict,我很容易覺得:
Git 又出問題了。
但後來我才慢慢理解,merge conflict 本身不代表程式壞掉。
可以用一個「簡化概念」理解:
我的 branch 修改了某段程式
↓
Git 發現 dev 也修改了同一個地方
↓
Git 無法自己決定最後要保留哪一種結果
Git 可以告訴我:
這裡有兩份不同的修改。
但它不知道最後的功能應該長什麼樣子。
它不知道要留 A、留 B,還是兩邊其實都需要。
因為 Git 看得到程式碼的差異,卻不知道這些程式背後真正負責的是什麼功能。
所以:
Git 可以幫我找到衝突,但不能替我決定正確答案。
第一次真的遇到這種情況時,我不太敢直接亂改。
因為這次不是自己做練習。
PR 裡除了我的修改,也牽涉到其他組員已經放進 dev 的內容。
所以我當時的做法很直接:
把 PR 頁面的網址複製給 AI。
請它先幫我看問題可能出在哪裡、為什麼會發生 conflict,以及兩邊大概修改了什麼。
AI 可以先幫我整理狀況,也可以提供處理建議。
但最後哪些程式要留下,我還是會自己再看過一次。
因為如果我連兩邊的程式在做什麼都不知道,就直接照著建議把其中一邊刪掉:
就算 conflict 消失了,也不代表功能是對的。
這也是我後來越來越習慣的方式。
AI 可以先幫我理解問題。
但最後做決定的人,還是我自己。
後來回頭看 PawPal 的 Git 紀錄,我才發現,開發期間其實真的留下了不少 conflict 的紀錄。
其中有一次,是我在做「新增寵物基本資料」功能的時候。
當時我的功能分支需要和最新的 dev 同步。
同步的過程中,管理寵物資料的 petStore.js 發生了 conflict。
簡單來說,就是:
我正在修改寵物資料功能,而 dev 裡也出現了新的寵物資料處理。
真正去看內容後,會發現兩邊都有需要留下來的東西。
我這邊有新增、選取寵物等功能。
dev 裡也已經加入其他頁面會使用到的寵物資料處理。
這時候我才更明顯感受到,conflict 並不是兩份程式裡一定有一份是錯的。
我的修改是為了完成目前正在做的功能,但 dev 裡的新內容也可能已經被其他功能使用。
如果只是因為其中一邊是自己比較熟悉的程式,就直接把另一邊蓋掉,表面上看起來 conflict 是解決了,實際上卻可能讓原本正常的功能一起消失。
所以如果直接:
只留我的
可能會把 dev 原本新增的功能蓋掉。
反過來如果:
只留 dev
也可能讓我正在做的功能消失。
真正要做的是:
先理解兩邊
↓
找出各自負責的功能
↓
再把需要的內容整合起來
這個案例不是我現在能確認的「第一次 conflict」。
因為我已經不記得第一次真正發生衝突的是哪一張 PR。
但從 Git 紀錄可以確認,這確實是我在 PawPal 開發期間處理過的一次合併衝突。
而這種經驗也讓我開始真正理解:
解 conflict 不是選「我要哪一邊」,而是判斷「最後正確的程式應該包含什麼」。
處理 conflict 時,編輯器通常會看到:
Accept Current
Accept Incoming
Accept Both
以前看到這些選項,很容易覺得:
所以我選一個就好了吧?
但現在我比較不會急著按。
比起先記哪一顆按鈕要選,我會先看:
兩邊到底改了什麼?
所以如果現在再遇到 conflict,我的處理順序大概會是:
確認自己的 branch 狀態
↓
確認 dev 的最新狀態
↓
看 diff
↓
理解兩邊修改
↓
決定怎麼整合
↓
處理 conflict
↓
重新驗證
至於自己的 branch 要怎麼和 dev 同步,有時候可能會用 merge,也可能會用 rebase。
這些在課程裡都有學過,我在 PawPal 裡也實際使用過。
但現在我比較不會只記:
branch 落後 dev,就一定要用某一個方法。
而是先確認目前的狀態,再決定怎麼處理。
這也是我後來很在意的一件事。
把 conflict 解掉之後,我不會只看到 Git 顯示可以繼續,就覺得完成了。
我還會做兩件事:
重新跑網站
+
再看一次 diff
重新跑網站,是確認功能有沒有被我解壞。
再看一次 diff,則是確認:
我剛才實際改了哪些東西?
有沒有不小心刪掉原本需要的程式?
因為:
Conflict Resolved
和:
功能正常
其實是兩件不同的事。
Git 可以知道衝突已經被處理。
但它不知道按鈕還能不能按、資料還能不能正常顯示,也不知道隊友原本完成的功能是不是還存在。
這些都還是要自己重新確認。
回頭看,第一次真的遇到 merge conflict 時,我怕的其實不是 Git 指令。
我真正怕的是:
自己的判斷會影響到隊友已經完成的東西。
但也是因為真的遇過幾次,我後來比較不會看到 conflict 就緊張。
現在我會把它理解成:
Git 發現兩邊都有修改,但不知道我們真正想留下什麼。
所以真正重要的不是趕快把 conflict 消掉。
而是:
理解差異
↓
做出整合
↓
確認結果
我後來才明白:
merge conflict 真正考驗的,不是會不會按下某一個按鈕,而是能不能看懂彼此的修改,再安全地把它們放在一起。
這也是我在 PawPal 第一次真正感受到「團隊開發」和自己寫程式最大的不同。
當分支、PR、Code Review 和 conflict 都慢慢處理完,專案也開始越來越接近真正上線。
但我後來才發現:
程式在自己的電腦可以跑,不代表放到正式環境也會一樣。
下一篇,我想聊我在部署前開始遇到的另一個問題:環境變數與不同環境的設定。
下一篇:
Day 23|部署前的最後一哩路:環境變數與設定檔。