很贴心的提示
- 16 Jan 2011
在把Mac OS X升级到10.6.6版本以后,当我从Download目录下直接启动程序时,系统给出了下面这个很贴心的新提示:

如此体贴,我想不遵守最佳实践都难。
View Comments
在把Mac OS X升级到10.6.6版本以后,当我从Download目录下直接启动程序时,系统给出了下面这个很贴心的新提示:

如此体贴,我想不遵守最佳实践都难。
星期五下午,我需要通过bisect的方法来定位系统中的一个bug。一次次地更新、编译和测试很耗时也很无聊,所以我决定用新学的Groovy计算bisect所需的最大次数来打发等待时间。
以下是测试:
class BisectTest extends GroovyTestCase {
void testMaxSteps() {
def bisect = new bisectStepsCounter()
assertEquals 0, bisect.maxSteps(1, 2)
assertEquals 1, bisect.maxSteps(1, 3)
assertEquals 2, bisect.maxSteps(1, 4)
assertEquals 3, bisect.maxSteps(1, 7)
assertEquals 4, bisect.maxSteps(1, 10)
}
}
下面是实现代码:
class BisectStepsCounter {
def maxSteps(int good, int bad, int stepsSoFar = 0) {
if (bad - good <= 1) return stepsSoFar;
stepsSoFar += 1
int middle = (bad + good)/2
Math.max(maxSteps(good, middle, stepsSoFar), maxSteps(middle, bad, stepsSoFar))
}
}
应用这段逻辑,我算了一下自己的实际需要:bisectCounter.maxSteps(167932, 168223),结果是9。唉,看来我还得再找点什么事情继续打发时间……
在写 OCUnitReportConverter 的时候,我发现Groovy对用正则表达式进行模式匹配支持得非常棒。比如说下面这段代码,遍历 OCUnit 输出的每一行文本,提取其中的信息:
def parse(String ocunitOutput) {
def expSuite = /^Test Suite '([^']+)' started at (.*)/
def expTestCount = /^Executed ([\d]+) tests, with ([\d]+) failure[s]? \([\d]+ unexpected\) in [\S]+ \(([^\)]+)\).*/
def expTestCase = /^Test Case '-[\S]+ ([^\]]+)\]' (passed|failed) \(([\S]*) seconds\).*/
def expTestFailure = /(.+): error: -\[[^]]+\] : (.*)/
ocunitOutput.split(LINE_BREAK).each {
switch (it) {
case ~expSuite:
def matcher = Matcher.getLastMatcher()
testSuites << new TestSuite(name: matcher[0][1], timestamp: matcher[0][2])
break
case ~expTestCount:
// capture test count data...
case ~expTestFailure:
// capture test failure data...
case ~expTestCase:
// capture test case data...
}
// ...
}
同样的逻辑如果在Java里处理就比较麻烦了。由于Java中switch只支持byte、short、char、int(以及它们的包装类)和枚举类型,因此要么写一堆if-else,要么就得动用strategy等面向对象手段来掩盖语言层面的不足了,写起来总感觉不如Groovy那样顺畅自然。
值得一提的是,上面代码中的Matcher.getLastMatcher方法来自GDK的扩展,用来提供上一次正则表达式匹配的结果(比如获取其中的组信息),这个方法格外适用于switch-case这种无法直接获取正则匹配结果的场景。
最近一段时间在摆弄Groovy。
Groovy提供了多种用于实现AOP的方法,其中之一是让对象实现GroovyInterceptable接口,然后通过实现InvokeMethod方法来对方法调用进行拦截处理。举例如下:
class MyClass implements GroovyInterceptable {
def invokeMethod(String name, args) {
System.out.println("Intercepted ${name}")
def methodToCall = MyClass.metaClass.getMetaMethod(name, args)
methodToCall?.invoke(this, args)
}
String greet(String name) {
"Hello ${name}"
}
}
println new MyClass().greet("dyang")
运行该脚本,Groovy会打印出以下输出:
Intercepted greet Hello dyang
这是对的。不过如果你把InvokeMethod中的System.out.println改成println并再次运行该脚本,那么有趣的事就发生了——Groovy会提示出错StackOverflowError。这是为什么呢?
原来,Groovy对JDK做了扩充,其中就包括对java.lang.Object类增加了println方法。由于MyClass最终继承于java.lang.Object,因此当我们在MyClass实例上调用println时,Groovy同样会对该调用进行拦截,从而导致InvokeMethod会被执行,并进而导致了StackOverflowError的最终产生。
同理,如果我们在MyClass实例上调用java.lang.Object中的其它扩展方法(例如dump),invokeMethod也会被调用,因此我们在实现invokeMethod时一定要格外小心。
在Windows命令行下使用Doskey相关命令可以实现类似于Linux shell中的命令别名(alias)功能。
举个例子,假如你和我一样每次想查看当前目录下的文件的时候总是先敲ls的话(Windows会温柔地提醒你ls命令不存在),那么你可以用下面的命令来创建一个从ls到dir的别名:
doskey ls=dir
然后在同一个session里再敲ls命令就会执行dir了。
如果你想再把命令搞得复杂点,比如说让它支持输入参数,那么也是有办法的。下面这条命令是我在工作时常用的:
doskey record=Connectivity.VaultPro.exe /record=Record\$1.autoscr /recordOpts=$2
在使用的时候,我就可以敲入类似下面的的命令了:
record test GuiDump
Doskey会帮我把这条简化了的命令展开成:
record=Connectivity.VaultPro.exe /record=Record\test.autoscr /recordOpts=GuiDump
当然,这种直接通过敲入doskey命令来设定别名的方法有个缺陷,就是这些别名的生命周期仅限于你敲入时所打开的命令行session,一旦session关闭那么这些别名就失效了。如果这些命令是你一直常用的,那么可以考虑用下面方法把常用别名保存在文件里:
doskey /macros:all > %userprofile%\macros.mac
这条命令的含义是把当前session已经设置好的所有别名都打印出来,并且通过管道的方式重定向到%userprofile%\macros.mac文件里。打开该文件,你会看到此前你定义的那些别名都是在的。
有了这个文件,下次再打开一个新的命令行session的时候我们只需要把该文件重新加载一遍就好了,doskey也提供了相应的命令:
doskey /macrofile=%userprofile%\macros.mac
如果你像我一样主要使用 Console2 命令环境的话,那么在Console2配置中的Shell一项里把启动命令设成下面这样就一劳永逸了:
cmd /k doskey /macrofile=%userprofile%\macros.mac
有了这个文件,以后如果想增加、删除、修改别名的话直接打开它编辑即可。为此,我写了个别名来自动打开它:
alias=gvim "%userprofile%\macros.mac"
在编译好并且保存以后,可以通过以下命令来刷新当前session:
reload=doskey /macrofile=%userprofile%\macros.mac