《卓有成效程式設計師》第四章
自動化
在從前的一個專案中,我們需要定時更新幾個電子資料表檔案。我需要用Excel開啟這些檔案,但手工做這件事實在費勁(偏偏Excel又不允許在命令列中傳入多個檔名)。所以,我花了幾分鐘時間寫出了下面這段Ruby小指令碼:
class DailyLogs
private
@@Home_Dir = "c:\\MyDocuments\\Documents\\"
def doc_list
docs = Array.new
docs << "Sisyphus Project Planner.xls"
docs << "TimeLog.xls"
docs << "NFR.xls"
end
def open_daily_logs
excel = WIN32OLE.new("excel.application")
workbooks = excel.WorkBooks
excel.Visible = true
doc_list.each do |f|
begin
workbooks.Open(@@Home_Dir + f, true)
rescue
puts "Cannot open workbook:", @@Home_Dir + f
end
end
excel.Windows.Arrange(7)
end
end
DailyLogs.daily_logs
雖說手工開啟這幾個檔案花不了多少工夫,但那也是在浪費時間,所以我將這件事自動化了。而且我還從中學到一點東西:在Windows上可以用Ruby來驅動COM物件,比如Excel。
計算機原本就該從事簡單重複的工作。只要把任務指派給它們,它們就可以一遍又一遍毫不走樣地重複執行,而且速度飛快。但我卻經常看見一種奇怪的現象:人們在計算機上手工做一些簡單重複的工作,計算機們則在大半夜裡扎堆閒聊取笑這些可憐的使用者。怎麼會這樣?
圖形化環境是用來幫助新手的。微軟在Windows桌面的左下角放上一個大大的“開始”按鈕,因為很多剛從舊版本切換過來的使用者一頭霧水不知道該怎麼開始操作。(奇怪的是,關機操作也是從“開始”按鈕開始的。)但正是這些提高新手效率的東西卻恰巧成為高階使用者的束縛:比如軟體開發中的各種雜事,在命令列裡處理幾乎一定比圖形使用者介面來得快。在過去二十年裡,高階使用者執行常規任務的速度反而降低了,這不能不說是一個大大的反諷。過去那些典型的Unix使用者比如今習慣了圖形介面的使用者更高效,因為他們把一切都自動化了。
要是你去過老木匠的作坊,一定會看見那兒到處都擺著專用的工具(沒準還有一臺鐳射定位螺旋平衡的車床,只是你認不出來)。儘管有那麼多工具,在大部分專案裡,木匠師傅還是會從地上找一兩塊邊角廢料,臨時用來隔開兩個零件、或是把兩個零件夾在一起。按照工程學術語,這樣的小塊材料被稱為“填木”或是“夾鐵”。作為軟體開發者,我們很少創造這種用過即扔的小工具,很多時候是因為我們還沒意識到這些也是工具。
軟體開發中有很多顯而易見的東西需要自動化:構建、持續整合、還有文件。本章會介紹一些不那麼顯而易見、價值卻毫不遜色的自動化方法。小到一次敲擊鍵盤,大到小規模的應用程式,我們都會有所介紹。
不要重新發明輪子
每個專案都需要準備一些通用的基礎設施:版本控制、持續整合、使用者帳號之類的。對於Java專案,Buildix[5](ThoughtWorks開發的一個開源專案)能夠大大簡化這些準備工作。很多Linux發行版本會提供“Live CD”選項,用這張CD就可以直接試用這個Linux版本。Buildix也採用了這種發行方式,只不過加上了預先配置好的專案基礎設施:它本身其實是一張Ubuntu Live CD,預裝了一些軟體開發中常用的工具。Buildix預裝了下列工具:
lSubversion,流行的開源版本控制工具
lCruiseControl,開源的持續整合伺服器
lTrac,開源的問題跟蹤和wiki工具
lMingle,ThoughtWorks推出的敏捷專案管理工具
用Buildix CD啟動,你的專案基礎設施就準備好了。你也可以用這張CD在已有的Ubuntu系統上進行安裝。
建立本地緩存
在開發軟體時你經常需要到網際網路上查資料。不管你的網路有多快,在網際網路上瀏覽網頁終究是要花時間的。所以,對於經常查閱的資料(例如程式設計API),你應該把它快取到本地(這樣你在飛機上也可以看了)。有些東西很容易儲存,只要在瀏覽器裡儲存頁面就行了;但很多時候你需要儲存一大堆的網頁,這時就應該求助於工具。
wget是一個*-nix工具,用於將網際網路上的內容儲存到本地。在所有*-nix平臺上都可以找到這個工具,在Windows上也可以透過Cygwin找到它。在抓取網頁方面,wget有很多選項,其中最常用的當屬mirror選項:將整個網站映象到本地。比如說,下列命令就可以在本地建立一個網站的映象:
wget --mirror -w 2 --html-extension --convert-links -P c:\wget_files\example1
表4.1. 使用wget命令
文字 |
含義 |
wget |
啟動wget工具 |
--mirror |
給網站建立本地映象。wget會遞迴地跟蹤網站上的連結,下載所有需要的檔案。預設情況下,它只會下載上次映象操作之後有更新的檔案,以避免無用功。 |
--html-extension |
很多網站使用非HTML的副檔名(例如cgi或是php),實際上它們最終也生成HTML頁面。這個選項告訴wget,應該把這些檔案的副檔名改為HTML。 |
--convert-links |
把頁面上所有的連結轉為本地連結,以免因為頁面上有指向絕對URI的連結而導致頁面無法使用。wget會轉換頁面上所有的連結,使其指向本地資源。 |
-P c:\wget_files\example1 |
指定儲存網站映象的本地目錄。 |
自動訪問網站
有些網站需要你登入或是做些別的操作才能得到你需要的資訊,cURL能幫你自動化這些互動。cURL也是一個開源工具,有所有主要作業系統的安裝版本。它和wget有些相似,不過更偏重於與頁面互動以獲取內容或是抓取資源。譬如說,假設網站上有以下表單:
cURL就可以提供兩個引數,獲得表單提交後的結果:
curl ""
在命令列輸入“-d”引數,就可以用HTML POST方法(而非預設的GET方法)與頁面互動:
curl -d "birthyear=1905&press=%20OK%20"
cURL最妙的一點是它能用各種協議(例如HTTPS)與加密的網站互動。cURL網站上有關於這方面的詳細介紹。能夠使用安全協議訪問網站,再加上其他功能,使得cURL成為了與網站互動的絕佳工具。Max OS X和大部分Linux發行版本都預設安裝了cURL,也可以在 http://www.curl.org 網站下載它的Windows版本。
與RSS源互動
Yahoo!有一個名叫Pipes的服務(一直處於beta狀態,目前還是),可以用來操作RSS源(例如blog)。你可以組合、過濾和處理RSS資訊,用於建立網頁或是新的RSS源。Pipes借鑑了Unix的“命令列管道”的概念,提供了一個基於web的拖拽介面來建立RSS“管道”:資訊從一個RSS源流向另一個。從使用的角度來說,這個功能很類似於Mac OS X的Automator工具──每個命令(或者管道中的一個階段)的輸出成為下一個管道的輸入。
舉例來說,圖1.4所示的管道會抓取“No Fluff, Just Stuff”會議網站的blog聚合RSS,後者包含了最近的blog文章。每個blog條目都遵循“作者 – 標題”的格式,而我又只想要其中的作者資訊,所以我用了一個正規表示式管道將“作者 - 標題”替換成作者姓名。
圖4.1 實用正規表示式管道
管道的輸出可以是一個HTML頁面,也可以是另一個RSS源(從中獲取RSS時就會導致管道被執行)。
RSS日益成為一種重要的資訊格式,尤其是對於軟體開發者們關注的那些資訊,而Yahoo! Pipes則允許你以程式設計的方式處理RSS,對資訊加以提煉。不僅如此,Pipes還逐漸加上了“從網頁上採集資訊”的功能,從而可以自動獲取各種基於web的資訊。
在構建之外使用Ant
提示
即便不是工具最初的設計意圖,只要是合適的場合,同樣可以使用這些工具。
在作業系統的層面上,批處理檔案和bash指令碼都可以用於將工作自動化,但這兩者的語法都不夠靈活,命令也常常顯得笨拙。舉例來說,如果你需要對一大批檔案執行某一操作,用批處理檔案或是bash指令碼的原生命令想要得到這些檔案的列表就是件麻煩事。幹嘛不用專門為此而設計的工具呢?
其實我們常用的構建工具已經知道如何獲取檔案列表、如何對其進行過濾或是執行別的操作。在對檔案進行批次操作這件事情上,Ant、NAnt和Rake的語法都比批處理檔案或是bash指令碼要友好得多。
下面就是用Ant來處理檔案的一個例子──這事情用批處理做起來實在太難,以至於我根本就不打算嘗試。我曾經教過很多程式設計課程,在課堂上常會寫一些例子程式。為了解答學生們提出的問題,也經常需要寫一些小程式來講解。一週課程結束以後,所有學生都想要複製我在課上寫的小程式。但這些小程式還產生了很多額外的檔案(輸出檔案、JAR檔案、臨時檔案等等),所以我得把這些無關的檔案清理掉,然後建立一個乾淨的ZIP包複製給學生們。這件事我沒有手工去做,而是為之建立了一個Ant檔案。Ant的一大好處就是它內建對批次檔案的支援:
在Ant的幫助下,我可以寫一個總任務來執行以前手工完成的那些步驟:
同樣的事情要用批處理檔案寫出來,不啻是一場噩夢!哪怕用Java寫都會很麻煩:Java並沒有內建對批次檔案進行模式匹配的支援。使用構建工具,你不需要建立main方法,也無須操心太多基礎設施的問題,因為構建工具通常已經提供了足夠的支援。
Ant最糟糕的一點莫過於對XML的依賴,這使得Ant指令碼既難寫又難讀,同樣難以重構,連diff都變得困難。Gant[]是一個不錯的替代品:它能夠與已有的Ant任務互動,但它的構建指令碼是用Groovy編寫的──和XML不同,那是一種真正的程式語言。
用Rake執行常見任務
Rake就是Ruby的make工具(同時它本身也是用Ruby編寫的)。Rake是shell指令碼的完美替代品,因為它不僅具備了Ruby強大的表現力,而且能夠與作業系統輕鬆互動。
下面這個例子是我經常講的。我在各種開發者大會上做很多演講,這也就意味著我有很多幻燈片和對應的示例程式碼。長期以來,我總是先開啟幻燈片,然後憑記憶開啟其他需要開啟的工具和示例。難免有時,我會忘記開啟某個示例,於是就不得不在演講中途手忙腳亂地翻找。有這麼幾次經驗之後,我意識到這個問題,並把這個過程自動化了:
require File.dirname(__FILE__) + '/../base'
TARGET = File.dirname(__FILE__)
FILES = [
"#{PRESENTATIONS}/building_dsls.key",
"#{DEV}/java/intellij/conf_dsl_builder/conf_dsl_builder.ipr",
"#{DEV}/java/intellij/conf_dsl_logging/conf_dsl_logging.ipr",
"#{DEV}/java/intellij/conf_dsl_calendar_stopping/conf_dsl_calendar_stopping.ipr",
"#{DEV}/thoughtworks/rbs/intarch/common/common.ipr"
]
APPS = [
"#{TEXTMATE} #{GROOVY}/dsls/",
"#{TEXTMATE} #{RUBY}/conf_dsl_calendar/",
"#{TEXTMATE} #{RUBY}/conf_dsl_context"
]
這個Rake檔案列出所有我需要開啟的檔案,以及在演講中需要用到的應用程式。Rake的妙處之一在於它可以使用Ruby檔案來作為輔助。前面這個Rake檔案本身實際上只起宣告作用,實際的工作都在名為base的Rake檔案中實現了,別的Rake檔案則依賴於它。
require 'rake'
require File.dirname(__FILE__) + '/locations'
require File.dirname(__FILE__) + '/talks_helper'
task :open do
TalksHelper.new(FILES, APPS).open_everything
end
可以看到,在這個檔案的頂上,我又引入了一個名為task_helper的檔案:
class TalksHelper
attr_writer :openers, :processes
def initialize(openers, processes)
@openers, @processes = openers, processes
end
def open_everything
@openers.each { |f| `open #{f.gsub /\s/, '\\ '}` } unless @openers.nil?
@processes.each do |p|
pid = fork {system p}
Process.detach(pid)
end unless @processes.nil?
end
end
實際做事的程式碼都在這個輔助類裡。這樣一來,我就可以為每次演講寫一個簡單的Rake檔案,然後就可以自動開啟所有我需要的東西。Rake的一大優勢是能夠非常輕鬆地與作業系統互動。只要把一個字串用反引號(`)括起來,這句話就會被當作shell命令被執行。所以,包含了`open #{f.gsub /\s/, '\\ '}`的這行程式碼實際上是在作業系統層面上執行了open命令(我的作業系統是Mac OS X,對於Windows可以替換成start命令),並傳入前面定義的變數作為引數。用Ruby驅動底層作業系統比編寫bash指令碼或者批處理檔案要容易多了。
用Selenium瀏覽網頁
Selenium []是一個開源的測試工具,用於web應用的使用者驗收測試。Selenium藉助JavaScript自動化了瀏覽器操作,從而可以模擬使用者的行為。Selenium完全是用瀏覽器端技術編寫的,所以在所有主流瀏覽器中都能使用。這是一個極其有用的web應用測試工具,不論被測的web應用是用什麼技術開發的。不過現在我不是來介紹怎麼用Selenium做測試的。Selenium有一個叫做Selenium IDE的派生專案,那是一個Firefox瀏覽器外掛,能將使用者與網站的互動記錄為Selenium指令碼,然後可以用Selenium的TestRunner或者Selenium IDE來執行這些指令碼。這個工具在用來建立測試時非常有用,而如果你需要將你與網站的互動自動化,那它更是一個無價之寶。
下面就是一個常見的情景:假設你要開發一個嚮導形式的web應用,現在前三個頁面都已經完成,它們的行為(包括輸入校驗等)都運轉良好,你正在編寫第四個頁面。為了除錯這個一頁面上的行為,你必須反覆走過前三個頁面,一遍,又一遍,又一遍……你不斷地對自己說:“好吧,這是最後一次了,這次我肯定能把問題都解決掉。”但似乎永遠沒有最後一次!要不然,你的測試資料庫裡怎麼會充斥著那麼多“Fred Flintstone”、“Homer Simpson”,還有在焦躁中輸入的“ASDF”之類的名字?
Selenium IDE可以幫你做這些煩瑣的事情。你只要點選導航來到第四個頁面,用Selenium IDE把你的操作記錄下來(就像圖4-2這樣)。下一次當你需要到達第四個頁面並填入合法的輸入值時,只要回放這段Selenium指令碼就行了。
圖4-2. Selenium IDE和錄製好的指令碼
順理成章地,Selenium的另一個絕妙用途也浮出水面了。當QA部門的同事發現一個bug時,他們通常會用某些原始的方法來記錄bug出現的情況:寫下他們的操作,附上一張模糊不清的截圖圖或是別的什麼幫不上忙的東西。現在,請讓他們用Selenium IDE把發現bug的過程記錄下來,然後提交他們的Selenium指令碼;然後你就可以反覆地、毫釐不差地重複他們的操作步驟,直到bug被修復為止。這不僅節約時間,而且省了很多麻煩。Selenium可以把使用者與網站的互動變成一段可執行的指令碼,請使用它吧!
提示
不要浪費時間動手去做可以被自動化的事情。
用bash統計異常數
這裡有一個使用bash的例子,你可能會在一個典型的專案中遇到類似的情況。當時我在一個已經有6年曆史的大型Java專案中工作(我只是一個訪客,在第6年進入這個專案,並在上面工作了大概8個月)。我的任務之一就是清理一些經常發生的異常,為此我做的第一件事就是提問:“哪些異常會被丟擲?以什麼樣的頻率?”當然了,沒人知道,所以我的第一個任務就是自己動手找到答案。但問題是這個應用程式每星期會吐出超過2 GB的日誌,很快我就意識到:即便只是嘗試用文字編輯器開啟這個檔案,那都是在浪費時間。於是我坐下來,寫了這麼一段指令碼:
#!/bin/bash
for X in $(egrep -o "[A-Z]\w*Exception" log_week.txt | sort | uniq) ;
do
echo -n -e "processing $X\t"
grep -c "$X" log_week.txt
done
表4-2解釋了這段bash小指令碼的作用。
表4-2. 用於統計異常數量的複雜bash命令
文字 |
用途 |
egrep -o |
找出日誌檔案中出現在“Exception”字眼之前的文字,對它們進行排序,然後得到一個消除重複之後的列表 |
"[A-Z]\w*Exception" |
用於定位異常資訊的正則模式 |
log_week.txt |
龐大的日誌檔案 |
| sort |
將前面的查詢結果管道給sort,生成一個排序後的異常列表 |
| uniq |
去掉重複的異常資訊 |
for X in $(. . .) ; |
迴圈前面生成的異常列表,針對其中的每個異常執行這些程式碼 |
echo -n -e "processing $X\t" |
把找到的異常輸出在控制檯上(這樣我才知道這段指令碼還在工作) |
grep -c "$X" log_week.txt |
在龐大的日誌檔案中找出這個異常出現的次數 |
這個專案到現在還在使用這段小程式。這是一個好例子:藉助自動化工具,你可以從專案中找出一些從未有人發現的、有價值的資訊。與其絞盡腦汁地猜測有哪些異常被丟擲,不如把它們都找出來,這樣也可以更有目的性、更容易地修復這些丟擲異常的程式。
用Windows Power Shell替代批處理檔案
作為Windows Vista的一部分,Microsoft對批處理語言做了大幅度的改進。新的批處理環境代號叫Monad,不過在發行版中叫做Windows Power Shell。(為了避免每次都寫出這麼長的名字,為了節約紙張保護樹木,我打算繼續稱它為“Monad”。)它是內建在Windows Vista中的,如果你想在Windows XP上使用,可以從Microsoft網站下載。
Monad從bash和DOS等類似的命令列shell語言中借鑑了很多理念,例如你可以把一個命令的輸出管道給另一個命令作為輸入。它們之間最大的區別在於Monad不是使用文字(像bash那樣),而是使用物件:Monad的命令(被稱為cmdlet)知道一組代表作業系統構造的物件,像檔案、目錄、甚至Windows事件檢視器(event viewer)之類的東西。Monad的語義和bash大同小異(管道運算子還是那個歷史悠久的“|”符號),但它的能力著實強大。下面就是一個例子:假設你需要把在2006年12月1日以後更新過的檔案複製到DestFolder目錄中,相應的Monad命令大概會是這樣:
dir | where-object { $_.LastWriteTime -gt "12/1/2006" } |
move-item -destination c:\DestFolder
由於Monad的cmdlet能“理解”其他cmdlet,也能“理解”它們輸出的東西,所以Monad的指令碼可以寫得比其他指令碼語言更簡潔。譬如說,假設你想要殺掉所有佔用超過15 MB記憶體的程式,用bash來做大概是這樣:
ps -el | awk '{ if ( $6 > (1024*15)) { print $3 } }'
| grep -v PID | xargs kill
這可不怎麼好看!這裡用到了5個不同的bash命令,包括用awk來解析ps命令的結果。再看看用Monad怎麼做同樣的事:
get-process | where { $_.VS -gt 15M } | stop-process
針對get-process的結果,你可以用where命令來根據某個特定屬性加以過濾(在這裡我們根據VS屬性過濾,也就是佔用記憶體的大小)。
Monad是用.NET編寫的,也就是說你還可以使用標準的.NET型別。字串處理對於命令列shell來說一直是個難題,現在你可以用.NET的String類來解決這個問題了。譬如說,下列Monad命令:
get-member -input "String" -membertype method
會輸出String類的所有方法。這就有點像在*-nix中使用man工具一樣。
Monad是Windows世界的一大進步。它把作業系統層面上的程式設計當作一等公民來對待,很多原本需要求助於Perl、Python和Ruby等指令碼語言的任務現在可以用Monad輕鬆完成。由於它是作業系統核心的一部分,你還可以用它來查詢、作業系統特有的物件(例如事件檢視器)。
用Mac OS X的Automator來刪除過時的下載檔案
Mac OS X提供了一種以圖形化方式編寫批處理檔案的途徑,那就是Automator。從很多方面來說,這簡直就是一個圖形化版本的Monad,而且比後者早出現了幾年。要建立一個Automator工作流(也就是Mac OS X版本的指令碼),只要從Automator的工作區拖拽命令,然後把命令之間的輸入輸出“連線”起來就行了。每個應用程式在安裝時都會向Automator註冊,告訴後者自己能做什麼。還可以用Objective C(Mac OS X底層的開發語言)來編寫程式碼擴充套件Automator。
下面是一個Automator工作流的例子:刪除所有在硬碟上放了超過兩週的下載檔案。這個工作流(如圖4.3所示)由以下步驟組成:
1.這個工作流會把最近兩週的下載檔案暫存在recent目錄下。
2.清空recent目錄,為儲存新下載的檔案做好準備。
3.找出所有修改日期在兩週以內的下載檔案。
4.把上述檔案移到recent目錄下。
5.找出downloads目錄下所有非目錄的檔案。
6.刪除上述檔案。
圖4.3. 用於刪除過時下載檔案的Mac OS X Automator工作流
這個工作流比前面的Monad指令碼做了更多的工作,因為在工作流中沒有一種簡單的辦法能描述“在過去兩週內沒有被修改過的檔案”,於是最好的辦法就是找出那些在過去兩週中被修改過的檔案,把這些檔案移到一個暫存目錄(也就是recent目錄),然後刪除downloads目錄下所有的檔案。你永遠也不會手工去幹這些麻煩事,但由於這是自動化工具,做些額外工作也無妨。另一種辦法是編寫一段bash shell指令碼,然後在工作流中使用它(可以呼叫這段bash指令碼),但這樣一來你又回到了“解析shell指令碼的結果來找出檔名”的老問題上。如果你真的要這麼做,還不如把整件事情都用shell指令碼解決呢。
馴服Subversion命令列
總有些時候,沒有別的什麼工具或是開源專案能恰好滿足你的需要,這時就該你自己動手製造“填木”和“夾鐵”了。這一章介紹了很多種製造工具的方式,下面就是一些在真實專案中用這些工具來解決問題的例子。
我是開源版本控制系統Subversion的忠實粉絲,在我看來它就是強大、簡單和易用的完美結合。歸根到底Subversion是一個基於命令列的版本控制系統,不過有很多開發者為它開發了前端工具(我的最愛是與Windows資源管理器整合的Tortoise)。儘管如此,Subversion最大的威力還是在命令列,我們來看一個例子。
我經常會一次往Subversion裡新增一批檔案。在使用命令列做這件事時,你必須指定所有想要新增的檔名。如果檔案不多的話這還不算太糟糕,但如果你要新增20個檔案,那就費事了。當然你也可以用萬用字元,但這樣一來就可能匹配到已經在版本控制之下的檔案(這不會有什麼損害,只不過會輸出一堆錯誤資訊,可能會跟別的錯誤資訊混淆)。為了解決這個問題,我寫了一行簡單的bash命令:
svn st | grep '^\?' | tr '^\?' ' ' | sed 's/[ ]*//' | sed 's/[ ]/\\ /g' | xargs svn add
表4.3詳細解釋了這一行命令。
表4.3. svnAddNew命令詳解
命令 |
效果 |
svn st |
獲取當前目錄及子目錄中所有檔案的Subversion狀態,每個檔案一行。尚未加入版本控制的新檔案會以一個問號(“?”)開頭,隨後是一個tab,最後是檔名。 |
grep '^\?' |
找出所有以“?”開頭的行。 |
tr '^\?' ' ' |
把“?”替換成空格(tr命令會把一個字元替換為另一個字元)。 |
sed 's/[ ]*//' |
用sed(基於流的編輯器)把每行開頭的空格去掉。 |
sed 's/[ ]/\\ /g' |
檔名內部也可能包含空格,所以再動用一次sed,把檔名中的空格替換成跳脫字元(也就是在空格符前面加上“\”字元)。 |
xargs svn add |
針對前面處理的結果,逐一呼叫svn add命令。 |
我大概花了15分鐘寫出這條命令,然後用了它成百上千次。
用Ruby編寫SQL拆分工具
在從前的一個專案中,我和一個同事需要解析一個巨大(38,000行)的遺留SQL檔案。為了讓解析的工作變得容易一點,我們想把這個鐵板一塊的檔案分成每塊1,000行左右的小塊。我們稍微考慮了一下手工做這件事,不過很快就明白將其自動化會是更好的辦法。我們也考慮用sed來實現,不過似乎會很複雜。最終,我們選定了Ruby。大約一個小時以後,我們得到了這個:
SQL_FILE = "./GeneratedTestData.sql"
OUTPUT_PATH = "./chunks of sql/"
line_num = 1
file_num = 0
Dir.mkdir(OUTPUT_PATH) unless File.exists? OUTPUT_PATH
file = File.new(OUTPUT_PATH + "chunk " + file_num.to_s + ".sql",
File::CREAT|File::TRUNC|File::RDWR, 0644)
done, seen_1k_lines = false
IO.readlines(SQL_FILE).each do |line|
file.puts(line)
seen_1k_lines = (line_num % 1000 == 0) unless seen_1k_lines
line_num += 1
done = (line.downcase =~ /^\W*go\W*$/ or
line.downcase =~ /^\W*end\W*$/) != nil
if done and seen_1k_lines
file_num += 1
file = File.new(OUTPUT_PATH + "chunk " + file_num.to_s + ".sql",
File::CREAT|File::TRUNC|File::RDWR, 0644)
done, seen_1k_lines = false
end
end
這個Ruby小程式從原始檔中逐行讀取,直到讀滿1,000行為止,然後從中尋找包含GO或者END的行,如果找到就結束當前檔案的查詢,開始下一個檔案。
我們計算了一下,如果手工強行分解這個檔案,大概需要10分鐘;而我們將其自動化用了大概1小時。後來我們又把分解的工作做了5次,所以花在自動化上面的時間基本上算是值回票價。但這不是重點。手工執行簡單重複的任務會讓你變傻,會消耗你的注意力,而注意力是最重要的生產力之源。
提示
做簡單重複的事是在浪費你的注意力。
相反,找出一種聰明的方法來自動化這些任務,這會讓你變聰明,因為你能從中學到一些東西。我們之所以用了這麼長時間來寫這段程式,原因之一是我們不熟悉Ruby的底層檔案處理機制。現在我們學會了,於是我們可以把這些知識應用到別的專案。而且我們學會了如何對部分專案基礎設施進行自動化,這樣將來我們會更有可能找出別的一些方式來自動化簡單重複的任務。
提示
以創造性的方式解決問題,有助於在將來解決類似的問題。
我應該把它自動化嗎?
有那麼一個應用程式,它的部署只需要三個步驟:在資料庫上執行“create tables”指令碼,把應用程式檔案複製到web伺服器上,然後更新配置檔案使之反映應用程式的路由變更。很簡單的幾個步驟。你需要每隔兩三天就部署一次,那又怎麼樣呢?畢竟做這一切只需要15分鐘。
假如這個專案持續8個月又如何呢?你要把這個儀式重複64遍(實際上到專案後期這個速率還會提高,那時你會更加頻繁地部署)。把它加起來:64次 x 15分鐘 = 960分鐘 = 16小時 = 2工作日。整整兩個工作日不幹別的,就是一遍又一遍地重複這同一件事!這還沒考慮你會因為一時粗心忘記其中的某個步驟,然後不得不花更多的時間來除錯和修復錯誤。所以,只要將其自動化的工作量不超過兩天,這筆買賣就根本不需要考慮,因為你完全是在節約時間。但如果需要三天時間來將其自動化呢──還值得去做嗎?
我見過一些系統管理員,他們寫bash指令碼來執行每項任務。這樣做的原因有兩條。第一,既然你已經做了一次,那麼幾乎可以肯定以後你還會做同樣的事。bash命令本身非常簡潔,有時就連有經驗的程式設計師也得花上好幾分鐘才能弄對;但如果你需要再次執行這個任務,儲存下來的命令就能節省你的時間。第二,把一切有用的命令都儲存在指令碼中,就等於給你做的事情──甚至還可能包括為什麼要做這些事──建立了一份活的文件。記錄所做的每件事有些極端,但儲存裝置非常便宜──比起重新創造某些東西所需的時間來要便宜多了。你也可以做個折衷:不必儲存做過的每件事,不過一旦發現自己第二次做某件事,就將其自動化。一旦某件事需要你做兩次,很可能你還需要做100次。
幾乎所有*-nix使用者都會在自己的.bash_profile配置檔案裡建立各種別名,給常用的命令列工具創造捷徑。下面的例子展示了別名的語法:
alias catout='tail -f /Users/nealford/bin/apache-tomcat-6.0.14/logs/catalina.out'
alias derby='~/bin/db-derby-10.1.3.1-bin/frameworks/embedded/bin/ij.ksh'
alias mysql='/usr/local/mysql/bin/mysql -u root'
Any frequently used command can appear in this file, freeing you from having to remember some incantation that does magic. In fact, this ability significantly overlaps that of using key macro tools (see the section called “Key Macro Tools”” in Chapter 2). I tend to use bash aliases for most things (less overhead with expanding the macro), but one critical category exists for which I use key macro tools. Any command line you have that contains a mixture of double and single quotes is hard to get escaped exactly right as an alias. The key macro tools handle that much better. For example, the svnAddNew script. (shown earlier in the section called “Tame Command-Line Subversion””) started as a bash alias, but it was driving me nuts trying to get all the escaping just right. It now lives as a key macro, and life is much simpler.
所有常用的命令都可以放在這個檔案裡,這樣你就不必費心記住那些複雜的魔法咒語了。實際上,這個功能與鍵盤宏工具(參見第2章“鍵盤宏工具”一節)有很大程度的重合。對於大部分命令,我都比較喜歡用bash別名(從而不必進行宏展開),但有那麼一種重要的情況會讓我使用鍵盤宏工具:如果一條命令裡同時包含雙引號和單引號,就很難為它建立別名,因為很難把引號轉碼弄對。鍵盤宏工具能更好地處理這種情況。比如說svnAddNew這個指令碼(在前面的“馴服Subversion命令列”中展示過了)一開始是一個bash別名,但為了把引號轉碼弄對幾乎把我給搞瘋了,所以後來我把它實現為一個鍵盤宏,生活從此變得輕鬆。
提示
是否應該自動化的關鍵在於投資回報率和緩解風險。
專案中會有很多雜事讓你想要自動化,這時你應該先拿下列問題來問自己(並且誠實地作答):
l長期來看,將其自動化能節省時間嗎?
l這件任務是否很容易出錯(因為其中包含很多複雜的步驟)?一旦出錯是否會浪費大把時間?
l執行這件任務是否在浪費我的注意力?(幾乎所有任務都會使你的注意力為之轉移,你必須花些工夫才能再回到全神貫注的狀態。)
l如果手工操作失誤會造成什麼危害?
最後一個問題之所以重要,因為它涉及到風險的考量。在我以前工作過的一個專案裡,人們由於歷史原因而把程式碼和測試輸出在同一個目錄裡。要執行測試,我們需要建立三個不同的測試套件,分別對應一種型別的測試(單元測試、功能測試和整合測試)。專案經理建議我們手工建立這些測試套件,但我們還是決定藉助反射機制來自動建立它們。手工更新測試套件很容易出錯,開發者很可能會寫了測試卻忘記把它加入到測試套件,從而導致新增的測試不被執行。我們認為,不將這個環節自動化可能造成很大的危害。
當你打算把某個任務自動化時,專案經理可能會擔心你的工作失控。我們都有過這樣的經驗:原本以為只要兩個小時就能搞定的事,最終用了4天才總算做完。要控制這種風險,最好的辦法就是“時間盒”(timebox):首先定好一段時間來探索和了解情況,時間一到就客觀地評估是否值得去做這件事。使用時間盒是為了掌握更多資訊,以便做出切合實際的決策。時間盒到期以後,如果掌握的資訊不夠,你也可以再增加一個時間盒,以便找出更多資訊。我知道,巧妙的自動化指令碼比那些無聊的專案工作更讓你感興趣,但還是現實點吧,你的老闆一定比較喜歡腳踏實地的工作量估算。
提示
研究性的工作應該放在時間盒裡做。
別給犛牛剪毛
最後,別讓自動化的努力變成“剪犛牛毛”──這是一句在電腦科學界源遠流長的黑話,它代表了諸如此類的情況:
1.你打算根據Subversion日誌自動生成一些文件。
2.你嘗試給Subversion加上一個鉤子,然後發現當前使用的Subversion版本與你的web伺服器不相容。
3.你開始更新web伺服器的版本,隨後又發現這個新版本在作業系統當前的這個補丁級別上不被支援,於是你開始更新作業系統。
4.作業系統的更新包存在一個已知的問題,與用於備份的磁碟陣列不相容。
5.你下載了尚未正式釋出的針對磁碟陣列的作業系統補丁,它應該能用……它確實能用,但又導致顯示卡驅動出了問題。
終於在某個時候,你停下來回想自己一開始到底是想幹什麼。然後你發現自己正在給犛牛剪毛,這時你就應該停下來想想:這一大堆犛牛毛跟“從Subversion日誌生成文件”到底有什麼關係呢?
剪犛牛毛是件危險的事,因為它會吃掉你大把的時間。這也能解釋為什麼任務工作量估算常常出現偏差:剪光一頭犛牛的毛需要多少時間?始終牢記你到底要做什麼,如果情況開始失控就及時抽身而出。
小結
本章用大量例項展示瞭如何將工作中的任務自動化。但這些例子本身並非重點所在,它們只是用於展示我和其他人業已發現的自動化手段而已。計算機之所以存在就是為了執行簡單重複的任務的,你應該讓它們去工作!找出那些每天、每週都在做的重複工作,問問你自己:我能把這件事自動化嗎?把重複的工作自動化,就能給你更多的時間來做有用的事,而不是一遍又一遍地解決沒有營養的問題。手工做那些簡單重複的事會浪費你的注意力,將這些煩人的雜事自動化,你就可以把寶貴的精力用來做其他更有價值的事。
5 可以在下載
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/16502878/viewspace-573132/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 卓有成效的程式設計師程式設計師
- 《卓有成效程式設計師》第一章程式設計師
- 卓有成效的程式設計師 讀書筆記程式設計師筆記
- 書評:卓有成效的程式設計師的45個習慣程式設計師
- 程式設計師何苦為難程式設計師?程式設計師
- 程式設計師的“非程式設計師”之路程式設計師
- 程式設計師何必難為程式設計師程式設計師
- Java程式設計第四章作業Java程式設計
- 好程式設計師:Java程式設計師面試秘籍程式設計師Java面試
- 程式設計師必看的書-Ruby程式設計師程式設計師
- 國外程式設計師 VS 中國程式設計師程式設計師
- 《程式設計師的春天:EOM與程式設計師》程式設計師
- 中國程式設計師和外國程式設計師程式設計師
- Lisp程式設計師眼中的其他程式設計師Lisp程式設計師
- 程式設計師歌曲《程式設計師偏頭痛》程式設計師
- 程式設計師程式設計師
- 《程式設計師程式設計藝術》程式設計師
- 程式設計師OR非程式設計師,有些程式設計的事需要知道程式設計師
- 以前的程式設計師,現在的程式設計師程式設計師
- 程式設計師常有,優秀程式設計師不常有程式設計師
- 程式設計師必看的書之Java程式設計師程式設計師Java
- 程式設計師漫畫系列(1):程式設計師人生程式設計師
- 美女程式設計師觀點:程式設計師最重要的非程式設計技巧程式設計師
- 程式設計師程式設計10大原則程式設計師
- 盲人程式設計師的程式設計生涯程式設計師
- 程式設計將死,程式設計師永存程式設計師
- 程式設計師,請你不要在坑程式設計師了?程式設計師
- 漫談程式設計師系列:程式設計師零門檻?程式設計師
- 程式設計師快速睡眠攻略 失眠程式設計師的福音程式設計師
- 《程式設計師健康指南》:給程式設計師的健康書程式設計師
- 愛偷懶的程式設計師是好程式設計師程式設計師
- 程式設計師啊,程式設計師,究竟是咋的程式設計師
- 程式設計師生活智慧集——卓越程式設計師密碼程式設計師密碼
- 普通程式設計師和厲害程式設計師的差距!程式設計師
- 韭菜程式設計師程式設計師
- 你好,程式設計師程式設計師
- 程式設計師性格程式設計師
- 程式設計師神器程式設計師