「經驗總結」高效開發,老代碼可以這樣動

語言: CN / TW / HK

theme: condensed-night-purple highlight: atelier-lakeside-light


前言

一般項目存在2年及以上,代碼就會變得臃腫且不好維護。如果有人員變動,新成員加入,可能面臨的第一個難題就是,如何在老代碼中加新需求。

我之前剛入職,接手老項目,內心活動是這樣的:

  • 新功能的添加會不會影響老功能?
  • 工期如此緊張,我還有時間做功能設計嗎?要不直接上手碼代碼得了?
  • 新增的功能跟已有的很相似,我是提公共還是在下面直接粘貼一份改改?如果提公共,原來的功能要不要測試一下?
  • 緊急的線上的bug,但是代碼第一次見,今天必須修復上線,時間太緊了怎麼辦?
  • 業務邏輯太複雜,平時迭代頻繁,我有時間捋代碼嗎?
  • 老的功能不熟悉,新的需求要修改原來的邏輯,但是邏輯挺繞的,交互多,UI還不好實現,我應該怎麼排期才合理?

雖然看上去像個戲精,但是句句都是真情實感。接手老項目遇到的這些疑問,不僅實際發生的頻率較高,而且很大一部分會影響開發效率和質量。

雖然沒有研究出什麼武學奧祕「降龍十八掌」,但是我用畢生所學,總結了「老代碼的神行百變」。

WechatIMG708.jpeg

功能變通

老頁面添加新功能,並不是所有時候都會影響原有的功能,所以開發之前做一下功能分類,會有「事半功倍」的效果。

功能隔離

有時候,需要添加的功能點,可能只是一個單選項或者下拉框,這個時候封裝個組件也沒什麼必要。但是畢竟是在老的頁面上添加,既要不影響現有功能,又要考慮之後做功能擴展。

我一般會做功能隔離。得益於JSX的靈活寫法,封裝代碼段十分方便。相對獨立的功能就不過多説了,這種情況下做功能隔離很簡單。主要是有依賴關係的功能,如何做隔離?

其實功能隔離也有點解耦的思維在裏面,雖然功能有依賴性,但是不一定要把代碼寫在一起。

比如產品訂單,所有產品類型的訂單都在訂單管理中,A產品的產品經理説,A產品的訂單撤單的時候要給二次確認提示,但是B產品沒有這個要求。

我把是否進行二次確認彈窗的邏輯放在了彈窗外側的列表操作上。撤單彈窗裏面中直接對二次彈窗的處理。這樣一來,撤單彈窗中的代碼不用關心哪條業務線需要進行二次確認操作。外側的撤單按鈕不需要關心撤單彈窗裏面是怎麼操作的,只需要把控制二次確認彈窗的變量reconfirmFlag(是否二次確認布爾值)傳入彈窗即可。

這樣,未來如果再加其他的產品類型,只需求維護列表撤單操作代碼裏面的條件判斷即可。

列表撤單操作

js record.reconfirmFlag = false; // =>true: 帽子產品需要二次確認彈窗 if (record.type === 'hat') { record.reconfirmFlag = true; }

撤單彈窗確定操作

