為什么優(yōu)秀的程序員喜歡命令行?
一、優(yōu)秀的程序員
要給“優(yōu)秀的程序員”下一個(gè)明確的定義無疑是一件非常困難的事情。擅長(zhǎng)抽象思維、動(dòng)手能力強(qiáng)、追求效率、喜歡自動(dòng)化、愿意持續(xù)學(xué)習(xí)、對(duì)代碼質(zhì)量有很高的追求等等,這些維度都有其合理性,不過又都略顯抽象和主觀。
(圖片來自:http://t.cn/R6I1yhJ)
我對(duì)于一個(gè)程序員是否優(yōu)秀,也有自己的標(biāo)準(zhǔn),那就是TA對(duì)命令行的熟悉/喜愛程度。這個(gè)特點(diǎn)可以很好的看出TA是否是一個(gè)優(yōu)秀的(或者潛在優(yōu)秀的)程序員。我周圍就有很多非常牛的程序員,無一例外都非常擅長(zhǎng)在命令行中工作。那什么叫熟悉命令行呢?簡(jiǎn)單來說,就是90%的日常工作內(nèi)容可以在命令行完成。
當(dāng)然,喜歡/習(xí)慣使用命令行可能只是表象,其背后包含的實(shí)質(zhì)才是優(yōu)秀的程序員之所以優(yōu)秀的原因。
二、自動(dòng)化
Perl語言的***Larry Wall有一句名言:
The three chief virtues of a programmer are: Laziness, Impatience and Hubris. – Larry Wall |
懶惰(Laziness)這個(gè)特點(diǎn)位于程序員的三大美德之首:唯有懶惰才會(huì)驅(qū)動(dòng)程序員盡可能的將日常工作自動(dòng)化起來,解放自己的雙手,節(jié)省自己的時(shí)間。相比較而言,不得不說,GUI應(yīng)用天然就是為了讓自動(dòng)化變得困難的一種設(shè)計(jì)(此處并非貶義,GUI有著自己完全不同的目標(biāo)群體)。
(圖片來自:http://t.cn/R6IBgYV)
GUI更強(qiáng)調(diào)的是與人類的直接交互:通過視覺手段將信息以多層次的方式呈現(xiàn),使用視覺元素進(jìn)行指引,***系統(tǒng)在后臺(tái)進(jìn)行實(shí)際的處理,并將最終結(jié)果以視覺手段展現(xiàn)出來。
這種更強(qiáng)調(diào)交互過程的設(shè)計(jì)初衷使得自動(dòng)化變得非常困難。另一方面,由于GUI是為交互而設(shè)計(jì)的,它的響應(yīng)就不能太快,至少要留給操作者反應(yīng)時(shí)間(甚至有些用戶操作需要人為的加入一些延遲,以提升用戶體驗(yàn))。
三、程序員的日常工作
程序員除了寫代碼之外,還有很多事情要做,比如自動(dòng)化測(cè)試、基礎(chǔ)設(shè)施的配置和管理、持續(xù)集成/持續(xù)發(fā)布環(huán)境,甚至有些團(tuán)隊(duì)還需要做一些與運(yùn)維相關(guān)的事情(線上問題監(jiān)控,環(huán)境監(jiān)控等)。
- 開發(fā)/測(cè)試
- 基礎(chǔ)設(shè)施管理
- 持續(xù)集成/持續(xù)發(fā)布
- 運(yùn)維(監(jiān)控)工作
娛樂而這一系列的工作背后,都隱含了一個(gè)自動(dòng)化的需求。在做上述工作時(shí),優(yōu)秀的程序員會(huì)努力將其自動(dòng)化,如果有工具就使用工具;如果沒有,就開發(fā)一個(gè)新的工具。這種努力讓一切都盡可能自動(dòng)化起來的哲學(xué)起源于UNIX世界。
而UNIX哲學(xué)的實(shí)際體現(xiàn)則是通過命令行來完成的。
Where there is a shell, there is a way. |
四、UNIX編程哲學(xué)
關(guān)于UNIX哲學(xué),其實(shí)坊間有多個(gè)版本,這里有一個(gè)比較詳細(xì)的清單。雖然有不同的版本,但是有很多一致的地方:
- 小即是美
- 讓程序只做好一件事
- 盡可能早地創(chuàng)建原型(然后逐步演進(jìn))
- 數(shù)據(jù)應(yīng)該保存為文本文件
- 避免使用可定制性低下的用戶界面
審視這些條目,我們會(huì)發(fā)現(xiàn)它們事實(shí)上促成了自動(dòng)化一切的可能性。這里列舉一些小的例子,我們來看看命令行工具是如何通過應(yīng)用這些哲學(xué)來簡(jiǎn)化工作、提高效率的。一旦你熟練掌握這些技能,就再也無法離開它,也再也忍受不了低效而復(fù)雜的各種GUI工具了。
五、命令行如何提升效率
一個(gè)高階計(jì)算器
在我的編程生涯早期,讀過的最為振奮的一本書是《UNIX編程環(huán)境》,和其他基本UNIX世界的大部頭比起來,這本書其實(shí)還是比較小眾的。我讀大二的時(shí)候這本書已經(jīng)出版了差不多22年(中文版也已經(jīng)有7年了),有一些內(nèi)容已經(jīng)過時(shí)了,比如沒有返回值的main函數(shù)、外置的參數(shù)列表等等,不過在學(xué)習(xí)到HOC(High Order Calculator)的全部開發(fā)過程時(shí),我依然被深深的震撼到了。
簡(jiǎn)而言之,這個(gè)HOC語言的開發(fā)過程需要這樣幾個(gè)組件:
- 詞法分析器lex
- 語法分析器yacc
- 標(biāo)準(zhǔn)數(shù)學(xué)庫(kù)stdlib
另外還有一些自定義的函數(shù)等,***通過make連接在一起。我跟著書上的講解,對(duì)著書把所有代碼都敲了一遍。所有的操作都是在一臺(tái)很老的IBM的ThinkPad T20上完成的,而且全部都在命令行中進(jìn)行(當(dāng)然,還在命令行里聽著歌)。
這也是我***次徹底被UNIX的哲學(xué)所折服的體驗(yàn):
- 每個(gè)工具只做且做好一件事
- 工具可以協(xié)作起來
- 一切面向文本
下面是書中的Makefile腳本,通過簡(jiǎn)單的配置,就將一些各司其職的小工具協(xié)作起來,完成一個(gè)編程語言程序的預(yù)編譯、編譯、鏈接、二進(jìn)制生成的動(dòng)作。
- YFLAGS = -d
- OBJS = hoc.o code.o init.o math.o symbol.o
- hoc5: $(OBJS)
- cc $(OBJS) -lm -o hoc5
- hoc.o code.o init.o symbol.o: hoc.h
- code.o init.o symbol.o: x.tab.h
- x.tab.h: y.tab.h
- -cmp -s x.tab.h y.tab.h || cp y.tab.h x.tab.h
- pr: hoc.y hoc.h code.c init.c math.c symbol.c
- @pr $?
- @touch pr
- clean:
- rm -f $(OBJS) [xy].tab.[ch]
雖然現(xiàn)在來看,這本書的很多內(nèi)容已經(jīng)過期(特別是離它***次出版已經(jīng)過去了近30年),有興趣的朋友可以讀一讀。這里有一個(gè)Lex/Yacc的小例子,有興趣的朋友可以看看。
當(dāng)然,如果你使用現(xiàn)在***進(jìn)的IDE(典型的GUI工具),其背后做的事情也是同樣的原理:生成一個(gè)Makefile,然后在幕后調(diào)用它。
六、基礎(chǔ)設(shè)施自動(dòng)化
開發(fā)過程中,工程師還需要關(guān)注的一個(gè)問題是:軟件運(yùn)行的環(huán)境。我在學(xué)生時(shí)代剛開始學(xué)習(xí)Linux的時(shí)候,會(huì)在Windows機(jī)器上裝一個(gè)虛擬機(jī)軟件VMWare,然后在VMWare中安裝一個(gè)Redhat Linux 9。
這樣當(dāng)我不小心把Linux玩壞了之后,只需要重裝一下就行了,不影響我的其他數(shù)據(jù)(比如課程作業(yè)、文檔之類)。不過每次重裝也挺麻煩,需要找到iso鏡像文件,再掛載到本地的虛擬光驅(qū)上,然后再用VMWare來安裝。
而且這些動(dòng)作都是在GUI里完成的,每次都要做很多重復(fù)的事情:找鏡像文件,使用虛擬光驅(qū)軟件掛載,啟動(dòng)VMWare,安裝Linux,配置個(gè)人偏好,配置用戶名/密碼等等。熟練之后,我可以在30 - 60分鐘內(nèi)安裝和配置好一個(gè)新的環(huán)境。
1. Vagrant
后來我就發(fā)現(xiàn)了Vagrant,它支持開發(fā)者通過配置的方式將機(jī)器描述出來,然后通過命令行的方式來安裝并啟動(dòng),比如下面這個(gè)配置:
- VAGRANTFILE_API_VERSION = "2"
- Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
- config.vm.box = "precise64"
- config.vm.network "private_network", :ip => "192.168.2.100"
- end
它定義了一個(gè)虛擬機(jī),使用Ubuntu Precise 64的鏡像,然后為其配置一個(gè)網(wǎng)絡(luò)地址192.168.2.100,定義好之后,我只需要執(zhí)行:
- $ vagrant up
我的機(jī)器就可以在幾分鐘內(nèi)裝好,因?yàn)檫@個(gè)動(dòng)作是命令行里完成的,我可以在持續(xù)集成環(huán)境里做同樣的事情 – 只需要一條命令。定義好的這個(gè)文件可以在團(tuán)隊(duì)內(nèi)共享,可以放入版本管理,團(tuán)隊(duì)里的任何一個(gè)成員都可以在幾分鐘內(nèi)得到一個(gè)和我一樣的環(huán)境。
2. Ansible
一般,對(duì)于一個(gè)軟件項(xiàng)目而言,一個(gè)全新的操作系統(tǒng)基本上沒有任何用處。為了讓應(yīng)用跑起來,我們還需要很多東西。比如Web服務(wù)器、Java環(huán)境、cgi路徑等,除了安裝一些軟件之外,還有大量的配置工作要做,比如apache httpd服務(wù)器的文檔根路徑,JAVA_HOME環(huán)境變量等等。
(圖片來自:http://t.cn/R6IBZKm)
這些工作做好了,一個(gè)環(huán)境才算就緒。我記得在上一個(gè)項(xiàng)目上,不小心把測(cè)試環(huán)境的Tomcat目錄給刪除了,結(jié)果害的另外一位同事花了三四個(gè)小時(shí)才把環(huán)境恢復(fù)回來(包括重新安裝Tomcat,配置一些JAVA_OPTS,應(yīng)用的部署等)。
不過好在我們有很多工具可以幫助開發(fā)者完成環(huán)境的自動(dòng)化準(zhǔn)備,比如:Chef、Puppet、Ansible。只需要一些簡(jiǎn)單的配置,然后結(jié)合一個(gè)命令行應(yīng)用,整個(gè)過程就可以自動(dòng)化起來了:
- - name: setup custom repo
- apt: pkg=python-pycurl state=present
- - name: enable carbon
- copy: dest=/etc/default/graphite-carbon content='CARBON_CACHE_ENABLED=true'
- - name: install graphite and deps
- apt: name={{ item }} state=present
- with_items: packages
- - name: install graphite and deps
- pip: name={{ item }} state=present
- with_items: python_packages
- - name: setup apache
- copy: src=apache2-graphite.conf dest=/etc/apache2/sites-available/default
- notify: restart apache
- - name: configure wsgi
- file: path=/etc/apache2/wsgi state=directory
上邊的配置描述了安裝graphite-carbon、配置apahce等很多手工的勞動(dòng),開發(fā)者現(xiàn)在只需要執(zhí)行:
- $ ansible
就可以將整個(gè)過程自動(dòng)化起來?,F(xiàn)在如果我不小心把Tomcat刪了,只需要幾分鐘就可以重新配置一個(gè)全新的,當(dāng)然整個(gè)過程還是自動(dòng)的。這在GUI下完全無法想象,特別是在有如此多的定制內(nèi)容的場(chǎng)景下。
七、持續(xù)集成/持續(xù)發(fā)布
日常開發(fā)任務(wù)中,除了實(shí)際的編碼和環(huán)境配置之外,另一大部分內(nèi)容就是持續(xù)集成/持續(xù)發(fā)布了。借助于命令行,這個(gè)動(dòng)作也可以非常高效和自動(dòng)化。
Jenkins
持續(xù)集成/持續(xù)發(fā)布已經(jīng)是很多企業(yè)IT的基本配置了。各個(gè)團(tuán)隊(duì)通過持續(xù)集成環(huán)境來編譯代碼、靜態(tài)檢查、執(zhí)行單元測(cè)試、端到端測(cè)試、生成報(bào)告、打包、部署到測(cè)試環(huán)境等等。
比如在Jenkins環(huán)境中,在最前的版本中,要配置一個(gè)構(gòu)建任務(wù)需要很多的GUI操作,不過在新版本中,大部分操作都已經(jīng)可以寫成腳本。
這樣的方式,使得自動(dòng)化變成了可能,要復(fù)制一個(gè)已有的pipline,或者要修改一些配置、命令、變量等等,再也不需要用鼠標(biāo)點(diǎn)來點(diǎn)去了。而且這些代碼可以納入項(xiàng)目代碼庫(kù)中,和其他代碼一起被管理、維護(hù),變更歷史也更容易追蹤和回滾(在GUI上,特別是基于Web的,回滾操作基本上屬于不可能)。
- node {
- def mvnHome
- stage('Preparation') { // for display purposes
- git 'https://github.com/jglick/simple-maven-project-with-tests.git'
- mvnHome = tool 'M3'
- }
- stage('Build') {
- sh "'${mvnHome}/bin/mvn' -Dmaven.test.failure.ignore clean package"
- }
- stage('Results') {
- junit '**/target/surefire-reports/TEST-*.xml'
- archive 'target/*.jar'
- }
- }
上面這段groovy腳本定義了三個(gè)階段,每個(gè)階段中分別有自己的命令,這種以代碼來控制的方式顯然比GUI編輯的方式更加高效,自動(dòng)化也變成了可能。
八、運(yùn)維工作
1. 自動(dòng)化監(jiān)控
Graphite是一個(gè)功能強(qiáng)大的監(jiān)控工具,不過其背后的理念倒是很簡(jiǎn)單:
- 存儲(chǔ)基于時(shí)間線的數(shù)據(jù)
- 將數(shù)據(jù)渲染成圖,并定期刷新
用戶只需要將數(shù)據(jù)按照一定格式定期發(fā)送給Graphite,剩下的事情就交給Graphite了,比如它可以消費(fèi)這樣的數(shù)據(jù):
- instance.prod.cpu.load 40 1484638635
- instance.prod.cpu.load 35 1484638754
- instance.prod.cpu.load 23 1484638812
***個(gè)字段表示數(shù)據(jù)的名稱,比如此處instance.prod.cpu.load表示prod實(shí)例的CPU負(fù)載,第二個(gè)字段表示數(shù)據(jù)的值,***一個(gè)字段表示時(shí)間戳。
這樣,Graphite就會(huì)將所有同一名稱下的值按照時(shí)間順序畫成圖。
(圖片來自:http://t.cn/R6IxKYL)
默認(rèn)地,Graphite會(huì)監(jiān)聽一個(gè)網(wǎng)絡(luò)端口,用戶通過網(wǎng)絡(luò)將信息發(fā)送給這個(gè)端口,然后Graphite會(huì)將信息持久化起來,然后定期刷新。簡(jiǎn)而言之,只需要一條命令就可以做到發(fā)送數(shù)據(jù):
- echo "instance.prod.cpu.load 23 `date +%s`" | nc -q0 graphite.server 2003
date +%s會(huì)生成當(dāng)前時(shí)間戳,然后通過echo命令將其拼成一個(gè)完整的字符串,比如:
- instance.prod.cpu.load 23 1484638812
然后通過管道|將這個(gè)字符串通過網(wǎng)絡(luò)發(fā)送給graphite.server這臺(tái)機(jī)器的2003端口。這樣數(shù)據(jù)就被記錄在graphite.server上了。
2. 定時(shí)任務(wù)
如果我們要自動(dòng)的將數(shù)據(jù)每隔幾秒就發(fā)送給graphite.server,只需要改造一下這行命令:
- 獲取當(dāng)前CPU的load
- 獲取當(dāng)前時(shí)間戳
- 拼成一個(gè)字符串
- 發(fā)送給graphite.server的2003端口
- 每隔5分鐘做重復(fù)一下步驟1-4
獲取CPU的load在大多數(shù)系統(tǒng)中都很容易:
- ps -A -o %cpu
這里的參數(shù):
- -A表示統(tǒng)計(jì)所有當(dāng)前進(jìn)程
- -o %cpu表示僅顯示%cpu列的數(shù)值
這樣可以得到每個(gè)進(jìn)程占用CPU負(fù)載的數(shù)字:
- %CPU
- 12.0
- 8.2
- 1.2
- ...
下一步是將這些數(shù)字加起來。通過awk命令,可以很容易做到這一點(diǎn):
- $ awk '{s+=$1} END {print s}'
比如要計(jì)算1、2、3的和:
- $ echo "1\n2\n3" | awk '{s+=$1} END {print s}'
通過管道可以將兩者連起來:
- $ ps -A -o %cpu | awk '{s+=$1} END {print s}'
我們測(cè)試一下效果:
- $ ps -A -o %cpu | awk '{s+=$1} END {print s}'
- 28.6
看來還不錯(cuò),有個(gè)這個(gè)腳本,通過crontab來定期調(diào)用即可:
- #!/bin/bashSERVER=graphite.server
- PORT=2003
- LOAD=`ps -A -o %cpu | awk '{s+=$1} END {print s}'`
- echo "instance.prod.cpu.load ${LOAD} `date +%s`" | nc -q0 ${SERVER} ${PORT}
當(dāng)然,如果使用Grafana等強(qiáng)調(diào)UI的工具,可以很容易的做的更加酷炫:
(圖片來源:http://t.cn/R6IxsFu)
想想用GUI應(yīng)用如何做到這些工作。
九、娛樂
命令行的MP3播放器
最早的時(shí)候,有一個(gè)叫做mpg123的命令行工具,用來播放MP3文件。不過這個(gè)工具是商用的,于是就有人寫了一個(gè)工具,叫mpg321,基本上是mpg123的開源克隆。不過后來mpg123自己也開源了,這是后話不提。
將我的所有mp3文件的路徑保存成一個(gè)文件,相當(dāng)于我的歌單:
- $ ls /Users/jtqiu/Music/*.mp3 > favorites.list
- $ cat favorites.list
- ...
- /Users/jtqiu/Music/Rolling In The Deep-Adele.mp3
- /Users/jtqiu/Music/Wavin' Flag-K'Naan.mp3
- /Users/jtqiu/Music/藍(lán)蓮花-許巍.mp3
- ...
然后我將這個(gè)歌單交給mpg321去在后臺(tái)播放:
- $ mpg321 -q --list favorites.list &
- [1] 10268
這樣我就可以一邊寫代碼一邊聽音樂,如果聽煩了,只需要將這個(gè)后臺(tái)任務(wù)切換到前臺(tái)fg,然后就可以關(guān)掉了:
- $ fg
- [1] + 10268 running mpg321 -q --list favorites.list
十、小結(jié)
綜上,優(yōu)秀的程序員借助命令行的特性,可以成倍(有時(shí)候是跨越數(shù)量級(jí)的)提高工作效率,從而有更多的時(shí)間進(jìn)行思考、學(xué)習(xí)新的技能,或者開發(fā)新的工具幫助某項(xiàng)工作的自動(dòng)化。這也是優(yōu)秀的程序員之所以優(yōu)秀的原因。而面向手工的、原始的圖形界面會(huì)拖慢這個(gè)過程,很多原本可以自動(dòng)化起來的工作被淹沒在“簡(jiǎn)單的GUI”之中。
(圖片來自:http://cargocollective.com/)
***補(bǔ)充一點(diǎn),本文的關(guān)鍵在于強(qiáng)調(diào)優(yōu)秀的程序員與命令行的關(guān)系,而不在GUI程序和命令行的優(yōu)劣對(duì)比。GUI程序當(dāng)然有其使用場(chǎng)景,比如做3D建模、GIS系統(tǒng)、設(shè)計(jì)師的創(chuàng)作、圖文并茂的字處理軟件、電影播放器、網(wǎng)頁瀏覽器等等。
應(yīng)該說,命令行和優(yōu)秀的程序員之間更多是關(guān)聯(lián)關(guān)系,而不是因果關(guān)系。在程序員日常的工作中,涉及到的更多的是一些需要命令行工具來做支持的場(chǎng)景。如果走極端,在不適合的場(chǎng)景中強(qiáng)行使用命令行,而置效率于不顧,則未免有點(diǎn)矯枉過正,南轅北轍了。
【本文是51CTO專欄作者“ThoughtWorks”的原創(chuàng)稿件,微信公眾號(hào):思特沃克,轉(zhuǎn)載請(qǐng)聯(lián)系原作者】