Awk学习笔记

Posted on

Awk学习笔记

Awk学习笔记

整理:Jims of 肥肥世家

[jims.yang@gmail.com](mailto:jims.yang@gmail.com) Copyright © 2004 本文遵从GPL协议,欢迎转载、修改、散布。

第一次发布时间:2004年8月6日

Table of Contents 1. awk简介2. awk命令格式和选项2.1. awk的语法有两种形式2.2. 命令选项3. 模式和操作3.1. 模式3.2. 操作4. awk的环境变量5. awk运算符6. 记录和域6.1. 记录6.2. 域6.3. 域分隔符7. gawk专用正则表达式元字符8. POSIX字符集9. 匹配操作符(~)10. 比较表达式11. 范围模板12. 一个验证passwd文件有效性的例子13. 几个实例14. awk编程14.1. 变量14.2. BEGIN模块14.3. END模块14.4. 重定向和管道14.5. 条件语句14.6. 循环14.7. 数组14.8. awk的内建函数15. How-to

1. awk简介

awk是一种编程语言,用于在linux/unix下对文本和数据进行处理。数据可以来自标准输入、一个或多个文件,或其它命令的输出。它支持用户自定义函数和动态正则表达式等先进功能,是linux/unix下的一个强大编程工具。它在命令行中使用,但更多是作为脚本来使用。awk的处理文本和数据的方式是这样的,它逐行扫描文件,从第一行到最后一行,寻找匹配的特定模式的行,并在这些行上进行你想要的操作。如果没有指定处理动作,则把匹配的行显示到标准输出(屏幕),如果没有指定模式,则所有被操作所指定的行都被处理。awk分别代表其作者姓氏的第一个字母。因为它的作者是三个人,分别是Alfred Aho、Brian Kernighan、Peter Weinberger。gawk是awk的GNU版本,它提供了Bell实验室和GNU的一些扩展。下面介绍的awk是以GUN的gawk为例的,在linux系统中已把awk链接到gawk,所以下面全部以awk进行介绍。

2. awk命令格式和选项

2.1. awk的语法有两种形式

  • awk [options] 'script' var=value file(s)
  • awk [options] -f scriptfile var=value file(s)

    2.2. 命令选项

-F fs or --field-separator fs 指定输入文件折分隔符,fs是一个字符串或者是一个正则表达式,如-F:。 -v var=value or --asign var=value 赋值一个用户定义变量。 -f scripfile or --file scriptfile 从脚本文件中读取awk命令。 -mf nnn and -mr nnn 对nnn值设置内在限制,-mf选项限制分配给nnn的最大块数目;-mr选项限制记录的最大数目。这两个功能是Bell实验室版awk的扩展功能,在标准awk中不适用。 -W compact or --compat, -W traditional or --traditional 在兼容模式下运行awk。所以gawk的行为和标准的awk完全一样,所有的awk扩展都被忽略。 -W copyleft or --copyleft, -W copyright or --copyright 打印简短的版权信息。 -W help or --help, -W usage or --usage 打印全部awk选项和每个选项的简短说明。 -W lint or --lint 打印不能向传统unix平台移植的结构的警告。 -W lint-old or --lint-old 打印关于不能向传统unix平台移植的结构的警告。 -W posix 打开兼容模式。但有以下限制,不识别:\x、函数关键字、func、换码序列以及当fs是一个空格时,将新行作为一个域分隔符;操作符//和//=不能代替^和^=;fflush无效。 -W re-interval or --re-inerval 允许间隔正则表达式的使用,参考(grep中的Posix字符类),如括号表达式[[:alpha:]]。 -W source program-text or --source program-text 使用program-text作为源代码,可与-f命令混用。 -W version or --version 打印bug报告信息的版本。

3. 模式和操作

awk脚本是由模式和操作组成的: pattern {action} 如$ awk '/root/' test,或$ awk '$3 < 100' test。

两者是可选的,如果没有模式,则action应用到全部记录,如果没有action,则输出匹配全部记录。默认情况下,每一个输入行都是一条记录,但用户可通过RS变量指定不同的分隔符进行分隔。

3.1. 模式

模式可以是以下任意一个:

  • /正则表达式/:使用通配符的扩展集。
  • 关系表达式:可以用下面运算符表中的关系运算符进行操作,可以是字符串或数字的比较,如$2>%1选择第二个字段比第一个字段长的行。
  • 模式匹配表达式:用运算符~(匹配)和~!(不匹配)。
  • 模式,模式:指定一个行的范围。该语法不能包括BEGIN和END模式。
  • BEGIN:让用户指定在第一条输入记录被处理之前所发生的动作,通常可在这里设置全局变量。
  • END:让用户在最后一条输入记录被读取之后发生的动作。

3.2. 操作

操作由一人或多个命令、函数、表达式组成,之间由换行符或分号隔开,并位于大括号内。主要有四部份:

  • 变量或数组赋值
  • 输出命令
  • 内置函数
  • 控制流命令

4. awk的环境变量

Table 1. awk的环境变量

变量描述$n当前记录的第n个字段,字段间由FS分隔。$0完整的输入记录。ARGC命令行参数的数目。ARGIND命令行中当前文件的位置(从0开始算)。ARGV包含命令行参数的数组。CONVFMT数字转换格式(默认值为%.6g)ENVIRON环境变量关联数组。ERRNO最后一个系统错误的描述。FIELDWIDTHS字段宽度列表(用空格键分隔)。FILENAME当前文件名。FNR同NR,但相对于当前文件。FS字段分隔符(默认是任何空格)。IGNORECASE如果为真,则进行忽略大小写的匹配。NF当前记录中的字段数。NR当前记录数。OFMT数字的输出格式(默认值是%.6g)。OFS输出字段分隔符(默认值是一个空格)。ORS输出记录分隔符(默认值是一个换行符)。RLENGTH由match函数所匹配的字符串的长度。RS记录分隔符(默认是一个换行符)。RSTART由match函数所匹配的字符串的第一个位置。SUBSEP数组下标分隔符(默认值是\034)。

5. awk运算符

Table 2. 运算符

运算符描述= += -= /= /= %= ^= //=赋值?:C条件表达式||逻辑或&&逻辑与~ ~!匹配正则表达式和不匹配正则表达式< <= > >= != ==关系运算符空格连接+ -加,减/ / &乘,除与求余+ - !一元加,减和逻辑非^ ///*求幂++ --增加或减少,作为前缀或后缀$字段引用in数组成员

6. 记录和域

6.1. 记录

awk把每一个以换行符结束的行称为一个记录。

记录分隔符:默认的输入和输出的分隔符都是回车,保存在内建变量ORS和RS中。

$0变量:它指的是整条记录。如$ awk '{print $0}' test将输出test文件中的所有记录。

