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