ITPub博客

首页 > Linux操作系统 > Linux操作系统 > 《卓有成效程序员》第四章

《卓有成效程序员》第四章

原创 Linux操作系统 作者:hzbook2008 时间:2009-03-19 16:17:50 0 删除 编辑
Normal 0 7.8 磅 0 2 false false false MicrosoftInternetExplorer4 4

在从前的一个项目中,我们需要定时更新几个电子数据表文件。我需要用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[25]ThoughtWorks开发的一个开源项目)能够大大简化这些准备工作。很多Linux发行版本会提供“Live CD”选项,用这张CD就可以直接试用这个Linux版本。Buildix也采用了这种发行方式,只不过加上了预先配置好的项目基础设施:它本身其实是一张Ubuntu Live CD,预装了一些软件开发中常用的工具。Buildix预装了下列工具:

lSubversion,流行的开源版本控制工具

lCruiseControl,开源的持续集成服务器

lTrac,开源的问题跟踪和wiki工具

lMingleThoughtWorks推出的敏捷项目管理工具

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 "www.hotmail.com/when/junk.cgi?birthyear=1905&press=OK"

在命令行输入“-d”参数,就可以用HTML POST方法(而非缺省的GET方法)与页面交互:

curl -d "birthyear=1905&press=%20OK%20" www.hotmail.com/when/junk.cgi

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 XAutomator工具──每个命令(或者管道中的一个阶段)的输出成为下一个管道的输入。

举例来说,图1.4所示的管道会抓取“No Fluff, Just Stuff”会议网站的blog聚合RSS,后者包含了最近的blog文章。每个blog条目都遵循作者标题的格式,而我又只想要其中的作者信息,所以我用了一个正则表达式管道将作者 - 标题替换成作者姓名。

4.1 实用正则表达式管道

管道的输出可以是一个HTML页面,也可以是另一个RSS源(从中获取RSS时就会导致管道被执行)。

RSS日益成为一种重要的信息格式,尤其是对于软件开发者们关注的那些信息,而Yahoo! Pipes则允许你以编程的方式处理RSS,对信息加以提炼。不仅如此,Pipes还逐渐加上了从网页上采集信息的功能,从而可以自动获取各种基于web的信息。

在构建之外使用Ant

提示

即便不是工具最初的设计意图,只要是合适的场合,同样可以使用这些工具。

在操作系统的层面上,批处理文件和bash脚本都可以用于将工作自动化,但这两者的语法都不够灵活,命令也常常显得笨拙。举例来说,如果你需要对一大批文件执行某一操作,用批处理文件或是bash脚本的原生命令想要得到这些文件的列表就是件麻烦事。干嘛不用专门为此而设计的工具呢?

其实我们常用的构建工具已经知道如何获取文件列表、如何对其进行过滤或是执行别的操作。在对文件进行批量操作这件事情上,AntNAntRake的语法都比批处理文件或是bash脚本要友好得多。

下面就是用Ant来处理文件的一个例子──这事情用批处理做起来实在太难,以至于我根本就不打算尝试。我曾经教过很多编程课程,在课堂上常会写一些例子程序。为了解答学生们提出的问题,也经常需要写一些小程序来讲解。一周课程结束以后,所有学生都想要拷贝我在课上写的小程序。但这些小程序还产生了很多额外的文件(输出文件、JAR文件、临时文件等等),所以我得把这些无关的文件清理掉,然后创建一个干净的ZIP包拷贝给学生们。这件事我没有手工去做,而是为之创建了一个Ant文件。Ant的一大好处就是它内建对批量文件的支持:

   

   

       

       

       

       

       

       

       

       

       

       

   

   

   

       

           

       

   

Ant的帮助下,我可以写一个任务来执行以前手工完成的那些步骤:

   

   

   

同样的事情要用批处理文件写出来,不啻是一场噩梦!哪怕用Java写都会很麻烦:Java并没有内置对批量文件进行模式匹配的支持。使用构建工具,你不需要创建main方法,也无须操心太多基础设施的问题,因为构建工具通常已经提供了足够的支持。