变量NR:一个计数器,每处理完一条记录,NR的值就增加1。如$ awk '{print NR,$0}' test将输出test文件中所有记录,并在记录前显示记录号。

6.2. 域

记录中每个单词称做“域”,默认情况下以空格或tab分隔。awk可跟踪域的个数,并在内建变量NF中保存该值。如$ awk '{print $1,$3}' test将打印test文件中第一和第三个以空格分开的列(域)。

6.3. 域分隔符

内建变量FS保存输入域分隔符的值,默认是空格或tab。我们可以通过-F命令行选项修改FS的值。如$ awk -F: '{print $1,$5}' test将打印以冒号为分隔符的第一,第五列的内容。

可以同时使用多个域分隔符,这时应该把分隔符写成放到方括号中,如$awk -F'[:\t]' '{print $1,$3}' test,表示以空格、冒号和tab作为分隔符。

输出域的分隔符默认是一个空格,保存在OFS中。如$ awk -F: '{print $1,$5}' test,$1和$5间的逗号就是OFS的值。

7. gawk专用正则表达式元字符

一般通用的元字符集就不讲了,可参考我的SedGrep学习笔记。以下几个是gawk专用的,不适合unix版本的awk。

\Y 匹配一个单词开头或者末尾的空字符串。 \B 匹配单词内的空字符串。 \< 匹配一个单词的开头的空字符串,锚定开始。 > 匹配一个单词的末尾的空字符串,锚定末尾。 \w 匹配一个字母数字组成的单词。 \W 匹配一个非字母数字组成的单词。 \‘ 匹配字符串开头的一个空字符串。 \' 匹配字符串末尾的一个空字符串。

8. POSIX字符集

可参考我的Grep学习笔记

9. 匹配操作符(~)

用来在记录或者域内匹配正则表达式。如$ awk '$1 ~/^root/' test将显示test文件第一列中以root开头的行。

10. 比较表达式

conditional expression1 ? expression2: expression3,例如:$ awk '{max = {$1 > $3} ? $1: $3: print max}' test。如果第一个域大于第三个域,$1就赋值给max,否则$3就赋值给max。

$ awk '$1 + $2 < 100' test。如果第一和第二个域相加大于100,则打印这些行。

$ awk '$1 > 5 && $2 < 10' test,如果第一个域大于5,并且第二个域小于10,则打印这些行。

11. 范围模板

范围模板匹配从第一个模板的第一次出现到第二个模板的第一次出现之间所有行。如果有一个模板没出现,则匹配到开头或末尾。如$ awk '/root/,/mysql/' test将显示root第一次出现到mysql第一次出现之间的所有行。

12. 一个验证passwd文件有效性的例子

1$ cat /etc/passwd | awk -F: '\ 2NF != 7{\ 3printf("line %d,does not have 7 fields:%s\n",NR,$0)}\ 4$1 !~ /[A-Za-z0-9]/{printf("line %d,non alpha and numeric user id:%d: %s\n,NR,$0)}\ 5$2 == "/*" {printf("line %d, no password: %s\n",NR,$0)}'

1 cat把结果输出给awk,awk把域之间的分隔符设为冒号。2 如果域的数量(NF)不等于7,就执行下面的程序。3 printf打印字符串"line ?? does not have 7 fields",并显示该条记录。4 如果第一个域没有包含任何字母和数字,printf打印“no alpha and numeric user id" ,并显示记录数和记录。5 如果第二个域是一个星号,就打印字符串“no passwd”,紧跟着显示记录数和记录本身。

13. 几个实例

  • $ awk '/^(no|so)/' test-----打印所有以模式no或so开头的行。
  • $ awk '/^[ns]/{print $1}' test-----如果记录以n或s开头,就打印这个记录。
  • $ awk '$1 ~/[0-9][0-9]$/(print $1}' test-----如果第一个域以两个数字结束就打印这个记录。
  • $ awk '$1 == 100 || $2 < 50' test-----如果第一个或等于100或者第二个域小于50,则打印该行。
  • $ awk '$1 != 10' test-----如果第一个域不等于10就打印该行。
  • $ awk '/test/{print $1 + 10}' test-----如果记录包含正则表达式test,则第一个域加10并打印出来。
  • $ awk '{print ($1 > 5 ? "ok "$1: "error"$1)}' test-----如果第一个域大于5则打印问号后面的表达式值,否则打印冒号后面的表达式值。
  • $ awk '/^root/,/^mysql/' test----打印以正则表达式root开头的记录到以正则表达式mysql开头的记录范围内的所有记录。如果找到一个新的正则表达式root开头的记录,则继续打印直到下一个以正则表达式mysql开头的记录为止,或到文件末尾。

14. awk编程

14.1. 变量

  • 在awk中,变量不需要定义就可以直接使用,变量类型可以是数字或字符串。
  • 赋值格式:Variable = expression,如$ awk '$1 ~/test/{count = $2 + $3; print count}' test,上式的作用是,awk先扫描第一个域,一旦test匹配,就把第二个域的值加上第三个域的值,并把结果赋值给变量count,最后打印出来。
  • awk可以在命令行中给变量赋值,然后将这个变量传输给awk脚本。如$ awk -F: -f awkscript month=4 year=2004 test,上式的month和year都是自定义变量,分别被赋值为4和2004。在awk脚本中,这些变量使用起来就象是在脚本中建立的一样。注意,如果参数前面出现test,那么在BEGIN语句中的变量就不能被使用。
  • 域变量也可被赋值和修改,如$ awk '{$2 = 100 + $1; print }' test,上式表示,如果第二个域不存在,awk将计算表达式100加$1的值,并将其赋值给$2,如果第二个域存在,则用表达式的值覆盖$2原来的值。再例如:$ awk '$1 == "root"{$1 ="test";print}' test,如果第一个域的值是“root”,则把它赋值为“test”,注意,字符串一定要用双引号。
  • 内建变量的使用。变量列表在前面已列出,现在举个例子说明一下。$ awk -F: '{IGNORECASE=1; $1 == "MARY"{print NR,$1,$2,$NF}'test,把IGNORECASE设为1代表忽略大小写,打印第一个域是mary的记录数、第一个域、第二个域和最后一个域。

    14.2. BEGIN模块

BEGIN模块后紧跟着动作块,这个动作块在awk处理任何输入文件之前执行。所以它可以在没有任何输入的情况下进行测试。它通常用来改变内建变量的值,如OFS,RS和FS等,以及打印标题。如:$ awk 'BEGIN{FS=":"; OFS="\t"; ORS="\n\n"}{print $1,$2,$3} test。上式表示,在处理输入文件以前,域分隔符(FS)被设为冒号,输出文件分隔符(OFS)被设置为制表符,输出记录分隔符(ORS)被设置为两个换行符。$ awk 'BEGIN{print "TITLE TEST"}只打印标题。

