1.引言
1.1 為什么要寫高質(zhì)量的代碼
在業(yè)務開發(fā)中,我們經(jīng)常出現(xiàn)的一種情形,在項目初期高效地實現(xiàn)業(yè)務需求,但隨著時間推移,添加新功能的速度逐漸減慢。我們需要花費更多的事件去思考如何將新功能塞進現(xiàn)有的代碼庫,不斷蹦出來的bug修復起來也變得越來越難。代碼庫看起來就像在補丁上打補丁,最終需要進行繁瑣的考古工作才能理解系統(tǒng)的運行方式。
高質(zhì)量的代碼通常更易于理解和修改,這可以減少在維護和更新代碼時所需的時間和精力。高質(zhì)量的代碼通常更易于重用和擴展,這可以幫助開發(fā)者更快地開發(fā)新的功能和應用。高質(zhì)量的前端代碼通常意味著更少的錯誤,更快的加載速度,更好的響應性,這些都可以直接影響用戶的體驗。
好文章的標準有三條:思想、邏輯和修辭。思想是根本,是內(nèi)容。邏輯與修辭則是形式,是工具。邏輯是理性的形式與工具,修辭是感性的形式與工具。寫代碼和寫文章一樣,首先保證的根本是功能可用,準確表達出業(yè)務含義;其次也要追求形式上的優(yōu)雅,編碼規(guī)范就是對代碼的組織形式與風格的一種約束。好的代碼不僅邏輯清晰,風格一致,而且無論多少人參與,都如同是一個人在編碼。
1.2 什么是高質(zhì)量的代碼
有良好的可讀性,代碼應易于理解。如使用清晰的命名、保持函數(shù)和方法簡短、使用注釋來解釋復雜的代碼段等。應該盡可能地簡潔,避免不必要的復雜性和冗余。在一個項目中,代碼應該有一致的風格和模式。這使得代碼更易于閱讀和理解。
可為維護性高,應該易于修改和擴展。這通常意味著代碼應該遵循某種設(shè)計模式,避免過度復雜的依賴關(guān)系,并且有良好的模塊化。好的代碼應該是直觀的,當有人需要進行修改時,他們應能迅速找到需要修改的部分,能夠快速進行更改,而且不容易引入新的錯誤。
健壯性有考量,對于邊界場景有覆蓋和處理。應該能夠處理各種預期和未預期的輸入情況,并在出現(xiàn)問題時優(yōu)雅地失敗。遵循最佳的安全實踐,避免可能的安全漏洞。
高效運行,提供優(yōu)異的用戶體驗。較少的資源文件數(shù)量和大小;地理位置更近的CDN資源請求,資源懶加載;合理的數(shù)據(jù)結(jié)構(gòu)和算法,減少CPU時間、內(nèi)存使用和磁盤I/O。
1.3 如何編寫高質(zhì)量的代碼
工程師的編碼素養(yǎng)
編程不僅僅是關(guān)于如何編寫出能夠運行的代碼,它更是關(guān)于如何編寫出優(yōu)雅高效、易于維護的代碼。這種藝術(shù)涉及到的不僅僅是技術(shù)層面的問題,更是關(guān)于思維方式、解決問題的策略、以及對于質(zhì)量和細節(jié)的追求。優(yōu)秀的編程藝術(shù),就像一座建筑的設(shè)計,既要考慮到功能和效率,也要考慮到美感和人性化。
前端開發(fā)涉及到許多不同的技術(shù)和工具,如果開發(fā)者對這些技術(shù)和工具的理解不夠深入,或者缺乏必要的編程技能,可能會難以編寫高質(zhì)量的代碼。在工作協(xié)同上,我們也常常需要在項目里程碑節(jié)點和有限的資源中尋找平衡。因此,是對我們軟性素質(zhì)和專業(yè)技能的雙重考驗。
電商前端研發(fā)規(guī)范
前端開發(fā)已經(jīng)成為了軟件工程的重要組成部分。它是用戶與應用程序之間的橋梁,直接影響著用戶體驗和滿意度。然而,前端開發(fā)的復雜性和挑戰(zhàn)性也在不斷增加,涉及到多種技術(shù)、工具、框架,以及不斷變化的業(yè)務需求和用戶期望。因此,建立一套有效的前端團隊研發(fā)規(guī)范,對于提高開發(fā)效率、保證代碼質(zhì)量、促進團隊協(xié)作,以及滿足業(yè)務和用戶需求,具有至關(guān)重要的意義。
前端團隊研發(fā)規(guī)范不僅包括編碼規(guī)范,也包括研發(fā)流程規(guī)范、代碼審查規(guī)范、安全與性能規(guī)范等等。這些規(guī)范應該反映出對質(zhì)量、效率、協(xié)作和持續(xù)改進的追求。大家應對規(guī)范活學活用,使我們能夠更好地應對挑戰(zhàn),提供優(yōu)秀的產(chǎn)品和服務,以滿足業(yè)務和用戶的期望。
2.工程師的編碼素養(yǎng)
先從形成良好的編碼習慣開始,注重編程的基本素養(yǎng)和要求。先寫出可讀性、可維護性高的代碼。再逐步提升專業(yè)技能,寫出健壯、高效、交互優(yōu)異的代碼,對業(yè)務工程的全聲明周期進行把控,負責其功能迭代、架構(gòu)設(shè)計、甚至項目重構(gòu)。
2.1 有意義的命名
命名是開發(fā)過程中至關(guān)重要的技能,有一個易于理解的名字可以承載很信息,某種程度上是一種更好的注釋,一個糟糕的命名,可能會引起別人的誤解,對開發(fā)效率和項目質(zhì)量影響很大;相反,遵循一套嚴格的命名規(guī)范,無論是對自己還是接手項目的人,都會大大降低代碼的維護成本。命名規(guī)范涵蓋的面比較廣,一般包括變量或常量名、函數(shù)或類名、文件或工程目錄名、工程名以及空間名等。
把信息裝到名字里,從字面含義可以關(guān)聯(lián)其代碼中的用途。名字應該盡量精確、專業(yè)、不要有多余。不會誤解的名字,閱讀你代碼的人應該理解你的本意,并且不會有其他的理解。
2.1.1 基本要求
選擇專業(yè)的單詞。比如分發(fā)事件時 send 可以用 dispatch 替代。
避免空泛的名字。如在定義變量時使用temp、arr、obj。
用具體的名字代替抽象的名字。比如我們定義一個訂單狀態(tài),應使用orderState,而不是寫成thisState。
使用前綴或后綴來給名字附帶更多信息。比如用setPageSize來描述設(shè)置列表的分頁條目數(shù)。
合理的名字長度。為作用域大的名字采用更長的名字。
利用名字的格式來表達含義。有目的的使用命名方式、大小寫、下劃線等。比如用全大寫下劃線命(MY_CONSTANT_NAME)方式表達常亮。
使用行業(yè)/團隊范式命名。加上像is、has、can 或should這樣的詞,讓布爾值變得更明確。類或者構(gòu)造函數(shù)首字母大寫。
以下列舉的不規(guī)范的命名方式,在任何情況下,你都不應該考慮使用它們:
單詞拼寫錯誤
提交表單中,把 Form 寫成了 From,如submitFrom
中英文混用
let chanpinList;這個變量名混用中英文,很不容易理解。除非是一些被創(chuàng)造出來但已經(jīng)被廣泛接受了的名詞,如淘寶-taobao,微博-weibo,其他的情況都建議用英文;
中文詞匯縮寫
比如表達服務市場時,直接使用fwsc,對于第一次接觸的人完全不理解含義
以1-9或a-z命名
比如頁面上有幾個按鈕,直接命名成 btn1,btn2,btn3,...或者 btnA,btnB,btnC,...,這樣看似簡單,實際上從這些命名里面讀取不到任何信息,時間久了就加無法與業(yè)務對應
混用命名格式
比如表示評論列表,有地方叫 comments,另一個地方叫 comment-list,還有的地方叫 commentList,幾種規(guī)范混在一起,就感覺很不規(guī)范
單復數(shù)不分
比如有兩個操作,一個是下載全部訂單數(shù)據(jù),一個是下載當前訂單數(shù)據(jù),結(jié)果分別命名為 downloadOrderData 與 downloadOrder,如果沒有單復數(shù),是不能很好地表達出業(yè)務含義的
正反義詞錯用
比如有兩個操作,一個是顯示彈窗,一個是關(guān)閉彈窗,結(jié)果分別命名為 showEditDialog 與 closeEditDialog。show 和 close ,一個是顯示,一個是關(guān)閉,顯然不是一組正反義詞
容易被過濾的單詞
ad、banner、gg、guanggao 等有機會和廣告掛勾的字眠不建議直接用來做ClassName,因為有些瀏覽器插件(Chrome的廣告攔截插件等)會直接過濾這些類名
2.1.3 團隊規(guī)范
變量命名規(guī)范
變量名【應該】使用小駝峰式命名法,且前綴應當是名詞,盡量在名字中體現(xiàn)類型,如 length、count 表示數(shù)字,而name、title表示字符串;
var tableTitle = 'LoginTable';
var getTitle = 'LoginTable';
函數(shù)命名規(guī)范
函數(shù)名【應該】使用小駝峰式命名法,且前綴應當是動詞,常用的動詞前綴如下表所示;
動詞 | 含義 | 返回值 |
can | 判斷是否可執(zhí)行某個動作 | 函數(shù)返回一個布爾值。true:可執(zhí)行;false:不可執(zhí)行 |
has | 判斷是否含有某個值 | 函數(shù)返回一個布爾值。true:含有此值;false:不含有此值 |
get | 獲取某個值 | 函數(shù)返回一個非布爾值 |
set | 設(shè)置某個值 | 無返回值、返回是否設(shè)置成功或者返回鏈式對象 |
load/query | 加載某些數(shù)據(jù) | 無返回值或者返回是否加載完成的結(jié)果 |
save/update | 保存或修改某些數(shù)據(jù) | 無返回值或者返回是否操作成功的結(jié)果 |
function queryProductList() {
常量命名規(guī)范
常量名【應該】使用全部使用大寫字母和下劃線來組合來命名,下劃線用以分割單詞;
const MAX_IMAGE_SIZE = 10 * 1024 * 1024;
const MaxImageSize = 10 * 1024 * 1024;
const maximagesize = 10 * 1024 * 1024;
const maxImageSize = 10 * 1024 * 1024;
類或構(gòu)造函數(shù)命名規(guī)范
類名或構(gòu)造函數(shù)【應該】使用大駝峰式命名法,即首字母大寫。類的成員屬性和方法的命名跟變量和函數(shù)保持一致,只是私有屬性和方法名應該以下劃線開頭;
this.getName = function () {
this.setName = function (value) {
2.2 恰如其分的注釋
注釋是對于代碼中巧妙的、 晦澀的或重要的地方加以解釋。
2.2.1 基本要求
優(yōu)先考慮命名而不是注釋。注釋固然很重要,但最好的文檔其實就是代碼本身。優(yōu)先考慮使用有意義的類型名和變量名,不要為了注釋而注釋,某種程度上,因為需要注釋常常因為它不是很好讀,這個時候應該先考慮你的函數(shù)名和變量名是不是應該改改。
不要給不好的名字加注釋
應該把名字改好
聲明高層次的意圖而非細節(jié)。不要描述顯而易見的現(xiàn)象,永遠不要用自然語言翻譯代碼,而應當解釋代碼為什么要這么做,或者是為了讓代碼文檔化。比如 為接口提供功能說明,為復雜的實現(xiàn)提供邏輯說明,以闡述為什么是這樣而不是那樣,標注代碼中的缺陷,解釋讀者意料之外的行為等。
對代碼的翻譯,是沒有價值的注釋
說明背后為什么是它,而不是其他寫法
公布可能得陷阱,提供總結(jié)性注釋。難免在實現(xiàn)中引入hack代碼或考慮但未處理的邊界場景,此時應為后來者顯示標注,以便后續(xù)回溯和修復。在大塊長函數(shù)前,總結(jié)其用途和用法。
2.2.2 團隊規(guī)范
JS支持兩種不同類型的注釋:單行注釋和多行注釋。
使用 // 作為單行注釋,【應該】在注釋前插入一個空行且使 // 與注釋文字之間保留一個空格。
console.log('fetching type...');
var type = this.type || 'no type';
console.log('fetching type...');
var type = this.type || 'no type';
使用 /** ... */ 作為多行注釋,包含描述、指定所有參數(shù)和返回值的類型和值。若開始 /* 和結(jié)束 */ 都在一行,【應該】采用單行注釋。若至少三行注釋時,【應該】第一行為 /*,最后行為 */,其他行以 * 開始,并且注釋文字與 * 保留一個空格。
函數(shù)(方法)注釋也是多行注釋的一種,但是包含了特殊的注釋要求,常見的注釋關(guān)鍵字有@param、@return、@author、@version、@example,更多用法參照JSDoc
function mergeCells(grid: Ext.Grid.Panel, cols: Number[]) {
使用 // @TODO 標注問題及問題的解決方式;
2.3 合理地組織代碼
把流程控制變得易讀
條件語句中變化的值放左邊,穩(wěn)定的值放右邊
優(yōu)先處理條件為true的邏輯、簡單的情況、有趣和可疑的情況
fs.readFile('/file-does-not-exist', (err, data) => {
通過提早返回來減少嵌套
if (user_result == 'SUCCESS') {
if (permission_result != 'SUCCESS') {
if (user_result != 'SUCCESS') {
if (permission_result != 'SUCCESS') {
拆分過長的表達式
三目運算符只在最簡單的情況下使用,優(yōu)先用if/else;不要濫用短路邏輯,部分判斷邏輯可以交由后端處理。
mode === 'multi' ? hasSelectedAll ? '已選中所有項' : '未選中所有項' : mode === 'single' ? '僅可單選' : null
state === 'INIT' && sign_state === ''
|| state === 'CHECKING' && sign_state === 'NOT_SIGNABLE'
|| state === 'AUDITING' && sign_state === 'SIGNED'
使用易懂的臨時變量,或封裝成函數(shù)
if (request.user.id == document.owner_id){
const user_owns_document = (request.user.id = document.owner_id)
別引入無謂的變量,減小變量的作用域
變量當然是越少越好,太多則難以跟蹤它們的動向,要去掉那些臨時變量、中間結(jié)果、控制流變量。
const isCurrent = timestamp === now
縮小變量的作用域,讓你的變量對盡量少的代碼可見,防止命名空間污染。
function getUserName () {
只寫一次的變量更好,不斷變化的值讓人難以理解,跟蹤這種變量的值很有難度,善用typescript與const。
通用邏輯提取與封裝
對于多次重復使用的值,可以提取為定義為變量/常量。
if (response.data.user.status === 1) {
} else if (response.data.user.status === 2) {
} else if (response.data.user.status === 3) {
const status = response.data.user.status;
case STATUS_MAP.PROCESSING:
case STATUS_MAP.ACTICATED:
case STATUS_MAP.DISABLED:
提取重復且通用的函數(shù),以提供更好的可讀性、可維護性、及復用的可能。一段代碼一次只做一件事,可以通過拆分為段落/函數(shù)/類,讓其更清晰。
function errorHandler(error) {
log('axios response err', error)
async function getUser() {
const response = await axios.get('/user?ID=12345')
組件的封裝,遵循物料規(guī)范封裝。
2.4 前端技能提升
對于前端基礎(chǔ)與框架,經(jīng)常通讀其整個API,主動了解其原理,保持對他們的熟悉程度。熟知其能力邊界,并在編碼過程中加以應用和實踐。
善用已有的類庫/物料庫,比如用浮點數(shù)運算的decimal.js、時間和日期計算的day.js,以及團隊的工程模板、函數(shù)庫、物料庫。
一方面已封裝的類庫有較完備的建設(shè),如使用文檔、測試用例、符合團隊規(guī)范等。另一方面,也通過廣泛應用完成踩坑,有更好的穩(wěn)定性。一個好的類庫,也是我們學習的對象,可以多問幾個為什么,它為什么產(chǎn)生?它能做什么?它的代碼組織形式有什么優(yōu)點?它存在什么潛在風險?等等。
2.5 前端安全與用戶體驗
安全無小事,前端是Web安全的第一道屏障。
用戶輸入驗證和清理:需要驗證和清理用戶輸入的數(shù)據(jù),以防止SQL注入和其他形式的攻擊。不要信任用戶輸入的任何數(shù)據(jù)。服務端返回的HTML不要直接渲染在頁面中。
跨站腳本攻擊(XSS):這是最常見的前端安全問題之一。攻擊者通過在網(wǎng)站上注入惡意腳本,當其他用戶訪問該網(wǎng)站時,這些腳本會在他們的瀏覽器上運行。為了防止XSS攻擊,需要確保你的應用不會接受或執(zhí)行用戶提供的未經(jīng)驗證和清理的HTML代碼。
跨站請求偽造(CSRF):在這種攻擊中,攻擊者會誘導用戶去點擊一個鏈接或者加載一個頁面,這個鏈接或頁面會包含一個請求,這個請求會在用戶的瀏覽器中向另一個網(wǎng)站發(fā)送。為了防止CSRF攻擊,你可以使用一些防護措施,例如使用同步的防偽令牌(anti-forgery token)。
HTTPS和HTTP嚴格傳輸安全(HSTS):HTTPS通過加密你的網(wǎng)站的流量來提供安全,而HSTS則確保瀏覽器只通過HTTPS與你的網(wǎng)站通信,即使用戶嘗試通過HTTP訪問。
內(nèi)容安全策略(CSP):CSP是一個額外的安全層,它幫助檢測和緩解某些類型的攻擊,包括XSS和數(shù)據(jù)注入攻擊。CSP允許網(wǎng)頁開發(fā)者聲明頁面的內(nèi)容來源,瀏覽器只會執(zhí)行或渲染從這些來源加載的代碼。
前端用戶體驗是指用戶在使用網(wǎng)站或應用時的感受。包括了網(wǎng)站的性能、頁面布局、頁面交互、錯誤處理等多個方面。
在處理需求時,需要多站在用戶角度思考,針對不同手機(小屏幕/折疊屏)是怎樣的體驗;對于按鈕點擊/數(shù)據(jù)請求時,會不會產(chǎn)生頁面無響應、無UI反饋、多次請求等異常CASE;操作流暢性、學習和認知成本會不會過高等等。
3.電商前端研發(fā)規(guī)范
3.1 語言框架與物料
基于團隊共識,約定Web開發(fā)中心所有增量工程均采用 React 技術(shù)棧
PC端中后臺項目基礎(chǔ)組件庫默認為 MUI
移動端增量頁面首選一碼多投解決方案 :RTX文檔,組件庫:KproM、Kid-ui-plus
注:MUI、RTX、KproM、Kid-ui-plus等為快手自研技術(shù)產(chǎn)品,后續(xù)會一一為大家介紹。
3.2 工程編碼規(guī)范
代碼風格上的一致性,借助于Lint工具和類型檢查器,既約束了代碼風格,有規(guī)避了語法錯誤。
編碼風格
ESLint 負責JavaScript 的語法檢測
StyleLint 負責 CSS 的語法檢查及排版優(yōu)化
Prettier 負責所有文件的格式化
在團隊的工程體系下,可以使用 Jia 命令來觸發(fā)代碼進行檢查和自動修復。其背后使用code-spec-unid,他將ESLint、Stylelint、Prettier 三者結(jié)合使用,Prettier 的配置為基礎(chǔ),去覆蓋 ESLint 與 Stylelint 的配置中關(guān)于代碼格式化的部分,讓 ESLint 與 Stylelint 專注于做 JS 與 CSS的語法檢查, 而所有的代碼格式化工作交由 Prettier 完成。
jia check
注:jia命令為快手電商前端自研開發(fā)工具,后續(xù)會為大家介紹。
提交規(guī)范
Husky 結(jié)合 Lint-staged 與 CommitLint 規(guī)范代碼提交
提交消息時須遵循約定格式 <type>: <subject>,type 是 提交的類別,subject 是對提交的簡短描述。注意冒號后有空格,比如:git commit -m 'feat: 增加國際化功能'。以下是 type 的枚舉:
1. upd:更新某功能(不是 feat, 不是 fix)
4. docs:文檔(documentation)
6. refactor:重構(gòu)(即不是新增功能,也不是修改bug的代碼變動)
7. chore:構(gòu)建過程或輔助工具的變動
兼容性處理
Browserslist 提供瀏覽器兼容性個性化配置
Babel 轉(zhuǎn)譯高版本 JavaScript 以向后兼容
Postcss 結(jié)合其插件 Autoprefixer 為 CSS進行預處理以向后兼容
在實際開發(fā)過程中,絕大部分情況無需感知其配置,通過 Jia 命令生成的項目已實現(xiàn)相關(guān)配置。
3.4 代碼靜態(tài)檢查
1. kdev 代碼掃描(代碼分)
天穹
源碼檢查:對倉庫源碼進行敏感詞、代碼規(guī)范、內(nèi)外部域名、License等檢查,打造符合規(guī)范的倉庫源碼
產(chǎn)物檢查:檢查構(gòu)建產(chǎn)物類型、大小,是否含有sourcemap文件等, 保障線上產(chǎn)物安全穩(wěn)定
動態(tài)檢查:檢查頁面代碼覆蓋率、LCP、FMP等指標,提供性能優(yōu)化建議,帶您全面感知頁面性能
注:kdev、天穹為快手自研技術(shù)產(chǎn)品,后續(xù)為大家介紹詳細功能。
該文章在 2024/11/21 17:28:28 編輯過