这也许是因为“懒”。
懒有三种表现:
l 拖延不喜欢的任务
l 迅速做完不喜欢的任务,以摆脱之
l 编写某个工具来完成不喜欢的任务,这样以后再也不用做这件事了
程序员要具备第三种懒,一劳永逸的懒,因为苦干没有光环,别人只会觉得你笨。
忙碌不等于多产,行动不等于进展,有效编程最重要的工作是思考,人思考的时候常常看上去不是很忙。
我总结了我在工作中用到的一些高效的方法,他们来源于网络,我的同事,还有图书和文档。
大家可以从加速,专注,自动化和规范性这几方面去理解这些方法是如何促使我们更高效的。
容易的开始
设置你的终端,实现“无限滚屏”,这样你可以随时找到以前显示的信息。
关掉收到电子邮件的提示音,减少随时被干扰的可能,养成每天定时查看邮件的习惯。
为每个工作空间分配单一的功能,形成内聚的工作空间,保持注意力,避免大脑情境切换的开销。比如我们可以设置:通信空间,编码空间,文档空间……
项目组规定一天里不能被打扰的工作时间段,制定交流规则来管理干扰。
重视代码规范,在非关键之处建立规范,从而在重要的地方倾力发挥你的创造性。
自动登录
要达到的目的:客户机A登录目标机B无需输入密码。
使用ssh-keygen
单向登录的操作过程:
l 登录A机器
l ssh-keygen –t [rsa|dsa],rsa和dsa是加密方式,任选其一均可,这个命令将会产生公钥(id_rsa.pub,id_dsa.pub)和私钥文件(id_rsa,id_dsa)。
l 登录目标机B,将产生的公钥文件如id_rsa.pub的内容追加到~/.ssh/authorized_keys文件的最后
l 大功告成
如果还需要双向登录不输入密码,你还需要:
l 设置.ssh目录权限,chmod 700 -R .ssh
l 设置authorized_keys的权限,chmod 600 authorized_keys,只允许自己有写权限,否则验证无效。
使用expect
expect是建立在Tcl基础上的一个工具,它和Tcl有着相同的语法。
人们常常用expect自动完成一些需要交互的任务。
要自动登录,我们可以创建一个可执行的文件,例如myssh,它的内容如下:
#!/usr/bin/expect
set timeout -1;
spawn bash;
expect "$ " {
send "ssh oakland\r"
}
expect "password: " {
send "password\r"
}
expect "$ " {
send "ssh gate2\r"
}
expect "password: " {
send "password\r"
}
expect "$ " {
if { [llength $argv] == 0 } {
send "ssh gui100\r"
} else {
send "ssh $argv\r"
}
}
interact
spawn命令启用了一个新的进程执行bash。
expect语句在返回的信息中查询$符号,一旦成功就向标准输入send一个ssh oakland命令加回车。
接下来你输入密码,继续登录。
输入密码的时候,有时候登陆有延迟,如果10秒钟之后依然没有password:字符串出现,expect会退出。我们用set timeout -1让expect永远等下去。
最后一句interact把这个脚本的控制权交给用户,expect运行完毕之后自动退出进程。
这样当你使用myssh gui100时,expect就先登录oakland,再登录gate2,然后登陆gui100,大功告成。
好了,你已经告别了每天上班登录3次到gui100的痛苦了,坐下来喝杯咖啡吧。
Shell命令
使用find的-regex选项,利用正则表达式查找文件。
例如:
find ./ -regex ‘.*_Model.*’
注意,这里-regex后面的正则表达式匹配的是整个文件路径,不是单个文件名。
所以对于一个文件” ./analysis/agiPowerModel.cpp”,你不能用:
find ./ -regex ‘agi.*_Model.*’
去匹配它。
使用find的-exec选项,针对每个找到的文件执行命令,进一步处理每个找到的文件。
find ./ -regex '.*\.h\|.*\.cpp' -exec sed -i "s/\<tm\([^p].*\)/Agi\1/g" {} \;
{}是占位符,代表前面用find找到的文件名。
最后的”\;”代表这条find命令结束。
为什么要有一个结束符?
因为这是一个Unix命令,你可以把这条命令的结果管道给另一个命令,所以find命令需要这个结束符来知道”exec”部分到哪里为止。
所以这段shell的意思是,先用find找到所有以.h或者.cpp结尾的文件,然后用sed在这些文件中找到以tm开头的字符串,并且忽略tmp这个特殊的变量,用Agi替换tm。比如tmAClass替换之后就成了AgiAClass。其中,在sed的替换表达式中,()符号表示分组,/1表示引用这个组,否则tmAClass会被替换成Agi而不是AgiAClass。
使用find+grep的组合,根据其内容进一步过滤找到的文件。
find –name “*.java” –not –regex “.*Db\.java” –exec grep –n “new .*Db” {} \;
所有不是以Db.java结尾的文件,都不应该初始化与数据库相关的类,而我们要找出这些不守规范的文件。
使用xargs逐一调用命令,常用于批处理大量文件。
grep -lwr ams_fmtprint actions/agiCharAPL.cpp actions/agiCharCreate.cpp actions/agiChipPartition.cpp actions/agiExportGuiConf.cpp actions/agiImportAPL.cpp | xargs -I '{}' tkdiff '{}'
-I后面定义的字符’{}’将替换前面的搜索结果。
所以这段shell的意思是,先找到这五个文件中出现过ams_fmtprint的文件。然后对他们逐一调用tkdiff。
换句话说,对tkdiff的调用不是同时执行的,而是执行了一次tkdiff结束之后,再执行下一次。
使用alias创建常用命令的别名
少敲几个键总是能让我们更专注于解决问题本身,我们不用每次因为忘记具体的选项去翻技术手册。
我的alias包括去到常用的工作目录,设置环境变量,查看系统状态,更新源代码和回归测试集。
alias rd 'cd /nfs/gui100.home/jdeng/Code/RD'
alias set92 'setenv APACHEROOT /nfs/gui100.home/jdeng/Code/RH9v2; setenv PATH $APACHEROOT/bin:$PATH'
alias showsp 'du -hs ./*'
alias upcode 'cvs update -dP; apmake -j 4; apmake dist -j 4; apmake install dist; tagsrc;'
alias upqa 'cd /home/jdeng/unitQA; rm -rf bin; cvs up -d bin; cvs up -dP; source setup'
使用pushd和popd快速切换工作目录。
pushd执行两个动作:将你置于你作为参数传进去的目录下,并将当前目录入栈。pushd是cd的一个替代品。一旦完成了工作,调用popd就可以回到起始的位置。
pushd是一个FILO列表,相当于自助餐厅的一摞碟子。因为他是栈,你可以入栈任意多次,出栈的顺序则恰好相反。
每次pushd被触发时,它都会显示栈中已有的目录。
我们用
cd –
回到上次浏览的目录。
我们也可以自己用C shell做一个back命令:
alias cd 'set old=$cwd; chdir \!*; set prompt="$user@`hostname`:$cwd-! >"'
alias back 'set back=$old; cd $back; unset $back'
这样你可以用back回到刚才的目录。
使用!$, !*, !:[num]引用上一行命令中的字段
!$,引用上一行命令的最后一个字段
!*,引用上一行命令的所有参数字段
!:[num],引用上一行命令的第num个字段
如:
$> ls actions/ reports/
!$,是reports/
!*,是actions/ reports/
!:0,是ls
正则表达式
捕获子模式和回退引用。
使用圆括号()来捕获子模式,与圆括号中的模式匹配的字符串会被记录在匹配变量中。
回退引用,匹配用圆括号捕获的子模式的值。
你可以在一次匹配中捕获许多的子模式,所以你就可以回过头来使用\1,\2等等来指代各个被捕获的表达式。
所以,在前面的例子中:
sed -i "s/\<tm\([^p].*\)/Agi\1/g" x.cpp
这一句,捕获的子模式就是([^p].*\),后面的\1是回退引用匹配成功的字符串。
另外,假设你要匹配一个用单引号或者双引号括起来的字符串,你可以:
(“[^”]*”|’[^’]*’)
你也可以用回退引用\1:
(‘|”).*?\1
是不是要简单一些?
Vim编辑器
进入Vim
在终端输入
vim filename +line_number
可以直接打开文件并定位到某一行。
vim filename +/search_pattern
打开文件,并定位到search_pattern出现的地方。
配置Vim
每次启动vim时,vim的配置文件~/.vimrc会被自动读取,该文件可以包含一些设置或者脚本。
我们可以配置.vimrc,来实现强力编辑。
下面给出一个例子:
“双引号后面是注释
“去掉有关vi一致性模式,避免以前版本的bug和局限
set nocompatible
"tags
set tags=/nfs/gui100.home/jdeng/Code/RD-2/tags
set tags+=/nfs/gui100.home/jdeng/Code/xgraph/tags
set tags+=/nfs/gui100.home/jdeng/Code/qt_src_453/tags
"TlistToggle
let Tlist_Ctags_Cmd="/usr/bin/ctags"
let Tlist_Inc_Winwidth=0
let Tlist_Sort_Type='name'
"auto indent
set cindent "cindent will make smartindent useless
set shiftwidth=4
set tabstop=4
set expandtab "use spaces when user type tab
"alias
nmap <F3> :w<cr>
nmap <F4> :wq<cr>
nmap <F5> :w!<cr>:!%<cr>
nmap <F6> :'a,. d<cr>
nmap <F7> :'a,. y<cr>
nmap <F8> <C-w><C-w>
nmap <F9> :TlistToggle<CR>
"backspace
set backspace=eol,start,indent
"detect file type
filetype on
"command line history
set history=1000
syntax on
set ruler
"match {}
set showmatch
if has("vms")
set nobackup " do not keep a backup file, use versions instead
else
else
set backup " keep a backup file
endif
set clipboard+=unnamed
set backup
set backupdir=/home/jdeng/.vim/backup
set directory=/home/jdeng/.vim/temp "where to put swap file
set runtimepath+=/home/jdeng/.vim
"automatically open and close the popup menu/preview window
au CursorMovedI,InsertLeave * if pumvisible() == 0|silent! pclose|endif
set completeopt=menuone,menu,longest,preview
我觉得比较有用的是alias,它让我们可以敲更少的键去完成一项功能。
auto indent让我们更少去操心缩进的问题,这样我们更专注于编程本身。
定位光标
在命令模式下:
$,将光标移动到行尾
^,将光标移动到这一行头一个字符处。
0,将光标移动到行首。
gm,将光标移动到行中间。
f{char},把光标定位到,这一行,当前光标后面的{char}处。
F{char},把光标定位到,这一行,当前光标前面的{char}处。
,和;符号重复F和f命令,‘,’向前定位,‘;’向后定位。
M,将光标定位在当前页面的中间。
L,将光标定位在当前页面最下面。
H,将光标定位在当前页面最上面。
命令宏
q{0-9a-zA-Z"},即q后面加一个寄存器名字,我比较喜欢用qq,因为键入很方便。
qq是用来记录动作的,它就像一个录像机,录制你的动作,在需要时再放映出来。
按下qq,开始录制,页面下显示recording,然后你开始操作,完了之后按Esc退回命令模式,再按一下q表示录制完毕。
然后,你就可以用@q放映这些动作了。
这个“录像机”避免了我们很多重复的劳动。当你要处理很多行的数据时,100@q可以执行录制动作100次,大大加快了我们的速度。
多重剪贴板
操作系统限制我们只有一个剪贴板,但是Vim支持多重剪贴板(也叫寄存器)。
当你遇到一个任务,需要从一个文件中复制粘贴一些不连续的内容到另一个文件,多数人会复制,然后跳转到另一个文件,再跳转,再复制。你花费了太多时间在环境的切换。如果使用多重剪贴板,你可以一次性复制粘贴所有内容到另一个文件。成批的复制粘贴比反复多次复制粘贴快得多。
Vi中:
["x]yy,将当前行的内容粘贴到寄存器x中,x包括a-zA-Z0-9.%#:-"
:[range]y[ank] [x],将{range}的内容粘贴到剪贴板x。
["x]p,将剪贴板x中的内容粘贴到光标后。
:di[splay] [arg]和:reg[isters] {arg},显示arg中指定的剪贴板内容。
当你习惯了多重剪贴板之后,你会觉得它是一个非常强大的工具,在关键时刻他一定可以帮到你。
配合Taglist和ctags查看源代码
taglist插件,可以显示当前文件的“目录”,包括用到的宏,类,函数,等……通过输入:Taglist打开和关闭功能窗口。
ctags可以进入到一个类或者函数,是我们阅读代码的好帮手。
在终端输入:
ctags -R --links=no --c++-kinds=+p --fields=+iaS --extra=+q src/
然后我们就可以用vim –t function_name来定位函数。
用vim打开文件以后,我们还可以用ctrl+]进入函数,ctrl+t退出。也可以使用:tag target_name来定位
调试
你也许觉得调试是一件没有技术含量的事情,不就是用gdb或者ddd等工具定位错误源吗,任何人都会。
不要过分依赖调试工具,如果有一天,你要远程调试程序,并且网速很慢,或者你的客户出于保密等方面的考虑,不能让你亲临现场,不给你实时的第一手资料,你该怎么办呢?
调试是很容易被忽略的技术,人和人是不同的。
优秀的程序员效率更高,他们用更少的时间,找到更多的错误,他们更正确的修改错误,并且更少引入新的错误。
这是怎么做到的呢?
首先是要调整心态。
如果一出问题,你的反应是怎么可能呢?你就败了,一切都有可能,现在它不但可能,而且已经发生了。
不要怕出错,缺陷让你有所收获。
错误的调试方法包括,不去理解程序,凭猜测找出缺陷,这样你可能永远都不知道自己错在哪里,下一次仍然犯相同的错误。出错并不可怕,可怕的是回回都犯相同的错。
那么科学的调试方法是什么呢?
1. 将错误的状态稳定下来,让错误可再现。这样我们可以通过可重复的试验收集数据。
2. 理解程序,确定错误来源。
a) 收集产生缺陷的相关数据,分析并构造假设
b) 证实或者证伪你的假设,通过试验,测试或者检查代码
c) 对假设做出结论
3. 修补缺陷
4. 对修补进行测试,增加测试到QA regression,让他们有能力检测出这个故障
5. 查找并修改类似的错误
在稳定错误状态时,强迫你自己隔离引发bug的环境,最好只需要一个步骤bug就显露出来了,这样做常常使你洞见到修正它的方法。
在理解程序的时候,一个非常简单又特别有用的技术是向别人解释它。在这个过程中,你必须详细陈述你在编写代码时觉得想当然的事,这个过程中,你的条理越来越清晰,你可能突然发现错误在哪儿。别停下,证明你的假设。
如果修补一个错误你费了很大的劲,问问你自己为什么?你是否能做点什么让下次修正类似的bug容易一些。
你可以建立一个自己犯过的错误类型检查表,每次修bug的时候统计一下,不断警醒自己,磨练自己的弱项,从错误中吸取营养。
最后,我要说的是,良好的思维很重要,但在一些情况下,思维无法取代优秀的调试器,优秀的工具加上良好的思维才是最为有效的组合。
使用crontab让琐碎的工作自动化
crontab创建周期性执行的命令。
键入:
crontab –e
开始编辑你的任务,终端显示:
20 0 * * * /home/jdeng/bin/update_source_code
意思是每天0点20分开始升级源代码。
用cvs创建你的宠物项目
把有用的小工具,或者你自己的小项目放进SCCS,这样你再也不会弄丢你的“宝贝”了。
输入:
cvs –d /home/jdeng/cvs_root_directory init
初始化目录。
然后,进入一个项目目录:
cd my_pets
再输入:
cvs –d /home/jdeng/cvs_root import project_name ver_tag rel_tag
导入这个项目。
此时,my_pet下的项目将会导入/home/jdeng/cvs_root/project_name中。
在稍后的阶段,你可以用cvs add和cvs commit等命令,将新的文件加入到这个版本库中,大功告成。