14.3. END模块

END不匹配任何的输入文件,但是执行动作块中的所有动作,它在整个输入文件处理完成后被执行。如$ awk 'END{print "The number of records is" NR}' test,上式将打印所有被处理的记录数。

14.4. 重定向和管道

  • awk可使用shell的重定向符进行重定向输出,如:$ awk '$1 = 100 {print $1 > "output_file" }' test。上式表示如果第一个域的值等于100,则把它输出到output_file中。也可以用>>来重定向输出,但不清空文件,只做追加操作。
  • 输出重定向需用到getline函数。getline从标准输入、管道或者当前正在处理的文件之外的其他输入文件获得输入。它负责从输入获得下一行的内容,并给NF,NR和FNR等内建变量赋值。如果得到一条记录,getline函数返回1,如果到达文件的末尾就返回0,如果出现错误,例如打开文件失败,就返回-1。如:

$ awk 'BEGIN{ "date" | getline d; print d}' test。执行linux的date命令,并通过管道输出给getline,然后再把输出赋值给自定义变量d,并打印它。

$ awk 'BEGIN{"date" | getline d; split(d,mon); print mon[2]}' test。执行shell的date命令,并通过管道输出给getline,然后getline从管道中读取并将输入赋值给d,split函数把变量d转化成数组mon,然后打印数组mon的第二个元素。

$ awk 'BEGIN{while( "ls" | getline) print}',命令ls的输出传递给geline作为输入,循环使getline从ls的输出中读取一行,并把它打印到屏幕。这里没有输入文件,因为BEGIN块在打开输入文件前执行,所以可以忽略输入文件。

$ awk 'BEGIN{printf "What is your name?"; getline name < "/dev/tty" } $1 ~name {print "Found" name on line ", NR "."} END{print "See you," name "."} test。在屏幕上打印”What is your name?",并等待用户应答。当一行输入完毕后,getline函数从终端接收该行输入,并把它储存在自定义变量name中。如果第一个域匹配变量name的值,print函数就被执行,END块打印See you和name的值。

$ awk 'BEGIN{while (getline < "/etc/passwd" > 0) lc++; print lc}'。awk将逐行读取文件/etc/passwd的内容,在到达文件末尾前,计数器lc一直增加,当到末尾时,打印lc的值。注意,如果文件不存在,getline返回-1,如果到达文件的末尾就返回0,如果读到一行,就返回1,所以命令 while (getline < "/etc/passwd")在文件不存在的情况下将陷入无限循环,因为返回-1表示逻辑真。

  • 可以在awk中打开一个管道,且同一时刻只能有一个管道存在。通过close()可关闭管道。如:$ awk '{print $1, $2 | "sort" }' test END {close("sort")}。awd把print语句的输出通过管道作为linux命令sort的输入,END块执行关闭管道操作。
  • system函数可以在awk中执行linux的命令。如:$ awk 'BEGIN{system("clear")'。
  • fflush函数用以刷新输出缓冲区,如果没有参数,就刷新标准输出的缓冲区,如果以空字符串为参数,如fflush(""),则刷新所有文件和管道的输出缓冲区。

14.5. 条件语句

awk中的条件语句是从C语言中借鉴过来的,可控制程序的流程。

14.5.1. if语句格式: {if (expression){ statement; statement; ... } }

$ awk '{if ($1 <$2) print $2 "too high"}' test。如果第一个域小于第二个域则打印。

$ awk '{if ($1 < $2) {count++; print "ok"}}' test.如果第一个域小于第二个域,则count加一,并打印ok。

14.5.2. if/else语句,用于双重判断。格式: {if (expression){ statement; statement; ... } else{ statement; statement; ... } }

$ awk '{if ($1 > 100) print $1 "bad" ; else print "ok"}' test。如果$1大于100则打印$1 bad,否则打印ok。

$ awk '{if ($1 > 100){ count++; print $1} else {count--; print $2}' test。如果$1大于100,则count加一,并打印$1,否则count减一,并打印$1。

14.5.3. if/else else if语句,用于多重判断。格式: {if (expression){ statement; statement; ... } else if (expression){ statement; statement; ... } else if (expression){ statement; statement; ... } else { statement; statement; ... } }

14.6. 循环

  • awk有三种循环:while循环;for循环;special for循环。
  • $ awk '{ i = 1; while ( i <= NF ) { print NF,$i; i++}}' test。变量的初始值为1,若i小于可等于NF(记录中域的个数),则执行打印语句,且i增加1。直到i的值大于NF.
  • $ awk '{for (i = 1; i<NF; i++) print NF,$i}' test。作用同上。
  • breadkcontinue语句。break用于在满足条件的情况下跳出循环;continue用于在满足条件的情况下忽略后面的语句,直接返回循环的顶端。如: {for ( x=3; x<=NF; x++) if ($x<0){print "Bottomed out!"; break}} {for ( x=3; x<=NF; x++) if ($x==0){print "Get next item"; continue}}
  • next语句从输入文件中读取一行,然后从头开始执行awk脚本。如: {if ($1 ~/test/){next} else {print} }
  • exit语句用于结束awk程序,但不会略过END块。退出状态为0代表成功,非零值表示出错。

14.7. 数组

awk中的数组的下标可以是数字和字母,称为关联数组。

14.7.1. 下标与关联数组

  • 用变量作为数组下标。如:$ awk {name[x++]=$2};END{for(i=0;i<NR;i++) print i,name[i]}' test。数组name中的下标是一个自定义变量x,awk初始化x的值为0,在每次使用后增加1。第二个域的值被赋给name数组的各个元素。在END模块中,for循环被用于循环整个数组,从下标为0的元素开始,打印那些存储在数组中的值。因为下标是关健字,所以它不一定从0开始,可以从任何值开始。
  • special for循环用于读取关联数组中的元素。格式如下: {for (item in arrayname){ print arrayname[item] } } $ awk '/^tom/{name[NR]=$1}; END{for(i in name){print name[i]}}' test。打印有值的数组元素。打印的顺序是随机的。
  • 用字符串作为下标。如:count["test"]
  • 用域值作为数组的下标。一种新的for循环方式,for (index_value in array) statement。如:$ awk '{count[$1]++} END{for(name in count) print name,count[name]}' test。该语句将打印$1中字符串出现的次数。它首先以第一个域作数组count的下标,第一个域变化,索引就变化。
  • delete函数用于删除数组元素。如:$ awk '{line[x++]=$1} END{for(x in line) delete(line[x])}' test。分配给数组line的是第一个域的值,所有记录处理完成后,special for循环将删除每一个元素。

    14.8. awk的内建函数

