原文刊登在githubcss
前端從誕生依賴能夠說經歷了一個從不規範到規範,再到自動化的一系列過程。最開始的時候沒有前端這個領域,全部的前端職責都是由服務端來代替完成,當時的情況是很是混亂的,派別叢生,瀏覽器廠商規範不統一,模塊機制混亂,代碼風格寫法各自爲派,真有點羣雄逐鹿的感受。從HTML5,CSS3,ES6(536)的發佈開始,前端已經趨於規範化。瀏覽器的規範趨向一致,微軟的瀏覽器也逐漸跟上了步伐,瀏覽器兼容性問題向前邁進了一大步。現在前端開發的過程也愈來愈規範,愈來愈有規律可循。所以你們在想這些有規律的東西能不可以實現自動化(程序員最喜歡偷懶),能不能將枯燥無味的工做交給電腦來作,減小重複的工做,提升工做效率。 答案固然是能夠的,並且目前業內已經有很是多的成功案例。從facebook的Waitir,google的自動化流程系統,到國內阿里的def,再到小公司使用的gitlab ci,發現不少公司都已經開始了流程自動化的探索。本章我會先講述前端的工做流程是怎樣的,而後對前端工做流程進行分析,分析哪些任務能夠自動化,實現自動化的思路是什麼,最後我會講述如何搭建一個自動化的小平臺。html
讓咱們開始以前,先來看下咱們目前前端的工做流程是怎麼樣的,我分享的只是我我的的工做流程,不一樣公司和我的可能略有不一樣,但總的思路應該是差很少的,請不要太介意。前端
這個階段又能夠分爲以下三個小階段:vue
正常狀況下,從需求產生到準備開發,應該有一個需求評審會,這時候相關開發和產品經理會彙集在一塊兒,商討需求是如何產生的(敏捷開發的需求一般是從客戶的反饋中產生的),咱們作的功能有沒有解決客戶的問題,咱們對需求的理解有沒有誤差和需求的優先級等。通過完全充分的討論以後,若是需求理解沒有問題,而且需求確實能夠解決客戶的問題,咱們會爲工做安排時間和優先級,將task列到看板(同時列到系統和白板上),並天天追蹤更新。java
理解產品經理的真正意圖,明白需求產生的背景node
前端目前比較推薦的工做方法是組件式開發,即將頁面拆分紅足夠粒度的小組件和模塊。由單獨的人負責相關獨立模塊和組件的開發,作到組件和模塊的複用,這個已經在第二章講過了,不在此贅述。react
目前大多數互聯網公司的版本管理工具都是使用git,包括咱們的公司,並且公司內部一般也有本身的git flow,咱們作新功能的時候一般會創建一個feature分支,開發完畢後合併到release分支等待測試和發佈。固然就算你是SVN思路也是同樣的。git
這裏一般有兩個比較常見的問題。問題一是能不能不寫單元測試,或者說不寫單元測試有什麼很差的影響?第二個問題是爲何要先寫單元測試而不是先寫代碼?咱們對這兩個問題一一進行解答。先說第一個問題,其實不寫單元測試是很容易作到的,就好像咱們不努力鍛鍊身體同樣簡單,可是當咱們面對本身的一大塊腹肌的時候,是否是有那麼一丁點兒後悔呢?單元測試也是同樣,當你開始寫的時候,沒有任何收效,據統計寫單元測試的時間要高於寫代碼的時間,那麼咱們爲何還要「白」花那麼多時間寫測試用例呢?那是由於當隨着應用規模逐漸擴大,複雜度逐漸上升的時候,完善的測試用例,給你修改代碼的勇氣。當單元測試顯示all pass的時候,彷彿有人跟你說「幹吧,哥們,沒毛病。」。你不會由於怕改壞了代碼而躡手躡腳。但這裏要強調一點的是,覆蓋率低的單元測試不但不可以起做用,反而會給人一種「幹吧,哥們,沒毛病。」的假象。這也是爲何我後面強調使單元測試覆蓋率足夠高的緣由。咱們再來看下第二個問題,這個問題涉及到一個概念叫測試驅動開發(TDD)。TDD的理念就是先寫測試用例,而後寫具體實現。TDD的一個重大優勢就是你將具體實現放到後面,這樣你就不會深陷細節的泥潭,你就擁有更清晰的視野,你會對業務或者邏輯理解更佳深入。這就好像你看過不少高手寫代碼,它們會先將思路寫下來,而後再寫代碼是一個道理。程序員
回答了關於測試的常見問題,咱們來看下單元測試究竟怎麼寫。其實也簡單,單元測試一般來自於測試人員整理的測試用例,固然一些特殊的算法邏輯須要本身整理啦。一個原則就是能用測試同窗就用測試同窗,不用白不用,對吧?可是前端寫單元測試的時候,總會感受很困難,我一開始也是這麼以爲的。後來我接觸了函數式編程,整我的就感受豁然開朗。傳統的面向對象編程,命令式編程有一個很是大的弊端,引用Joe Armstrong(Erlang語言的創造者)的一句話就是:github
你想要一個香蕉,但獲得的倒是一個拿着香蕉的大猩猩
沒錯,當我回顧我好久以前的代碼,的確如此。這也不能徹底怪咱們,咱們已經習慣了各類假設,各類外部依賴。咱們將這些變成理所固然,咱們不斷地改變狀態的狀態,致使狀態難以追蹤。所以咱們寫單元測試很是困難。當你開始以函數式編程理念寫代碼的時候,你會發現代碼很是好測試。好比目前比較流行的react的展現組件,就是一個純函數,對這樣的組件進行測試就很簡單。當你研究redux的代碼的時候,你會發現redux的reducer設計的精妙,他將reduce在空間上的抽象變成reducer在時間上的抽象,而且reducer純函數的理念也讓代碼更容易測試,具體能夠看個人這篇博文
寫代碼以前要先想有沒有輪子可用。若是沒有在開始造輪子。我喜歡將邏輯劃分爲多個函數,而後在函數開頭對入參進行校驗,並對每一步可能出錯的地方進行校驗,典型的是npe(null pointer exception)。這就是典型的防護性編程。
// String a -> Number b -> Boolean
function testA(a, b) {
if (!a) return false;
if (!isString(a)) return false;
if (!b) return false;
if(!isNumber(b)) return false
return !!a.concat(b);
}
複製代碼
這種方法在前端尤爲有效,由於js是動態語言,若是你不使用typescript等增長靜態檢測的功能的話,代碼會變得脆弱不堪。一個簡單的作法就是,假設每一行代碼都會遇到異常狀況,都會報錯。
前面講了單元測試的重要性,以及單元測試的寫法思路。這裏強調一下單元測試的覆蓋問題,低的單元測試覆蓋率毫無用處,甚至會起副作用,所以保持足夠高的單元測試覆蓋率顯得很是重要,業界廣泛承認單元測試覆蓋率在95%以上是比較合適的。
這個階段咱們的代碼已經通過了自測和測試人員的迴歸,咱們認爲能夠發佈上線了。一般咱們還會讓產品經理進行驗收,看看是否是他們想要的效果。
目前寫的代碼是在多個文件的,咱們的代碼使用了不少瀏覽器不支持的特性,咱們須要將css從js中提取出來等等。這些工做都須要經過編譯來完成。
咱們的項目的代碼是由許許多多的依賴構成的,咱們會依賴一些框架如react,vue,咱們會使用一些工具庫如lodash,ramdajs,mostjs等。這些都構成了項目的不穩定和不肯定。這也就是爲何大公司如阿里,會對外部依賴有着很強的執念。npm也察覺到了這一點,以致於如今的npm在安裝事後都是鎖定版本的。但這依舊沒法保證依賴包的質量。 一個查看包質量的原則就是文檔足夠豐富,單元測試覆蓋率足夠高,受歡迎(start足夠多),雖然上述條件不是一個庫質量良好的充分條件,但倒是必要條件,咱們能夠經過它篩選一大批不合格的庫。
咱們會對代碼進行檢查,有沒有語法錯誤等。這一步能夠經過eslint檢查,也能夠經過flow或者typescript這樣的靜態檢查工具檢查。總之,這一步是檢查有沒有錯誤代碼或者不符合規範的代碼。
代碼已經經過了檢查,這個時候咱們須要對代碼進行優化,好比壓縮,合併,去空格去console等,或者提取公共依賴,再或者刪除殭屍代碼(tree shaking)。
咱們會組織相關人員進行代碼評審,確保代碼質量。這部分是人工完成,是對前面工做的最後把關。
咱們資源發佈到CDN等待最終的發佈,保證版本發佈以後,用戶能夠直接得到最新的CDN資源。
咱們將代碼打tag,一個個tag就像是一個個里程碑。 當咱們須要對某一個版本代碼進行修復的時候,tag的做用就顯示出來了。
咱們的功能已經達到了能夠發佈的狀態,而後咱們會對版本進行發佈。修改線上的版本號,這樣咱們的用戶就能夠訪問到咱們最新寫的代碼了。
咱們已經將代碼發佈上線了,一般咱們須要驗證下代碼是否正確發佈,有沒有影響線上其餘功能。
上面的過程多是大多數互聯網公司的工做流程了。那麼下一節我會對每個階段進行分析,找出能夠自動化的點,並講述自動化的技術思路是怎樣的。
上面講述了常規的需求產生到功能發佈的完整過程,經過上面的分析,咱們發現階段三和階段四是能夠高度自動化的,階段三和階段四進行作了什麼事,爲了方便你們的理解,我整理了一個圖:
圖中的虛線表示自動完成,無需人工。實線表示須要人工操做。圖的中心點是dev,能夠看出dev的操做有三個,分別是提交(commit)打tag,以及提交pr(pull request)。不一樣的操做會觸發不一樣的鉤子,完成不一樣的操做。咱們一個節點一個節點進行分析,它們分別作了什麼事,以及設計實現的思路。圖中須要實現的系統有三個,第一個是包分析引擎(package analyser),第二個是CI中心,第三個是CD中心,咱們分別來看。
包分析工具這裏能夠是前端的npm包分析,也能夠是後端的好比maven包分析。這裏以npm包分析爲例,maven等其餘包分析同理,只是具體技術實現細節不一樣,npm包分析和maven包分析只是具體策略不一樣,咱們能夠經過策略模式將具體的分析算法封裝起來。首先看下包分析引擎實現的功能,其實包分析引擎就是分析應用依賴的包,並逐個遞歸分析其依賴包,找到其中有風險的依賴,並通知給使用者(項目擁有者)。具體功能包括但不限於分析有安全風險的包,提示有補丁更新,有了這些依賴數據,咱們甚至能夠統計公司範圍內包的使用狀況(包括各個版本),這些數據是頗有用的。後面一節咱們會具體分析包分析引擎的實現細節,使讀者能夠自行搭建一個npm包分析引擎。
CI(Continuous Integration)是持續將新功能集成到現有系統的一種作法,極限編程也借鑑了CI的基本思想。那麼咱們是怎樣使用CI的呢?我剛纔在圖中也體現了,開發者的提交會觸發CI,CI會作一些單元測試,代碼檢測等工做,若是不經過則反饋給相關人員,不然將經過的代碼合併到庫中。也就是說CI並非一項技術,二是一種最佳實踐,更多能夠參考wikipedia。後面我會介紹實現一個CI的基本思路。
CD(Continuous Delivery)一樣也是一種最佳實踐,並非一項技術。它的基本思想是保證代碼隨時可發佈,它保證了代碼發佈的可信賴性,同時持續集成減小了開發人員的工做量。更多能夠參考wikipedia。後面我會介紹實現一個CD的基本思路。
固然咱們的系統還比較不完善,咱們還能夠增長配置中心,方便對版本進行管理,還能夠增長監測平臺(第四章有講),你們能夠發揮本身的聰明才智。
上面講述了一個需求產生到功能上線的過程,咱們也分析了若是進行自動化。咱們還忽略了一個項目初期的一個過程,就是搭建腳手架的過程。如何將搭建腳手架的過程也自動化,配置化,最好還能在公司範圍內保持一致性。
當咱們開始一個新項目的時候,要先進行技術調研和選型,當肯定了技術方向的時候,咱們須要一個架子,項目成員能夠根據這個架子寫代碼。這個架子能夠手工生成,很早以前我就是這麼幹的,可是手工生成的有不少缺點。第一就是效率低,第二就是不利於統一。爲了解決這個問題,咱們引入了雲腳手架的概念。要明白雲腳手架咱們須要先知道腳手架。咱們先來看下腳手架作了什麼事,簡單來講腳手架就是生成項目的初始代碼。咱們經過目前比較火的react的配套腳手架工具create-react-app(如下簡稱cra)來認識一下腳手架的工做原理。咱們先來看下cra的使用方法:
npm install -g create-react-app
create-react-app my-app
cd my-app/
npm start
複製代碼
這樣咱們就有了下面的項目結構:
若是要實現以上功能,一個最簡單的方法就是執行create-react-app xxx 以後去遠端下載文件到本地。其實cra的思路就是這樣的,我用一張圖來表示cra的基本過程:
每一步的實現也比較簡單,你們能夠直接查看源碼
上面介紹了腳手架的做用和實現原理。可是上述的腳手架沒法實現中心化,也就是說不一樣團隊沒法造成相互感知,具體來講就是不一樣項目的腳手架是不一樣的或者不可以實現高度定製化,所以咱們須要構建一箇中心化,可配置的腳手架服務,我稱之爲雲腳手架。腳手架採用CS架構。客戶端能夠是一個CLI,服務端則是一個配置中心,模塊發現中心。以下圖是一個雲腳手架的架構圖:
客戶端經過命令告訴服務端想要初始化的模版信息,服務端會從模板庫中查找對應模板,若是有則返回,沒有則請求npm registry,若是有則返回並將其同步到模板庫中,供下一次使用。若是沒有返回失敗。 這樣對不一樣團隊來講,腳手架是透明的,團隊能夠定製適合本身的腳手架,上傳到模板庫,供其餘團隊使用,這就造成了一個閉環。
我將包分析引擎的工做過程分爲如下三個階段
咱們須要對包分析,分析的結果固然須要數據支撐。所以黑白名單是不可少的,咱們能夠本身補充黑白名單,咱們甚至能夠創建本身的黑白名單系統,固然也能夠接入第三方的數據源。無論怎樣,第一步咱們須要有數據源,這是第一步也是最重要的一步。爲了簡單起見,我以JSON來描述一下咱們的數據源:
// 全部的key都是npm包的包名
{
"whiteList": ["react", "redux", "ant-design"],
"blackList": {
"kid": ["insecure dependencies 'ssh-go'"]
}
}
複製代碼
這一步須要遞歸分析包。 咱們的輸入只是一個配置文件,npm來講的話就是一個package.json文件。咱們須要提取package.json的兩個字段dependencies和devDependencies,二者就是項目的依賴包,區別在於後者是開發依賴。這個時候咱們能夠得到項目的一個依賴數組。形如:
const dependencies = [{
name: "react",
version: "15.4.2"
}, {
name: "react-redux",
version: "5.0.3"
}]
複製代碼
而後咱們須要遍歷數組,從npm registry(能夠是官方的registry, 也能夠是私有的鏡像源)獲取包的具體內容,並遞歸獲取依賴。這個時候咱們獲取了項目全部的依賴的和深層依賴的包。最後咱們須要根據包名去匹配黑白名單。咱們還有一步須要作,就是獲取包的更新日誌,將有意義的日誌(這裏能夠本身封裝算法,究竟什麼樣的更新日誌是有意義的,留給你們思考)輸出給項目擁有者。
咱們已經將全部的依賴包進行匹配,這個時候已經知道了系統依賴的白名單包,黑名單包和unknown包,咱們有了匹配以後的數據源。
const result = {
projectName: "demo"
whiteList: ["react", "redux"],
blackList: [{name: "kid", ["insecure dependencies 'ssh-go'"]}],
changeLog: [{name: ""react-redux, logs: {url: '', content: ''}}]
}
複製代碼
咱們要作到數據和顯示分離。這個時候咱們將數據單獨存起來,而後採用友好的信息展現出來,這部分應該比較簡單,很少說了。
若是想要搭建持續平臺的話,最基礎的三個服務是要有的,lint,test以及report。其實lint,test和report的具體實現已經超出了CI的範疇,這裏就大體講如下。對於lint來講,本質上是對js文本的檢查,而後匹配一些規則,業界比較有名的是紅寶書的做者nzakas的eslint,關於eslint的總體架構能夠查閱這裏zakas的初衷不是重複造一個輪子,而是在實際需求得不到JSHint團隊響應的狀況下,本身開發並開源了eslint:一個支持可擴展、每條規則獨立、不內置編碼風格爲理念編寫一個js lint工具。對於test,其實就是運行開發人員寫的測試用例,並保證運行正確且覆蓋率足夠高。若是上述步驟出錯,則會向相關人員告警。下面是CI的架構圖:
代碼會通過lint,途中會從配置中心拉取項目的presets和plugins,經過後進入下一個流水線test,test會將代碼分發到瀏覽器雲中進行單元測試和集成測試,並將結果發給相關人員,上述兩個步驟若是出錯也都會經過report service發送信息給相關人員。
持續部署在持續集成的基礎上,將集成後的代碼部署到更貼近真實運行環境的「類生產環境」(production-like environments)中。好比,咱們完成單元測試後,能夠把代碼部署到鏈接數據庫的 Staging 環境中更多的測試。若是代碼沒有問題,能夠繼續手動部署到生產環境中。所以持續部署最小的單元就是將代碼劃分爲一個個能夠發佈的狀態。下面是一個經典的企業級持續部署實現:
https://continuousdelivery.com/implementing/architecture/
能夠看出持續集成,實際上是將新模塊融合到系統裏面,造成一個可發佈單元,它自己不涉及到發佈的流程。真正將代碼發佈到線上,仍是須要人工來操做。
能夠經過配置中心實現代碼發佈
要想實現持續部署的架構,是須要代碼和系統架構的配合,這是和前面我講述的其餘系統不同。持續部署要求代碼的耦合度要足夠低,儘可能少地影響其餘模塊。每當發佈一個新功能的時候,不須要將所有代碼測試迴歸,而是採用mock,stub等方式模擬外部依賴。目前比較流行的微服務,也是一種將代碼解耦合的一種實踐,它將系統劃分爲若干獨立運行的服務,服務之間不知道彼此的存在,甚至服務之間的語言,技術架構都不相同。前面的章節講述了組件化和模塊化,在這裏又能夠看出組件化和模塊化的重要性。
更多關於持續部署實現的介紹
前面反覆提到了反饋系統feedback。可能咱們還須要其餘第三方系統,好比數據可視化系統等。這裏以接入通知服務爲例,講解如何接入一個通用的第三方系統。在談通知服務具體細節以前,咱們先來說下接入一個第三方服務須要作什麼。本質上接入不一樣的服務是服務治理的範疇,而目前服務治理當屬微服務佔據上風。這種經過不斷接入「第三方」服務的方式使得業務和應用分離。在微服務以前,你們廣泛的作法是將不一樣的系統作成不一樣的應用,而後經過某些手段進行通訊。這種方式有一個顯著的缺點就是應用有不少重複冗餘的邏輯和代碼。而微服務則不一樣,微服務將系統拆分紅足夠小的塊,這樣可以顯著減小冗餘。服務化有如下特色:
這裏並不打算討論服務治理的具體實施細節,可是須要明白的是經過這種微服務的思想。咱們須要通知服務,只須要發送一個信號,告訴通知服務,通知服務返回一個信號,表示輸出的結果。好比我須要接入郵件服務這個通知服務。代碼大概是這樣的:
'use strict';
const nodemailer = require('nodemailer');
const promisify = require('promisify')
// Generate test SMTP service account from ethereal.email
// Only needed if you don't have a real mail account for testing
exports default mailer = async context => {
// create reusable transporter object using the default SMTP transport
let transporter = nodemailer.createTransport({
host: 'smtp.ethereal.email',
port: 587,
secure: false, // true for 465, false for other ports
auth: {
user: account.user, // generated ethereal user
pass: account.pass // generated ethereal password
}
});
// setup email data with unicode symbols
let mailOptions = {
from: '"Fred Foo 👻" <foo@blurdybloop.com>', // sender address
to: 'bar@blurdybloop.com, baz@blurdybloop.com', // list of receivers
subject: 'Hello ✔', // Subject line
text: 'Hello world?', // plain text body
html: '<b>Hello world?</b>' // html body
};
// send mail with defined transport object
await promisify(transporter.sendMail(mailOptions, (error, info) => {
if (error) {
return console.log(error);
}
console.log('Message sent: %s', info.messageId);
// Preview only available when sending through an Ethereal account
console.log('Preview URL: %s', nodemailer.getTestMessageUrl(info));
// Message sent: <b658f8ca-6296-ccf4-8306-87d57a0b4321@blurdybloop.com>
// Preview URL: https://ethereal.email/message/WaQKMgKddxQDoou...
}));
return {
status: 200,
body: 'send sucessfully',
headers: {
'Foo': 'Bar'
}
}
};
複製代碼
若是每個應用須要使用郵件服務,就須要寫這樣的一堆代碼,若是公司的系統不一樣致使語言不一樣,還須要在不一樣語言都實現一遍,很麻煩,而若是將發送郵件抽象成通知服務的具體實現,就能夠減小冗餘代碼,甚至java也能夠調用咱們上面用js寫的郵件服務了。
小提示。當咱們須要使用郵件服務的時候,最好不要在代碼中直接向郵件服務發送消息,而是向通知服務這種抽象層次更高的服務發送。
有一個流行的概念是faas(function as a service)。它每每和無服務一塊兒被談起,無服務不是說沒有服務器,而是將服務架構透明,對於普通開發者來講就好像沒有服務器同樣,這樣就能夠將咱們從服務器環境中解放出來,專一於邏輯自己。fission(Fast Serverless Functions for Kubernetes)是一個基於k8s的無服務框架。經過它開發者能夠只關注邏輯自己,咱們能夠直接將上面的mail方法做爲部署單元。下面是一個例子:
$ fission env create --name nodejs --image fission/node-env
$ curl https://notification.severless.com/mailer.js > mailer.js
# Upload your function code to fission
$ fission function create --name mailer --env nodejs --code mailer.js
# Map GET /mailer to your new function
$ fission route create --method GET --url /mailer --function mailer
# Run the function. This takes about 100msec the first time.
$ curl -H "Content-Type: application/json" -X POST -d '{"user":"user", "pass": "pass"}' http://$FISSION_ROUTER/mailer
複製代碼
這樣若是有一個系統須要將mailer服務切換成sms服務就很簡單了:
$ fission env create --name nodejs --image fission/node-env
$ curl https://notification.severless.com/sms.js > sms.js
# Upload your function code to fission
$ fission function create --name sms --env nodejs --code sms.js
# Map GET /mailer to your new function
$ fission route create --method GET --url /sms --function sms
# Run the function. This takes about 100msec the first time.
$ curl -H "Content-Type: application/json" -X POST -d '{"user":"user", "pass": "pass"}' http://$FISSION_ROUTER/sms
複製代碼
服務的實現也足夠簡單,只須要關心具體邏輯就OK了。
上面講述了軟件開發的過程,以及咱們能夠將哪些過程自動化。這一節,咱們講述自動化的第二部分自動化腳本。刨除軟件開發自己,計算機中其實也充滿了重複性工做,一樣也充滿了解決這些重複工做的自動化解決方案,這些解決方案能夠是一個腳本,能夠是一個軟件或者插件等,總之它將人們從重複性的工做中解脫了出來。舉個例子,咱們都有過下載視頻的經歷,咱們看上了某個網上的一個視頻,咱們想下載下來,可是在下載的時候,發現只有VIP能夠下載。咱們就去網上查找解決方案。咱們按照教程歷經千辛萬苦終於將視頻下載了下來。下次咱們又要下載視頻了,咱們還要經歷了上面的步驟(咱們甚至還要再看一遍教程)。因而自動下載在線網站視頻的自動化解決方法出現了,人們只須要簡單的操做就能夠將本身心愛的視頻下載下來,多麼省心!相似的還有不少,好比批量處理工具,一鍵重裝系統工做等等,根本數不過來。
本質上,任何應該自動化的都應該被自動化。只要是重複性工做,都應該將其自動化。爲何電腦能夠處理的東西,非要人工來處理呢?開發者應該將精力花費在更值得花費的地方,一些有創造性的工做上。上面介紹了普通用戶的例子,如今我舉一個工做中的例子。好比以前個人公司的發佈,就是一個簡單的流程,每次發佈都要按照流程執行,這就很適合去自動化。咱們當時的發佈流程是這樣的
git tag publish/版本號
git push origin publish/版本號
複製代碼
而後去cdn(https://g.alicdn.com/dingding/react-hrm-h5/version/index.js) 上查看是否發佈成功。 我以爲這已經花費我必定的時間了,並且新來的同窗不得不學習這種繁瑣的東西。 爲何不能自動化呢? 借用墨菲定律下, 該自動化的終將實現自動化。 自動化真的不止是減小時間,更重要的是減小出錯的可能。 軟件工程領域有這麼一句話,減小bug有兩種方式,一種是少寫代碼以致於沒有明顯bug。 二是寫不少代碼以致於沒有明顯bug。
少寫代碼,少作重複的事,這是個人信條。
若是你認真觀察的話,你會發現能夠自動化的東西實在是太多了。當你真正將自動化貫徹到實際編碼生活中去的時候,你會發現你花費在重複工做上的時間在縮少,而且你的幸福感會提高,你會頗有成就感。以前看過一個文章,文章裏面說它會將任何能夠自動化的東西自動化(不只限於工做),它會編碼控制若是在晚上下班的時候,本身的session還在的話(還沒下班),就發自動給老婆發短信,短信內容是從預先設置好的短語中隨機選取的。
以前我在微博上看到一個研究,它經過分析女友的微博,來分析女友的情緒。我以爲上面發送短信的時候,若是能夠根據老婆的情緒對應智能回覆不一樣內容會更棒。
若是你們有運維經驗的話,會知道運維常常須要開啓,中止,重啓服務。這些工做有很強的規律性,很適合作自動化。幸運的是,咱們藉助shell,能夠與操做系統深度對話,輕鬆實現上面提到的運維需求。下面是一個shell腳本實現服務啓動,中止和重啓的例子。
#! /bin/sh
DIR= `pwd`;
NODE= `which node`
// 第一個參數是action,是start,stop和restart中的一個。
ACTION=$1
# utils
get_pid() {
echo `ps x|grep my-server-name |awk '{print $1}'`
}
# 啓動
start() {
pid= `get_pid`;
if [ -z $pid ]; then
echo 'server is already running';
else
$NODE $DIR/server.js 2>&1 &
echo 'server is running now'
fi
}
// stop和restart代碼省略
case "$ACTION" in
start)
start
;;
stop)
stop
;;
esac
複製代碼
藉助shell強大的編程能力,還有將數據抽象成流,並經過組合流完成複雜任務的能力,咱們能夠構建很是複雜的腳本。咱們甚至能夠將檢測三方庫潛在風險信息功能作成腳本,而後集成到CI中,只有想不到,沒作作不到。這裏只是給你們提供思路,但願你們能夠根據這個思路進行延伸,從而將一切應該被自動化的東西所有自動化。
上面講述了哪些地方應該被自動化。你們看了後極可能是拿了錘子的瘋子,你會發現全部的東西都是釘子,是否是任何東西均可以自動化?固然不是! 能夠自動化的應該是有着極強規律的枯燥活動,而充滿創新的任務仍是要讓開發者本身享受。所以當你以爲某項工做枯燥無味,而且有着很強的規律和判別指標的時候就是你拿起手裏錘子的時候。好比我天天都要回報個人工做,將本身的進度同步給組裏其餘人,雖然很枯燥乏味(但願個人領導不會看到),可是並無規律性。所以不該該被自動化。
這裏再舉一個例子。我將開發過程當中須要處理的事情進行了分類,我稱爲元腳本(meta-script),分別有以下內容:
concat-readme (將項目中全部的readme組合起來,造成一個完整的readme)
generate-changelog (根據commit msg 生成 changelog)
serve-markdown (根據markdown生成靜態網站)
lint (代碼質量檢測)
start server (開啓開發服務器)
stop server (中止運行開發服務器)
restart server (重啓開發服務器)
start-attach (attach到瀏覽器,以在編輯器中進行調試)
每個meta-script都是一個小的腳本或者一個外部庫(external library),因爲我使用的是npm做爲包管理工具,所以我將meta-script放到了package.json文件中的script裏面。這樣我就能夠經過運行npm run xxx
執行對應的腳本或者外部庫了。可是別忘了,有時候咱們須要作一些複雜的任務,好比我須要在瀏覽器中查看項目中全部的readme。那麼我須要先把項目中的readme所有concat起來,而後將concat的內容做爲數據源,傳給serve-markdown,而後serve-markdown傳給start-server。代碼大概是這樣的:
npm run concat-readme > npm run serve-markdown > npm run start-server --port 1089
複製代碼
我稱上面的代碼task,而後咱們把上面的代碼也放到package.json的script中,彷佛這種作法很好地解決了問題。可是它有幾個缺點。
所以個人作法是將meta-script放到版本庫(這個例子咱們放到了package.json中),而後將task放到編輯器中控制。我使用的編輯器是VSCODE,它有一個task manager功能,也能夠下載第三方插件進行擴展。而後咱們能夠本身定義task,好比上面的咱們能夠做爲我的配置保存起來,命名爲start doc-site。
咱們能夠繼續組合:
npm run changelog > npm run serve-markdown > npm run start-server --port 1089
複製代碼
咱們經過meta-script又增長了一個很好用的task,咱們能夠命名爲start changelog-site。
還有更多:
npm run stop > npm run start-attach
複製代碼
咱們又實現了一個「放棄」瀏覽器調試,而用editor調試的task,咱們稱之爲editor-debug。
咱們能夠增長更多的meta-script,咱們能夠根據meta-script組合更多task。而後咱們只須要one-key就能夠實現任意中組合的功能,是否是很棒?本身動手試試把!
上面舉的例子是一個典型的開發流程,那麼其餘平常的自動化怎麼去作呢?好比我要控制電腦發送郵件,好比我要控制電腦睡眠,我要調整電腦的音量等。雖然咱們也能夠按照上面的思路,寫一個元腳本,而後將元腳本組合。可是這裏的元腳本彷佛並不能經過node的cli程序或者shell腳本實現。可是這剛好是自動化必不可少的一環。所以咱們面臨一個GUI的自動化運行過程,將咱們繁瑣重複的UI工做中解脫出來。這裏介紹在mac下自動化的例子。
說到mac中自動化,jxa是一種使用javaScript與mac中的app進行通信的技術。經過它開發者能夠經過js獲取到app的實例,以及實例的屬性和方法。經過jxa咱們能夠輕鬆經過JavaScript來自動化腳本完成諸如給某人發送郵件,打開特定軟件,獲取iTunes的播放信息等功能。下面舉個發送郵件的例子:
const Mail = Application("Mail");
const body = "body";
let message = Mail.OutgoingMessage().make();
message.visible = true;
message.content = body;
message.subject = "Hello World";
message.visible = true;
message.toRecipients.push(Mail.Recipient({address: "a@duiba.com.cn", name: "zhangsan"}));
message.toRecipients.push(Mail.Recipient({address: "b@duiba.com.cn", name: "lisi"}));
message.attachments.push(Mail.Attachment({ fileName: "/Users/lucifer/Downloads/sample.txt"}));
Mail.outgoingMessages.push(message);
Mail.activate();
複製代碼
有兩種方式運行上面的例子,一種是命令行方式,另外一種是直接做爲腳本運行。
osascript -l JavaScript -e 'Application("iTunes").isrunning()'
複製代碼
一種方式是保存爲文件以後運行
osascript /Users/luxiaopeng/jxa/hello.js
複製代碼
另外一種是用蘋果自帶的腳本編輯器:
運行以後效果是這樣的:
jxa提供了豐富的api供咱們使用。詳細能夠查看腳本編輯器-文件-字典:
好比我要查看dash的api:
可是遺憾的是並非全部的app都提供了不少有用的api,好比釘釘,也並非全部的程序都有字典,好比qq,微信。好消息是mac自帶的程序接口仍是比較豐富的。可是咱們發現儘管如此,咱們想要實現某些功能,仍是會比較複雜。對於不想太深刻了解而且想要自動化的開發者來講一款簡單的工具是有必要的,下面我介紹一款在mac下的神器。
JXA的功能很是強大,可是其功能比較繁瑣。若是你只是想簡單地寫一個自動化腳本,作一些簡單的瞭解。介紹你們一個更加簡單卻不失強大的工具-alfred- workflow。 你能夠自定義本身的工做流,支持GUI,shell腳本甚至前面提到的jxa寫工做流。其簡單易用性,以及其獨特的流式處理,各類組合特性使得它功能很是強大。下面是個人alfred-workflow:
你能夠像我拆分開發流程同樣將你的工做流拆解,每一部分實現本身的功能,設置語言能夠不同。好比處理用戶輸入用bash,而後bash將輸入流重定向到perl腳本等都是能夠的。 alfred-workflow能夠容許你簡單地添加文件操做,web操做,剪貼板等,設置不用寫任何代碼。
本章經過前端工做流程入手,講解了前端開發中的工做,而且試圖將其中能夠自動化的步驟進行自動化集成。而後講述了完善的一個自動化平臺系統是怎樣的,以及各個子系統實現的具體思路是怎樣的,經過個人講解,我相信你們應該已經理解了自動化的工做內容,甚至能夠本身動手搭建一個簡單的自動化平臺了。可是程序員中的自動化遠不止將實現需求的流程自動化,咱們還會搞一些提升效率的小工具,本質上它們也是自動化。只不過他不屬於工程化,在本書的附錄部分,我也會提供一些自動化小腳本。