網頁

2021/5/6

2021 老舊專案程式碼更新到新專案步驟 migrate legacy old function code base to new project

從去年10月開始至今約半年都在進行搬移老舊程式碼到新的專案工作,記錄心得及注意事項。

舊專案是一大包由早期內部發人員和多個外包公司開發的單一的Java網路應用程式大雜燴,裡面包含了多個系統,如後台管理系統、供應商系統、客服系統、API、各類排程、及其他未知的小系統。此外也參雜了各種老舊框架與技術,光存取資料庫的框架就三、四種,沒有依賴套件管理,MVC有Servlet、struts及Spring,JSP頁面有一堆Java code,手動上版。因此需要把上面一大包切分開來為各自獨立的專案,凝聚核心業務邏輯,微服務化並替換成新的框架技術。

新專案的系統及程式架構皆由技術組長架構好,所以PG的工作重點在搬運舊程式的業務邏輯到新專案並以新的框架來撰寫及重構。

搬移舊程式到新專案的步驟分為以下:

  1. 確認功能:確認要搬移的功能為何,進入點、程式執行的資料來源及執行後的產出結果。
  2. 盤點程式:盤點要搬移的功能邏輯散落在哪些類別及方法,頁面、資料表、預存程式、檔案、API。
  3. 估算工時:根據盤點的結果估算搬移時間(不含測試)。
  4. 複製貼上:複製貼上要搬移的舊程式到新專案。
  5. 重構程式:修改舊程式的命名、寫法及架構,提高可讀性、維護性、重用性。
  6. 單元測試:撰寫單元測試確保搬移後的每個程式單元不會噴錯。
  7. 整合測試:以測試資料進行各原件的測試確保業務邏輯正確。
  8. UAT測試:由測試人員或PM或SA以實際資料進行測試,確保功能正確。
  9. 持續修改:上線後一定會有問題,所以必定要持續監控並修改。

確認功能

確認要搬移的程式功能為何?位置在哪裡?怎麼操作?結果是什麼?是否有文件?例如功能選單的位置、API進入點在哪支Controller、在哪支程式的main方法、存取的資料庫位置,與哪些模組或系統介接。操作的前置步驟,例如登入帳號,權限,要先操作哪些功能產生資料源。操作後產生的結果是什麼,例如輸出什麼檔案、修改哪些資料表。詢問丟工作給你的人、PM、SA、QA及經手過的工程師以上問題。前先蒐集以上資料才會比較清楚自己要做的東西是什麼,在搬移及後續的重構過程中會比較清楚哪些程式的作用,哪些可以刪哪些不能刪,是否需要重新命名,該如何抽像等。並以高階的角度了解業務邏輯。

理想上是指派工作的人應該"主動"提供給上述資料,例如上傳到issue tracker或專案管理系統上,讓被指派搬移又從未接觸過此功能的工程師有個脈絡及方向。想像一下被指派去搬移舊專案上的支付排程但又沒給任何資訊,光要找到功能位置,進入點,怎麼操作就非常困擾,雖然可以透過上述問題去抽絲剝繭,但安排任務的人若能主動提供資訊可減少雙方溝通的時間。不過實際上收到任務時通常沒有任何資料,唯一的資料是issue tracker上「搬移舊專案的支付排程到新專案」的一行標題,沒了。而以上只是列出可蒐集的資料,至於怎麼蒐集又是另一門小學問。


盤點程式

找出待搬移功能的程式有哪些,包括程式檔,頁面檔,函式庫,SQL,資料庫預存程式,檔案,API等,目的是為了工時估算及搬移時的參考。盤點的方式很簡單,就是從功能的進入點開始掃程式碼直到結束,紀錄經過了哪些類別、方法、存取哪些資料表、讀取哪些檔案,呼叫哪些API等。比較難拿捏的是紀錄的粒度要多細,紀錄太細幾乎就等於重寫code浪費時間且沒重點,紀錄太粗無法無法正確估計工時。最後覺得紀錄到每個方法,寫下該方法的開始行號及結束行號,執行哪些SQL、API等是還不錯的作法。例如下面為盤點的結果:

com.abc.service.ShipService
    ShipService.process() // line:100-300
        line:120 -> ShipService.setFunction() // line:310-330
        line:150 -> CalendarDao.getWorkday() // line:50-100
            line:60 -> CalendarDao.getWorkingWeek() // line:200-230
                       sql select table: 
                       * store_calendar, 
                       * supplier_calendar, 
                       * supplier_info
        line:180 -> ShipService.calculateShipDay() // line:340-360
        line:250 -> ShipService.updateShipDay() // line:370-400
            line:358 -> ShipDao.save() // line:20-50
                        sql update table:
                        * ship
                        * ship_status

上面追蹤程式方法範圍的過程應該可以寫個程式去完成,市面上應該也有解決方案,不過台灣公司不會付錢買這種服務,所以乖乖自己看吧。


估算工時

盤點完要搬移的程式範圍後就可得到一個量化的範圍,包括程式支數,行數,檔案數,SQL等,如此便能依個人經驗估算出要花多少時間搬移及重構,例如500行程式估一天。以我為例是估300-500行一天。或許有人會想也太少了吧,但如果看過那些沒註解且有一堆奇杷命名及寫法的程式就會覺得剛剛好(有時還太少),在搬移時會花很多時間了解為什麼當初那樣寫或某段程式的用意。此外還需要重構,要讀懂程式碼才能給予正確的命名及適當的抽象出方法或類別等。而建立一些新框架缺少的物件等也要考量進去,例如原本舊專案存取資料庫是用是用純JDBC,新專案是用Hibernate或JPA,這時就要多考慮建立那些實體物件的時間。不過若主管仍認為不需要那麼多時間就無腦複製貼上吧。


複製貼上

盤點及工時估算後就可以開始搬磚了。作法是把舊專案的程式碼複製貼上到新專案中。此時要初步決定複製貼上原始碼檔在新專案的包(package)或目錄(directory)位置,而那些僅相依於舊程式框架的檔案就不一定要直接複製到新專案,例如舊專案存取資料庫用的是MyBatis的xml檔,而新專案是用Spring Data JPA,則MyBatis的相關檔案就不用複製過去,而是建立對映的entity類及Repository。

複製貼上的主要對象是核心邏輯的程式碼,透過複製貼上避免修改到原本已運作正常的邏輯。或許看舊程式的時候會覺得舊程式碼有一堆可笑低能的寫法,又醜又爛效能又差,儘管如此那依舊是能運行正確的程式,請把想要修改重構的衝動留到下一步。這也是這一步獨立出來的原因,一邊複製貼上一邊重構很容易疏漏或犯錯。

有些人的作法是是把舊程式看懂後直接在新專案撰寫新的程式,但這作法的難處是我們通常沒那麼大的腦容量去記憶那些流程步驟,就算能看懂一個有複雜業務邏輯的程式也很難記清每一個細節步驟,此時就必須撰寫規格文件或pseudo code來輔助,也一樣會有邏輯疏漏(看走眼的意思)的問題。


重構程式

接著開始對複製貼上的程式進行重構。重構的順序由小至大、由近而遠、從簡單到困難,也就是先從刪除冗瑜的程式碼(dead code)及無用的註解開始,然後重新命名變數、方法、類別等,然後才開始調整程式的結構,例如調整邏輯順序、抽取新的方法、類別,抽象類別,定義介面等,並套用適合設計模式等。

重構的好壞沒有絕對,如果公司訂有程式規範就依照該規範進行,如果沒有就依Oracle Java程式慣例Google Java Style Guide,剩下就靠自己累積的技術實力。有疑問的地方就找同事或主管一起討論決定。

至於重構的範圍是越小越好,盡可能不要重構。看到這邊可能覺得奇怪,又要重構又盡可能不要重構是什麼意思。因為重構範圍越大越可能損害舊程式既有的邏輯,而且花費的時間越多,通常是件吃力不討好的事。總之要觀察公司是如何看待這件事,例如是否給予充分的時間、是否有定義coding rule、是否有code review等。如果公司不是很重視那就重構到滿足規範即可。記住多做多錯。

重構時要考慮到團隊對於技術的掌握,可能因為多數成員不熟某項技術而不能使用某些寫法,例如Java lambda。

重構時建議在方法的Javadoc助解上標註對應舊程式的類別及方法名稱,行數範圍等,日後有問題回去翻找舊程式時很方便,方便證明「阿舊程式就這樣寫阿」以釐清責任。


沒有留言:

張貼留言