14.8.1. 字符串函数

  • sub函数匹配记录中最大、最靠左边的子字符串的正则表达式,并用替换字符串替换这些字符串。如果没有指定目标字符串就默认使用整个记录。替换只发生在第一次匹配的时候。格式如下: sub (regular expression, substitution string): sub (regular expression, substitution string, target string)

实例:

$ awk '{ sub(/test/, "mytest"); print }' testfile $ awk '{ sub(/test/, "mytest"); $1}; print }' testfile

第一个例子在整个记录中匹配,替换只发生在第一次匹配发生的时候。如要在整个文件中进行匹配需要用到gsub

第二个例子在整个记录的第一个域中进行匹配,替换只发生在第一次匹配发生的时候。

  • gsub函数作用如sub,但它在整个文档中进行匹配。格式如下: gsub (regular expression, substitution string) gsub (regular expression, substitution string, target string)

实例:

$ awk '{ gsub(/test/, "mytest"); print }' testfile $ awk '{ gsub(/test/, "mytest"), $1 }; print }' testfile

第一个例子在整个文档中匹配test,匹配的都被替换成mytest。

第二个例子在整个文档的第一个域中匹配,所有匹配的都被替换成mytest。

  • index函数返回子字符串第一次被匹配的位置,偏移量从位置1开始。格式如下: index(string, substring)

实例:

$ awk '{ print index("test", "mytest") }' testfile

实例返回test在mytest的位置,结果应该是3。

  • length函数返回记录的字符数。格式如下: length( string ) length

实例:

$ awk '{ print length( "test" ) }' $ awk '{ print length }' testfile

第一个实例返回test字符串的长度。

第二个实例返回testfile文件中第条记录的字符数。

  • substr函数返回从位置1开始的子字符串,如果指定长度超过实际长度,就返回整个字符串。格式如下: substr( string, starting position ) substr( string, starting position, length of string )

实例:

$ awk '{ print substr( "hello world", 7,11 ) }'

上例截取了world子字符串。

  • match函数返回在字符串中正则表达式位置的索引,如果找不到指定的正则表达式则返回0。match函数会设置内建变量RSTART为字符串中子字符串的开始位置,RLENGTH为到子字符串末尾的字符个数。substr可利于这些变量来截取字符串。函数格式如下: match( string, regular expression )

实例:

$ awk '{start=match("this is a test",/[a-z]+$/); print start}' $ awk '{start=match("this is a test",/[a-z]+$/); print start, RSTART, RLENGTH }'

第一个实例打印以连续小写字符结尾的开始位置,这里是11。

第二个实例还打印RSTART和RLENGTH变量,这里是11(start),11(RSTART),4(RLENGTH)。

  • toupper和tolower函数可用于字符串大小间的转换,该功能只在gawk中有效。格式如下: toupper( string ) tolower( string )

实例:

$ awk '{ print toupper("test"), tolower("TEST") }'

  • split函数可按给定的分隔符把字符串分割为一个数组。如果分隔符没提供,则按当前FS值进行分割。格式如下: split( string, array, field separator ) split( string, array )

实例:

$ awk '{ split( "20:18:00", time, ":" ); print time[2] }'

上例把时间按冒号分割到time数组内,并显示第二个数组元素18。

14.8.2. 时间函数

  • systime函数返回从1970年1月1日开始到当前时间(不计闰年)的整秒数。格式如下: systime()

实例:

$ awk '{ now = systime(); print now }'

  • strftime函数使用C库中的strftime函数格式化时间。格式如下: systime( [format specification][,timestamp] )

Table 3. 日期和时间格式说明符

格式描述%a星期几的缩写(Sun)%A星期几的完整写法(Sunday)%b月名的缩写(Oct)%B月名的完整写法(October)%c本地日期和时间%d十进制日期%D日期 08/20/99%e日期,如果只有一位会补上一个空格%H用十进制表示24小时格式的小时%I用十进制表示12小时格式的小时%j从1月1日起一年中的第几天%m十进制表示的月份%M十进制表示的分钟%p12小时表示法(AM/PM)%S十进制表示的秒%U十进制表示的一年中的第几个星期(星期天作为一个星期的开始)%w十进制表示的星期几(星期天是0)%W十进制表示的一年中的第几个星期(星期一作为一个星期的开始)%x重新设置本地日期(08/20/99)%X重新设置本地时间(12:00:00)%y两位数字表示的年(99)%Y当前月份%Z时区(PDT)%%百分号(%)

实例: $ awk '{ now=strftime( "%D", systime() ); print now }' $ awk '{ now=strftime("%m/%d/%y"); print now }'

14.8.3. 内建数学函数

Table 4.

函数名称返回值atan2(x,y)y,x范围内的余切cos(x)余弦函数exp(x)求幂int(x)取整log(x)自然对数rand()随机数sin(x)正弦sqrt(x)平方根srand(x)x是rand()函数的种子int(x)取整,过程没有舍入rand()产生一个大于等于0而小于1的随机数

14.8.4. 自定义函数

在awk中还可自定义函数,格式如下:

function name ( parameter, parameter, parameter, ... ) { statements return expression /# the return statement and expression are optional }

15. How-to

  • 如何把一行竖排的数据转换成横排?

awk '{printf("%s,",$1)}' filename

unix系统中字符集的查看及修改方法

Posted on

unix系统中字符集的查看及修改方法

蔡蔡博客

#

*

用户操作[留言] [发消息] [加为好友] 订阅我的博客XML聚合 FeedSky订阅到鲜果订阅到Google订阅到抓虾[编辑]wuxicxh的公告[编辑]文章分类* (RSS)C++

一、查看字符集方法:

  1、输入命令:echo $LANG

  2、输入命令:env | grep LANG

  3、输入命令:locale

二、修改字符集的方法:

  编辑/etc/sysconfig/language文件,将该文件中的rc_lang=" "修改成rc_lang = "zh_CN.GBK"。然后在命令行中运行SuSEconfig命令激活更改。当运行完毕后注意一定要注销并重新登录,此时再运行locale时,就能看到当前系统的字符集是zh_CN.GBK了。

发表于 @ 2009年12月11日 11:26:00 | 评论( loading... )| 编辑| 举报| 收藏

旧一篇:JDBC调用MySQL5存储过程 | 新一篇:LoadRuner选择录制方式的原则

查看最新精华文章 请访问博客首页相关文章

我的重构哪里不规范?

Posted on

我的重构哪里不规范? - 软件测试

您还未登录 !我的应用登录注册

JavaEye-最棒的软件开发交流社区

论坛首页软件开发和项目管理版软件测试

我的重构哪里不规范?

全部项目管理敏捷开发软件测试配置管理UseCaseUML单元测试XPTDDUPCMM « 上一页 1 2 3 4 5 下一页 »

浏览 17393 次 锁定老贴子 主题:我的重构哪里不规范?

精华帖 (3) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (1) 作者 正文 * ojava

  • 等级: 初级会员
  • ojava的博客
  • 文章: 7
  • 积分: 33
  • 来自: 烟台
  • 发表时间:2007-06-15

<>猎头职位: 北京: Java搜索工程师

相关文章:

  • 你们的项目经常重构代码吗?
  • 重构的几大重要特点
  • 讨论:重构的前提是不是 TDD 推荐圈子: 火星常驻JE办事处 更多相关推荐 在项目中,由于没有经过大脑思考,结果产生了流水账形式的代码。 流水账代码:就是根据是详细设计书把整个业务的流程顺序完成到一个类的一个方法中, 而没有根据功能划分成若干个小的方法。 这种流水账式的代码非常不容易测试,因为详细设计中已经将设计细化到对字符串如何操作了, 所以从这样的设计书的高度看业务,简直就是乱七八糟! 所幸,还有重构这个工具,就重构,发现很多的局部变量,因为在多处改变值,而且后续还要使用, 所以只能把这种变量,提到类变量的高度,好多啊。 这样一来, 1。如果要用junit测试,还需要再给相应的提出来的变量加上set,get方法。 2。因为重构出来的方法都是private的,所以测试的时候还要用反射的方法。 上面这两种情况可以避免吗?这是一个问题。 还有一个对自己的警告:小心费力不讨好! 声明:JavaEye文章版权属于作者,受法律保护。没有作者书面许可不得转载。

推荐链接

面向接口不要面向实现,谢谢 返回顶楼 回帖地址

0 0 请登录后投票 * 抛出异常的爱

  • 等级: 五钻会员
  • 抛出异常的爱的博客
  • 文章: 12050
  • 积分: 2782
  • 来自: 北京
  • 发表时间:2007-06-15

ojava 写道: 在项目中,由于没有经过大脑思考,结果产生了流水账形式的代码。 流水账代码:就是根据是详细设计书把整个业务的流程顺序完成到一个类的一个方法中, 而没有根据功能划分成若干个小的方法。 这种新式的代码非常不容易测试,因为详细设计中已经将设计细化到对字符串如何操作了, 所以从这样的设计书的高度看业务,简直就是乱七八糟! 所幸,还有重构这个工具,就重构,发现很多的局部变量,因为在多处改变值,而且后续还要使用, 所以只能把这种变量,提到类变量的高度,好多啊。 这样一来, 1。如果要用junit测试,还需要再给相应的提出来的变量加上set,get方法。 2。因为重构出来的方法都是private的,所以测试的时候还要用反射的方法。 上面这两种情况可以避免吗?这是一个问题。 还有一个对自己的警告:小心费力不讨好! 无设计,无定义(对每个类与方法),无规则,的重构。。。叶公好龙 返回顶楼 回帖地址

0 0 请登录后投票 * ojava

  • 等级: 初级会员
  • ojava的博客
  • 文章: 7
  • 积分: 33
  • 来自: 烟台
  • 发表时间:2007-06-15

无设计,无定义(对每个类与方法),无规则,的重构。。。叶公好龙 这句话,一针见血!!! 刚学习测试,准备在项目中实践,结果发现自己的代码根本没有办法测试,因为这个巨大的方法太大,看不清要实现的业务是什么! 头脑中就是因为缺少测试的思想,才导致出现这样的问题,果然没有思想是不行的,亡羊补牢,现在晚了吗? 返回顶楼 回帖地址

0 0 请登录后投票 * gigix

  • 等级: 资深会员资深会员
  • gigix的博客
  • 文章: 4810
  • 积分: 2991
  • 来自: 北京
  • 发表时间:2007-06-15

First of all you should have tests for you original method. During your refactoring those test should always pass. So you don't have to test new (and quite possible private) methods/fields you extract out. 返回顶楼 回帖地址

0 1 请登录后投票 * 抛出异常的爱

  • 等级: 五钻会员
  • 抛出异常的爱的博客
  • 文章: 12050
  • 积分: 2782
  • 来自: 北京
  • 发表时间:2007-06-15

gigix 写道

First of all you should have tests for you original method. During your refactoring those test should always pass. So you don't have to test new (and quite possible private) methods/fields you extract out. 是的,不用再加测试了,先写着, 不过对于拿不准的,写出来出错看不出来,找不到错的 还是要写中间测试帮助编码 PS:如果要测试中间的private方法,可以写在主函数中一个 public XXX (){ return 你要测试的方法 ;} PS:天啊。。。变的够快。。。认不出来了 返回顶楼 回帖地址

0 0 请登录后投票 * gigix

  • 等级: 资深会员资深会员
  • gigix的博客
  • 文章: 4810
  • 积分: 2991
  • 来自: 北京
  • 发表时间:2007-06-15

抛出异常的爱 写道

不过对于拿不准的,写出来出错看不出来,找不到错的 还是要写中间测试帮助编码 nope... if you make any mistake during refactoring and break any test, and if you can't fix it immediately, you should revert to previous successful step immediately. (that's why you should check in as frequently as you can.) 返回顶楼 回帖地址

0 0 请登录后投票 * 抛出异常的爱

  • 等级: 五钻会员
  • 抛出异常的爱的博客
  • 文章: 12050
  • 积分: 2782
  • 来自: 北京
  • 发表时间:2007-06-15

gigix 写道

抛出异常的爱 写道

不过对于拿不准的,写出来出错看不出来,找不到错的 还是要写中间测试帮助编码 nope... if you make any mistake during refactoring and break any test, and if you can't fix it immediately, you should revert to previous successful step immediately. (that's why you should check in as frequently as you can.) 那么不动老的代码。。。 写新方法,新的测试, 所有需要的方法都写完了(小步前进?) 再一次把老的代码删去 用新的代码顶上? 返回顶楼 回帖地址

0 0 请登录后投票 * gigix

  • 等级: 资深会员资深会员
  • gigix的博客
  • 文章: 4810
  • 积分: 2991
  • 来自: 北京
  • 发表时间:2007-06-15

抛出异常的爱 写道

gigix 写道

抛出异常的爱 写道

不过对于拿不准的,写出来出错看不出来,找不到错的 还是要写中间测试帮助编码 nope... if you make any mistake during refactoring and break any test, and if you can't fix it immediately, you should revert to previous successful step immediately. (that's why you should check in as frequently as you can.) 那么不动老的代码。。。 写新方法,新的测试, 所有需要的方法都写完了 再一次把老的代码删去 用新的代码顶上? please man... what is "refactoring"? answer: to improve the internal structure of code, WITHOUT CHANGING ITS EXTERNAL BEHAVIOR when you refactoring, you move forward by SMALL STEPS. you remove the bad smells little by little, and always keep all tests passing. 返回顶楼 回帖地址

