好久沒寫文章了,標題起的有點膨脹。
你猜我想說什麼
我想寫一個結對程式設計小記。最近在和 S
(帥氣的花名) 利用業餘時間,進行了一次結對程式設計。現在我準備把結對程式設計的一些思考分享給大家,下面開始吧。
PS:
此篇文章,不拘泥於細節,將會聚焦整體上的見解,希望對各位小夥伴有所幫助。
為什麼就叫新時代下的結對程式設計
這裡我說明一下原因,請往下看。
傳統結對程式設計
百度百科的定義如下:
結對程式設計(英語:Pair programming)是一種敏捷軟體開發的方法,兩個程式設計師在一個計算機上共同工作。一個人輸入程式碼,而另一個人審查他輸入的每一行程式碼。輸入程式碼的人稱作駕駛員,審查程式碼的人稱作觀察員(或導航員)。兩個程式設計師經常互換角色。 在結對程式設計中,觀察員同時考慮工作的戰略性方向,提出改進的意見,或將來可能出現的問題以便處理。這樣使得駕駛者可以集中全部注意力在完成當前任務的“戰術”方面。觀察員當作安全網和指南。結對程式設計對開發程式有很多好處。比如增加紀律性,寫出更好的程式碼等。結對程式設計是極端程式設計的組成部分。
為什麼我覺得過時了呢,聽我簡單(胡)析(謅)下:
兩個人共用一臺計算機,這可能在 ACM
競賽或者組隊 PK
中常見。但對於業務開發來說,有點不科學,首先效率就不高,如果對方程式碼太可愛,可能會忍不住打他。當然了,在特殊場景下也會出現,但是 95%
的場景下,是不可能存在下圖這種場景的。
下面這種場景猶如六月飛雪:
不過這種場景倒是很樂意接受的:
當然,別問我為什麼,因為:
那什麼是我認為的新時代的結對程式設計呢?
新時代的結對程式設計
我認為新時代的結對程式設計,應該是這樣一種狀態:
兩(N
)個 coder
, 兩(N
)臺電腦,坐在一起(後續也可以不用坐在一起)進行開發。程式碼可以互相看到。最關鍵的一點就是,我們可以互相看到並且看懂對方的程式碼。
什麼意識呢,這裡我舉個例子:
我和 S
達成意見,S
負責 node
, 也就是後端,我負責前端,前後端分離。然後各自的程式碼我們都能互相看到,要開發什麼功能,我們都知道,我可以 review
S
的程式碼,S
也可以 review
我的程式碼。當開發的時候,我可以通過 review
S
的程式碼,提前知道 S
的介面邏輯。如果我認為他的邏輯寫的有問題,我會和他進行溝通,提前把可能存在的疑問處理掉。我也會提前按照他寫的介面來預留好請求這塊的邏輯處理。總之,在我理解的結對程式設計中。應該是能力相同,程式碼相同,通過互相 review
對方的功能程式碼,來對比自己的程式碼。從而告訴對方可能存在的問題,而這種問題的解決會讓開發的效率大大增加,更早的解決了潛在的一些問題。
PS: 當然這種新的方式,需要一定的技術基礎,比如你要能看懂後端的程式碼。嗯,如果是
Java
,你可能會比較艱難。當然,他也會更艱難。所以還是node
大法好,手動滑稽。總之,這是我認為的新時代下的結對程式設計,它不具有普適性,當然這種形式的好處是顯而易見的。
結對程式設計之前的狂歡?
嗯?按照傳統,當然是來一次結對前的慶祝。去了趟 1912
,吃了個大餐,享受了下日式服務。然後去咖啡館開始 face to face talking
,愉快的結對程式設計就要開始了。
怎麼玩出花呢?
我分為兩個部分去介紹,第一部分是前端,第二部分是後端。
前端
技術架構選擇
首先,框架的選擇這塊,在衡量之後,選擇的是 Vue
。這裡我對用什麼框架,其實沒什麼看法,唯一的看法就是:
根據實際情況,來選擇合適的開發框架,從而提高整體的開發效率。
要不要使用腳手架,這塊我也持上面的那句話,根據實際情況做出合適的選擇。這裡我選擇 Vue-cli3
來快速搭建應用。怎麼使用,自己去研究一下就知道了。
這裡可以提供幾個路徑
第一個路徑:去 cli.vuejs.org/ 官網研究
第二個路徑:去看 vue-cli3
的原始碼,瞭解一下內部的一些機制,比如 chainWebpack
的實現,vue.config.js
的預設設定是如何實現的。
第三個路徑:去 github
上,找一些用 vue-cli3
搭建的專案,clone
下來,研究一下別人是如何使用的。
PS: 我個人認為腳手架是不會存在技術上的難度的,多看看文件,不行的話再去看原始碼搗鼓搗鼓,就差不多了。
樣式方案的選擇
選完技術框架,我們再來看看樣式的選擇,通用的樣式方案,業界的標準基本都是相同的。但是關於元件中的樣式該放哪,目前在業界,大概有這麼兩種。
樣式和元件目錄分離
樣式和元件目錄分離,這個是目前很多 ui
庫採取的方式。
比如 iview
elementui
,我來截圖展示一下。
如圖所示 iview
的截圖:
從上圖可以發現,元件的樣式全部放在了 style
目錄裡,並且用的是 less
。
樣式和元件目錄不分離
說到這個,目前具有代表的大概就是 ant-design
了。
如圖所示 ant-design
的截圖:
我們可以看到,在 ant-design
中,元件的樣式是放在元件目錄裡面的,同時也是用的 less
。
使用哪種樣式進行編寫
至於使用哪種樣式風格編寫。為了玩出花,我採取的是同時使用三種樣式:scss
less
stylus
。在這個過程中,我更喜歡 stylus
,因為其類似 python
的縮排寫法,簡單快速。一不用寫分號,二不用寫括號,三也可以不用寫冒號。如果你看過 vue-cli3
的原始碼,你會發現其中的 ui
部分,樣式使用的就是 stylus
。
vue-cli3
原始碼中的的 ui
部分如圖所示:
總結
關於樣式方案的選擇,我想表達的是:
在元件的樣式處理方面,業界存在兩個標準,不存在好和不好,但是我們需要知道這些標準,通過知曉,來給自己的實踐中多提供一些參考和幫助。
我使用的是:
樣式和元件目錄不分離的方案,同時樣式編寫採用的是三個樣式,主用 stylus
。
關於狀態管理
業界的狀態管理有很多,vuex
、 redux
、 mobx
、 dva
等等。目的都很明確:通過抽象封裝掉混亂。
對於我來說,會思考一個問題,此專案是否真的需要
vuex
,為了玩出花,我做的處理是:
切了兩個分支,一個不使用 vuex
,一個使用 vuex
。
什麼時候使用
vuex
當元件中需要共享一些資料的時候,並且資料較多,就可以使用 vuex
。如果資料不需要共享,或者較少,其實也沒有必要用 vuex
。
vuex 的兩種形式
使用 vuex
時,如果需要管理的狀態不是很多,那可以直接寫到一個檔案中。如果需要處理的狀態很多,那就使用 module
的寫法。將狀態進行分類,分成不同的 module
。
關於請求的處理
對於請求,我這邊做的是,將請求部分儘可能的抽離出來, 預留好以後可能會出現的坑。比如,預留請求攔截,預留對請求進行加密,預留公共的引數,預留好模擬資料的坑。
我一般將這部分邏輯分成三個類。一個是 StateService
,一個是 ApiService
,一個是Service
。當然如果服務過多,可以對 Service
進行細分,比如 UploadService
、 MonitorService
等。
關於模擬資料
說到模擬資料,大家第一反應可能是,使用 easy-mock
,使用 rap
等。而在我這邊,選擇了一種很少人知道的做法,那就是通過裝飾器來進行資料模擬。
什麼意識呢?我簡單介紹下
如圖所示:
從上圖可以知道,我對 Service
中的 getuserinfo
進行了裝飾攔截,通過裝飾器來將模擬資料注入進去。再通過環境判斷來確保在生產環境上,此裝飾器方法失效。
雖然會有入侵程式碼的味道,但是在真正嘗試了後,你會感受到利用裝飾器來解決資料模擬 的魅力,因為你可以隨心所欲的控制所有請求邏輯。從而達到完全不依賴後端介面來實現前端全流程的模擬。
關於工具方法
對於這個,需要去思考此專案需不需要依賴一些工具庫,比如 lodash
、 ramda
。如果不怎麼依賴,那就用原生寫吧。
對於工具方法,我也會進行分類,我會分為內部方法和外部方法。如果只是提供給另一個方法使用,並不會暴露到業務中,那我會把它認為是內部方法。
關於效能
這個是老生常談的事情了,我一般把效能這塊分為兩個方面。
腳手架能做到的事情
- 將依賴包和業務程式碼進行分離
- 對
js
css
等進行壓縮,是否對css
進行分離 - 是否對圖片進行壓縮,是否對小圖片進行轉成
base64
- 根據業務情況決定是否使用
cdn
- 對第三方
ui
庫進行效能處理,比如後置編譯 - 和後端進行溝通,決定是否開啟
gzip
腳手架做不到的事情
關於圖片壓縮
雖然 webpack
有圖片壓縮,但是根據對比,其壓縮的質量和效果還是沒有 tinypng
優秀,如果有不知道 tinypng
的小夥伴,可以點選 tinypng.com/ 進行 TP
。
tinypng
的壓縮演算法沒有開源,雖然可以免費使用,但是會存在一些限制,對此 github
上有人專門開發了 tinypng for mac
。 小夥伴可以自行了解一下,工具還是很好用的。可以原位置替換檔案,壓縮後的圖片基本沒有損失。
程式碼級別的效能提升
這是腳手架做不了的,我們在寫程式碼的時候,要注意程式碼方面的效能。比如將資料存到區域性變數、優化迴圈、函式去抖、函式節流、圖片懶載入等等吧。效能這塊我就不提了,太老生常談了。
關於async await 和 promise
這塊我提一下。在專案中,我們究竟要怎麼靈活使用 async await
和 promise
,首先明確的一點是,不能為了使用而使用。這裡我發表一下個人看法,下面是幾種使用兩者的場景。
- 當你想用
try catch
捕捉非同步操作的異常時 - 當你要在一個非同步操作中巢狀另一個非同步操作時
- 當你想將一個函式包裹成
promise
的時候
我簡單舉個例子,如下程式碼:
fn(res => {
// TODO:
})
複製程式碼
上圖中,fn
是一個非同步操作,該函式中會執行回撥函式。現在有兩個問題:
- 如果我們想讓
fn
變成promise
呢? - 如果我們想在回撥函式中繼續執行其他的非同步操作呢?
針對上面的兩個問題,現在來依次解決一下。
解決第一個問題:
首先我們把非同步操作的 fn
變成 promise
形式,很簡單。
大致程式碼如下:
function wrap() {
return new Promise((resolve, reject) => {
fn(res => {
resolve()
})
})
}
複製程式碼
解決第二個問題:
如果 fn
的回撥函式中,還要進行非同步操作,那該如何優雅的解決這個問題呢?這個時候就要使用 async await
了。
大致程式碼如下:
function wrap() {
return new Promise(async (resolve, reject) => {
try {
fn( async res => {
let result = await fn2()
})
}catch(err) {
// TODO:
}
})
}
複製程式碼
是不是發現,程式碼都是同步的,可以使用 try catch
。嗯,就是這麼簡單自然。
關於動畫銜接
我們知道,互動這塊要友好,不能太生硬,跳轉等需要保證流暢性。所以在部分頁面中,使用了 gsap
,不瞭解的小夥伴可以去查一下資料。
簡潔程式碼如下:
// 只用到了 TweenLite 、TimelineLite 兩個方法
import { TweenLite, TimelineLite } from 'gsap'
let tw = new TimelineLite()
tw.add(TweenLite.to(this.style.blocks[0], 0.5, { opacity: 1, delay: 0.5 }))
tw.add(TweenLite.to(this.style.blocks[2], 0.5, { opacity: 1 }), '-=0.3')
複製程式碼
通過對樣式的設定來達到銜接的流暢性。
關於 loading 元件
我們在開發微信 H5
、天貓淘寶 H5
、 開發快應用、開發小程式等的時候。 loading
這塊是一個必須要做的事情。比如請求開始時,loading
開始,使用者不能進行其他操作,請求結束或者異常時,loading
結束。
那麼在面對第三方
ui
庫 或者APP
自帶的實現時,我們要怎麼去選擇呢,是使用現成的,還是寫一個最適合自己的呢?
這塊我個人的意見是自己手寫一個適合自己的 loading
。這樣做的原因我高度概括一下,就是:統一和靈活。
PS: 當然,這個不是絕對的。只是提供一個思路,比如微信 H5
, 天貓 H5
, 就不要用自帶的 loading
,使用同一的 loading
元件。
關於開屏特效
開屏特效有很多方式,比如可以使用 vue-touch
來完成向下滑屏的操作。
大致效果如下 gif
圖:
在完成以後,感覺不夠炫酷。於是進行改良,執行第二種方案,採用序列幀來完成開屏的特效。
什麼是序列幀呢?
序列幀就是通過將一段視訊分解成一幀一幀的形式。然後通過 canvas
來按照一定的時間間隔畫出來,從而讓使用者感覺到是一連串的特效畫面。可以想象成 PPT
的快閃效果。
既然需要序列幀,那麼就需要載入的進度條。進度條這塊可以封裝成元件,思路就是通過資料載入的進度來設定百分比,同時設定一些動畫的效果。
大致效果如下 gif
圖:
從上面可以看到,當 godkun: 原始碼終結者
顯示完全的時候,也就是進度條達到 100%
的時候。表明序列幀需要的資料已經載入完成。
關於圖片合成
需要做分享圖,如果交給後端去做,由於需要合成的圖片很多,後端的壓力會變大。商量後,決定在前端合成,通過 canvas
來將10
張圖片,合成為一張分享圖,這裡的注意點不多。需要注意的就是在圖片 onload
事件回撥中再進行畫圖。
大致程式碼如下:
async drawCanvas() {
let canvas = this.$refs.myCanvas
canvas.width = xxx
canvas.height = xxx
let bg = await this.drawBg()
this.drawImage(bg) // 不變
// TODO: 繪製其他
let data = canvas.toDataURL('image/jpeg')
const params = {
base64: data
}
let result = await Server.getImage(params)
this.$refs.canvasImg.setAttribute('src', result)
}
複製程式碼
PS: 這裡有個坑,安卓手機下無法儲存
src
為base64
的圖片,後面經過採坑,決定通過將base64
資料發到後端,然後返回png
的地址來解決這個坑。
關於埋點
埋點的目的是收集各種資訊,比如 PV
、 UV
、點選率、異常。那麼埋點該怎麼做,一般的前端埋點,我認為,需要遵循兩點:
第一點:不要過度入侵程式碼,也就是俗稱的硬編碼
第二點:做到複用性,也就是進行統一的封裝
比如,將對應的埋點進行重新命名,如 b1
b2
b3
。
大致程式碼如下,簡單粗暴:
burryDetail ={
b1: {
actionName: 'gotohome'
}
}
export default function burry(key) {
let burryObject = burryDetail[key]
// TODO: args
let argN = burryObject.actionName
send(...args, argN)
}
複製程式碼
這樣做的好處是將所有埋點放在一個檔案中進行集中處理,不要在業務程式碼裡面直接寫,有利於解耦。對於有一些特殊情況,需要在業務程式碼裡面寫的,那就特殊對待吧。
關於執行模式
說到執行模式,可以想到有開發模式,生產模式,甚至還有分析模式。目前在 vue-cli3
下,按照其規定的方法進行設定就好了。比如新建 .env.dev
.env.prd
.env.xxx
檔案
如下 .env.dev
程式碼:
NODE_ENV = "development"
VUE_APP_BASE_URL = "xxxxxxxxxxxx"
複製程式碼
然後在 scripts
中寫入
"dev": "vue-cli-service serve --mode dev",
複製程式碼
就可以通過 npm run dev
來執行開發模式了。
關於第三方 sdk
的程式碼提示問題
有時候,你會發現,引入 sdk
後,沒有程式碼提示。然後去網上找,也找不到官方解決方案,這個時候怎麼辦呢?
顯然,只能自己去編寫宣告檔案了,這裡是針對
VSCODE
來說的,標準方法就是新建一個型別目錄,然後新建sdk.d.ts
。
程式碼大致如下:
declare namespace SDK {
function fn1(callback: () => void): void
function fn2(text: String): void
function fn3(): void
function fn4(
object: {
arg1: String
arg2: String
arg3: String
arg4: String
},
callback: (res: {}) => void
): void
}
複製程式碼
這塊自己根據具體的 SDK
去編寫你需要的提示就好了。
後端
後端是 S
寫的,所以我就以一個 code review
的形式去大致說一下後端的整體情況吧。
後端要做什麼
大致有如下幾點:
- 編寫
API
介面 - 編寫後臺管理
- 編寫一些其他的輔助功能
- 設計資料庫
- xxxx...
後端的框架選擇
這裡使用的是 egg.js
,其通過約定的哲學,來快速搭建應用,提高開發效率。egg
的學習文件有很多,貼個官網文件地址吧:
如何編寫介面
這個沒啥好說的,通過路由攔截,然後轉到對應的邏輯處理,中間可能需要經過相應的中介軟體進行處理。
如何集中處理響應
如何更好地設計返回,這裡的做法是將返回封裝成 plugin
,然後擴充套件到 context
上。這樣的話,你就可以通過 ctx.sendresult
來統一處理返回內容了。
中介軟體的使用
中介軟體也沒啥好說的,本質上就是一個匯出的函式。用來處理接收到的資料,並進行對應的資料處理。
資料處理基本有兩種結局:
- 第一種結局:走到下一步
- 第二種結局:在這個中介軟體內就
game over
舉個真實場景:
比如最常見的,鑑權。我們可以在路由中就進行控制:
程式碼如下:
router.post('/api/xxx', auth(), controller.xxx);
複製程式碼
在處理受限介面的請求時,先進行 auth
處理,如果沒有成功,就中斷後續的邏輯處理,直接返回沒有登入或者沒有授權之類的響應內容。
node 的 MVC
和前端的 MV*
V
這裡我簡單提一下,在 node
層,和在前端層,其 V
大家都是能理解的,都是檢視的意識,node
層,也要寫頁面的,比如我們寫一個 404
頁面,配合處理異常的中介軟體。如果中介軟體捕捉到了 404
,那麼直接返回 404
頁面給前端。
C
C
在前端來說,其實是最富有變化的一個點,所以我加了 *
表示。C
對 node
來說,時候也是最重要的一個部分,它專門去處理接收到的請求,並且分發給各個邏輯處理中心,最後通過一系列的處理,再通過 C
層返回給前端。
M
M
對 node
來說很簡單,在處理邏輯的時候,需要和資料庫進行互動,來實現 CRUD
從而實現資料的增刪改查。M
對前端來說,也很簡單,就是對 V
層的資料進行 CRUD
。前後端的 M
層還是有點區別的。
總結一下,後端的 MVC
看起來,是比前端的 MVC
好理解,前端的 MVC
就好像被強行安排了一波?。
前端的配置和後端的配置
隨著前端工程化的持續完善,前端的配置和後端的配置 ,在思想上,基本是一致的,這裡我就不提了。
關於資料庫
上面提了,使用 mysql
,當然了,像 egg-sequelize
mysql2
這些都要用的。模型定義通常情況下是在 model
目錄下,定義好模型,然後掛在到 Application
下,通過 app.model.xxx
來拿到對應模型的上下文。對模型的處理,一般是放在 service
目錄下。
關於 publicPath
絕大多數情況下,前端的專案在打包後,會放到後端的 public
目錄下,所以看到這,小夥伴應該明白了構建工具中的 publicPath
是什麼意識了吧(手動滑稽)。
總結
看完後端部分,是不是發現也挺簡單的,程式碼都是 js
,前端怎麼寫業務邏輯,後端就怎麼寫業務邏輯。
兩者的區別主要是前後端需要考慮的點不一樣。
後端需要考慮安全、考慮伺服器的壓力、考慮介面的設計、考慮資料庫的設計。當然高層次一點,還要考慮負載均衡,高併發等。而前端則需要考慮頁面的美觀性、頁面的流暢性、使用者互動的友好性、頁面的開啟速度等等。總之,大家都不容易的,唯有執子之手,才能與子偕老。(手動滑稽 +2
)
文末總結
讀完,是不是有點感覺和結對程式設計沒啥關係,我特喵的讀完也感覺到了。
寫了 6000
字,我圖個啥。
開玩笑的,開玩笑的,其實,本文和結對程式設計是有強聯絡的,這個過程是我和 S
一起參與完成的。
下圖體現在整個過程中。
畢竟不能只說結對程式設計,還是要說一下其他的東西的。
我認為結對程式設計的魅力,就在於可以:
互相反饋,及時調 整,共同提高開發效率。當然最重要的是可以 搞(你在說)基(什麼?)
備註
- 五一的番外篇,寫的有點倉促,記錄一下近期結對程式設計的體驗
- 玩出花這個事情,其實從文中也能體會到,比如三套樣式、兩套狀態管理、裝飾器解決資料模擬,序列幀解決開屏,實時
review
後端程式碼,根據後端程式碼寫前端請求邏輯等等,好像也是有點花的。 - 文章難免有錯,還請多多包涵,歡迎在評論處指出錯誤,多多交流
參考
-
本人的一次結對程式設計體驗
-
@當愛的表達遇上現實的裝飾器 歡迎關注這位大佬的微信公眾號: 欣仔互動
我的一些其他系列文章
交流
掘金系列技術文章彙總如下,覺得不錯的話,點個 star 鼓勵一下哦。
我是原始碼終結者,歡迎技術交流。
也可以進 前端狂想錄群 大家一起頭腦風暴。有想加的,因為人滿了,可以先加我好友,我來邀請你進群。
風之語
明天就五一了,祝小夥伴五一快樂,我不管什麼安康,我只要你們快樂!
最後:尊重原創,各位首富,轉載請註明出處哈?