写好你程序之共通基础规约

笑着 胖胖兰原创,转载请注明。(格式稍后调整)

bluesmile979@hotmail.com

http://blog.csdn.net/bluesmile979/archive/2008/10/18/3097197.aspx

学习大多数语言刚开始的时候要接触到的东西都是差不多的。命名空间,访问权限,类,方法,属性,加减乘除的数学运算,接收键盘输入,输出到控制台。这些就是所谓的3天会走5天会跑7天会飞中能够学到的大部分的内容。学习了这些就可以开始编写一些程序,做一些联系,甚至紧急状况下顶一下,做一些低端的开发工作。当然,这篇文章并不是这些基础知识的教程,那些3天会走5天会跑7天会飞的教程网络上有很多,写得都挺好的,可以随便找一本来学习。而且笑着本人推荐先找一本这样的书来学习一下。整体了解一下这一门语言大概是怎么样的,可以快速的开始我们的第一个非HelloWord程序。



这篇文章要讨论的,是关于高质量编程方面的。高质量编程将要贯穿我们的整个职业生涯,能在学习开始的时候有个不错的开始,那么对未来的发展将会产生不可估量的好处。高质量编程一般是从可读性,可靠性,高效率,代码结构,重用性,使用适当的底层功能等等方面来考虑代码是否优秀。学习了一门语言的基础语法时候,主要涉及到的就是代码的可读性,其他方面需要对一门语言的深入了解。目前一般讨论这些问题的书籍文章都是分开的,技术就是技术,技术OK了,站在一个较高的高度来回头学习高质量编程。但是这样的缺点就是习惯养成了不太好改,很多人就不会再重视高质量编程这方面的事情。而且习惯的养成需要时间,先学好技术,然后回头重视程序质量这并不是一个好的学习方式。但是要提高某方面的程序质量又必须有相应的技术基础,所以笑着这个系列的文章打算从另外一个角度,按照学习的不同阶段来慢慢涉及到高质量编程以及其他种种注意事项的方方面面。会在后面的文章中慢慢讨论。



好了,下面我们来看看学会了Java的基础语法之后,我们应该,可以注意些什么。当然,所有的一切努力都是为了让您的代码美观,易读,高效,易维护。请牢记我们的目的,规矩不是死的,这里只是给出一些建议。事实上,每一个观点具体如何做好都曾经,或者还在引起广泛的讨论。笑着掌握的也许不是最新的。而且每个公司都会有自己的开发标准。适合自己的,才是最好的。记住我们的目的,其他人能够给你的,只是建议,参考。



首先,是一些基础的规范。大家也许已经知道,简单介绍一下。

1. 良好的命名。包括,

a. 类,函数,变量的名称要尽量体现出其所具有的功能。单词之间用大小写,下划线等明确区分。

b. 全局变量,局部变量,函数参数有明确的区分。比如函数的参数采用下划线开头的写法等。(一些编译器会自动采用不懂得颜色来帮忙区别,比如RAD)

c. 常量与变量有明确区别,比如常量所有字符大写。

d. 表明变量数据类型,比如nInput,strInput。这一点,曾经是C++里面经典的规范,但是Java世界里这条规范没有引起太广泛的共鸣。就个人喜好来讲,小这是比较喜欢这个规范的。

e. 尽量不要定义相同的类名。虽然不同的包下允许使用相同的类名,但是两个同样的名称为User类,会给自己带来不必要的麻烦。比如Java里面就有java.util.Date和java.sql.Date,这会给我们编程的时候造成困扰。所以不要认为不会出现这种情况。很容易,你就会“不小心”的定义两个同名的类。这多少是个设计上的问题,已经超出了基础的范围。

2. 区分你的代码块儿。

a. 把你的类的属性统一定义到类的开始或者结束的位置。现在应该很少人把类的属性跟类的方法穿插定义了。存在特殊情况,但是这里不想讨论。

b. 把你的属性,或者方法,按照访问权限不同来顺序定义。比如先定义public的属性(这里是举个例子,原则上是不推荐定义public属性的),然后protected的属性,然后private的属性,然后public的方法。。。。。以此类推。之所以这样,是因为代码阅读的时候会更加关心接口部分,也就是public出来,公开给别人部分的代码会更加引起关注。