js /** * 提交操作 * @param {Object} value 接口入參 * @return {void} 無 */ const handleSubmit = value => { let { data } = props; if (data.reconfirmFlag) { // 二次確認提示 } else { // 提交操作 } };

相似功能的提煉

新增的功能和現有的功能非常相似,也是在開發中經常遇到的情況。可能習慣性的或者為了求穩直接把老代碼複製一份,修改修改完事。

看似又快又穩的操作,會導致代碼變得越來越宂餘,老代碼變得越來越難維護。

怎麼提煉?

相似功能主要有兩點,同和異。相同部分是需要提煉的內容。對於差異的部分,我之前習慣用條件判斷,現在習慣用枚舉的方式。

不同產品分類展示產品信息

```js /* * 產品介紹 * @param {string} type 產品分類 / const goodContent = type => { const good = { hat: { title: '帽子', name: '帽子A', color: '黑色', }, shoses: { title: '鞋子', name: '鞋子B', color: '白色', }, }; const goodItem = good[type]; return (

{goodItem.title}
產品名稱:{goodItem.name}
產品顏色:{goodItem.color}
); };

return (

{goodContent('hat')} {goodContent('shoses')}

); ```

不同類型圖片展示和編輯

```js /* * 圖片展示 * @param {Array} imgList 圖片列表 * @return {void} 無 / const colImgContent = imgList => { return ( <> {imgList.length != 0 && imgList.map((img, index) => ( ))}
); };

/* * 圖片展示 * @param {string} type 圖片分類 / const imgContent = type => { const imgObj = { hat: { title: '帽子產品展示', imgList: [], }, shoses: { title: '鞋子產品展示', imgList: [], }, }; const item = imgObj[type]; return (

{item.title}: {colImgContent(item.imgList)}
); };

return (

{goodContent('hat')} {goodContent('shoses')}

); ```

老功能要不要測?

當然,測試是肯定要測的。不過提測的時間點可能會因情況而異。

我一般在需求設計階段就會把需要額外測試的地方標註出來。但是有些需求任務量小不需要設計評審,這種情況下如果要增加額外的測試功能點,需要提前跟測試的同事溝通,一般不是翻天覆地的修改,測試的同事是很好溝通的。如果改動影響範圍太大,測試的同事要做風險評估之後,才能確定是否可以進行代碼改造。

注:可能每個公司的工作流程不一樣,請大家參考公司的實際流程進行提測要求和提測操作。

精準定位

線上的緊急bug、臨時上線的緊急需求等,即考驗開發者的抗壓能力,又考驗自身的技術能力。有些技術難題,確實需要過硬的技術功底。但是有些問題,找準問題的關鍵點,其實不難解決。

不是逗悶子,先來看幾個例子。

無法喚起的收銀台

前情提要

同事離職之後,葉一一暫時接手了同事負責的工作,還沒有完全熟悉那部分業務。這天,葉一一拎着早餐來上班,產品經理過來説一個活動的收銀台無法喚起了,讓葉一一幫忙看一下。

第一次知道這個活動的葉一一是這樣做的

雖然葉一一對這個活動非常陌生,好在所有的活動功能都在項目的activity目錄下。找到對應的頁面,葉一一沒有急吼吼的去熟悉業務,她在做其他業務線購買流程時很清楚一點,進行提交操作之後才會喚起支付收銀台,所以葉一一先找到提交按鈕,然後再去找喚起收銀台的代碼,發現這個活動的收銀台是跳轉第三方的,而跳轉第三方url中帶有test字符的域名,葉一一就知曉了問題所在。

果然不出所料

葉一一問產品,跳轉第三方收銀台的鏈接,測試環境和正式環境的分別是什麼。拿到鏈接之後,果然測試環境帶"test"線上不帶。葉一一加上環境判斷區分不同環境的跳轉鏈接,再上線之後,線上就能正常跳轉收銀台了。

後續

葉一一後來熟悉這個活動功能的時候發現,活動流程還挺長的,到達購買頁面之前至少有5、6個步驟。所以,如果葉一一沒有精準定位問題,到達購買頁面還得費點功夫。

js // 區分線上和測試環境的地址 let prodEnv = env === 'production' let urlPrefix = prodEnv ? 'http://api' : 'http://testapi'; const url = `${urlPrefix}.com`;

測試通過之後的線上報錯

前情提要

葉一一剛接手了從別的組並過來的項目,還沒來得及看代碼,就來活了,有大概10個頁面需要改動。葉一一看不是改文案就是在原來的基礎上新增模塊,就愉快的定好排期開始幹活。

測試通過

很順利的開發完功能,自測沒有什麼問題,葉一一就提測了。測試過程也很順利,上線日,代碼早就準備好,就等晚上上線了。

線上驗收

線上驗收時,測試的小夥伴發現有個接口報錯了,後端的同事説這是一個老接口沒有做過調整。葉一一找到出現問題的接口,發現是一個很簡單的獲取數據的接口,都不需要入參。怎麼會有問題呢,測試環境明明沒有問題。

「全組的等待」+「就差這一個問題就驗收通過」的雙重壓力並沒有讓葉一一慌亂。葉一一遇到過多次玄妙問題,只要找準問題關鍵,不難解決。

正式環境和測試環境唯一的區別就是接口加密,會不會加密的接口需要什麼特殊處理?葉一一本地運行加密命令,果然接口報錯。葉一一又對比了其他接口,唯一區別就是這個接口沒有入參。於是葉一一嘗試在入參傳了一個空對象,果然接口成功並返回數據。葉一一讓後端幫忙看加密的邏輯,確實有特殊的邏輯處理。

後續

後來葉一一發現其他項目沒有這個問題,是因為在axios封裝時做了特殊處理,新接手的項目沒有進行處理。

```js // 處理過 config.params = JSON.stringify(params);

// 未做處理 config.params = params; ```

小結

有些問題,在特殊場景才會出現,這個時候通過對比特殊場景和常規場景的異同,找到不同點,能夠幫助快速解決問題。

業務變通

熟悉業務

與其擔心不熟悉業務,不好改需求或者評估開發排期,不如抽時間熟悉業務。業務熟悉起來也可以間接的提升開發速度和開發質量。

業務線太多怎麼辦?

我個人是「好記性不如爛筆頭」這一派的。我無法一次記住全部的業務功能,那就寫寫文檔,將功能梳理出來。

如果業務線多,那就一條一條的捋。切記「貪多嚼不爛」,以及避免「迷路蜜蜂」的狀態。

單個業務如何梳理?

不同的業務,梳理的側重點也會有所不同。

  1. 流程較長的業務,需要捋順整個流程。可以通過繪製流程圖的方式,輔助理解和記憶。
  2. 交互複雜的業務,需要新定邊框,再細化交互設計。交互是比較分散的,但是類型比較確定,彈層是彈層,下拉項是下拉項。整體的交互風格先定好,可以減少後期的修改。
  3. 內部邏輯複雜的業務,需要拆分層級。先拆成最小顆粒度,再做功能組合,有點像搭積木,先把形狀分類好,再搭結構,最終完成「圖紙」上的模樣。
  4. 偏活動類的業務,梳理文檔中,最好列出來活動入口、參與規則(可能會影響前端的功能)、參與記錄的查看方式等。

已整理業務文檔數量

| 類型 | 文檔數量/個 | | ------- | ------ | | 產品線文檔 | 12 | | 理賠組業務文檔 | 7 |

抽離業務組件

先繪製出全部業務模塊,分門別類。然後橫向對比業務功能,思索今後的發展,確定哪些功能是可以抽離業務組件的。

抽離的本意,一方面提升代碼的複用率,不必重複開發;另一方面,可以將複雜功能拆解,便於後續的快速迭代。

抽離的好處,功能寫的又快又好,一次開發,造福未來。

我之前寫過一篇關於業務組件的思考,這裏不展開説了,指路☞ 【工作小記】關於業務組件的思考

思維變通

設計文檔

我的設計文檔主要包括三部分內容,開發前準備、開發排期、需求拆分和重點功能設計。

開發前準備

準備的內容主要是功能開發之外的事情。

比如小程序開發是否需要新增業務域名,新增的項目是否需要運維的同事幫忙配置域名,公眾號新增頁面上線之後是否需要提供入口鏈接等。這些可以再設計文檔中進行記錄,避免遺忘。

開發排期

我的開發排期中包含每一項開發需要的具體時間,以頁面為維度,以小時為最小的時間單位,然後彙總成天數。實際的開發週期和聯調週期,還需要結合手上有沒有其他並行的需求和後端同事的開發週期。

需求拆分和重點功能設計

我會把所有的需求羅列出來,避免漏掉哪個。但是功能設計一般只會做重點功能,或者一些組件封裝的設計。

重點功能設計主要是處理方案和重點功能代碼塊,如果流程複雜的會附上流程圖;如果狀態值較多的項,附上枚舉值對應表格。

抽絲剝繭

並不是所有的功能是複雜的,把功能抽離到最小功能,肯定有是簡單的部分,而複雜的內容只佔其中一角。

比如一個列表頁,搜索項和列表展示比較簡單,刪除操作頁很簡單。但是新增的表單很複雜。

如果卡在複雜的功能處,不妨先完成簡單的部分。如果經常卡在複雜的功能上,就要自我反思一下了,平時有沒有做功能整理和技術提升,來幫助自己更好更快的開發複雜功能。

排期變通

正向排期總是失誤,不妨試試反問式排期方法。

  • 為什麼只是在原來功能中加了一個小需求,但是寫的排期卻這麼長?它難在兼容老功能還是新增的功能?
  • 為什麼修改的項這麼多,但是寫的排期時間卻這麼短?複雜程度是不是真的不高,不要被想當然坑了自己?
  • 別的頁面需要的時間都很短,為什麼唯獨這個頁面需要這麼長的時間?它的複雜點到底在哪?
  • 為什麼聯調的時間反而比開發的時間長?是我聯調時間寫長了?還是開發時間寫短了?還是聯調接口太多,參數太複雜?

總結

對於老代碼的改動,確實耗時又費心神。所以我有時候會在我的設計文檔中,寫一些鼓勵自己的俏皮話。一定要好玩一點不要太中規中矩,才能逗笑自己的同時,激發幹活的熱情。

凡事留一線,代碼好擴展。

功能設計好,bug見得少。

今天努力捋順的需求,都是日後寶貴的財富。

我正在參與掘金技術社區創作者簽約計劃招募活動,點擊鏈接報名投稿