轉(zhuǎn)載一篇非常好的項(xiàng)目工程管理策略猎塞,原文
項(xiàng)目規(guī)范
JavaScript工程項(xiàng)目的一系列最佳實(shí)踐策略
當(dāng)您在青蔥的田野里翻滾一般歡樂(而不受約束)地開發(fā)一個(gè)新項(xiàng)目圃伶,對(duì)其他人而言維護(hù)這樣一個(gè)項(xiàng)目簡(jiǎn)直就是一個(gè)潛在的可怕的噩夢(mèng)化撕。以下列出的指南是我們?cè)?a target="_blank">elsewhen的大多數(shù)JavaScript項(xiàng)目中發(fā)現(xiàn)签舞,撰寫和收集的最佳實(shí)踐(至少我們是這樣認(rèn)為的)划煮。如果您想分享其他最佳實(shí)踐照筑,或者認(rèn)為其中一些指南應(yīng)該刪除窗宇。歡迎隨時(shí)與我們分享。
<a name="git"></a>
1. Git
<a name="some-git-rules"></a>
1.1 一些Git規(guī)則
這里有一套規(guī)則要牢記:
-
在功能分支中執(zhí)行開發(fā)工作侦另。
為什么:
因?yàn)檫@樣秩命,所有的工作都是在專用的分支而不是在主分支上隔離完成的。它允許您提交多個(gè) pull request 而不會(huì)導(dǎo)致混亂褒傅。您可以持續(xù)迭代提交弃锐,而不會(huì)使得那些很可能還不穩(wěn)定而且還未完成的代碼污染 master 分支。更多請(qǐng)閱讀...
-
從
develop
獨(dú)立出分支殿托。為什么:
這樣霹菊,您可以保持
master
分支中的代碼穩(wěn)定性,這樣就不會(huì)導(dǎo)致構(gòu)建問題支竹,并且?guī)缀蹩梢灾苯佑糜诎l(fā)布(當(dāng)然旋廷,這可能對(duì)某些項(xiàng)目來說要求會(huì)比較高)。 -
永遠(yuǎn)也不要將分支(直接)推送到
develop
或者master
礼搁,請(qǐng)使用合并請(qǐng)求(Pull Request)饶碘。為什么:
通過這種方式,它可以通知整個(gè)團(tuán)隊(duì)他們已經(jīng)完成了某個(gè)功能的開發(fā)馒吴。這樣開發(fā)伙伴就可以更容易對(duì)代碼進(jìn)行 code review扎运,同時(shí)還可以互相討論所提交的需求功能瑟曲。
-
在推送所開發(fā)的功能并且發(fā)起合并請(qǐng)求前,請(qǐng)更新您本地的
develop
分支并且完成交互式變基操作(interactive rebase)豪治。為什么:
rebase 操作會(huì)將(本地開發(fā)分支)合并到被請(qǐng)求合并的分支(
master
或develop
)中洞拨,并將您本地進(jìn)行的提交應(yīng)用于所有歷史提交的最頂端,而不會(huì)去創(chuàng)建額外的合并提交(假設(shè)沒有沖突的話)鬼吵,從而可以保持一個(gè)漂亮而干凈的歷史提交記錄扣甲。 更多請(qǐng)閱讀 ... 請(qǐng)確保在變基并發(fā)起合并請(qǐng)求之前解決完潛在的沖突篮赢。
-
合并分支后刪除本地和遠(yuǎn)程功能分支齿椅。
為什么:
如果不刪除需求分支,大量僵尸分支的存在會(huì)導(dǎo)致分支列表的混亂启泣。而且該操作還能確保有且僅有一次合并到
master
或develop
涣脚。只有當(dāng)這個(gè)功能還在開發(fā)中時(shí)對(duì)應(yīng)的功能分支才存在。 -
在進(jìn)行合并請(qǐng)求之前寥茫,請(qǐng)確保您的功能分支可以成功構(gòu)建遣蚀,并已經(jīng)通過了所有的測(cè)試(包括代碼規(guī)則檢查)。
為什么:
因?yàn)槟磳⒋a提交到這個(gè)穩(wěn)定的分支纱耻。而如果您的功能分支測(cè)試未通過芭梯,那您的目標(biāo)分支的構(gòu)建有很大的概率也會(huì)失敗。此外弄喘,確保在進(jìn)行合并請(qǐng)求之前應(yīng)用代碼規(guī)則檢查玖喘。因?yàn)樗兄谖覀兇a的可讀性,并減少格式化的代碼與實(shí)際業(yè)務(wù)代碼更改混合在一起導(dǎo)致的混亂問題蘑志。
-
使用 這個(gè)
.gitignore
文件累奈。為什么:
此文件已經(jīng)囊括了不應(yīng)該和您開發(fā)的代碼一起推送至遠(yuǎn)程倉(cāng)庫(kù)(remote repository)的系統(tǒng)文件列表。另外急但,此文件還排除了大多數(shù)編輯器的設(shè)置文件夾和文件澎媒,以及最常見的(工程開發(fā))依賴目錄。
-
保護(hù)您的
develop
和master
分支波桩。為什么:
這樣可以保護(hù)您的生產(chǎn)分支免受意外情況和不可回退的變更戒努。 更多請(qǐng)閱讀... Github 以及 Bitbucket
<a name="git-workflow"></a>
1.2 Git 工作流
基于以上原因, 我們將 功能分支工作流 , 交互式變基的使用方法 結(jié)合一些 Gitflow中的基礎(chǔ) (比如镐躲,命名和使用一個(gè)develop branch)一起使用储玫。 主要步驟如下:
-
針對(duì)一個(gè)新項(xiàng)目, 在您的項(xiàng)目目錄初始化您的項(xiàng)目。 如果是(已有項(xiàng)目)隨后的功能開發(fā)/代碼變動(dòng)匀油,這一步請(qǐng)忽略缘缚。
cd <項(xiàng)目目錄> git init
-
檢出(Checkout) 一個(gè)新的功能或故障修復(fù)(feature/bug-fix)分支。
git checkout -b <分支名稱>
-
新增代碼變更敌蚜。
git add git commit -a
為什么:
git commit -a
會(huì)獨(dú)立啟動(dòng)一個(gè)編輯器用來編輯您的說明信息桥滨,這樣的好處是可以專注于寫這些注釋說明。更多請(qǐng)閱讀 章節(jié) 1.3。 -
保持與遠(yuǎn)程(develop分支)的同步齐媒,以便(使得本地 develop 分支)拿到最新變更蒲每。
git checkout develop git pull
為什么:
當(dāng)您進(jìn)行(稍后)變基操作的時(shí)候,保持更新會(huì)給您一個(gè)在您的機(jī)器上解決沖突的機(jī)會(huì)喻括。這比(不同步更新就進(jìn)行下一步的變基操作并且)發(fā)起一個(gè)與遠(yuǎn)程倉(cāng)庫(kù)沖突的合并請(qǐng)求要好邀杏。
-
(切換至功能分支并且)通過交互式變基從您的develop分支中獲取最新的代碼提交,以更新您的功能分支唬血。
git checkout <branchname> git rebase -i --autosquash develop
為什么:
您可以使用
--autosquash
將所有提交壓縮到單個(gè)提交望蜡。沒有人會(huì)愿意(看到)develop
分支中的單個(gè)功能開發(fā)就占據(jù)如此多的提交歷史。 更多請(qǐng)閱讀... -
如果沒有沖突請(qǐng)?zhí)^此步驟拷恨,如果您有沖突, 就需要解決它們并且繼續(xù)變基操作脖律。
git add <file1> <file2> ... git rebase --continue
-
推送您的(功能)分支。變基操作會(huì)改變提交歷史, 所以您必須使用
-f
強(qiáng)制推送到遠(yuǎn)程(功能)分支腕侄。 如果其他人與您在該分支上進(jìn)行協(xié)同開發(fā)小泉,請(qǐng)使用破壞性沒那么強(qiáng)的--force-with-lease
參數(shù)。git push -f
為什么:
當(dāng)您進(jìn)行 rebase 操作時(shí)冕杠,您會(huì)改變功能分支的提交歷史微姊。這會(huì)導(dǎo)致 Git 拒絕正常的
git push
。那么分预,您只能使用-f
或--force
參數(shù)了兢交。更多請(qǐng)閱讀... 提交一個(gè)合并請(qǐng)求(Pull Request)。
Pull Request 會(huì)被負(fù)責(zé)代碼審查的同事接受噪舀,合并和關(guān)閉魁淳。
-
如果您完成了開發(fā),請(qǐng)記得刪除您的本地分支与倡。
git branch -d <分支>
(使用以下代碼)刪除所有已經(jīng)不在遠(yuǎn)程倉(cāng)庫(kù)維護(hù)的分支界逛。
git fetch -p && for branch in `git branch -vv | grep ': gone]' | awk '{print $1}'`; do git branch -D $branch; done
<a name="writing-good-commit-messages"></a>
1.3 如何寫好 Commit Message
堅(jiān)持遵循關(guān)于提交的標(biāo)準(zhǔn)指南,會(huì)讓在與他人合作使用 Git 時(shí)更容易纺座。這里有一些經(jīng)驗(yàn)法則 (來源):
-
用新的空行將標(biāo)題和主體兩者隔開息拜。
為什么:
Git 非常聰明,它可將您提交消息的第一行識(shí)別為摘要净响。實(shí)際上少欺,如果您嘗試使用
git shortlog
,而不是git log
馋贤,您會(huì)看到一個(gè)很長(zhǎng)的提交消息列表赞别,只會(huì)包含提交的 id 以及摘要(,而不會(huì)包含主體部分)配乓。 -
將標(biāo)題行限制為50個(gè)字符仿滔,并將主體中一行超過72個(gè)字符的部分折行顯示惠毁。
為什么:
提交應(yīng)盡可能簡(jiǎn)潔明了,而不是寫一堆冗余的描述崎页。 更多請(qǐng)閱讀...
標(biāo)題首字母大寫鞠绰。
不要用句號(hào)結(jié)束標(biāo)題。
-
在標(biāo)題中使用 祈使句 飒焦。
為什么:
與其在寫下的信息中描述提交者做了什么蜈膨,不如將這些描述信息作為在這些提交被應(yīng)用于該倉(cāng)庫(kù)后將要完成的操作的一個(gè)說明。更多請(qǐng)閱讀...
使用主體部分去解釋 是什么 和 為什么 而不是 怎么做牺荠。
<a name="文檔"></a>
2. 文檔
[圖片上傳失敗...(image-faddca-1610332200848)]
- 可以使用這個(gè) 模板 作為
README.md
(的一個(gè)參考), 隨時(shí)歡迎添加里面沒有的內(nèi)容翁巍。 - 對(duì)于具有多個(gè)存儲(chǔ)庫(kù)的項(xiàng)目,請(qǐng)?jiān)诟髯缘?
README.md
文件中提供它們的鏈接志电。 - 隨項(xiàng)目的進(jìn)展曙咽,持續(xù)地更新
README.md
。 - 給您的代碼添加詳細(xì)的注釋挑辆,這樣就可以清楚每個(gè)主要部分的含義。
- 如果您正在使用的某些代碼和方法孝情,在github或stackoverflow上已經(jīng)有公開討論鱼蝉,請(qǐng)?jiān)谀淖⑨屩邪@些鏈接,
- 不要把注釋作為壞代碼的借口箫荡。保持您的代碼干凈整潔魁亦。
- 也不要把那些清晰的代碼作為不寫注釋的借口。
- 當(dāng)代碼更新羔挡,也請(qǐng)確保注釋的同步更新洁奈。
<a name="environments"></a>
3. 環(huán)境
[圖片上傳失敗...(image-e7031e-1610332200848)]
-
如果需要,請(qǐng)分別定義
development
,test
和production
三個(gè)環(huán)境绞灼。為什么:
不同的環(huán)境可能需要不同的數(shù)據(jù)利术、token、API低矮、端口等印叁。您可能需要一個(gè)隔離的
development
環(huán)境,它調(diào)用 mock 的 API军掂,mock 會(huì)返回可預(yù)測(cè)的數(shù)據(jù)轮蜕,使自動(dòng)和手動(dòng)測(cè)試變得更加容易』茸叮或者您可能只想在production
環(huán)境中才啟用 Google Analytics(分析)跃洛。 更多請(qǐng)閱讀...
-
依據(jù)不同的環(huán)境變量加載部署的相關(guān)配置,不要將這些配置作為常量添加到代碼庫(kù)中终议, 看這個(gè)例子.
為什么:
您會(huì)有令牌汇竭,密碼和其他有價(jià)值的信息闲延。這些配置應(yīng)正確地從應(yīng)用程序內(nèi)部分離開來,這樣代碼庫(kù)就可以隨時(shí)獨(dú)立發(fā)布韩玩,不會(huì)包含這些敏感配置信息垒玲。
怎么做:
使用.env
文件來存儲(chǔ)環(huán)境變量,并將其添加到.gitignore
中使得排除而不被提交(到倉(cāng)庫(kù))找颓。另外合愈,再提交一個(gè).env.example
作為開發(fā)人員的參考配置。對(duì)于生產(chǎn)環(huán)境击狮,您應(yīng)該依舊以標(biāo)準(zhǔn)化的方式設(shè)置環(huán)境變量佛析。
更多請(qǐng)閱讀 -
建議您在應(yīng)用程序啟動(dòng)之前校驗(yàn)一下環(huán)境變量。 看這個(gè)例子 彪蓬,它使用了
joi
去校驗(yàn)提供的值寸莫。為什么:
它可能會(huì)將其他人從上小時(shí)的故障排查中解救。
<a name="consistent-dev-environments"></a>
3.1 一致的開發(fā)環(huán)境:
-
在
package.json
里的engines
中設(shè)置您的node版本档冬。為什么:
讓其他人可以清晰的知道這個(gè)項(xiàng)目中用的什么node版本膘茎。 更多請(qǐng)閱讀...
-
另外,使用
nvm
并在您的項(xiàng)目根目錄下創(chuàng)建一個(gè).nvmrc
文件酷誓。不要忘了在文檔中標(biāo)注披坏。為什么:
任何使用
nvm
的人都可以使用nvm use
來切換到合適的node版本。 更多請(qǐng)閱讀... -
最好設(shè)置一個(gè)檢查 node 和 npm 版本的
preinstall
腳本盐数。為什么:
某些依賴項(xiàng)可能會(huì)在新版本的 npm 中安裝失敗棒拂。
-
如果可以的話最好使用 Docker 鏡像。
為什么:
它可以在整個(gè)工作流程中為您提供一致的環(huán)境玫氢,而且不用花太多的時(shí)間來解決依賴或配置帚屉。 更多請(qǐng)閱讀...
-
使用本地模塊,而不是使用全局安裝的模塊漾峡。
為什么:
您不能指望您的同事在自己的全局環(huán)境都安裝了相應(yīng)的模塊攻旦,本地模塊可以方便您分享您的工具。
<a name="consistent-dependencies"></a>
3.2 依賴一致性:
-
確保您的團(tuán)隊(duì)成員獲得與您完全相同的依賴灰殴。
為什么:
因?yàn)槟Ma在任何開發(fā)環(huán)境中運(yùn)行都能像預(yù)期的一樣敬特。 更多請(qǐng)閱讀...
怎么做:
在
npm@5
或者更高版本中使用package-lock.json
。我們沒有 npm@5:
或者牺陶,您可以使用
yarn
伟阔,并確保在README.md
中標(biāo)注了使用yarn
。您的鎖文件和package.json
在每次依賴關(guān)系更新后應(yīng)該具有相同的版本掰伸。更多請(qǐng)閱讀...我不太喜歡
Yarn
:居然不喜歡 Yarn皱炉,太糟糕了。對(duì)于舊版本的
npm
狮鸭,在安裝新的依賴關(guān)系時(shí)使用-save --save-exact
合搅,并在發(fā)布之前創(chuàng)建npm-shrinkwrap.json
多搀。 更多請(qǐng)閱讀...
<a name="dependencies"></a>
4. 依賴
[圖片上傳失敗...(image-d66227-1610332200848)]
持續(xù)跟蹤您當(dāng)前的可用依賴包: 舉個(gè)例子,
npm ls --depth=0
。更多請(qǐng)閱讀...-
查看這些軟件包是否未使用或者與開發(fā)項(xiàng)目無關(guān):
depcheck
灾部。 更多請(qǐng)閱讀...為什么:
您可能會(huì)在代碼中包含未使用的庫(kù)康铭,這會(huì)增大生產(chǎn)包的大小。請(qǐng)搜索出這些未使用的依賴關(guān)系并去掉它們吧赌髓。
-
在使用依賴之前从藤,請(qǐng)檢查他的下載統(tǒng)計(jì)信息,看看它是否被社區(qū)大量使用:
npm-stat
. 更多請(qǐng)閱讀...為什么:
更多的使用量很大程度上意味著更多的貢獻(xiàn)者锁蠕,這通常意味著擁有更好的維護(hù)夷野,這些能確保錯(cuò)誤能夠被快速地發(fā)現(xiàn)并修復(fù)。
-
在使用依賴之前荣倾,請(qǐng)檢查它是否具有良好而成熟的版本發(fā)布頻率與大量的維護(hù)者:例如悯搔,
npm view async
。更多請(qǐng)閱讀...為什么:
如果維護(hù)者沒有足夠快地合并修補(bǔ)程序舌仍,那么這些貢獻(xiàn)者也將會(huì)變得不積極不高效妒貌。
如果需要使用那些不太熟悉的依賴包,請(qǐng)?jiān)谑褂弥芭c團(tuán)隊(duì)進(jìn)行充分討論抡笼。
-
始終確保您的應(yīng)用程序在最新版本的依賴包上面能正常運(yùn)行苏揣,而不是無法使用:
npm outdated
。 更多請(qǐng)閱讀...為什么:
依賴關(guān)系更新有時(shí)包含破壞性更改推姻。當(dāng)顯示需要更新時(shí),請(qǐng)始終先查看其發(fā)行說明框沟。并逐一地更新您的依賴項(xiàng)藏古,如果出現(xiàn)任何問題,可以使故障排除更容易忍燥∨≡危可以使用類似 npm-check-updates 的酷炫工具(來解決這個(gè)問題)。
檢查包是否有已知的安全漏洞梅垄,例如: Snyk厂捞。
<a name="testing"></a>
5. 測(cè)試
[圖片上傳失敗...(image-4c761c-1610332200848)]
-
如果需要循榆,請(qǐng)構(gòu)建一個(gè)
test
環(huán)境.為什么:
雖然有時(shí)在
production
模式下端到端測(cè)試可能看起來已經(jīng)足夠了主卫,但有一些例外:比如您可能不想在生產(chǎn)環(huán)境下啟用數(shù)據(jù)分析功能,只能用測(cè)試數(shù)據(jù)來填充(污染)某人的儀表板屿聋。另一個(gè)例子是机久,您的API可能在production
中才具有速率限制臭墨,并在請(qǐng)求達(dá)到一定量級(jí)后會(huì)阻止您的測(cè)試請(qǐng)求。 -
將測(cè)試文件放在使用
* .test.js
或* .spec.js
命名約定的測(cè)試模塊膘盖,比如moduleName.spec.js
為什么:
您肯定不想進(jìn)入一個(gè)層次很深的文件夾結(jié)構(gòu)來查找里面的單元測(cè)試胧弛。更多請(qǐng)閱讀...
-
將其他測(cè)試文件放入獨(dú)立的測(cè)試文件夾中以避免混淆尤误。
為什么:
一些測(cè)試文件與任何特定的文件實(shí)現(xiàn)沒有特別的關(guān)系。您只需將它放在最有可能被其他開發(fā)人員找到的文件夾中:
__test__
文件夾结缚。這個(gè)名字:__test__
也是現(xiàn)在的標(biāo)準(zhǔn)损晤,被大多數(shù)JavaScript測(cè)試框架所接受。 -
編寫可測(cè)試代碼红竭,避免副作用(side effects)尤勋,提取副作用,編寫純函數(shù)德崭。
為什么:
您想要將業(yè)務(wù)邏輯拆分為單獨(dú)的測(cè)試單元斥黑。您必須“盡量減少不可預(yù)測(cè)性和非確定性過程對(duì)代碼可靠性的影響”。 更多請(qǐng)閱讀...
純函數(shù)是一種總是為相同的輸入返回相同輸出的函數(shù)眉厨。相反地锌奴,不純的函數(shù)是一種可能會(huì)有副作用,或者取決于來自外部的條件來決定產(chǎn)生對(duì)應(yīng)的輸出值的函數(shù)憾股。這使得它不那么可預(yù)測(cè)鹿蜀。更多請(qǐng)閱讀...
-
使用靜態(tài)類型檢查器
為什么:
有時(shí)您可能需要一個(gè)靜態(tài)類型檢查器。它為您的代碼帶來一定程度的可靠性服球。更多請(qǐng)閱讀...
-
先在本地
develop
分支運(yùn)行測(cè)試茴恰,待測(cè)試通過后,再進(jìn)行pull請(qǐng)求斩熊。為什么:
您不想成為一個(gè)導(dǎo)致生產(chǎn)分支構(gòu)建失敗的人吧往枣。在您的
rebase
之后運(yùn)行測(cè)試,然后再將您改動(dòng)的功能分支推送到遠(yuǎn)程倉(cāng)庫(kù)粉渠。 -
記錄您的測(cè)試分冈,包括在
README.md
文件中的相關(guān)說明部分。為什么:
這是您為其他開發(fā)者或者 DevOps 專家或者 QA 或者其他如此幸運(yùn)能和您一起協(xié)作的人留下的便捷筆記霸株。
<a name="structure-and-naming"></a>
6. 結(jié)構(gòu)布局與命名
[圖片上傳失敗...(image-6d90ef-1610332200848)]
- 請(qǐng)圍繞產(chǎn)品功能/頁(yè)面/組件雕沉,而不是圍繞角色來組織文件。此外去件,請(qǐng)將測(cè)試文件放在他們對(duì)應(yīng)實(shí)現(xiàn)的旁邊坡椒。
**不規(guī)范**
```
.
├── controllers
| ├── product.js
| └── user.js
├── models
| ├── product.js
| └── user.js
```
**規(guī)范**
```
.
├── product
| ├── index.js
| ├── product.js
| └── product.test.js
├── user
| ├── index.js
| ├── user.js
| └── user.test.js
```
_為什么:_
> 比起一個(gè)冗長(zhǎng)的列表文件,創(chuàng)建一個(gè)單一責(zé)權(quán)封裝的小模塊尤溜,并在其中包括測(cè)試文件倔叼。將會(huì)更容易瀏覽,更一目了然靴跛。
-
將其他測(cè)試文件放在單獨(dú)的測(cè)試文件夾中以避免混淆缀雳。
為什么:
這樣可以節(jié)約您的團(tuán)隊(duì)中的其他開發(fā)人員或DevOps專家的時(shí)間。
-
使用
./config
文件夾梢睛,不要為不同的環(huán)境制作不同的配置文件肥印。為什么:
當(dāng)您為不同的目的(數(shù)據(jù)庫(kù)识椰,API等)分解不同的配置文件;將它們放在具有容易識(shí)別名稱(如
config
)的文件夾中才是有意義的。請(qǐng)記住不要為不同的環(huán)境制作不同的配置文件深碱。這樣并不是具有擴(kuò)展性的做法腹鹉,如果這樣,就會(huì)導(dǎo)致隨著更多應(yīng)用程序部署被創(chuàng)建出來敷硅,新的環(huán)境名稱也會(huì)不斷被創(chuàng)建功咒,非常混亂绞蹦。
配置文件中使用的值應(yīng)通過環(huán)境變量提供力奋。 更多請(qǐng)閱讀... -
將腳本文件放在
./scripts
文件夾中。包括bash
腳本和node
腳本幽七。為什么:
很可能最終會(huì)出現(xiàn)很多腳本文件景殷,比如生產(chǎn)構(gòu)建,開發(fā)構(gòu)建澡屡,數(shù)據(jù)庫(kù)feeders猿挚,數(shù)據(jù)庫(kù)同步等。
-
將構(gòu)建輸出結(jié)果放在
./build
文件夾中驶鹉。將build/
添加到.gitignore
中以便忽略此文件夾绩蜻。為什么:
命名為您最喜歡的就行,
dist
看起來也蠻酷的室埋。但請(qǐng)確保與您的團(tuán)隊(duì)保持一致性办绝。放置在該文件夾下的東西應(yīng)該是已經(jīng)生成(打包、編譯姚淆、轉(zhuǎn)換)或者被移到這里的八秃。您產(chǎn)生什么編譯結(jié)果,您的隊(duì)友也可以生成同樣的結(jié)果肉盹,所以沒有必要將這些結(jié)果提交到遠(yuǎn)程倉(cāng)庫(kù)中。除非您故意希望提交上去疹尾。 文件名和目錄名請(qǐng)使用
PascalCase
camelCase
風(fēng)格上忍。組件請(qǐng)使用PascalCase
風(fēng)格。
CheckBox/index.js
應(yīng)該代表CheckBox
組件纳本,也可以寫成CheckBox.js
窍蓝,但是不能寫成冗長(zhǎng)的CheckBox/CheckBox.js
或checkbox/CheckBox.js
。-
理想情況下繁成,目錄名稱應(yīng)該和
index.js
的默認(rèn)導(dǎo)出名稱相匹配吓笙。為什么:
這樣您就可以通過簡(jiǎn)單地導(dǎo)入其父文件夾直接使用您預(yù)期的組件或模塊。
<a name="code-style"></a>
7. 代碼風(fēng)格
[圖片上傳失敗...(image-f355ae-1610332200848)]
<a name="code-style-check"></a>
7.1 若干個(gè)代碼風(fēng)格指導(dǎo)
-
對(duì)新項(xiàng)目請(qǐng)使用 Stage2 和更高版本的 JavaScript(現(xiàn)代化)語(yǔ)法巾腕。對(duì)于老項(xiàng)目面睛,保持與老的語(yǔ)法一致絮蒿,除非您打算把老的項(xiàng)目也更新為現(xiàn)代化風(fēng)格。
為什么:
這完全取決于您的選擇叁鉴。我們使用轉(zhuǎn)換器來使用新的語(yǔ)法糖土涝。Stage2更有可能最終成為規(guī)范的一部分,而且僅僅只需經(jīng)過小版本的迭代就會(huì)成為規(guī)范幌墓。
-
在構(gòu)建過程中包含代碼風(fēng)格檢查但壮。
為什么:
在構(gòu)建時(shí)中斷下一步操作是一種強(qiáng)制執(zhí)行代碼風(fēng)格檢查的方法。強(qiáng)制您認(rèn)真對(duì)待代碼常侣。請(qǐng)確保在客戶端和服務(wù)器端代碼都執(zhí)行代碼檢查蜡饵。 更多請(qǐng)閱讀...
-
使用 ESLint - Pluggable JavaScript linter 去強(qiáng)制執(zhí)行代碼檢查。
為什么:
我們個(gè)人很喜歡
eslint
胳施,不強(qiáng)制您也喜歡溯祸。它擁有支持更多的規(guī)則,配置規(guī)則的能力和添加自定義規(guī)則的能力巾乳。 針對(duì) JavaScript 我們使用Airbnb JavaScript Style Guide , 更多請(qǐng)閱讀您没。 請(qǐng)依據(jù)您的項(xiàng)目和您的團(tuán)隊(duì)選擇使用所需的JavaScript 代碼風(fēng)格。
-
當(dāng)使用FlowType的時(shí)候胆绊,我們使用 ESLint的Flow樣式檢查規(guī)則氨鹏。。
為什么:
Flow 引入了很少的語(yǔ)法压状,而這些語(yǔ)法仍然需要遵循代碼風(fēng)格并進(jìn)行檢查仆抵。
-
使用
.eslintignore
將某些文件或文件夾從代碼風(fēng)格檢查中排除。為什么:
當(dāng)您需要從風(fēng)格檢查中排除幾個(gè)文件時(shí)种冬,就再也不需要通過
eslint-disable
注釋來污染您的代碼了镣丑。 -
在Pull Request之前,請(qǐng)刪除任何
eslint
的禁用注釋娱两。為什么:
在處理代碼塊時(shí)禁用風(fēng)格檢查是正齿航常現(xiàn)象,這樣就可以關(guān)注在業(yè)務(wù)邏輯十兢。請(qǐng)記住把那些
eslint-disable
注釋刪除并遵循風(fēng)格規(guī)則趣竣。 -
根據(jù)任務(wù)的大小使用
//TODO:
注釋或做一個(gè)標(biāo)簽(ticket)。為什么:
這樣您就可以提醒自己和他人有這樣一個(gè)小的任務(wù)需要處理(如重構(gòu)一個(gè)函數(shù)或更新一個(gè)注釋)旱物。對(duì)于較大的任務(wù)遥缕,可以使用由一個(gè)lint規(guī)則(
no-warning-comments
)強(qiáng)制要求其完成(并移除注釋)的//TODO(#3456)
,其中的#3456
號(hào)碼是一個(gè)標(biāo)簽(ticket)宵呛,方便查找且防止相似的注釋堆積導(dǎo)致混亂单匣。
-
隨著代碼的變化,始終保持注釋的相關(guān)性。刪除那些注釋掉的代碼塊户秤。
為什么:
代碼應(yīng)該盡可能的可讀码秉,您應(yīng)該擺脫任何分心的事情。如果您在重構(gòu)一個(gè)函數(shù)虎忌,就不要注釋那些舊代碼泡徙,直接把要注釋的代碼刪除吧。
-
避免不相關(guān)的和搞笑的的注釋膜蠢,日志或命名堪藐。
為什么:
雖然您的構(gòu)建過程中可能(應(yīng)該)移除它們,但有可能您的源代碼會(huì)被移交給另一個(gè)公司/客戶挑围,您的這些笑話應(yīng)該無法逗樂您的客戶礁竞。
-
請(qǐng)使用有意義容易搜索的命名,避免縮寫名稱杉辙。對(duì)于函數(shù)使用長(zhǎng)描述性命名模捂。功能命名應(yīng)該是一個(gè)動(dòng)詞或動(dòng)詞短語(yǔ),需要能清楚傳達(dá)意圖的命名蜘矢。
為什么:
它使讀取源代碼變得更加自然狂男。
-
依據(jù)《代碼整潔之道》的step-down規(guī)則,對(duì)您的源代碼文件中的函數(shù)(的聲明)進(jìn)行組織品腹。高抽象級(jí)別的函數(shù)(調(diào)用了低級(jí)別函數(shù)的函數(shù))在上岖食,低抽象級(jí)別函數(shù)在下,(保證了閱讀代碼時(shí)遇到未出現(xiàn)的函數(shù)仍然是從上往下的順序舞吭,而不會(huì)打斷閱讀順序地往前查找并且函數(shù)的抽象層次依次遞減)泡垃。
為什么:
它使源代碼的可讀性更好。
<a name="enforcing-code-style-standards"></a>
7.2 強(qiáng)制的代碼風(fēng)格標(biāo)準(zhǔn)
讓您的編輯器提示您關(guān)于代碼風(fēng)格方面的錯(cuò)誤羡鸥。 請(qǐng)將 eslint-plugin-prettier 與 eslint-config-prettier 和您目前的ESLint配置一起搭配使用蔑穴。 更多請(qǐng)閱讀...
-
考慮使用Git鉤子。
為什么:
Git的鉤子能大幅度地提升開發(fā)者的生產(chǎn)力惧浴。在做出改變存和、提交、推送至?xí)捍鎱^(qū)或者生產(chǎn)環(huán)境的過程中(充分檢驗(yàn)代碼)衷旅,再也不需要擔(dān)心(推送的代碼會(huì)導(dǎo)致)構(gòu)建失敗哑姚。 更多請(qǐng)閱讀...
-
將Git的precommit鉤子與Prettier結(jié)合使用。
為什么:
雖然
prettier
自身已經(jīng)非常強(qiáng)大芜茵,但是每次將其作為單獨(dú)的一個(gè)npm任務(wù)去格式化代碼,并不是那么地高效倡蝙。 這正是lint-staged
(還有husky
)可以解決的地方九串。關(guān)于如何配置lint-staged
請(qǐng)閱讀這里 以及如何配置husky
請(qǐng)閱讀這里。
<a name="logging"></a>
8. 日志
[圖片上傳失敗...(image-835898-1610332200848)]
-
避免在生產(chǎn)環(huán)境中使用客戶端的控制臺(tái)日志。
為什么:
您在構(gòu)建過程可以把(應(yīng)該)它們?nèi)サ糁砼ィ钦?qǐng)確保您在代碼風(fēng)格檢查中提供了有關(guān)控制臺(tái)日志的警告信息品山。
-
產(chǎn)出生產(chǎn)環(huán)境的可讀生產(chǎn)日志記錄。一般使用在生產(chǎn)模式下所使用的日志記錄庫(kù) (比如 winston 或者
node-bunyan)烤低。為什么:
它通過添加著色肘交、時(shí)間戳、log到控制臺(tái)或者文件中扑馁,甚至是夜以繼日地輪流log到文件涯呻,來減少故障排除中那些令人不愉快的事情。更多請(qǐng)閱讀...
<a name="api"></a>
9. API
[圖片上傳失敗...(image-182c62-1610332200848)]
<a name="api-design"></a>
9.1 API 設(shè)計(jì)
為什么:
因?yàn)槲覀冊(cè)噲D實(shí)施開發(fā)出結(jié)構(gòu)穩(wěn)健的 Restful 接口腻要,讓團(tuán)隊(duì)成員和客戶可以簡(jiǎn)單而一致地使用它們复罐。
為什么:
缺乏一致性和簡(jiǎn)單性會(huì)大大增加集成和維護(hù)的成本。這就是為什么
API設(shè)計(jì)
這部分會(huì)包含在這個(gè)文檔中的原因
-
我們主要遵循資源導(dǎo)向的設(shè)計(jì)方式雄家。它有三個(gè)主要要素:資源效诅,集合和 URLs。
- 資源具有數(shù)據(jù)趟济,嵌套乱投,和一些操作方法。
- 一組資源稱為一個(gè)集合顷编。
- URL標(biāo)識(shí)資源或集合的線上位置戚炫。
為什么:
這是針對(duì)開發(fā)人員(您的主要API使用者)非常著名的設(shè)計(jì)方式。除了可讀性和易用性之外勾效,它還允許我們?cè)跓o需了解API細(xì)節(jié)的情況下編寫通用庫(kù)和一些連接器嘹悼。
使用
kebab-case
(短橫線分割)的URL。在查詢字符串或資源字段中使用
camelCase
模式层宫。在URL中使用多個(gè)
kebab-case
作為資源名稱杨伙。-
總是使用復(fù)數(shù)名詞來命名指向一個(gè)集合的url:
/users
.為什么:
基本上,它可讀性會(huì)更好萌腿,并可以保持URL的一致性限匣。 更多請(qǐng)閱讀...
-
在源代碼中,將復(fù)數(shù)轉(zhuǎn)換為具有列表后綴名描述的變量和屬性毁菱。
為什么:
復(fù)數(shù)形式的URL非常好米死,但在源代碼中使用它卻很微妙而且容易出錯(cuò),所以要小心謹(jǐn)慎贮庞。
-
堅(jiān)持這樣一個(gè)概念:始終以集合名起始并以標(biāo)識(shí)符結(jié)束峦筒。
/students/245743 /airports/kjfk
-
避免這樣的網(wǎng)址:
GET /blogs/:blogId/posts/:postId/summary
為什么:
這不是在指向資源,而是在指向?qū)傩源吧鳌D耆梢詫傩宰鳛閰?shù)傳遞物喷,以減少響應(yīng)卤材。
-
URLs里面請(qǐng)盡量少用動(dòng)詞
為什么:
因?yàn)槿绻鸀槊總€(gè)資源操作使用一個(gè)動(dòng)詞,您很快就會(huì)維護(hù)一個(gè)很大的URL列表峦失,而且沒有一致的使用模式扇丛,這會(huì)使開發(fā)人員難以學(xué)習(xí)。此外尉辑,我們還要使用動(dòng)詞做別的事情帆精。
-
為非資源型請(qǐng)求使用動(dòng)詞。在這種情況下隧魄,您的API并不需要返回任何資源卓练。而是去執(zhí)行一個(gè)操作并返回執(zhí)行結(jié)果。這些不是 CRUD(創(chuàng)建堤器,查詢昆庇,更新和刪除)操作:
/translate?text=Hallo
為什么:
因?yàn)閷?duì)于 CRUD,我們?cè)?code>資源或
集合
URL上使用 HTTP 自己帶的方法闸溃。我們所說的動(dòng)詞實(shí)際上是指Controllers
整吆。您通常不會(huì)開發(fā)這些東西。更多請(qǐng)閱讀... -
請(qǐng)求體或響應(yīng)類型如果是JSON辉川,那么請(qǐng)遵循
camelCase
規(guī)范為JSON
屬性命名來保持一致性表蝙。為什么:
這是一個(gè) JavaScript 項(xiàng)目指南,其中用于生成JSON的編程語(yǔ)言以及用于解析JSON的編程語(yǔ)言被假定為 JavaScript乓旗。
-
即使資源類似于對(duì)象實(shí)例或數(shù)據(jù)庫(kù)記錄這樣的單一概念府蛇,您也不應(yīng)該將
table_name
用作資源名稱或?qū)?code>column_name作為資源屬性。為什么:
因?yàn)槟哪康氖欠治鲑Y源屿愚,而不是分析數(shù)據(jù)庫(kù)模式汇跨。
-
再次,只有在您的URL上面命名資源時(shí)才使用名詞妆距,不要嘗試解釋其功能穷遂。
為什么:
只能在資源URL中使用名詞,避免像
/addNewUser
或/updateUser
這樣的結(jié)束點(diǎn)娱据。也避免使用參數(shù)作為發(fā)送資源的操作蚪黑。 -
如何使用HTTP方法來操作CRUD功能
怎么做:
GET
: 查詢資源的表示法POST
: 創(chuàng)建一些新的資源或者子資源PUT
: 更新一個(gè)存在的資源PATCH
: 更新現(xiàn)有資源。它只更新所提供的字段中剩,不管其他字段DELETE
: 刪除一個(gè)存在的資源
-
對(duì)于嵌套資源忌穿,請(qǐng)?jiān)赨RL中把他們的關(guān)系表現(xiàn)出來。例如结啼,使用
id
將員工與公司聯(lián)系起來掠剑。為什么:
這是一種自然的方式,方便資源的認(rèn)知郊愧。
怎么做:
GET /schools/2/students
, 應(yīng)該從學(xué)校2得到所有學(xué)生的名單GET /schools/2/students/31
, 應(yīng)該得到學(xué)生31的詳細(xì)信息澡腾,且此學(xué)生屬于學(xué)校2DELETE /schools/2/students/31
, 應(yīng)刪除屬于學(xué)校2的學(xué)生31PUT /schools/2/students/31
, 應(yīng)該更新學(xué)生31的信息沸伏,僅在資源URL上使用PUT方式,而不要用收集POST /schools
, 應(yīng)該創(chuàng)建一所新學(xué)校动分,并返回創(chuàng)建的新學(xué)校的細(xì)節(jié)。在集合URL上使用POST -
對(duì)于具有
v
前綴(v1红选,v2)的版本澜公,使用簡(jiǎn)單的序數(shù)。并將其移到URL的左側(cè)喇肋,使其具有最高的范圍表述:http://api.domain.com/v1/schools/3/students
為什么:
當(dāng)您的 API 為第三方公開時(shí)坟乾,升級(jí)API會(huì)導(dǎo)致發(fā)生一些意料之外的影響,也可能導(dǎo)致使用您API的人無法使用您的服務(wù)和產(chǎn)品蝶防。而這時(shí)使用URL中版本化可以防止這種情況的發(fā)生甚侣。 更多請(qǐng)閱讀...
-
響應(yīng)消息必須是自我描述的。一個(gè)很好的錯(cuò)誤消息響應(yīng)可能如下所示:
{ "code": 1234, "message" : "Something bad happened", "description" : "More details" }
或驗(yàn)證錯(cuò)誤:
{ "code" : 2314, "message" : "Validation Failed", "errors" : [ { "code" : 1233, "field" : "email", "message" : "Invalid email" }, { "code" : 1234, "field" : "password", "message" : "No password provided" } ] }
為什么:
開發(fā)人員在使用這些由API??構(gòu)建的應(yīng)用程序時(shí)间学,難免會(huì)需要在故障排除和解決問題的關(guān)鍵時(shí)刻使用到這些精心設(shè)計(jì)的錯(cuò)誤消息殷费。好的錯(cuò)誤消息設(shè)計(jì)能節(jié)約大量的問題排查時(shí)間。
_注意:盡可能保持安全異常消息的通用性低葫。例如详羡,別說`不正確的密碼`,您可以換成`無效的用戶名或密碼`嘿悬,以免我們不知不覺地通知用戶他的用戶名確實(shí)是正確的实柠,只有密碼不正確。這會(huì)讓用戶很懵逼善涨。
-
只使用這8個(gè)狀態(tài)代碼窒盐,并配合您自定義的響應(yīng)描述來表述程序工作一切是否正常,客戶端應(yīng)用程序發(fā)生了什么錯(cuò)誤或API發(fā)生錯(cuò)誤钢拧。
選誰呢:
200 OK
GET
,PUT
或POST
請(qǐng)求響應(yīng)成功.201 Created
標(biāo)識(shí)一個(gè)新實(shí)例創(chuàng)建成功蟹漓。當(dāng)創(chuàng)建一個(gè)新的實(shí)例,請(qǐng)使用POST
方法并返回201
狀態(tài)碼娶靡。304 Not Modified
發(fā)現(xiàn)資源已經(jīng)緩存在本地牧牢,瀏覽器會(huì)自動(dòng)減少請(qǐng)求次數(shù)。400 Bad Request
請(qǐng)求未被處理姿锭,因?yàn)榉?wù)器不能理解客戶端是要什么塔鳍。401 Unauthorized
因?yàn)檎?qǐng)求缺少有效的憑據(jù),應(yīng)該使用所需的憑據(jù)重新發(fā)起請(qǐng)求呻此。403 Forbidden
意味著服務(wù)器理解本次請(qǐng)求轮纫,但拒絕授權(quán)。404 Not Found
表示未找到請(qǐng)求的資源焚鲜。500 Internal Server Error
表示請(qǐng)求本身是有效掌唾,但由于某些意外情況放前,服務(wù)器無法實(shí)現(xiàn),服務(wù)器發(fā)生了故障糯彬。為什么:
大多數(shù) API 提供程序僅僅只使用一小部分 HTTP 狀態(tài)代碼而已凭语。例如,Google GData API 僅使用了10個(gè)狀態(tài)代碼撩扒,Netflix 使用了9個(gè)似扔,而 Digg 只使用了8個(gè)。當(dāng)然搓谆,這些響應(yīng)作為響應(yīng)主體的附加信息炒辉。一共有超過 70 個(gè) HTTP 狀態(tài)代碼。然而泉手,大多數(shù)開發(fā)者不可能全部記住這 70 個(gè)狀態(tài)碼黔寇。因此,如果您選擇不常用的狀態(tài)代碼斩萌,您將使應(yīng)用程序開發(fā)人員厭煩構(gòu)建應(yīng)用程序缝裤,然后您還要跑到維基百科上面找出您要告訴他們的內(nèi)容,多累啊术裸。 更多請(qǐng)閱讀...
在您的響應(yīng)中提供資源的總數(shù)
接受
limit
和offset
參數(shù)-
還應(yīng)考慮資源暴露的數(shù)據(jù)量倘是。 API消費(fèi)者并不總是需要資源的完整表述∠眨可以使用一個(gè)字段查詢參數(shù)搀崭,該參數(shù)用逗號(hào)分隔的字段列表來包括:
GET /student?fields=id,name,age,class
分頁(yè),過濾和排序功能并不需要從所有資源一開始就要得到支持猾编。記錄下那些提供過濾和排序的資源瘤睹。
<a name="api-security"></a>
9.2 API 安全
這些是一些基本的安全最佳實(shí)踐:
-
除非通過安全的連接(HTTPS),否則不要只使用基本認(rèn)證答倡。不要在URL中傳輸驗(yàn)證令牌:
GET /users/123?token=asdf....
為什么:
因?yàn)榱钆坪浯⒂脩鬒D和密碼通過網(wǎng)絡(luò)是明文傳遞的(它是base64編碼,而base64是可逆編碼)瘪撇,所以基本認(rèn)證方案是不安全的获茬。 更多請(qǐng)閱讀...
必須使用授權(quán)請(qǐng)求頭在每個(gè)請(qǐng)求上發(fā)送令牌:
Authorization: Bearer xxxxxx, Extra yyyyy
授權(quán)代碼應(yīng)該是短暫的。
通過不響應(yīng)任何HTTP請(qǐng)求來拒絕任何非TLS請(qǐng)求倔既,以避免任何不安全的數(shù)據(jù)交換恕曲。響應(yīng)
403 Forbidden
的HTTP請(qǐng)求。-
考慮使用速率限制
為什么:
保護(hù)您的API免受每小時(shí)數(shù)千次的機(jī)器人掃描威脅渤涌。您應(yīng)該在早期就考慮實(shí)施流控佩谣。
適當(dāng)?shù)卦O(shè)置HTTP請(qǐng)求頭可以幫助鎖定和保護(hù)您的Web應(yīng)用程序。更多請(qǐng)閱讀...
您的API應(yīng)將收到的數(shù)據(jù)轉(zhuǎn)換為規(guī)范形式实蓬,或直接拒絕響應(yīng)茸俭,并返回400錯(cuò)誤請(qǐng)求(400 Bad Request)的錯(cuò)誤吊履,并在其中包含有關(guān)錯(cuò)誤或丟失數(shù)據(jù)的詳細(xì)信息。
所有通過Rest API交換的數(shù)據(jù)必須由API來校驗(yàn)调鬓。
-
序列化JSON
為什么:
JSON編碼器的一個(gè)關(guān)鍵問題是阻止任意的可執(zhí)行代碼在瀏覽器或在服務(wù)器中(如果您用nodejs的話)執(zhí)行艇炎。您必須使用適當(dāng)?shù)腏SON序列化程序?qū)τ脩糨斎氲臄?shù)據(jù)進(jìn)行正確編碼,以防止在瀏覽器上執(zhí)行用戶提供的輸入腾窝,這些輸入可能會(huì)包含惡意代碼冕臭,而不是正常的用戶數(shù)據(jù)。
-
驗(yàn)證內(nèi)容類型燕锥,主要使用
application/*.json
(Content-Type 頭字段).為什么:
例如,接受
application/x-www-form-urlencoded
MIME類型可以允許攻擊者創(chuàng)建一個(gè)表單并觸發(fā)一個(gè)簡(jiǎn)單的POST請(qǐng)求悯蝉。服務(wù)器不應(yīng)該假定Content-Type
归形。缺少Content-Type
請(qǐng)求頭或異常的Content-Type
請(qǐng)求頭,應(yīng)該讓服務(wù)器直接以4XX
響應(yīng)內(nèi)容去拒絕請(qǐng)求鼻由。
<a name="api-documentation"></a>
9.3 API 文檔
- 在README.md模板為 API 填寫
API Reference
段落暇榴。 - 盡量使用示例代碼來描述 API 授權(quán)方法
- 解釋 URL 的結(jié)構(gòu)(僅 path,不包括根 URL)蕉世,包括請(qǐng)求類型(方法)
對(duì)于每個(gè)端點(diǎn)(endpoint)說明:
-
如果存在 URL 參數(shù)就使用 URL 參數(shù)蔼紧,并根據(jù)URL中使用到的名稱來指定它們:
Required: id=[integer] Optional: photo_id=[alphanumeric]
如果請(qǐng)求類型為 POST,請(qǐng)?zhí)峁┤绾问褂玫氖纠萸帷I鲜龅腢RL參數(shù)規(guī)則在這也可以適用奸例。分為
可選
和必需
。-
響應(yīng)成功向楼,應(yīng)該對(duì)應(yīng)什么樣的狀態(tài)代碼查吊,返回了哪些數(shù)據(jù)?當(dāng)人們需要知道他們的回調(diào)應(yīng)該是期望的樣子湖蜕,這很有用:
Code: 200 Content: { id : 12 }
-
錯(cuò)誤響應(yīng)逻卖,大多數(shù)端點(diǎn)都存在許多失敗的可能。從未經(jīng)授權(quán)的訪問到錯(cuò)誤參數(shù)等昭抒。所有的(錯(cuò)誤描述信息)都應(yīng)該列在這里评也。雖然有可能會(huì)重復(fù),但它卻有助于防止別人的猜想(灭返,減少使用時(shí)的排錯(cuò)時(shí)間)盗迟。例如
{ "code": 403, "message" : "Authentication failed", "description" : "Invalid username or password" }
- 使用API??設(shè)計(jì)工具,有很多開源工具可用于提供良好的文檔婆殿,例如 API Blueprint and Swagger.
<a name="licensing"></a>
10. 證書
[圖片上傳失敗...(image-a65614-1610332200848)]
確保您有權(quán)使用的這些資源诈乒。如果您使用其中的軟件庫(kù),請(qǐng)記住先查詢MIT婆芦,Apache或BSD(以更好地了解您所能夠擁有的權(quán)限)怕磨,但如果您打算修改它們喂饥,請(qǐng)查看許可證詳細(xì)信息。圖像和視頻的版權(quán)可能會(huì)導(dǎo)致法律問題肠鲫。
資源:
RisingStack Engineering,
Mozilla Developer Network,
Heroku Dev Center,
Airbnb/javascript,
Atlassian Git tutorials,
Apigee,
Wishtack
本文件圖標(biāo)來自 icons8