c. 把同种类别的属性(方法)定义在一起,用换行来明确分隔。比如:

// 用户信息

private String a;

private String b;

private String c;



// 业务信息

private String d;

private String e;

private String f;



不分块儿的话就会很难快。效果如下:

private String a;

private String b;

private String c;

private String d;

private String e;

private String f;



d. 关于函数内使用的局部变量的定义,现在函数开始的地方统一定义,然后开始业务处理。这一条曾经争议非常大,因为存在另外一条矛盾的规则,尽量让你定义的变量保持最小的作用范围。不知道现在有什么结论。笑着的倾向是尽量遵守这个规则,但是可以根据具体情况将少量的变量定义在使用开始的地方。或则折衷一点每一个作用范围开始的地方定义所有的变量。这也许需要根据具体情况来采取不同的策略。

e. 函数内每一段相对独立的业务逻辑结束之后用换行来分割一下。参照c.把同种类别的属性(方法)定义在一起,用换行来明确分隔。

3. 适当的缩进,空格,对齐。

a. 适当的缩进。现在编译器应该都提供这个格式化功能了。但是这里有一个问题,缩进,你是用4个空格好呢?还是用Tab好呢?知道我再说什么?不同的IDE对于Tab的长度的解释是不一样的,它默认是4个空格,但是有的IDE可能把它定义为8个空格,程序员可能有的时候用Tab,有的时候用4个空格。于是乎,当你换一个IDE来看统一份代码的时候。。。。。天下大乱了。。。。。

b. 适当的空格,比如

a=a+1;

a = a+1;

a = a + 1;

哪一种看起来比较舒服?最后一种被采用的比较多。第二种也不错。第一种?有可能你需要调整一下自己的审美观点以保持跟大众的审美观一致。

c. 适当的对齐。一般出现在类的属性定义的时候。比如:

对齐前:

private String abc = “1”;

private String abcd = “1”;

private String abcde = “1”;



对齐后:

private String abc = “1”;

private String abcd = “1”;

private String abcde = “1”;

有一些极端喜欢对齐的人存在。但是笑着个人观点,差不多就可以了。属性定义这里会非常有效果,其他的地方,自己看着办就好了。

4. 适当的列数。

适当的列数。当一行代码太多会影响阅读。从前一般定义列数不要超过80,现在因为种种原因,也有定义到120的。跟这一条相关的一个规则就是不要在函数调用中欠套过多的函数调用。比如,下面是一个反面例子。

g.draw(t.getText(s.getXXX(c.getXXX())));

下面列举几个其他的常见的例子。

例子1,函数参数过多。

反:

public void setXXX(String _param1, String _param2, String _param3, String _param4)

正:

public void setXXX(String _param1,

String _param2,

String _param3,

String _param4)



例子2,append

反:

s.append(a).append(b) .append(c) .append(d)

正:

s.append(a)

.append(b)

.append(c)

.append(d)

意思一下,类似的状况很多。不在编译器里面代码格式调整比较费劲不多举了。

5. 适当的行数。

一个函数,一个类,应该提供相对单纯的功能。如果你的函数,类的行数超过了一定的范围。那么一方面会影响到阅读代码时对代码的理解。另外一方面,那么多行的代码,这里肯定包含了过多的业务逻辑,耦合性加大对你的维护会带来非常大的麻烦。

6. 善用常量

尽量用一些有意义的常量来代替无意义的数值。比如:

if(status == 0){}

就不如定义一个常量,比如int opened = 0; 然后

if(status == opened){}

7. 不要过多的嵌套逻辑分支控制,比如

if(){

if(){

if(){

if(){

if(){

}

}

}

}

}

这样的代码会让人很难读懂你的程序。你自己回头也很难读懂自己的程序。发生错误的可能性,测试的复杂度也会相应大幅度增加。而且,嵌套这么多层的话,你本身对业务需求的理解肯定或多或少的出了一些问题。最多嵌套3层左右也许是个比较合理的选择。

基础的,适用于任何开发语言的一些注意事项基本就是上面这么多。在基础之上,我们需要考虑一些语言特性。比如,

int i;

double a = 0;

for ( i = 0; i < 10 ; i++){

a = a + 0.1;

if (a == 1){

System.out.println("0.1加10次应该等于1" );

}

}



