做了才知道不要的東西:兩次 Revert 教我的事

ConjuGate 的 git log 裡面有兩個 revert commit。一個把無人機的水晶動畫砍回藍色方塊,一個把石磚道路砍回純色背景。兩個功能加起來花了五個 commit,最後全部退回原點。

為什麼 revert 不是浪費,以及怎麼 revert 才不會痛。

Case 1:水晶無人機

ConjuGate 裡有無人機繞著玩家轉圈射擊。一開始是 16×16 的藍色方塊,能用但醜。我找了一組水晶的 sprite sheet,重構了兩次才做好,而且code調很久:

  • 第一次重構:用水晶 sprite 替換藍色方塊,載入四個方向的動畫 atlas(上下左右各 6 幀)
  • 第二次重構:根據無人機繞圈的位置自動切換面向方向

加完之後跑起來看,水晶 sprite 的風格跟遊戲裡的史萊姆敵人完全不搭。史萊姆是圓潤的 pixel art,水晶是稜角分明的寶石風。兩個放在同一個畫面上,像把油畫跟水彩貼在一起。

我把相關的 crystal drone code 全砍了,退回 16×16 藍色方塊。

// revert 之後的 DroneNode
init() {
    super.init(texture: nil,
               color: SKColor(red: 0.3, green: 0.7, blue: 1, alpha: 0.9),
               size: CGSize(width: 16, height: 16))
}

兩行。原本的 atlas 載入、方向切換、動畫播放,全部不見了。

Case 2:石磚道路

這個投入更大。三次重構,從 tiled road 做到 stone texture、rock edges、side decorations。setupRoad() 從幾行純色矩形膨脹到 52 行,還額外建了一個 RoadTheme struct 做 data-driven 的每關道路主題。

跑起來的問題跟無人機不同。道路本身做得沒問題,但遊戲畫面變得太忙。敵人在上面跑的時候,石磚的紋理和邊緣裝飾分散了注意力。玩家要在閃避敵人的同時讀日文題目,背景太複雜會干擾閱讀。

setupRoad() 從 52 行砍回 15 行,回到純色矩形。

// revert 之後的 setupRoad
let road = SKSpriteNode(
    color: SKColor(red: 0.165, green: 0.149, blue: 0.188, alpha: 1),
    size: CGSize(width: roadW, height: size.height))
road.position = CGPoint(x: roadCenterX, y: size.height / 2)
road.zPosition = -1
worldNode.addChild(road)

但我做了一件跟無人機不同的事:RoadTheme.swift 沒有刪

什麼時候該保留,什麼時候該砍光

這兩個 revert 的處理方式不一樣,原因在於被 revert 的東西性質不同。

水晶無人機的問題是素材不對。Code 本身沒問題(atlas 載入、方向切換都能用),但換了另一組 sprite 之後這些 code 的結構可能完全不同。下次如果找到合適的無人機素材,幀數、方向數、尺寸都可能變。保留舊 code 反而是負擔,因為新素材大概率需要重寫。砍光。

石磚道路的問題是時機不對。RoadTheme 的架構設計是好的:一個 struct 定義每關的路面材質、邊緣、裝飾、顏色。現在不用是因為畫面太忙,但等遊戲的視覺風格更成熟,或者加了背景視差之後,這個架構可以直接拿來用。保留 RoadTheme.swift(45 行),砍掉 GameScene 裡的 tile rendering code。

讓 Revert 不痛的前提

這兩個 revert 都在一天之內完成,沒有掙扎。原因是被 revert 的功能都有清楚的邊界。

水晶無人機的 code 全部在 DroneNode 裡面,WeaponSystem 只有座標計算引用了方向判斷,砍掉之後 WeaponSystem 少了 10 行,其他地方零影響。

石磚道路的 tile rendering 全部在 setupRoad() 裡面。RoadTheme 是獨立檔案。砍 setupRoad() 的內容不影響任何其他 method。

如果當初無人機的動畫邏輯散在 GameScene 的五個不同 method 裡,revert 就會變成在一千行的檔案裡到處找殘留 code。前面那篇拆 Class 的文章講的職責分離,在這裡的好處很具體:邊界清楚的 code,加的時候好加,砍的時候也好砍。

Revert 不是失敗

五個重構的 commit 的工作量歸零,聽起來像浪費時間。但這五個 commit 幫我確認了兩件事:

  • 水晶風格的素材不適合這個遊戲的美術方向
  • 背景紋理在學習類遊戲裡會干擾閱讀

這兩個結論不寫 code 是得不到的。設計稿上看起來合理的東西,跑起來才知道不行。你花五個 commit 證明一條路走不通,比花二十個 commit 硬撐到最後才砍,便宜太多了。

Revert 的成本取決於你多早發現問題、以及你的 code 邊界有多清楚。兩者都在你的控制範圍內。


ConjuGate 是一個用 SpriteKit 寫的 iOS 遊戲,在打怪的過程中學日文動詞變化。這篇是「費曼的實習生」系列,把開發路上踩過的坑記錄下來。