0 0 请登录后投票 * ojava

  • 等级: 初级会员
  • ojava的博客
  • 文章: 7
  • 积分: 33
  • 来自: 烟台
  • 发表时间:2007-06-15

公司新加代码规范条目:所定义的方法尽量不要超过100行。 某一方面来说,可以避免这种流水账式的代码吧! 并且强制我们进行Xiaohanne所说的面向接口不要面向实现的编程。 返回顶楼 回帖地址

0 0 请登录后投票

« 上一页 1 2 3 4 5 下一页 » 论坛首页软件开发和项目管理版软件测试 跳转论坛:Java编程和Java企业应用Web前端技术 移动编程和手机应用开发C/C++编程Ruby编程Python编程 PHP编程Flash编程和RIA Microsoft .Net综合技术软件开发和项目管理行业应用 入门讨论招聘求职海阔天空

© 2003-2010 JavaEye.com. 上海炯耐计算机软件有限公司版权所有 [ 沪ICP备05023328号 ]

BOSS业务介绍

Posted on

BOSS业务介绍 - 计费与营帐系统 - MSCBSC

BOSS系统介绍 营业分系统又可进一步分为营业受理子系统、营业管理子系统、客户服务子系统。营业受理子系统直接面对移动客户,主要进行移动运营商直辖的各大营业厅及代销点的营业受理,营业管理子系统主要由业务管理人员进行后台业务管理,客户服务子系统主要负责客户服务与投诉受理。 计费子系统分为格式化、一次批价、二次批价、漫游接收上发等流程。格式化是指从网络通信设备产生的原始话单中提取出计费所需的所有内容,并转化为计费程序能够处理的格式。计费系统必需的计费信息有:呼叫类型(如主叫、被叫、呼转等)、IMSI号(记录SIM卡的国际唯一标识号)、MSISDN(手机号)、对方号码、通话起始时间、通话时长、小区位置(LAC、CELLID等)、动态漫游号(MSRN)等。数据业务,如GPRS还必须提取出上行流量、下行流量、服务质量(QoS)等。各个话单采集点在某些异常情况下可能产生重复话单,因此格式化必须有剔除重单的功能,可采用记录一段时间内所有话单的做法,只须记录有唯一性标识作用的呼叫类型、IMSI、通话起始时间、对方号码等,在格式化新话单时将新话单与已记录的话单进行比较,相同的作为重单处理。MSCBSC 移动通信论坛(U;H5F6m8{'T 计费子系统一次批价根据格式化提供的标准格式话单,结合费率表、号段表、区号表等计费资料对话单进行计费。费率表中记录的信息主要有:基本计费单元、基本通话费率、长途计费单元、长途通话费率、优惠时段起始时间、优惠时段终止时间、优惠时段费率等。mscbsc 移动通信论坛拥有30万通信专业人员,超过50万份GSM/3G等通信技术资料,是国内领先专注于通信技术和通信人生活的社区。"U.C/#l9X6i9_ 计费子系统二次批价在一次批价的基础上,根据用户入网所享受的各项优惠对话单进行重计费,以最终生成向用户收费的话单。用户所享受的各项优惠记录在营业分系统的用户资料中,因此二次批价必须结合营业资料进行。如果对每一条话单进行二次批价时都进行一次对营业数据库的查询操作,势必会大大增加数据库的负担,严重影响营业、计费分系统的运行速度。因此必须为二次批价专门建立一个营业资料数据库,存放二次批价需要的用户数据,并定时与营业数据库进行同步操作。同时必须采用内存数据库技术,将二次批价中需要频繁用到的营业资料载入内存,以大大提高二次批价对营业资料访问的速度,同时减小主服务器的负担。采用内存数据库技术就必须对计费主机的内存实行优化,各个计费进程必须能合理地使用主机内存。二次批价根据不同的优惠业务采用流水线式的处理流程。各个不同的优惠业务彼此间相互渗透,因此二次批价中各个优惠业务间的处理顺序显得尤为重要。 :p V2T'L+{$k6A0c4TMSCBSC 移动通信论坛漫游是移动通信的特性,漫游类型分为非漫游、省内漫游、省际漫游、国际漫游等。许多原始话单中都没有用户是否漫游的标志,计费子系统就必须根据原始话单产生地结合号段表来进行判定。由于话单一般产生在到访地,如果原始话单中包含了异地手机用户的话单,则表明此用户漫游至话单产生地。如果许多不同地域的话单都集中于同一地点产生,根据原始话单产生地也无法判定用户的漫游类型,就必须通过原始话单中记录的用户通话时的位置(如LAC、CELLID等)来进行判定了。 $H$}+R&z3i&Twww.mscbsc.com动态漫游号主要用于判定移动通信计费中特殊的同城计费原则,即两个用户在同一城市行政区域内通话时不收取长途费。动态漫游号是用户发起主叫时由被叫用户所在的交换机分配给主叫用户的临时号码,不同的城市行政区域使用不同的号段。根据主叫方的动态漫游号结合被叫方的漫游位置即可判定主被叫用户是否同城。而对于被叫用户由于没有动态漫游号,同城原则只能根据主叫号码的归属地进行判定。若主叫方归属地不在被叫漫游位置,即使主叫方漫游与被叫同城,也无法对被叫实现同城优惠。例如:两个福州的用户同时漫游至北京时相互通话,主叫方可以享受同城优惠,而被叫方就无法享受同城优惠。/Z z"R/Y6^:c!R;N)H 计费子系统需将漫游至本地的外地用户的话单转换为标准格式上发给外地的移动运营商,同时还必须接收外地运营商传来的本地用户漫游至外地的话单并计费。这些话单也是结算的依据。漫游话单的上下发一般由一个全国性中心局来负责统一处理。 'n)f'|:T,x+J"A.}5NMSCBSC 移动通信论坛结算子系统用于处理与其它运营商之间的话费结算问题。结算的总原则是:若其它运营商的客户使用了我运营商的通信资源,则其运营商需向我运营商支付费用;若我运营商的客户使用了其它运营商的通信资源,则我运营商需向其它运营商支付费用。结算包括网内漫游结算及网间结算。网内漫游结算是指同一运营商内部各公司间因用户互相漫游而发生的结算,一般体现为各省之间的漫游结算,例如福建的用户漫游到北京,则福建需向北京支付结算费用。网间结算包括国内网间结算及国际网间结算。网间结算既可能因为漫游而使用了其它运营商的通信资源而产生,即网间漫游结算,也可能因为进行网间通信时使用了其它运营商的通信资源而产生,即网间通信结算。目前我国各移动运营商间尚未实现基站共享,因此国内网间结算均为网间通信结算。例如中国移动的用户如果直接拨打异地的中国电信固定电话(即区号+固定电话号码),则使用了中国电信的长途通信资源,中国移动须向中国电信支付长途结算费用;如果中国移动的用户使用17951拨打异地的中国电信固定电话(即17951+区号+固定电话号码),则使用中国移动的IP长途通信资源,中国移动就不必向中国电信支付长途结算费用。国际网间结算既可能有网间漫游结算,也可能有网间通信结算。例如,芬兰的移动客户漫游至中国,使用了中国的移动通信资源,则芬兰的移动运营商需向中国的移动运营商支付费用,这就是国际网间漫游结算。在移动通信网的设计中,可在不同运营商的网络间设立关口局用于处理网间通信结算问题,由关口局记录双方用户使用对方网络资源的状况,结算子系统直接从关口局读取原始话单计算结算费用。由于各运营商间的结算一般采用按月、季等时间单元进行,因而结算子系统对于实时性要求不高,在移动业务运营支撑系统的设计中显得相对简单。"\0E:F-~5~6E3v 帐务分系统MSCBSC 移动通信论坛,K)[/P6[-P'w+U/m/B"U"M/l 帐务分系统又可分为实时出帐子系统、月出帐子系统和销帐子系统。实时出帐子系统将计费子系统计费后的话单金额进行实时累加,达到一定额度后出帐。月出帐子系统负责对用户每月应固定缴纳的费用进行出帐。销帐子系统对各类帐单进行收费。4N7m!s x9Z/#v0D r 实时出帐子系统通过与计费分系统的接口获取计费后的话单,并对每一个用户的话单金额进行实时累加,生成实时帐单。由于用户可能因为SIM卡损坏等原因更换SIM卡,即可能变更IMSI号,因此实时帐单不能根据IMSI号进行累加;由于用户也可能申请变更手机号,因此实时帐单也不能根据MSISDN(手机号)进行累加。为此,在营业分系统中就必须对所有用户建立唯一标识,我们不妨称之为用户ID,无论用户更换SIM卡或是更改手机号,用户ID都保持不变,以唯一标识每一个用户,并对用户的IMSI、MSISDN的变更都留下历史记录。实时出帐子系统就可以根据用户ID来累加实时帐单。原始话单中不可能存在用户ID,计费子系统可在结合营业资料进行二次批价时在计费话单中加入用户ID,这本身也可以提高二次批价的速度。或是在实时出帐时再根据话单中的IMSI或MSISDN结合营业资料来查询用户ID。第一种方法显然是首选,可以避免实时出帐时再对营业资料进行访问。合帐时除了对用户累加总帐单金额外,还可以根据不同的帐目类别(如本地通话费、漫游通话费、长途通话费、短信费用等)分别累加帐目金额,以满足业务统计、财务报表等方面的需求。+U4b!p1D/n t&\ 实时出帐子系统与月出帐子系统生成的帐单通过销帐子系统送到缴费接口进行销帐,即向用户收费。销帐子系统中须为每一用户建立一个现金帐户,记录用户已缴金额。随着用户数量的不断增加,传统的营业厅现金缴费的方式已无法满足实际的需要,通过与银行配合的方式是解决这一矛盾的有效途径,如可以采用银行代缴、银行代扣等缴费方式。使用银行代缴的用户在银行的营业网点缴纳手机话费时,银行通过与移动运营商的网络通信接口将代收的话费信息传送至移动业务运营支撑系统,由销帐子系统将用户所缴纳的话费向用户的现金帐户充值,并向银行返回确认信息,银行的营业网点即通知用户缴费成功。除此以外,还可以采用充值卡等方式提供用户缴费。以上各类缴费方式中,银行代扣将用户帐户放在银行,我们称之为"代扣缴费",其余的都将用户帐户放在移动业务运营支撑系统,实时出帐子系统与月出帐子系统生成的帐单根据用户的缴费方式送往不同的缴费接口。采用现金缴费的用户的帐单直接从现金帐户上扣款,采用代扣缴费方式的用户的帐单通过与各大银行的接口送往用户指定的银行帐户。销帐结果可分为:完全销帐、部分销帐、未销帐。部分销帐与未销帐即用户欠费,若用户的欠费额超过用户的停机信用度,销帐子系统即向营业分系统发出停机命令,由营业分系统通过与HLR的接口修改HLR中的用户数据。停机可分为单停、全停,单停指的是用户有呼出限制,但仍可接听电话,全停指的是用户无法呼出也无法呼入。营业分系统可以根据用户的消费状况、缴费及时率等条件给不同的用户设立不同的单停信用度、全停信用度,对于不同的欠费额采用不同的停机处理。欠费帐单专门存储,在用户缴费后立即销帐,超过一定期限才被销帐的帐单由销帐子系统计算滞纳金,与欠费帐单同时销帐。为保证滞纳金计算合理,在用户缴费后应从最早出帐的帐单开始销帐。能够完全销帐的由销帐子系统向营业分系统发出复机命令,由营业分系统通过与HLR的接口修改HLR中的用户数据。超过一定期限始终未缴费的欠费停机用户,可由营业分系统进行欠费销户处理,其欠费帐单可作坏帐处理。 7G&l;K,H4c;F'x移动通信,通信工程师的家园,通信人才,求职招聘,网络优化,通信工程,出差住宿,通信企业黑名单未来发展方向 1D,H3G's/#u0` | 国内领先的通信技术论坛目前,移动通信业务运营支撑系统已经越来越朝着集中化的方向发展,从原先的地市级集中向省级集中。集中化可以方便地进行异地业务受理,节约设备成本及软件开发维护费用。随着电信市场的逐步开放,各移动运营商间的竞争也将日趋激烈,各类以吸引客户为目的的优惠业务将不断涌现,对于业务运营支撑系统的要求也将越来越高。在强大的市场竞争压力下,在用系统新增业务的开发周期直接影响到移动通信企业的竞争力。因此,移动业务运营支撑系统必须具有良好的可扩展性、强大的业务适应能力,这都对系统设计提出了更高的要求。一个理想化的业务运营支撑系统必须能够通过修改业务配置表的方式来实现绝大多数的优惠业务,而不必再开发额外的程序,这就要求业务运营支撑系统必须实现智能化。智能化的关键是在系统中能够对所有业务按照系统实现的方式进行分类,建立各类业务模型,如计费单位(以分钟计费、以秒计费)、计费原则(单向计费、双向计费)、套餐类业务(包月费、各档优惠资费标准、各档非优惠资费标准)、VPN类业务(集团内优惠资费标准、集团外非优惠资费标准)等。各类业务万变不离其宗,根据其本质内容建立抽象的业务模型,再根据各类业务的特征通过继承的方式建立子类,以及对子类的再继承来实现对所有业务模型的建立,这是面向对象的思想在软件高层设计中的应用。智能化已经成为业务运营支撑系统未来的发展方向,也是今后在这一领域需要重点研究的课题

对遗留系统组织重构项目

Posted on

对遗留系统组织重构项目 - 透明思考@CSDN

http://blog.csdn.net/gigix

思考着的程序员,程序员的思考

*

用户操作[留言] [发消息] [加为好友] 订阅我的博客XML聚合 FeedSky订阅到鲜果订阅到Google订阅到抓虾[编辑]gigix的公告[编辑]文章分类* (RSS)《软件工艺》

原创 对遗留系统组织重构项目 收藏

很多IT组织都面临一个难题:老系统的维护、升级越来越难做。特别是那些价值高、生命周期长、规模大的核心业务系统,越到后来,要修复一个缺陷或者新增一个功能就需要越大的工作量。这是为什么呢?

软 件的质量体现在两方面:商业方面的质量,以及技术方面的质量。从商业的角度看来,“成功的软件”意味着它所创造的价值超出在它身上付出的代价。从技术的角 度看来,“成功的软件”意味着所有测试都通过、代码结构良好、并且容易理解和维护。很多商业上非常成功的软件系统忽视了技术方面的质量,所以尽管它们仍然 在为IT组织创造价值,但对它们的维护和升级越来越困难。最终技术质量的欠缺会反过来阻碍软件系统创造更大的商业价值。

为了保留并最大化软件资产的价值,适应新的需求变更,老系统总会面对维护和升级。当维护和升级的困难达到一定程度时,很多IT组织就会决定投入一整块资源和时间来改善这些老系统的技术质量,以便将来的维护升级能顺利进行。这样的做法通常被称为“重构项目”。

根据我们的经验,很多重构项目在目标管理、任务划分和质量保证等方面存在比较严重的问题,这些问题导致重构项目不能充分发挥价值。

目标管理

很多重构项目缺乏清晰的、可测试的目标。以ThoughtWorks参 与过的一个重构项目为例,该项目最初的目标只有“提高代码可维护性”等感性的描述,缺乏量化指标。目标不清晰的直接结果是,进行重构的程序员难以明确每一 步的重构工作应该做到什么程度,导致系统各个部分的优化程度不一致,一些部分在经过重构之后仍然质量低下,而且难以确定完成重构的时间。

和 新功能开发一样,重构也同样应该是可验收的,并且验收过程应该尽可能自动化。除了确保其功能性行为仍然保持原样不变之外,重构工作的验收条件还包括单元测 试覆盖率、代码复杂度、编码规范等指标。这些指标的报告应该自动化地生成,及时地展现给所有项目成员,为此我们需要一个持续集成环境来自动、频繁地验证所 有重构工作。

在ThoughtWorks参与的重构项目中,我们采用CruiseControl作为持续集成工具。每次开发者往代码库中签入(check in)代码时,就会触发CruiseControl对项目进行构建,构建的内容包括编译、连接(对于后台部分)、单元测试、测试覆盖率统计、代码复杂度统计、编码规范检查、部署到测试环境、功能测试等。

很多遗留还缺乏有效的项目自动化(automation)机制,使得持续集成环境无法发挥出它最大的价值。在建立持续集成环境的过程中,还应该对项目自动化机制进行改进,确保所有构建工作都能自动完成。于是完整的构建不仅在持续集成服务器上频繁运行,还在每个开发者的工作机器上更加频繁地运行。

任务划分

很多重构项目并没有明确的、细粒度的任务划分,实际进行重构的程序员得到的任务往往是类似“对安全模块进行重构”这样粗粒度、模块级的任务,而项目经理得到的反馈也是类似“可能需要两个月”这样粗略的估计,因此重构项目的进度往往难以控制。

和别的任何开发任务一样,重构任务(以及其他“技术债”类型的任务)也应该符合INVEST的标准:彼此独立(Independent),可讨论(Negotiatable),有价值(Valuable),可估算工作量(Estimatable),足够小(Small),可测试(Testable)。尤其是,它们的工作量应该得到估算,它们应该按照业务价值排列优先级。因为归根到底,重构(以及其他任何开发任务)都归结为“花在代码上的成本”与“对业务创造的价值”之间的权衡。

我们建议按照软件功能模块划分任务,而不区分是新功能开发还是重构。在实际工作中我们发现,对于同样的功能模块,新开发和重构的工作量差别不大。这也意味着重构工作可以被安排在日常开发工作中进行,并统一管理进度。

质量保证

在过去的咨询项目中,我们曾经不止一次见到这样的项目:在缺乏测试覆盖的情况下,程序员们修改旧系统中的代码,试图提高其可读性和可维护性。这些项目往往自称“重构项目”,但实际上它们并不是重构,因为它们缺乏必要的质量保证机制。

按 照定义,重构是对软件内部结构的一种调整,目的是在不改变“软件之可观察行为”的前提下,提高其可理解性,降低其修改成本。如果没有一套可靠的测试机制, 重构就无从谈起,因为你根本就无从知道自己做的调整是否改变了“软件之可观察行为”,甚至可能已经搞得系统不能运行还一无所知。

对 这种没有测试的系统进行重构,就像是编织一张网:先针对一小块功能编写验收测试,在这张“粗网”的保护下再逐渐给代码添加单元测试,有了粗细两层网的保护 再深入重构。随着重构的开展,这张频繁自动化运行的安全网也渐渐铺开。从不断提升的测试覆盖率和不断降低的代码复杂度,我们就能清晰地看到重构的进展情 况。

有 时由于代码质量低劣,程序员们在试图重构时会遇到困难:那些最需要重构的代码也是最难进行单元测试的。通常这是因为模块之间的依赖过于复杂、建立依赖的方 式不佳,而导致无法将这些模块纳入测试。此时需要首先建立验收测试套件,然后用必要的解耦手段调整依赖关系,然后才能对这些模块进行单元测试,之后才能对 它们进行重构。

ThoughtWorks能做什么?

在重构项目的组织、管理、质量保证和实施技术等方面,ThoughtWorks具有无可比拟的能力和经验。我们曾为一些生命周期长至数年、规模大至上百万行代码的遗留系统进行过重构项目,并取得了良好的效果。

我们的咨询师可以和公司中的开发者共同工作,共同解决发现并解决问题。在这个过程中,ThoughtWorks的咨询师会迅速把更佳的工作方式和理念告诉作为客户的开发者,以便在将来没有ThoughtWorks咨询师的情况下,客户也可以坚持那些方发实践,并把它传播给更多的人。

发表于 @ 2008年02月25日 13:30:00 | 评论( loading... )| 编辑| 举报| 收藏

旧一篇:用一朵云重建软件开发者的声望——讲述iTechTag网站的故事 | 新一篇:Announcement: Fluorida 0.0.1

查看最新精华文章 请访问博客首页相关文章

热门招聘职位