这里期待值是0.1加10次应该等于1,但是实际上是不是这样的呢?这段代码预期值跟实际值是不是一样的呢?



再比如String和StringBuffer,等等。



作为结束,这里先举这个个例子。下一篇文章中会列举一些常见的语言特性相关的问题来讨论。好了,在进一步学习之前,我们可以把3天会走的教程扔掉了,去找一个详细介绍Java的书籍把基础打牢吧。



顺便说一句,Java编程思想虽然是本好书,但是书里面更多的是面向C++转Java程序员的论述,可以考虑适当的忽略,如果你就是要做Java,那些占了很大篇幅的论述对你可能不是那么的重要。


笑着 胖胖兰原创,转载请注明。

bluesmile979@hotmail.com

http://blog.csdn.net/bluesmile979/archive/2008/10/19/3105274.aspx

在这一篇文章中,笑着想要聊一聊跟Java语言相关的,需要注意的一些规范。



1. 关于基础数据类型。

还记得上一片文章中结尾部分的那个例子么?

因为计算机的2进制的特性,Java语言(其他语言好像也一样)无法准确地进行float,double类型数据的计算,所以,作这些计算的时候需要考虑使用BigDecimal着各类。



2. String类与StringBuffer类

Java的String类提供如下的简单的字符串连接方法。

String s1 = “a”;

String s2 = “b”;

String s3 = “c”;

String s = s1 + s2 + s3;

但是这里有一个问题,String类的每一次加操作都会产生一个新的对象。当你针对String类的加的操作开始变得多起来的时候,不但程序占用内存会增加得非常明显,程序运行时间也会大大增加。这个时候需要考虑StringBuffer类的append方法。笑着不是一个极端主义者,所以,笑着还是倾向,不要只要对字符串采用加的操作就用append方法。而是应该根据具体情况,自己衡量一下。具体的测试,自己写个for循环1w次,很容易测试出来的。



3. 关于import*

尽量不要采用import*的写法,而是应该把要import的类具体写出来。这里的原因,一个是因为import*的写法会影响程序的阅读,导致维护者不是很清楚你具体用到了哪些类。但是更主要的是,如果你import*,那么在你打包生成jar,war等等文件的时候,你的*代表的所有类都会被打包进去。一方面导致了你发布内容的大小大幅度增加,另外,很可能会泄漏了一些你并不想要泄露出去的内容。这才是问题的关键,安全第一。



4. 合理使用异常处理,不要过度使用。

Java得异常处理是个不错的东西。可以节省我们很多工作量。但是,有异常处理的的代码段比没有异常处理的代码段运行的时间要增加不少。所以不要把什么都交给异常处理。我们应该做出适当的判断,最后的最后,处理不了的东西才应该交给异常处理,这也符合业务处理的逻辑。

比如,存在FileNotFoundException,但是,

try {

} catch ( FileNotFoundException ) {

}

就不如

if ( f.exists() ) {

} else {

}



5. 尽量不要简单的捕捉Exception异常。

因为在Java里面旧的异常会被新的异常所淹没,而使得我们仅仅根据新的异常信息很难判断出问题究竟出在什么地方。而简单的捕捉Exception异常,会使得这种可能性大大增加。另外,简单的捕捉Exception异常会使得我们很难针对不同的异常采取对应的措施。所以请尽量捕捉具体的异常而不是Exception这个超级异常。可以考虑如下代码结构。

try {

} catch() {

} catch() {

} catch(Exception) {

}



关于Java里面旧的异常会被新的异常所淹没地解决办法,一般是需要自己来实现一些异常类的,因为不是这一篇文章的重点,这里不打算具体讨论。



6. 不要重复生成你的对象。Java中new一个新的对象,相对的是要花费比较多的时间的。虽然现在的CPU可能不太在乎这么点时间。但是积少成多啊。可能new一次的,就尽量不要new两次了。目前阶段能够接触到的是要在循环体外而不是循环体内new除你的对象,这很简单,也很容易注意到。但是在复杂的系统中,如何尽量减少你new一个对象的次数,是否需要使用static关键字,就并不是那么明显,那么容易被考虑到,解决掉了。而且考虑到可维护性,代码结构,等等因素,取舍角度也是不同的。至少,我们先记住这样的原则。不要重复生成你的对象。