Ant最糟糕的一点莫过于对XML的依赖,这使得Ant脚本既难写又难读,同样难以重构,连diff都变得困难。Gant[26]是一个不错的替代品:它能够与已有的Ant任务交互,但它的构建脚本是用Groovy编写的──XML不同,那是一种真正的编程语言。

Rake执行常见任务

Rake就是Rubymake工具(同时它本身也是用Ruby编写的)。Rakeshell脚本的完美替代品,因为它不仅具备了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文件本身实际上只起声明作用,实际的工作都在名为baseRake文件中实现了,别的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 [27]是一个开源的测试工具,用于web应用的用户验收测试。Selenium借助JavaScript自动化了浏览器操作,从而可以模拟用户的行为。Selenium完全是用浏览器端技术编写的,所以在所有主流浏览器中都能使用。这是一个极其有用的web应用测试工具,不论被测的web应用是用什么技术开发的。不过现在我不是来介绍怎么用Selenium做测试的。Selenium有一个叫做Selenium IDE的派生项目,那是一个Firefox浏览器插件,能将用户与网站的交互记录为Selenium脚本,然后可以用SeleniumTestRunner或者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网站下载。

MonadbashDOS等类似的命令行shell语言中借鉴了很多理念,例如你可以把一个命令的输出管道给另一个命令作为输入。它们之间最大的区别在于Monad不是使用文本(像bash那样),而是使用对象:Monad的命令(被称为cmdlet)知道一组代表操作系统构造的对象,像文件、目录、甚至Windows事件查看器(event viewer)之类的东西。Monad的语义和bash大同小异(管道操作符还是那个历史悠久的“|”符号),但它的能力着实强大。下面就是一个例子:假设你需要把在2006121以后更新过的文件复制到DestFolder目录中,相应的Monad命令大概会是这样:

  dir | where-object { $_.LastWriteTime -gt "12/1/2006" } |

      move-item -destination c:\DestFolder

由于Monadcmdlet理解其他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来说一直是个难题,现在你可以用.NETString类来解决这个问题了。譬如说,下列Monad命令:

get-member -input "String" -membertype method

会输出String类的所有方法。这就有点像在*-nix中使用man工具一样。

 

MonadWindows世界的一大进步。它把操作系统层面上的编程当作一等公民来对待,很多原本需要求助于PerlPythonRuby等脚本语言的任务现在可以用Monad轻松完成。由于它是操作系统核心的一部分,你还可以用它来查找、操作系统特有的对象(例如事件查看器)。

Mac OS XAutomator来删除过时的下载文件

Mac OS X提供了一种以图形化方式编写批处理文件的途径,那就是Automator。从很多方面来说,这简直就是一个图形化版本的Monad,而且比后者早出现了几年。要创建一个Automator工作流(也就是Mac OS X版本的脚本),只要从Automator的工作区拖拽命令,然后把命令之间的输入输出连接起来就行了。每个应用程序在安装时都会向Automator注册,告诉后者自己能做什么。还可以用Objective CMac 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遍(实际上到项目后期这个速率还会提高,那时你会更加频繁地部署)。把它加起来:64x 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日志生成文档到底有什么关系呢?

剪牦牛毛是件危险的事,因为它会吃掉你大把的时间。这也能解释为什么任务工作量估算常常出现偏差:剪光一头牦牛的毛需要多少时间?始终牢记你到底要做什么,如果情况开始失控就及时抽身而出。

小结

本章用大量实例展示了如何将工作中的任务自动化。但这些例子本身并非重点所在,它们只是用于展示我和其他人业已发现的自动化手段而已。计算机之所以存在就是为了执行简单重复的任务的,你应该让它们去工作!找出那些每天、每周都在做的重复工作,问问你自己:我能把这件事自动化吗?把重复的工作自动化,就能给你更多的时间来做有用的事,而不是一遍又一遍地解决没有营养的问题。手工做那些简单重复的事会浪费你的注意力,将这些烦人的杂事自动化,你就可以把宝贵的精力用来做其他更有价值的事。



2    5 可以在http://buildix.thoughtworks.com/下载

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/16502878/viewspace-573132/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2008-10-23

  • 博文量
    209
  • 访问量
    753144