[原创文章Q{载请保留或注明出处:(x)http://www.regexlab.com/zh/encoding.htm]
U别Q中U?/p>
摘要Q本文介l了(jin)字符与编码的发展q程Q相x늚正确理解。D例说明了(jin)一些实际应用中Q编码的实现Ҏ(gu)。然后,本文讲述?jin)通常对字W与~码的几U误解,׃q些误解而导致ؕ码生的原因Q以?qing)消除ؕ码的办法。本文的内容늛?jin)“中文问题”,“ؕ码问题”?/p>
掌握~码问题的关键是正确地理解相x念,~码所涉及(qing)的技术其实是很简单的。因此,阅读本文旉要慢d惻I多思考?/p>
“字W与~码”是一个被l常讨论的话题。即使这P时常出现的ؕ码仍然困扰着大家。虽然我们有很多的办法可以用来消除ؕ码,但我们ƈ不一定理解这些办法的内在原理。而有的ؕ码生的原因Q实际上׃底层代码本n有问题所D的。因此,不仅是初学者会(x)对字W编码感到模p,有的底层开发h员同样对字符~码~Z准确的理解?/p>
![]() |
|||
|
从计机对多国语a的支持角度看Q大致可以分Z个阶D:(x)
pȝ内码 | 说明 | pȝ | |
阶段一 | ASCII | 计算机刚开始只支持pQ其它语a不能够在计算Z存储和显C?/td> | 英文 DOS |
阶段?/td> | ANSI~码 Q本地化Q?/td> | Z计算机支持更多语aQ通常使用 0x80~0xFF 范围?2 个字节来表示 1 个字W。比如:(x)汉字 '? 在中文操作系l中Q?[0xD6,0xD0] q两个字节存储?br /> 不同的国家和地区制定?jin)不同的标准Q由此生了(jin) GB2312, BIG5, JIS {各自的~码标准。这些?2 个字节来代表一个字W的各种汉字延~码方式Q称?b> ANSI ~码。在体中文系l下QANSI ~码代表 GB2312 ~码Q在日文操作pȝ下,ANSI ~码代表 JIS ~码?br /> 不同 ANSI ~码之间互不兼容Q当信息在国际间交流Ӟ无法属于两U语a的文字,存储在同一D?b> ANSI ~码的文本中?/td> | 中文 DOSQ中?Windows 95/98Q日?Windows 95/98 |
阶段?/td> | UNICODE Q国际化Q?/td> | Z(jin)使国际间信息交流更加方便Q国际组l制定了(jin) UNICODE 字符?/b>Qؓ(f)各种语言中的每一个字W设定了(jin)l一q且唯一的数字编P以满语言、跨q_q行文本转换、处理的要求?/td> | Windows NT/2000/XPQLinuxQJava |
字符串在内存中的存放Ҏ(gu)Q?/p>
?ASCII 阶段Q?b>单字节字W串使用一个字节存放一个字W(SBCSQ。比如,"Bob123" 在内存中为:(x)
42 | 6F | 62 | 31 | 32 | 33 | 00 |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
B | o | b | 1 | 2 | 3 | \0 |
在?ANSI ~码支持多种语言阶段Q每个字W用一个字节或多个字节来表C(MBCSQ,因此Q这U方式存攄字符也被UC多字节字W?/b>。比如,"中文123" 在中?Windows 95 内存中ؓ(f)7个字节,每个汉字?个字节,每个英文和数字字W占1个字节:(x)
D6 | D0 | CE | C4 | 31 | 32 | 33 | 00 |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
||
?/td> | ?/td> | 1 | 2 | 3 | \0 |
?UNICODE 被采用之后,计算机存攑֭W串Ӟ改ؓ(f)存放每个字符?UNICODE 字符集中的序受目前计机一般?2 个字节(16 位)(j)来存放一个序PDBCSQ,因此Q这U方式存攄字符也被UC宽字节字W?/b>。比如,字符?"中文123" ?Windows 2000 下,内存中实际存攄?5 个序P(x)
2D | 4E | 87 | 65 | 31 | 00 | 32 | 00 | 33 | 00 | 00 | 00 | ??x86 CPU 中,低字节在?/font> |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
||||||
?/td> | ?/td> | 1 | 2 | 3 | \0 |
一共占 10 个字节?/p>
![]() |
|||
|
理解~码的关键,是要把字W的概念和字节的概念理解准确。这两个概念Ҏ(gu)hQ我们在此做一下区分:(x)
概念描述 | 举例 | |
字符 | Z使用的记P抽象意义上的一个符受?/td> | '1', '?, 'a', '$', 'K?, …?/td> |
字节 | 计算Z存储数据的单元,一?位的二进制数Q是一个很具体的存储空间?/td> | 0x01, 0x45, 0xFA, …?/td> |
ANSI 字符?/td> | 在内存中Q如果“字W”是?ANSI ~码形式存在的,一个字W可能用一个字节或多个字节来表C,那么我们U这U字W串?ANSI 字符?/b>或?b>多字节字W串?/td> | "中文123" Q占7字节Q?/font> |
UNICODE 字符?/td> | 在内存中Q如果“字W”是以在 UNICODE 中的序号存在的,那么我们U这U字W串?UNICODE 字符?/b>或?b>宽字节字W串?/td> | L"中文123" Q占10字节Q?/font> |
׃不同 ANSI ~码所规定的标准是不相同的Q因此,对于一个给定的多字节字W串Q我们必ȝ道它采用的是哪一U编码规则,才能够知道它包含?jin)哪些“字W”。而对?UNICODE 字符?/b>来说Q不在什么环境下Q它所代表的“字W”内Ҏ(gu)L不变的?/p>
1.3 字符集与~码
各个国家和地区所制定的不?ANSI ~码标准中,都只规定?jin)各自语a所需的“字W”。比如:(x)汉字标准QGB2312Q中没有规定韩国语字W怎样存储。这?ANSI ~码标准所规定的内容包含两层含义:(x)
各个国家和地区在制定~码标准的时候,“字W的集合”和“编码”一般都是同时制定的。因此,q_我们所说的“字W集”,比如QGB2312, GBK, JIS {,除了(jin)有“字W的集合”这层含义外Q同时也包含?jin)“编码”的含义?/p>
?b>UNICODE 字符?/b>”包含了(jin)各种语言中用到的所有“字W”。用来给 UNICODE 字符集编码的标准有很多种Q比如:(x)UTF-8, UTF-7, UTF-16, UnicodeLittle, UnicodeBig {?/p>
![]() |
|||
|
单介l一下常用的~码规则Qؓ(f)后边的章节做一个准备。在q里Q我们根据编码规则的特点Q把所有的~码分成三类Q?/p>
分类 | ~码标准 | 说明 |
单字节字W编?/td> | ISO-8859-1 | 最单的~码规则Q每一个字节直接作Z?UNICODE 字符。比如,[0xD6, 0xD0] q两个字节,通过 iso-8859-1 转化为字W串Ӟ直接得?[0x00D6, 0x00D0] 两个 UNICODE 字符Q即 "?D"?br /> 反之Q将 UNICODE 字符串通过 iso-8859-1 转化为字节串Ӟ只能正常转化 0~255 范围的字W?/td> |
ANSI ~码 | GB2312, BIG5, Shift_JIS, ISO-8859-2 …?/td> | ?UNICODE 字符串通过 ANSI ~码转化为“字节串”时Q根据各自编码的规定Q一?UNICODE 字符可能转化成一个字节或多个字节?br /> 反之Q将字节串{化成字符串时Q也可能多个字节转化成一个字W。比如,[0xD6, 0xD0] q两个字节,通过 GB2312 转化为字W串Ӟ得?[0x4E2D] 一个字W,?'? 字?br /> “ANSI ~码”的特点Q?br />1. q些“ANSI ~码标准”都只能处理各自语言范围之内?UNICODE 字符?br />2. “UNICODE 字符”与“{换出来的字节”之间的关系是h定的?/td> |
UNICODE ~码 | UTF-8, UTF-16, UnicodeBig …?/td> | 与“ANSI ~码”类似的Q把字符串通过 UNICODE ~码转化成“字节串”时Q一?UNICODE 字符可能转化成一个字节或多个字节?br /> 与“ANSI ~码”不同的是:(x) 1. q些“UNICODE ~码”能够处理所有的 UNICODE 字符?br />2. “UNICODE 字符”与“{换出来的字节”之间是可以通过计算得到的?/td> |
我们实际上没有必要去q每一U编码具体把某一个字W编码成?jin)哪几个字节Q我们只需要知道“编码”的概念是把“字W”{化成“字节”就可以?jin)。对于“UNICODE ~码”,׃它们是可以通过计算得到的,因此Q在Ҏ(gu)的场合,我们可以M(jin)解某一U“UNICODE ~码”是怎样的规则?/p>
![]() |
|||
|
?C++ ?Java 中,用来代表“字W”和“字节”的数据cdQ以?qing)进行编码的?gu)Q?/p>
cd或操?/b> | C++ | Java |
字符 | wchar_t | char |
字节 | char | byte |
ANSI 字符?/td> | char[] | byte[] |
UNICODE 字符?/td> | wchar_t[] | String |
字节东y(c)字符?/td> | mbstowcs(), MultiByteToWideChar() | string = new String(bytes, "encoding") |
字符东y(c)字节?/td> | wcstombs(), WideCharToMultiByte() | bytes = string.getBytes("encoding") |
以上需要注意几点:(x)
![]() |
|||
|
声明一D字W串帔RQ?/p>
// ANSI 字符Ԍ内容长度 7 字节
char sz[20] = "中文123"; // UNICODE 字符Ԍ内容长度 5 ?wchar_tQ?0 字节Q?/span> wchar_t wsz[20] = L"\x4E2D\x6587\x0031\x0032\x0033"; |
UNICODE 字符串的 I/O 操作Q字W与字节的{换操作:(x)
// q行时设定当?ANSI ~码QVC 格式 setlocale(LC_ALL, ".936"); // GCC 中格?/span> setlocale(LC_ALL, "zh_CN.GBK"); // Visual C++ 中用小?%sQ按?setlocale 指定~码输出到文?br />// GCC 中用大?%S fwprintf(fp, L"%s\n", wsz); // ?UNICODE 字符串按?setlocale 指定的编码{换成字节 wcstombs(sz, wsz, 20); // 把字节串按照 setlocale 指定的编码{换成 UNICODE 字符?br />mbstowcs(wsz, sz, 20); |
?Visual C++ 中,UNICODE 字符串常量有更简单的表示Ҏ(gu)。如果源E序的编码与当前默认 ANSI ~码不符Q则需要?#pragma setlocaleQ告诉编译器源程序用的~码Q?/p>
// 如果源程序的~码与当前默?ANSI ~码不一_(d) // 则需要此行,~译时用来指明当前源E序使用的编?/font> #pragma setlocale (".936") // UNICODE 字符串常量,内容长度 10 字节 wchar_t wsz[20] = L"中文123"; |
以上需要注?#pragma setlocale ?setlocale(LC_ALL, "") 的作用是不同的,#pragma setlocale 在编译时起作用,setlocale() 在运行时起作用?/p>
![]() |
|||
|
字符串类 String 中的内容?UNICODE 字符Ԍ(x)
// Java 代码Q直接写中文
String string = "中文123"; // 得到长度?5Q因为是 5 个字W?/span> System.out.println(string.length()); |
字符?I/O 操作Q字W与字节转换操作。在 Java ?java.io.* 中,以“Stream”结cM般是用来操作“字节串”的c,以“Reader”,“Writer”结cM般是用来操作“字W串”的cR?/p>
// 字符串与字节串间怺转化 // 按照 GB2312 得到字节Q得到多字节字符Ԍ(j) byte [] bytes = string.getBytes("GB2312"); // 从字节按?GB2312 得到 UNICODE 字符?/span> string = newString(bytes, "GB2312"); // 要将 String 按照某种~码写入文本文gQ有两种Ҏ(gu)Q?br /> // W一U办法:(x)?Stream cd入已l按照指定编码{化好的字节串 OutputStream os = new FileOutputStream("1.txt"); os.write(bytes); os.close(); // W二U办法:(x)构造指定编码的 Writer 来写入字W串 Writer ow = new OutputStreamWriter(new FileOutputStream("2.txt"), "GB2312"); ow.write(string); ow.close(); /* 最后得到的 1.txt ?2.txt 都是 7 个字?*/ |
如果 java 的源E序~码与当前默?ANSI ~码不符Q则在编译的时候,需要指明一下源E序的编码。比如:(x)
E:\>javac -encoding BIG5 Hello.java |
以上需要注意区分源E序的编码与 I/O 操作的编码,前者是在编译时起作用,后者是在运行时起作用?/p>
![]() |
|||
|
对编码的误解 | |
误解一 | 在将“字节串”{化成“UNICODE 字符东y(c)时Q比如在d文本文gӞ或者通过|络传输文本ӞҎ(gu)“字节串”简单地作ؓ(f)单字节字W串Q采用每“一个字节”就是“一个字W”的Ҏ(gu)q行转化?br /> 而实际上Q在非英文的环境中,应该“字节串”作?ANSI 字符Ԍ采用适当的编码来得到 UNICODE 字符Ԍ有可能“多个字节”才能得到“一个字W”?br /> 通常Q一直在英文环境下做开发的E序员们Q容易有q种误解?/td> |
误解?/td> | ?DOSQW(xu)indows 98 {非 UNICODE 环境下,字符串都是以 ANSI ~码的字节Ş式存在的。这U以字节形式存在的字W串Q必ȝ道是哪种~码才能被正地使用。这使我们Ş成了(jin)一个惯性思维Q“字W串的编码”?br /> ?UNICODE 被支持后QJava 中的 String 是以字符的“序号”来存储的,不是以“某U编码的字节”来存储的,因此已经不存在“字W串的编码”这个概念了(jin)。只有在“字W串”与“字节串”{化时Q或者,一个“字节串”当成一?ANSI 字符串时Q才有编码的概念?br /> 不少的h都有q个误解?/td> |
W一U误解,往往是导致ؕ码生的原因。第二种误解Q往往D本来Ҏ(gu)U正的ؕ码问题变得更复杂?/p>
在这里,我们可以看到Q其中所讲的“误解一”,即采用每“一个字节”就是“一个字W”的转化Ҏ(gu)Q实际上也就{同于采?iso-8859-1 q行转化。因此,我们常常使用 bytes = string.getBytes("iso-8859-1") 来进行逆向操作Q得到原始的“字节串”。然后再使用正确?ANSI ~码Q比?string = new String(bytes, "GB2312")Q来得到正确的“UNICODE 字符东y(c)?/p>
![]() |
|||
|
?UNICODE E序中的字符Ԍ都是以某U?ANSI ~码形式存在的。如果程序运行时的语a环境与开发时的语a环境不同Q将?x)导?ANSI 字符串的昄p|?/p>
比如Q在日文环境下开发的?UNICODE 的日文程序界面,拿到中文环境下运行时Q界面上显CZؕ码。如果这个日文程序界面改为采?UNICODE 来记录字W串Q那么当在中文环境下q行Ӟ界面上将可以昄正常的日文?/p>
׃客观原因Q有时候我们必d中文操作pȝ下运行非 UNICODE 的日文YӞq时我们可以采用一些工P比如Q南极星QAppLocale {,暂时的模拟不同的语言环境?/p>
![]() |
|||
|
当页面中的表单提交字W串Ӟ首先把字W串按照当前面的编码,转化成字节串。然后再每个字节{化成 "%XX" 的格式提交到 Web 服务器。比如,一个编码ؓ(f) GB2312 的页面,提交 "? q个字符串时Q提交给服务器的内容?"%D6%D0"?/p>
在服务器端,W(xu)eb 服务器把收到?"%D6%D0" 转化?[0xD6, 0xD0] 两个字节Q然后再Ҏ(gu) GB2312 ~码规则得到 "? 字?/p>
?Tomcat 服务器中Qrequest.getParameter() 得到qӞ常常是因为前面提到的“误解一”造成的。默认情况下Q当提交 "%D6%D0" l?Tomcat 服务器时Qrequest.getParameter() 返?[0x00D6, 0x00D0] 两个 UNICODE 字符Q而不是返回一?"? 字符。因此,我们需要?bytes = string.getBytes("iso-8859-1") 得到原始的字节串Q再?string = new String(bytes, "GB2312") 重新得到正确的字W串 "??/p>
![]() |
|||
|
通过数据库客L(fng)Q比?ODBC ?JDBCQ从数据库服务器中读取字W串Ӟ客户端需要从服务器获知所使用?ANSI ~码。当数据库服务器发送字节流l客L(fng)Ӟ客户端负责将字节按照正的~码转化?UNICODE 字符丌Ӏ?/p>
如果从数据库d字符串时得到qQ而数据库中存攄数据又是正确的,那么往往q是因ؓ(f)前面提到的“误解一”造成的。解决的办法q是通过 string = new String( string.getBytes("iso-8859-1"), "GB2312") 的方法,重新得到原始的字节串Q再重新使用正确的编码{化成字符丌Ӏ?/p>
![]() |
|||
|
当一D?Text 或?HTML 通过?sh)子邮g传送时Q发送的内容首先通过一U指定的字符~码转化成“字节串”,然后再把“字节串”通过一U指定的传输~码QContent-Transfer-EncodingQ进行{化得到另一东y(c)字节串”。比如,打开一电(sh)子邮件源代码Q可以看到类似的内容Q?/p>
Content-Type: text/plain; charset="gb2312" Content-Transfer-Encoding: base64 sbG+qcrQuqO17cf4yee74bGjz9W7+b3wudzA7dbQ0MQNCg0KvPKzxqO6uqO17cnnsaPW0NDEDQoNCg== |
最常用?Content-Transfer-Encoding ?Base64 ?Quoted-Printable 两种。在对二q制文g或者中文文本进行{化时QBase64 得到的“字节串”比 Quoted-Printable 更短。在对英文文本进行{化时QQuoted-Printable 得到的“字节串”比 Base64 更短?/p>
邮g的标题,用了(jin)一U更短的格式来标注“字W编码”和“传输编码”。比如,标题内容?"?Q则在邮件源代码中表CZؓ(f)Q?/p>
// 正确的标题格?/span>
Subject: =?GB2312?B?1tA=?= |
其中Q?/p>
如果“传输编码”改?Quoted-PrintableQ同P如果标题内容?"?Q?/p>
// 正确的标题格?/span>
Subject: =?GB2312?Q?=D6=D0?= |
如果阅读邮g时出Cؕ码,一般是因ؓ(f)“字W编码”或“传输编码”指定有误,或者是没有指定。比如,有的发邮件组件在发送邮件时Q标?"?Q?/p>
// 错误的标题格?/span>
Subject: =?ISO-8859-1?Q?=D6=D0?= |
q样的表C,实际上是明确指明?jin)标题?f) [0x00D6, 0x00D0]Q即 "?D"Q而不?"??/p>
![]() |
|||
|
非也。iso-8859-1 只是单字节字W集中最单的一U,也就是“字节编号”与“UNICODE 字符~号”一致的那种~码规则。当我们要把一个“字节串”{化成“字W串”,而又不知道它是哪一U?ANSI ~码Ӟ先暂时地把“每一个字节”作为“一个字W”进行{化,不会(x)造成信息丢失。然后再使用 bytes = string.getBytes("iso-8859-1") 的方法可恢复到原始的字节丌Ӏ?/p>
Java 中,字符串类 java.lang.String 处理的是 UNICODE 字符Ԍ不是 ANSI 字符丌Ӏ我们只需要把字符串作为“抽象的W号的串”来看待。因此不存在字符串的内码的问题?/p>
作者:(x)飞云侠 来自QCSDN
Z(jin)l朋友同事一些设计问题上的指|Ҏ(gu)写此文,很多观点都是从别人的文章中获取,有些观点肯定也有偏颇Q有些观点也仅仅是提出ƈ没有做详l论qͼ请多拍砖Q以便改正?
?strong>概述?/p>
在工作中Q作Z个程序员或者一个设计师QL要设计一些函数库或者一个框Ӟ当然最l常的还是做目Q即使是一个项目,也会(x)被经常改动,甚至交给别h改动?br /> 当你做这些工作的时候,你的q些成果都是要给别h?jin)解使用的,或者说l以后的你用的Qؓ(f)?jin)别人的方便或者ؓ(f)?jin)自q方便Q我们要可能做好设计?br />
?strong>放正?j)态,M东西都是不断发展?/strong>?/p>
技术是日新月异的,每一天都有新的技术出来,正所?山外有山Qh外有?Q每一个新的轮子出来,都可能比你要设计的轮子好Q所以在设计的时候,应该?jin)解一下是否已l有?jin)类似的轮子Q是否要设计一个新的轮子?/p>
即你的轮子已经设计好了(jin)Q也不好认ؓ(f)自己的轮子一定比别h的轮子好Q虽然你的轮子可能更适合你的实际使用?/p>
技术在不断的发展中Q你以及(qing)你的朋友/同事都在不断q步Q?士别三日Q当刮目相看"Q所以不要认Z的水q一定比别h高,"有所短,寸有所?Q所以别人对你的函数?框架提出意见Q提出疑问的时候,请不要惊奇,不要反感Q不要认为别人在"挑刺"Q也怽的函数库/框架早就不适合当前的发展了(jin)?br />
态度军_一切。你的领导或许更重视q一炏V?br />
?strong>必要的组成部?单元试Q文档,实例Q手册etc?/p>
单元试Q文档,API DocQ手册,演示E序QChange LogQReadmeQbuild。xml{等
有一天别Z用了(jin)你设计的函数?框架Q当你升U后Q原来的目却不能工作了(jin)Q经q一天的调试Q你l于扑ֈ?jin)原因,原来是不?j)写错?jin)一个东ѝ?/p>
你肯定不希望上述的事情发生,那么请你写单元测试吧Q这h不浪费自q旉Q也不耽误别h的工作,何乐而不为。你花在写单元测试的旉/带来的乐和你升U后Ҏ(gu)莫名其妙的错误的旉和苦恼相比,肯定更有价倹{你看到单元试的绿条,N不感到高兴吗?!
如果你不能保证你的程序修Ҏ(gu)有错误,不要指望你的同事认ؓ(f)你的错误是可以容忍的Q他们在?j)里早就开始骂你了(jin)Q呵c(din)写单元试?br />
看看M一个知名的框架Q都包含完善的文档,单元试Q示例程序,用户手册Q那么请你也包含q些吧。哦Q对?jin),误l地写好JavaDocQ它很重要?br />
使用你的框架/函数库的人如果到处去找用方法,L某个c?但是他不知道是否有这个类)Q那么说明你的文档没有到位。如果你希望别h使用你的q个cL者功能,那么请写好文档,不要指望别h去读你的源码然后p理解它是q什么用的?br />
如果你做到这些,那么你的函数?框架也有?知名"的前提,N不是?如果没有Q我x没法让别人更好地使用的?br />
对了(jin)Q有?jin)这些东西,q要有一个良好的目录l织Q这个也可以参考别的框架的l织方式?/p>
?strong>借鉴成熟的设计,参考已有的目?/p>
1. 要做一个新的东西,没有x。不要惊Ӟ我肯定先找一个现有的东西来借鉴?br />
当然前提是不要重新发明轮子,或者是你有充分条g要重新发明一个轮子?br /> StrutsQW(xu)ebWorkQ?a target="_blank">Spring{等都是成熟的框Ӟ不管你用v来是否符合你的习(fn)惯?br /> 在你成ؓ(f)大师之前Q你的设计思想估计前h都已l提出ƈ实践q了(jin)Q所以要勇敢地去借鉴?站在巨h的肩膀?我们能更q一步?br />
例如我们厌倦了(jin)在访问数据库时用如下的代码:
try
{
//your code here
}
catch(Exception e)
{
//catch Exception
}
finally
{
//must do something
}
我们可以借鉴Spring框架的JdbcTemplatec,看看它是如何利用回调函数来处理的?
我们使用hibernate时是不是也会(x)使用cM上面的代码,那么可以参考Spring框架的HibernateTemplate?br />
借鉴也是一U捷径?br />
警告:借鉴但不要抄袭,借鉴代码要注明来源,重他h也是重自己?br />
2. 在实际的目中,往往可以参考已l有的项目来做自q设计?br />
例如做一个网站,我不知道如何讉K数据库,如何布局Q如何分层,那么我们可以参考已l有的网站程序,看看别h是如何利用SiteMesh或者tiles布局Q如何用Hibernate来访问数据库或者用已l封装好的JDBCcL讉K数据库,如何利用StrutsQW(xu)ebWork或者其他访问来分层?/p>
?strong>遵守U定俗成的一些做?/strong>?
Z(jin)使别人更方便C用你的东西,那么在设计一些通用的函数或者类的时候,请遵守通用的做法,不要与众不同Q除非你的内部实现确实与众不同?/p>
例如实现一个类似ArrayList的类Q那么请不要q样?
public int count()
{
return list.size();
}
public Item getItem(int i)
{
return list.get(i);
}
而应该这?
public int size()
{
return list.size();
}
public Item get(int i)
{
return list.get(i);
}
当然每个人都有自qxQ如果你非常认ؓ(f)你原来的方式比普通的好,那么h?套方式供别h选择。它不会(x)l你带来ȝ(ch)Q只是一个一看就懂的做法Q不用怀疑,q样做有好处?br />
很多cȝ设计都有一些约定俗成的做法Q那么在你设计一个新cȝ时候,先借鉴一下吧Q多看看JDK的源?文档Q看看别人是怎么实现的。这更有助于推广你的成果?br />
?strong>不要q信权威?
在用已有的框架或者函数库Ӟ不要认ؓ(f)所有的东西都是正确的或者是最好的最好,肯定不是。没有完的东西Q已l存在的东西在设计的时候因为种U局限或者因Z者的水^Q对现在来说肯定存在不合理的设计Q或者过于理惛_的设计,而不能满_际情c(din)?br />
不迷信权威,才能到达新的境界?/p>
?strong>不要L排斥Q不?jin)解׃要草率发表意见,要严?/strong>?/p>
在网上经常看到。Net和Java的比?火拼Q或者是Struts VS Webwork或者是其他{等Q非怹多。经常看到的是一方对Ҏ(gu)的东西不甚了(jin)解,开始批评,l果说不到点子上Q反而被嘲笑一番?br /> 几种技术的比较有时候是必要的,例如技术选型的时候。但是如果一些对q些技术根本不?jin)解的h来选型Q来评判Q你能对l果信服?
存在是合理QQ何技术都有其存在的理由,虽然有些东西早就q时?jin),但是在当时它也是应运而生的?br /> 几种技术,都是来解军_L(fng)问题Q但是问题也有很多方面,解决方式也有很多U,每个人的x也都不一P思\也不一P所以没有绝对符合要求的技术,但是应该有符合你的技术,不符合你的技术不{于也不满别h的要求。所以不要轻易排斥别的东ѝ?br />
在做技术比较的时候,如果你不?jin)解Q那么请不要L发表意见Q至你可以亲自M(jin)解,d践之后在发表你的意见岂不是更好?br />
在发表意见的时候,也要严}Q不要轻易下l论Q要l过求证Q否则一旦错误只?x)让?gu)W话Q让你的同事看不起你。例如你说Hibernate3不支持jdk1?Q那么最好去好好扑ֈ你的证据Q否则就?x)成为错误?Hibernate3支持jdk1?)
作ؓ(f)一个技术h员,严}应该是我们的?fn)惯之一Q无论做开发还是做设计?/p>
?strong>处理好你的异?/strong>?/p>
异常处理是Java~程中非帔R要的一个部分。徏议在使用异常之前阅读或者?br />
下面从书中摘出几条徏?
* l对不要忽略异常
* 千万不要隐藏异常
* 仅在不正常的情况下用异?br /> * 对可恢复的情况用可(g)查异常,对程序错误用运行时异常(RunTimeException)
* l方法引发的异常做文?br /> * 在详l信息里面包括失败捕获信?br /> * 使用finally避免资源泄漏
* ....
在这里特别提出的是,在开发中要特别处理NULL的情况,否则l常引发NullPointException异常Q在Java里这是一个最令h头疼的异怺(jin)?br /> 如果你的E序因ؓ(f)一个NULL|而报?jin)几十个NullPointException的话Q不但得让h?ch)死Q而且q非帔R以找到错误所在。所以在Java中一定要注意q个问题?br /> 如果你的函数不允许Null|那么可以截获它,抛出一个异常,或者给客户更友好的提示Q难道不好吗?
让我们来看一个例?
public String getName(User aUser)
{
//如果aUser为NullQ会(x)发生什么情?br /> return aUser.getName();
}
很明显,如果参数为NullQ就?x)抛出异常。应该改?
public String getName(User aUser)
{
if(null=aUser)
{
return "";
}
else
{
return aUser.getName();
}
}
或者你要求参数不能为空Q还可以抛出一个异常,强制使用者不能传入空倹{?br />
q有l常被忽略的是RunTimeException和普通异常的区别Q在Java中,q是一个特D的异常c,E序中如果遇到这个异常,用户可以不截获它Q而如果是其他的普通异常,׃许要截获它。我们的代码l常q么?
try
{
//your code here
}
catch(Exception e)
{
//do warn
}
q样写的话,截获了(jin)所有异常,当然也包括了(jin)RunTimeException?在很多情况下Q这是不合适的处理方式Q我们只应截获必要的异常Q而应该忽略RuntimeException?br />
关于RunTimeExceptionQ在Spring中还有更好的利用方式Q徏议阅读Spring框架中在事务中对异常的处理代码,例如对Jdbc抛出的SqlException的{换?br />
关于异常处理Q我提出几点:
* 捕获异常而且再次抛出时要包含原来的异怿?br /> * 不要忘了(jin)RunTimeExceptionQ除非必要,否则不要用catch(Exception e)的方式捕h有异常?br /> * 不要用异常做程控制Q异常的性能代h(hun)比较高昂?Ҏ(gu)Q可能有Z同意。此处不详细讨论)
* 不要把异常处理都抛给别hQ本函数有能力处理的׃要抛出?br />
在此读者详l阅L者?br />
?strong>q度依赖?/p>
在定位错误的时候,l常遇到览?jin)?八个文gq是没有扑ֈ什么地Ҏ(gu)行了(jin)真正需要的函数Q这个时候就非常郁闷。A调用?jin)BQB调用?jin)CQC调用?jin)D。。。。。。让人找不到?br />
面对q样的程序,存在的问题不仅仅是定位错误麻?ch),而且如果需要维护这L(fng)函数?框架Q恐怕你的有非常高的lM能力才行Q否则打L也不ȝ护?br />
那么我们自己最好不要写q样的程序出来给人用?/p>
?strong>滥用接口?/p>
现在行"面对接口~程"Q这本n本来是不错,但是滥用接口的现象却l常发生?br /> "面向接口"Q于是所有的c都有一个对应的接口Q接口的函数声明和类一模一P而且一个接口只有一个类来实现它。这L(fng)面向接口有什么意义哪? (Z(jin)用Spring的事务的情况除外)
Ҏ(gu)"q比Ҏ(gu)?Law of Demter)"Q一个对象应当对其他对象有尽可能的?jin)解。一个接口内应该只定义对Ҏ(gu)需要的Ҏ(gu)Q而不要把一些没用的Ҏ(gu)声明攑֜接口里面?br />
例如如下一个类:
public class MyCounter
{
private int n1;
private int n2;
public MyCounter(int n1Qint n2)
{
this。n1=n1;
this。n2=n2;
}
public void setN1(int n1)
{
return this.n1 = n1;
}
public void setN2(int n2)
{
return this.n2 = n2;
}
public int getN1()
{
return n1;
}
public int getN2()
{
return n2;
}
public int getResult()
{
return n1 + n2;
}
}
我们可以看到Q这个类的主要目的是得到计算l果Q所以正的接口应该cM:
public interface Counter
{
int getResult();
}
但是很多情况下,l常是这L(fng)接口:
public interface Counter
{
int getResult();
int getN1();
int getN2();
void setN1(int n1);
void setN2(int n2);
}
我们想一惻Iq样做有2个后?
1. 除了(jin)getResult之外Q其他的函数我们Ҏ(gu)用不刎ͼ所以是多余的?br /> 2. 如果我们要自己实C个CounterQ如果接口中仅仅定义?jin)getResultQ我们仅仅需要实现它?yu)可以?jin)。我们自qcd能是多个数运,有乘除加减等{各U运,参数也有可能是一些数l。但是如果按照第二种Ҏ(gu)声明接口的话Q我们就必须实现后面的四个方法,如果q样的话Q实现这样东西不仅没用,而且费旉。我们恐怕要大声骂娘?jin)吧?br />
所以,接口有好的作用,但是不要滥用?br /> ?如果你的接口永远只有一个类实现Q那么可能就没有必要用接口?br /> ?你的接口只需要声明别人用到的函数卛_?/p>
【空接口的用?/strong>
在接口用的时候,I接口有2U情?
1. cMCloneableQSerializableQ他们往往是做一个标讎ͼ表示需要某个功能。当然你也可以这么用Q来表示你的cd有某个功能,实现?jin)你的某个接口?br /> 2. 你的接口l承?jin)别的接?非空)Q你的接口本w没有声明函数。这U情况一般是你不希望用户使用父接口来作ؓ(f)参数cdQ因Z们的用途可能不同,此时可以用I接口来实现?br />
W一U情冉|们不再多_(d)搜烦(ch)一下关于CloneableQSerializable的文章就?x)?jin)解很多?br /> 我们来看下面的代?
public interface Text
{
String getText();
}
public interface SqlText extends Text
{
}
可以看到QText接口是用于返回一个字W串。而SqlText是一个空接口Q它l承?jin)Text接口。也是说SqlText也是一UText。但是我们可以知道,M一个字W串不一定是Sql字符Ԍ所以此时声明了(jin)一个SqlText接口来用于表名当前的字符串是一个Sql字符丌Ӏ你的函数可以这样声?
public void doQuery(SqlText aSqlText)
而不是这?br />
public void doQuery(Text aText)
避免用户产生歧义的想法,一眼看去,明白应该传入一个Sql字符丌Ӏ?br />
【承层ơ过多?/strong>
一般来_(d)l承的层ơ不要过多,否则使用者可能会(x)讨厌Q找一个函C(x)很麻?ch)。很多Java语言(g)查工具都你的l承层次不要过3层?br />
【Has A QIs AQ不要滥用ѝ?/strong>
"我是一个Mp3"Q?我有一个Mp3"Q其实很Ҏ(gu)分L。但是在实际应用中,往往存在?我有一个Mp3"的情况当?我是一个Mp3"Q或者是Z(jin)h方便而放松了(jin)对自q要求Q甚臌沾沾自喜Q感觉找C个捷径?scud以前也干q这U事??br />
以前我曾l这样干q?我的逻辑cȝ接承了(jin)我的数据库访问类Q这h可以直接在逻辑c里面访?
public MyLogic extends MyDBA
aLogic.getInt("click");
aLogic.getString("name");
看v来是非常方便Q但是你的逻辑cd牢牢l在?jin)DBA上,是一U非怸好的做法。现在我q样声明:
public MyLogic
MyDBA adba;
adba.getInt("click");
adba.getString("name");
其实代码改动不大Q但是你的逻辑cM在牢牢绑在DBAw上?jin),何乐而不为?br />
其实q种现象在开发h员中间可能经常见刎ͼ我们要尽量避免。下面再来看一个例?
//一个保存分信息的c?br />
public class PageInfo
{
private int page;
private int pageCount;
private int recPerPage;
private int recCount;
//getQset method list...
}
一般的情况是,在Dao中进行分|询,计算总记录,总页数等{,所以需要把PageInfo传给Dao。而在逻辑cMQ把传回来的分页信息数据推到FormBean或者是Action中?br /> 也许你会(x)q么惻I如果我的Action或者FormBeanl承?jin)PageInfoQ岂不是要省很多事?br />
千万别这么干。ƈ不是所有的动作都需要分信息,你的FormBean和PageInfo没有l承的关pR也是说FormBean Has A PageInfoQ但是不是Is A PageInfo?br />
【保持外?行ؓ(f)一致?/strong>
外观一致其实很Ҏ(gu)理解Q例如你用size()表示得到一个List的大,那么在所有的ListcM你都用size()得到它的大小Q这是外观一致?br /> 外观一致让用户更方便用你的函数库Q不用记住几个不同的表示同一个功能的函数名字。或者几个名字相同功能却不同的函数。那很p糕?jin)?br />
行ؓ(f)一致相对外观一致就相对比较隑ց刎ͼ但是优秀的设计师肯定?x)让他的成果行?f)一_(d)而不是出人意料的行ؓ(f)Q也不是一套强行规定的行ؓ(f)?br />
我们来看下面的代?
import java.util.HashMap;
import java.util.Map;
class UserInfo
{
private String realname;
public UserInfo(String sName)
{
this.realname = sName;
}
public void setName(String sName)
{
this.realname = sName;
}
public String getName()
{
return this.realname;
}
}
public class MyTest
{
Map userInfoMap = new HashMap();
public void setUserInfo(String sName,UserInfo aInfo)
{
userInfoMap.put(sName,aInfo);
userInfoMap.put(aInfo.getName(),aInfo);
}
public UserInfo getUserInfo(String sName)
{
return (UserInfo)userInfoMap.get(sName);
}
public static void main(String args[])
{
MyTest aTest = new MyTest();
UserInfo aUserInfo = new UserInfo("王小?);
aTest.setUserInfo("儿童团团?,aUserInfo);
aTest.setUserInfo("三班班长",aUserInfo);
UserInfo 儿童团团?= aTest.getUserInfo("儿童团团?);
if(null!=儿童团团?
{
System.out.println(儿童团团?getName());
}
else
{
System.out.println("儿童团团?Not Found");
}
UserInfo 王小?= aTest.getUserInfo("王小?);
if(null!=王小?
{
System.out.println(王小?getName());
}
else
{
System.out.println("王小?Not Found");
}
}
}
可以看到Q上面的代码q行l果?王小?Q也是说儿童团团长是王二Q王二本n也是王小二,q一切正常?br />
现在我们把setUserInfo里面的第一句注释掉:
public void setUserInfo(String sName,UserInfo aInfo)
{
//userInfoMap.put(sName,aInfo);
userInfoMap.put(aInfo.getName(),aInfo);
}
再次q行上面的代码,我们发现儿童团团长不存在?jin),但是王小二还在。还可以看出Q如果找"三班班长"的话Q肯定也找不刎ͼ也就是说只有依据王小二的真名才能扑ֈ王小二,其他Ҏ(gu)׃行了(jin)?br />
从上面的setUserInfo和getUserInfo分析Q如果采用修改后的代码,我们的程序就出现?jin)行C一_(d)而这是o(h)惑不解的Q我们set?jin)半天,却找不到Q岂不是令h恼火!
当然上面的代码比较简单,通过单的修改p做到行ؓ(f)一_(d)但在实际~程中,往往因ؓ(f)复杂的行为操作,l常?x)造成行ؓ(f)不一_(d)从而给开发h员带来困惑?/p>
【MVCQMVC2QW(xu)EB设计~程的分层?/strong>
请阅L?http://forum.javaeye.com/viewtopic.php?t=11712&postdays=0&postorder=asc&start=0
【可扩展不等于功能强大,不要夸大其辞?/strong>
现在的系l,因ؓ(f)接口或者其他方法的使用Q都h很大的扩展性。但是扩展性不{于功能强大?br /> 存在一个接口,用户可以实现自己的接口,实非常方便。但是如果你的系l本w只实现?jin)一个接口或者根本没有实玎ͼ那么对用h说就谈不上方ѝ?br />
例如WebWork的validatorsQ本w是一个接口,但是实际上本w实现的具体cd,而且功能很差Q这个时候如果你说WebWork的校验器很厉宻I那么可能不太恰当了(jin)。当然扩展Webwork的Validatorq是非常方便的?br />
当然Q可扩展性还是需要的Q但是不要吹嘘,在这个Qw的q代Q让我们多干点实事?:)
?0/80原则?/strong>
在工作中Q我l常惛_20/80原则Q也是"巴雷多原?。例如我们可以看?
旉Q我?0%的时间会(x)产生成果?0%
产品Q品的20%带来利润?0%
阅读Q?0%的书幅包括?jin)内容?0%
工作Q?0%的工作给我们80%的满?br />
演讲Q?0%的演讲生媄(jing)响的80%
领导Q?0%的h作出80%的决?/p>
从上面可以看出,很多时候它都很有说服力?br /> 在这里我x到几点,但是和上面的可能出发Ҏ(gu)所不同:
1、程序的80%都是在处理特D情况,所以我们一定要对特D情况重视,不要因ؓ(f)是特D情况,׃很重视?0%的客户对Ҏ(gu)情况都很重视?br /> 文档对特D情况也要详l描qͼ因ؓ(f)开发h?0%的时候在查找q些东西Q而对那些l常用到的用法却很少查阅文档?br />
2、优化问?80%的瓶颈都出在20%的代码上Q所以在优化代码的时候不需要优化所有代码,只需要优?0%的关键代码就够了(jin)。当然追求完的人我们就不多说了(jin)?
记得有一条优化的原则?不要优化!不要优化"Q是非常有道理的?br />
3、如果你20%的事情做怺(jin)Q往往?x)导?0%的事情都怺(jin)Q或者是D别h认ؓ(f)你把事情几乎都做怺(jin)?br /> 如果你对一些事情发表了(jin)一些很不严谨的看法Q那么别Z(x)认ؓ(f)你在别的事情上也很不严}?br /> 依此cLQ代码质量,文档完整性等{,都会(x)让h产生cM的推理?br />
(当然一个代码写的很q人,往往文档也很乱?
【强制绑定是不受Ƣ迎的?/strong>
不要在程序中强制l定一些额外的功能?br />
有的框架往往功能很多Q是"大型计算?Q有很多功能Q但是在我需要打字的时候,l我打字的功能即可,不要强制我用网l功能,打印功能Q负载均衡功能等{?br />
一般来_(d)如果一个东西有很多功能Q那么做好做成可配置Q可插拔的,q样用户使用你的东西Q没必要在不使用高功能的时候,费用户的内存,盘。开发h员还得多copy好多lib文gQ占用调试时_(d)岂不是很ȝ(ch)?br />
不要C送一Q我不想要就别给我?:)
【有时候也得考虑兼容性?/strong>
一般来_(d)一个公司的客户?x)有很多Q用L(fng)q行环境是各U各L(fng)。jdk1.3Qjdk1.4甚至q有jdk1.2。这h们在~程的时候就必须做一些妥协,有些函数库就不能使用?br /> 如果q些用户的jdk不能升(一般来说都需要购买新的品才能升U?Q或者我们必dq些情况妥协Q那么我们就要在开发中考虑q些问题?br />
例如以前Q在Servlet 2.2的时候,因ؓ(f)没有setCharacterEncodingQ我们必L动对各种字符q行转换。当Servlet2.3的时候,可以使用q个函数?jin)。但是ؓ(f)?jin)客戯(g)虑Q我们只好没有升U还是用原来的Ҏ(gu)?当然后来大多数用户都使用?jin)新的App ServerQ我们就可以使用filter来处理编码问题了(jin))?br />
向下兼容性确实让人头|JDK1.5也发布好久了(jin)Q不q我们现在也不能使用Q只能自己没事测试测试?br />
在编E的时候,一定要讄好IDE的兼Ҏ(gu)设|,防止我们使用?jin)不能用的?gu)。JbuilderQEclipse都有cM的设|?br />
【成本与现实Q给用户以选择余地?/strong>
全文(g)索,luceneQlike是三U对大文本字D|索的Ҏ(gu)。那么你采用哪一U呢?
也许你会(x)毫不犹U的说"全文(g)? (我看你像TRS公司的托 :P)?br />
正如"强制l定是不受欢q的"里面所说的一P我还是觉得应该给用户以选择的余地?br />
全文(g)索是要花q或者需要配|,而且一般来说数据库专用的全文检索都是不通用的,lucene是需要开发h员开发的Q只有like最单了(jin)Q但是太单了(jin)Q而且性能也差?br />
q个时候,也许我们应该提供几U方式供用户选择?jin),用户如何选择那就看他们了(jin)。。?br />
【结束语?/strong>
实际开发设计中肯定q存在很多其他的问题Q本文不可能一一。到此ؓ(f)止?:)
希望各位在开发设计中成ؓ(f)高水q的设计师?:)
作者:(x)happy_forever 来自Q?/font> http://www.javafan.net
以下文章都是l典Q看不看随你的便Q我只希望知识掌握在更多中国人的手里Q?/font>
中国有很多小朋友Q他?8,9岁或21,2岁,通过自学也写?jin)不代码,他们有的代码写的很漂亮,一些技术细节相当出众,也很有钻研精,但是他们被一些错误的认识和观点左叻I~Z对系l,对程序的整体理解能力Q这些hQ一个网上的朋友说得很好Q他们实际上只是一些Coding fansQ压Ҏ(gu)有资格称为程序员Q但是据我所知,不少网l公司的CTO是q样的coding fans,拿着吓h的工资,做着吓h的项目,目的结局通常也很吓h?
E序员基本素质:(x)
作一个真正合格的E序员,或者说是可以真正合格完成一些代码工作的E序员,应该h的素质?
1Q团队精和协作能力
把它作ؓ(f)基本素质Qƈ不是不重要,恰恰相反Q这是程序员应该具备的最基本的,也是最重要的安w立命之本。把高水q程序员说成独行侠的都是在呓语,M个h的力量都是有限的Q即便如linusq样的天才,也需要通过l成强大的团队来创造奇q,那些遍布全球的ؓ(f)linux写核?j)的高手们,没有协作_是不可想象的。独行侠可以作一些赚qY件发点小财,但是一旦进入一些大pȝ的研发团队,q入商业化和产品化的开发Q务,~Zq种素质的h完全不合格?jin)?
2Q文档习(fn)?
说高水^E序员从来不写文档的肯定是^臭未q的毛孩子,良好的文档是正规研发程中非帔R要的环节Q作Z码程序员Q?0Q的工作旉写技术文档是很正常的Q而作为高U程序员和系l分析员Q这个比例还要高很多。缺乏文档,一个Y件系l就~Z生命力,在未来的查错Q升U以?qing)模块的复用时就都?x)遇到极大的麻?ch)?
3Q规范化Q标准化的代码编写习(fn)?/strong>
作ؓ(f)一些外国知名Y件公司的规矩Q代码的变量命名Q代码内注释格式Q甚臛_套中行羃q的长度和函数间的空行数字都有明规定,良好的编写习(fn)惯,不但有助于代码的UL和纠错,也有助于不同技术h员之间的协作?
有些coding fans叫嚣高水q程序员写的代码旁h从来看不懂,q种叫嚣只能证明他们自己压根不配自称E序员。代码具有良好的可读性,是程序员基本的素质需求?
再看看整个linux的搭建,没有规范化和标准化的代码?fn)惯Q全球的研发协作是绝对不可想象的?
4Q需求理解能?/strong>
E序员需要理解一个模块的需求,很多朋友写E序往往只关注一个功能需求,他们把性能指标全部归结到硬Ӟ操作pȝ和开发环境上Q而忽视了(jin)本n代码的性能考虑Q有人曾l放a说写一个广告交换程序很单,q种Z来不知道在百万甚臛_万数量的访问情况下的性能指标是如何实现的Q对于这L(fng)E序员,你给他深蓝那套系l,他也做不出太极链的ƈ访能力。性能需求指标中Q稳定性,q访支撑能力以及(qing)安全性都很重要,作ؓ(f)E序员需要评估该模块在系l运营中所处的环境Q将要受到的负荷压力以及(qing)各种潜在的危险和恶意d的可能性。就q一点,一个成熟的E序员至需??q的目研发和跟t经验才有可能有?j)得?
5Q复用性,模块化思维能力
l常可以听到一些程序员有这L(fng)抱怨,写了(jin)几年E序Q变成了(jin)熟练工,每天都是重复写一些没有Q何新意的代码Q这其实是中国Y件h才最大浪费的地方Q一些重复性工作变成了(jin)熟练E序员的主要工作Q而这些,其实是完全可以避免的?
复用性设计,模块化思维是要程序员在完成Q何一个功能模块或函数的时候,要多想一些,不要局限在完成当前d的简单思\上,x看该模块是否可以qq个pȝ存在Q是否可以通过单的修改参数的方式在其他pȝ和应用环境下直接引用Q这样就能极大避免重复性的开发工作,如果一个Y件研发单位和工作l能够在每一ơ研发过E中都考虑到这些问题,那么E序员就不会(x)在重复性的工作中耽误太多旉Q就?x)有更多旉和精力投入到创新的代码工作中厅R?
一些好的程序模块代码,即便?0q代写成的,拿到现在攑ֈ一些系l里面作为功能模块都能适合的很好,而现在我看到的是Q很多小公司软g一升或改q就动辄全部代码重写Q大部分重复性工作无谓的费?jin)时间和_֊?
6Q测试习(fn)?
作ؓ(f)一些商业化正规化的开发而言Q专职的试工程师是不可的Q但是ƈ不是说有?jin)专职的试工程师程序员可以不q行自测QY件研发作Z工E而言Q一个很重要的特点就是问题发现的早Q解决的代h(hun)p低,E序员在每段代码Q每个子模块完成后进行认真的试Q就可以量一些潜在的问题最早的发现和解冻Iq样Ҏ(gu)体系l徏讄效率和可靠性就有了(jin)最大的保证?
试工作实际上需要考虑两方面,一斚w是正常调用的试Q也是看程序是否能在正常调用下完成基本功能Q这是最基本的测试职责,可惜在很多公司这成了(jin)唯一的测试Q务,实际上还差的q那Q第二方面就是异常调用的试Q比如高压力负荷下的E_性测试,用户潜在的异常输入情况下的测试,整体pȝ局部故障情况下该模块受影响状况的测试,频发的异常请求阻塞资源时的模块稳定测试等{。当然ƈ不是E序员要对自q每段代码都需要进行这U完整测试,但是E序员必L醒认识自q代码d在整体项目中的地位和各种性能需求,有针Ҏ(gu)的q行相关试q尽早发现和解决问题Q当然这需要上面提到的需求理解能力?
7Q学?fn)和ȝ的能?/strong>
E序员是人才很容易被淘汰Q很Ҏ(gu)落伍的职业,因ؓ(f)一U技术可能仅仅在三两q内h领先性,E序员如果想安n立命Q就必须不断跟进新的技术,学习(fn)新的技能?
善于学习(fn)Q对于Q何职业而言Q都是前q所必需的动力,对于E序员,q种要求更加高?jin)。但是学?fn)也要找对目标,一些小coding fans们,他们也|z乐道于他们的学?fn)能力,一?x)学会(x)?jin)aspQ一?x)儿学?x)?jin)phpQ一?x)儿学?x)?jin)jspQ他们把q个作ؓ(f)炫耀的资本,盲目的追逐一些肤的Q表面的东西和名词,做网l程序不懂通讯传输协议Q做应用E序不懂中断向量处理Q这L(fng)技术h员,不管掌握?jin)多所谓的新语aQ永q不?x)有质的提高?
善于ȝQ也是学?fn)能力的一U体玎ͼ每次完成一个研发Q务,完成一D代码,都应当有目的的跟t该E序的应用状况和用户反馈Q随时ȝQ找到自q不Q这样逐步提高Q一个程序员才可能成长v来?
一个不具备成长性的E序员,即便眼前看是个高手,也不要选用Q因Z落伍的时候马上就C(jin)?
具备以上全部素质的hQ应当说是够格的E序员了(jin)Q请注意以上的各U素质都不是由IQ军_的,也不是大学某些课本里可以学习(fn)到的Q需要的仅仅是程序员对自己工作的认识Q是一U意识上的问题?
那么作ؓ(f)高E序员,以至于系l分析员Q也是对于一个程序项目的设计者而言Q除?jin)应该具备上q全部素质之外,q需要具备以下素质:(x)
W一Q需求分析能?/strong>
对于E序员而言Q理解需求就可以完成合格的代码,但是对于研发目的组l和理者,他们不但要理解客户需求,更多时候还要自行制定一些需求,Z么这么说呢?
一般而言Q进行研发Q务,也许是客h出需求,也许是市(jng)场和营销部门提出的需求,q时候对于研发部门,他们看到的不是一个完整的需求,通常而言Q该需求仅仅是一些功能上的要求,或者更正规些,可能获得一个完整的用户视图Q但是这都不够,因ؓ(f)客户׃非技术因素多一些,他们可能很难提出完整和清晎ͼ或者说专业性的性能需求,但是对于目l织者和规划者,他必能够清醒认识到q些需求的存在q在完成需求分析报告的时候适当的提出,同时要完整和清晰的体现在设计说明书里面,以便于程序员~码时不?x)失去这些准则?
E序设计者必L理解用户需求所处的环境Qƈ针对性做出需求的分析QD例而言Q同样一个Y仉过ASPU用方式发布和通过License方式发布Q性能需求可能就是有区别的,前者强调的是更好的支撑能力和稳定性,而后者则可能更强调在各种q_下的普适性和安装使用的简h?
W二Q项目设计方法和程处理能力
E序设计者必能够掌握不于两到三种的项目设计方法(比如自顶至下的设计方法,比如快速原型法{等Q,q能够根据项目需求和资源搭配来选择合适的设计Ҏ(gu)q行目的整体设计。设计方法上选择不当Q就?x)耽误研发周期Q浪费研发资源,甚至影响研发效果?
一个程序设计者还需要把很多功夫用在程囄设计和处理上Q他需要做数据图以确立数据词典;他需要加工逻辑图以Ş成整体的pȝ处理程。一个流E有问题的系l,q代码多漂亮,每个模块多精_(d)也不?x)成Z个好的系l。当?dng)做好程分析q择好项目设计方法,都需要在需求分析能力上h_的把握?
W三Q复用设计和模块化分解能?/strong>
q个g又是老调重谈Q前面基本素质上不是已经说明?jin)这个问题吗Q?
作ؓ(f)一个从事模块Q务的E序员,他需要对他所面对的特定功能模块的复用性进行考虑Q而作Z个系l分析h员,他要面对的问题复杂的多,需要对整体pȝ按照一U模块化的分析能力分解ؓ(f)很多可复用的功能模块和函敎ͼqҎ(gu)一模块形成一个独立的设计需求。D个例子,好比是汽车生产,最早每辆汽车都是独立安装的Q每个部仉是量w定做的Q但是后来不一样了(jin)Q机器化大生产了(jin)Q一个汽车厂开始通过水U来生汽RQ独立部件开始具有一定的复用性,在后来标准化成ؓ(f)大趋势,不同型号Q品牌甚至不同厂商的汽R部g也可以进行方便的换装和升U,q时候,汽R生的效率达到最大化。Y件工E也是同L(fng)道理Q一个成熟的软g行业Q在一些相关项目和pȝ中,不同的部件是可以随意换装的,比如微Y的许多桌面YӞ在很多操作模块(如打开文gQ保存文件等{)(j)都是复用的同一套功能模块,而这些接口又通过一些类库提供给?jin)桌面应用程序开发者方便挂接,q就是复用化的模块设计明昄一个佐证?
一个大型的Q错l复杂的应用pȝ分解成一些相对独立的Q具有高度复用性的Qƈ能仅仅依靠几个参数完成数据联pȝ模块l合Q是作ؓ(f)高E序员和pȝ分析员一Ҏ(gu)重要的工作,合适的目设计Ҏ(gu)Q清晰的程图,是实现这一目标的重要保证?
W四Q整体项目评估能?/strong>
作ؓ(f)pȝ设计人员Q必能够从全局出发Q对目又整体的清醒认识Q比如公司的资源配置是否合理和到位,比如工程q度安排是否能最大化体现效率又不至于无法按期完成。评估项目整体和各个模块的工作量Q评估项目所需的资源,评估目可能遇到的困难,都需要大量的l验U篏Q换a之,q是一U不断ȝ的篏计才能达到的境界。在西方一些Y件系l设计的带头人都是很q长的,比如4Q?0岁,甚至更老,他们在编码方面已l远q不如年Mh那样zȝQ但是就目评估而言Q他们几十年的经验积累就是最重要和宝늚财富。中国缺q么一代程序员Q主要还不是~那U年U的E序员,而是那种q纪的程序员基本上都是研I单位作出来的,都不是从专业的品化软g研发作出来的Q他们没有能U篏那种产品化研发的l验Q这也是没有办法的事情?
W五Q团队组l管理能?/strong>
完成一个项目工E,需要团队的齐心(j)协力Q作为项目设计者或研发的主hQ就应当有能力最大化发挥团队的整体力量,技术管理由于其专业性质Q不大同于一般的Z理Q因里面设计?jin)一些技术性的指标和因素?
首先是工作的量化Q没有量化就很难做到合适的l效考核Q而程序量化又不是单的代码行数可以计算的,因此要求技术管理h员需要能真正评估一个模块的复杂性和工作量?
其次是对团队协作模式的调_(d)一般而言Q程序开发的协作通常分ؓ(f)组q行Q小l有ȝ序员方式的,也有民主方式的,Ҏ(gu)E序员之间的能力水^差距Q以?qing)根据项目研发的需求,选择合适的l队方式Qƈ能将责权和成员的工作d紧密l合Q这h能最大发挥组队的效率?
一个代码水q高的hQ未必能成ؓ(f)一个合格的目研发ȝQ这斚w的能力欠~往往是容易被忽视的?
lg可以看到Q作Z个主研发的负责人,一个项目设计者,所需要具备的素质和能力ƈ不是E序代码~写的能力,当然一般情况下Q一个程序员通过不断的ȝ提高辑ֈ?jin)这U素质的时候,他所h的代码编写能力也已经相当不简单了(jin)Q但是请注意q里面的因果关系Q一个高水^的项目设计者通常已经是代码编写相当优U的h?jin),但是q不是一个代码相当优U的程序员可以胜任项目设计的工作Q这里面存在的也不是智商和课本的问题Q还是在于一个程序员在积累经验,逐步提升的时候没有意识到应当思考哪斚w的东西,没有有意识的项目的l织和复用设计进行揣摩,没有l常性的文档?fn)惯和ȝ?fn)惯Q不改变q些Q我们的合格的项目设计者还是非常欠~?
另外Qؓ(f)防止有无聊的人和我较真,补充一点,本文针对目标是作商业化的软g目和工E,那些U研机构的编E高手,比如法高手Q比如图象处理高手,他们的工作是研究N而非直接完成商业软gQ当然最l间接成为商业品,比如微Y研究院在作的研究NQ,因此他们的素质可能是另外的东西,q些人(专家Q,q不能说是程序员Q不能用E序员的标准去衡量?
最后补充一点东西,一个Y仉目研发的设计程是怎样的呢Q以通常标准的设计方法ؓ(f)例,Q不q笔者喜Ƣ快速原型法Q?
W一个步骤是?jng)场调?/strong>Q技术和?jng)场要结合才能体现最大h(hun)倹{?
W二个步骤是需求分?/strong>Q这个阶D需要出三样东西Q用戯图,数据词典和用h作手册。用戯图是该Y件用P包括l端用户和管理用P(j)所能看到的面样式Q这里面包含?jin)很多操作方面的程和条件。数据词典是指明数据逻辑关系q加以整理的东东Q完成了(jin)数据词典Q数据库的设计就完成?jin)一半多。用h作手册是指明?jin)操作流E的说明书。请注意Q用h作流E和用户视图是由需求决定的Q因此应该在软g设计之前完成Q完成这些,׃ؓ(f)E序研发提供?jin)约束和准Q很遗憾太多公司都不是这样做的,因果颠倒,序不分Q开发工作和实际需求往往因此产生隔阂p的现象?
需求分析,除了(jin)以上工作Q笔者以Z为项目设计者应当完整的做出目的性能需求说明书Q因为往往性能需求只有懂技术的人才可能理解Q这需要技术专家和需求方Q客h公司?jng)场部门Q能够有真正的沟通和?jin)解?
W三个步骤是概要设计Q将pȝ功能模块初步划分Qƈl出合理的研发流E和资源要求。作为快速原型设计方法,完成概要设计可以进入编码阶D了(jin)Q通常采用q种Ҏ(gu)是因为涉?qing)的研发d属于新领域,技术主h员一上来无法l出明确的详l设计说明书Q但是ƈ不是说详l设计说明书不重要,事实上快速原型法在完成原型代码后Q根据评结果和l验教训的ȝQ还要重新进行详l设计的步骤?
W四个步骤是详细设计Q这是考验技术专家设计思维的重要关卡,详细设计说明书应当把具体的模块以最‘干净’的方式(黑箱l构Q提供给~码者,使得pȝ整体模块化达到最大;一份好的详l设计说明书Q可以ɾ~码的复杂性减低到最低,实际上,严格的讲详细设计说明书应当把每个函数的每个参数的定义都精_l的提供出来Q从需求分析到概要设计到完成详l设计说明书Q一个Y仉目就应当说完成了(jin)一半了(jin)。换a之,一个大型Y件系l在完成?jin)一半的时候,其实q没有开始一行代码工作。那些把作Y件的E序员简单理解ؓ(f)写代码的Q就从根子上犯了(jin)错误?jin)?
W五个步骤是~码Q在规范化的研发程中,~码工作在整个项目流E里最多不?x)超q?/2Q通常?/3的时_(d)所谓磨刀不误砍柴功,设计q程完成的好Q编码效率就?x)极大提高,~码时不同模块之间的q度协调和协作是最需要小?j)的Q也怸个小模块的问题就可能影响?jin)整体进度,让很多程序员因此被迫停下工作{待Q这U问题在很多研发q程中都出现q。编码时的相互沟通和应急的解决手段都是相当重要的,对于E序员而言Qbug永远存在Q你必须永远面对q个问题Q大名鼎鼎的微YQ可曾有q箋三个月不发补丁的时候吗Q从来没有!
W六个步骤是试Q测试有很多U:(x)按照试执行方,可以分ؓ(f)内部试和外部测试;按照试范围Q可以分为模块测试和整体联调Q按照测试条Ӟ可以分ؓ(f)正常操作情况试和异常情冉|试;按照试的输入范_(d)可以分ؓ(f)全覆盖测试和抽样试。以上都很好理解Q不再解释?
MQ测试同h目研发中一个相当重要的步骤Q对于一个大型YӞ3个月?q的外部试都是正常的,因ؓ(f)永远都会(x)又不可预料的问题存在?
完成试后,完成验收q完成最后的一些帮助文档,整体目才算告一D落Q当然日后少不了(jin)升Q修补等{工作,只要不是想通过一锤子买卖骗钱Q就要不停的跟踪软g的运营状况ƈ持箋修补升Q知道这个Y件被d淘汰为止?
写这些步骤算不上卖弄什么,因ؓ(f)实话讲我手边是一本《Y件工E》,在大学里q是计算Z业的必修评Q但是我知道很多E序员似乎从来都只是热衷于什么?0天精通VC》之cȝQ他们有些和我一h击队nQ没有正规学q这个专业,q有一些则早就在够学分后把q些真正有用的东西还l了(jin)老师?
|上现在也很躁Q一些coding fans乱嚷Ph视听Q实际上真正的技术专家很在|上乱发帖子的,如笔者这样不知天高地厚的Q其实实在是不上什么高手,只不q看不惯q种Ҏ(gu)术,对程序员的误解和胡说Q只好挺w而出Q做拨ؕ反正之言Q也希望那些q沉q于一些错误h士的coding fans们能认真xQ走到正途上Q毕竟那些聪明的头脑q远q没有发挥应有的价倹{?/font>