7. 不要在你的循环体内做一些不必要的计算。比如:

for(; ; al.size()){}

不如:

int size = al.size();

for(; ; size){}

虽然看过文章好像说Java编译器会做优化,这两种写法是一样的。但是基本原则“不要在你的循环体内做一些不必要的计算。”我想这是不会有任何不同意见的。笑着不喜欢记那么多东西,记不住。对于编译器到底做哪些优化笑着也觉得是个没有什么承诺的事情。还是自己控制更安心一点。当然,当后一种写法严重影响了你的代码可读性的时候,可以在效率和可读性之间作一个权衡。毕竟现在的CPU还是很强悍的,循环次数不是很多的话效率的影响基本可以忽略。



8. String 的==与equal()

在对字符串的相等判断,==判断的是地址是否相同,equal()判断的是字符值是否相同。大多数时候==跟equal()的结果都是相同的。这是因为String对象是不变模式的,如果你不是明确地new一个String对象,Java对于String对象的保存默认的是会把新生成的String对象放到一个缓冲区,然后每次判断缓冲区中是否已经有了这个对象,如果有了,那么后建立的同样字符值的String对象也会指向最初建立是该字符值对象的地址。也就是说字符值相同的时候,大多数情况下地质也是相同的。==与equal()效果是相同的。但是当对象是str = new String(“abc”)生成的而不是直接str = “abc”这样赋值生成,或者经过了一些字符串连接处理,或者通过StringBuffer等对象生成,都会在内存中开辟新的地址的,这个时候==和equal()结果是不同的。



是不是稍微有些复杂?这里需要一些关于内存,堆栈,对象保存方面的理解。笑着不想纠缠于这个问题的讨论。如果不能理解,那么只要记住如果你想要判断两个字符串的字符值是否相等,没有别的要求的时候,那么请使用equal()而不是==,至于什么时候需要使用==,我想当你需要的时候,你自然就会明白了。实际上,对于字符串的判断,我们很少需要用的==的。



9. 关于str.equal(“abc”)和”abc”.equal(str)

这个好像争论也挺多的,第一种常量写在后面可能符合大多数人的习惯,也符合我们的逻辑思维。但是需要多一个str是否为null的判断。否则这里是有可能出现异常的。而后一种写法不需要多做关于是否为null这个判断。就笑着个人喜好来说,比较喜欢后一种写法。

10. Java是传值还是传引用?

曾经,Java程序员都是从C++转过来的,大家最爱讨论的就是这个冒似高深实际却很无聊的问题。笑着不懂什么叫做传值什么叫做传引用。笑着只想提醒大家记住下面的事实,记住就足够了。到底这种事实说明了Java是传值还是传引用,争论下去有什么意义么?

事实是,在一个方法里面,对他的参数对象本身是无法改变的。比如:

private void changeParam1(XXXClass _pc){

_pc = new XXXClass(); // 想要改变参数对象本身。

}

上面代码的_pc = new XXXClass(); 操作,只是在函数体内部生效。除了函数体,你传给这个函数的对象是什么,那他还是什么。就是说你在函数内部是无法改变参数所指向的内存地址的。你是无法改变参数对象本身本身的。这里,争论的产生就是C++程序员非常喜欢的一个好象叫做Swap的函数,在Java里面无法同样实现了。

但是,Java里面是可以对参数对象的属性进行变更的。

private void changeParam2(XXXClass _pc){

_pc.setField(fieldValue)// 想要改变参数对象的属性。

}

如上代码是可以实现你的意图的。



至于原因,需要画画内存中对象存储,对象指向才能说清楚。笑着觉得那没有研究的价值。记住结论就好了。画图好累的,笑着就偷懒不画了。

11.适当考虑Java的跨平台性,尽量使用Java提供给你的功能。

比如,换行回车到底是\r\n还是\r或者是\n,不同的系统是不一样的。所以考虑一下使用Java提供的功能吧

System类中提供public static String getProperty(String key)方法,其中key可以取如下的值。

file.separator
文件分隔符(在 UNIX 系统中是“/”)

path.separator
路径分隔符(在 UNIX 系统中是“:”)

line.separator
行分隔符(在 UNIX 系统中是“/n”)





12.

    推荐阅读