本帖最后由 我才是小灰灰 于 2015-11-8 12:39 编辑
要想使自己的代码可读性高,注释必不可少,注释可以方便理解代码,一方面减少我们理解代码的时间,另一方面使一些难以理解的代码易于理解。但并非说所有注释都是好注释,良好的代码风格要求我们有良好的注释风格,那么我们应当摒弃以下几类注释:
(1)废话式注释
看这么一个例子:
复制代码 此类注释没有必要存在,因为写了也没有什么卵用,如果是为了提醒自己的话,可以用TODO注释。
(2)故事性注释 我曾经见过一个奇异的算法,是解决一个数学问题的。尽管他将这个算法已经优化到了极致,但是他的注释风格不可取。他的注释非常详细,从问题的由来一直到之前的算法介绍都非常详细。但请知道,我们的算法不是给外行看的,没必要这么详细。
(3)不必要的注释
再看这么一个例子:
复制代码 此处的三个注释严重贬低了阅读者的智商,不要以为自己的代码是给外行看的,这些注释的内容不看也知道,因此这些注释不应当出现在我们的程序中。
(4)过时的注释
当程序早已经出了V7、V8了,我们的注释仍旧是V1,此时的注释已经形同虚设。
(5)注释代码
我们总会因各种原因去注释掉一些代码,但是当我们确定不需要这个代码的时候,请删除掉他,以免对他的理解性和代码连贯性产生影响。
那么什么样的注释是好的注释呢?
(1)解释意图的注释
此类注释去解释代码为什么要这么做,而不是怎么去这么做,像是此代码解决了什么,或是为什么过时之类的,这种注释才能起到注释的真正作用。
(2)警示性注释
此类注释常常被我们忽略,但是他的用处非常重要,这些注释用来提示一些信息,比如代码在不同操作系统上的不同表现,或者警告后人不要修改之类的。
(3)TODO注释
对于一些没有完成的任务需要加上TODO注释,并且注释什么还没有做完,这样可以提醒自己及时修改。
只有写好注释,才能使自己的代码更可读,更容易理解。
3.java基础一定要牢固
俗话说的好,工欲善其事,必先利其器。对我们javaer而言,java语言便是我们的“器”,所以要想写好插件,必须有牢固的java基础。学好java,不仅仅是语法上的理解,更要树立一种OOP(面向对象编程)的编程思想,否则,我们即使能保障程序的运行,也不能发挥程序的特色,发挥java的特色。也无法使代码更简洁,更高效,我们的程序依旧是死板的,模式化的。面向对象编程即是java的核心,所以想要学好java编程,必须先构建一个OOP的编程思想。
以上3点即是我针对新人的一些编程建议,更侧重于思想,有什么错误或者更好的建议欢迎指教。
下面是对于已经学完基本语法,略有实践的编程者的建议(偏向于语法):
1.注意符号与数据一定要分清
看一个简单的例子:
复制代码 输出的会是多少呢?22吗?并不是,是2。因为1后面的是l,是long型常量的标示符(Ps:打到论坛上一点混淆感觉都没有QAQ)。从这个例子我们应当明白,我们使用标识符的时候一定要区分开数据常量,以免降低代码的可读性,比如这里应该用大写L。当出现O的时候要大写而且要注释清楚。这样代码可读性更高,没有异义性。
2.常量只能是常量,不做它用
常量是值不可变的量,因此常量的值应当是已知且已确定的。所以在编程中,常量就应当是常量,不能以违法常量的定义存在,因此建议常量必须在编译时确定值。就如同下面这个代码是不应当出现的:复制代码
复制代码 从开始学习"++"运算时,我们就听过,"i++"是先自加再赋值,"++i"是先赋值再自加。但是这两句话什么意思呢?我们再想上面的例子,会输出什么呢?9?10?都不是。会输出0。
为什么输出0呢?因为"i++"形式的运算等同于下面的代码:
复制代码 看到这里可能就恍然大悟了,原来在执行类似"i++"的运算时,系统会把原值的一个拷贝值作为返回值,于是返回值就变成了原值,而自加在赋值前就完成了。
但遇到同样的问题,C语言和java的处理截然相反。
在java编程中,要警惕这个陷阱。但不要拿相同的思维考虑别的语言。
4.break万万不能忘
switch是我们常用的条件语句,在某些情况下switch语句的效率比多重if高的多,而且可读性强。但使用switch一定要注意一个问题,case块默认是向下传递的。看这样一个算法:
复制代码 假设我们调用这个方法,并输入参数1,打印的结果是"壹"吗?不是。那是多少呢?是"壹贰叁肆伍陆柒捌玖"为什么?因为switch的设定,当一个case块中没有跳转指令,将自动执行下一个case块。所以在这里当输入1时,块123456789都会被执行一次。所以这段代码每一个case块后面都要加一个break;跳出switch。谨记这一点,以防发生小细节的错误。
5.避免带有变长参数的方法重载
我们看一个计算折扣的代码示例:
复制代码 这段代码完全可以编译,但是具有严重的二义性。我并不知道它调用的是哪个方法。实际上它调用的是简单折扣,因为java的编译器会从简单的开始猜想,把75先猜想成基础类型,再考虑是不是编入一个变长参数的数组中。尽管我们知道代码会调用哪个方法,但还是应当避免这种二义性特别高的代码出现。
6.别让null和空值威胁到变长方法
看下面的代码:
复制代码
此代码有2处无法编译,最后两个methodA的调用。方法模糊不清,编译器并不知道调用哪个。变长参数允许参数是0个,但方法规范却无法调用,因为设计者违反了KISS原则(Keep It Simple,Stupid,即懒人原则),此类代码的参数需要让调用者去猜测,这是万万不可的。
最后一个调用不仅违反了懒人原则,而且有一个坏的代码风格,隐藏了参数的类型。方法中的null的类型被隐藏了。此处的代码应当改为下面的代码:
复制代码
在变长参数中需要注意,变长参数其实是一个数组参数,在编译生成的字节码里面有一个特殊标记。可以把空值,null,单个数据,数组“推测”为方法的实参,但当形参为数组的时候,编译器不会做此类“推测”。
7.覆写变长方法也循规蹈矩
我们对覆写有着严格的要求:
①覆写方法不能缩小访问权限
②参数列表必须与被覆写方法相同
③返回类型必须与被覆写方法相同或者其子类
④覆写方法不能抛出新的异常,或者超出父类范围的异常,但可以抛出更少、更有限的异常
满足以上条件的我们才能称之为覆写,那么看下面的代码:
复制代码
此代码不可编译,原因不在覆写上,这个覆写是完全正确的,因为变长参数也是个数组。只是eclipse会报不是一个良好的代码风格。报错在sub.fun(100,50)上,报错的原因是找不到方法fun(int,int)。事实上,base对象把Sub类向上转型,形参列表是由父类决定的,所以50会被编译器“猜测”变成{50},再由子类执行。但是Sub类不进行向上转型的时候,由于形参为数组,所以调用时不进行猜测,java又要求严格的类型匹配,于是报错。
[size=14.44444465637207px]
8.instanceof你会用了吗?
看下面一个关于instanceof的代码示例:
复制代码
请问这其中的5个代码,哪些能编译,哪些不能,哪些是true,哪些是false?我们来一个个分析:
首先,"abc"是String型的常量,因此和Object构成实例关系,所以这个是true。b2中new String()是一个新的String类对象,因此和String绝对有实例关系,返回true。b3无法编译,因为String和Date类没有继承关系,instanceof不会允许没有直接继承关系的类别进行instanceof判断的。b4是false,在instanceof语法中,左操作数是null直接返回false。b5也是false,即使null是String型,它也是null,所以返回false。
这么简单的instanceof,你答对几个?
[size=14.44444465637207px]
9.常量类在编译时不要只编译一个类
[size=14.44444465637207px]
下面这条建议我们要抛开IDE,退回到记事本编程时代说起。
我们用记事本写下面这个类:
复制代码
主类:
复制代码
然后写一个批处理来编译:
复制代码
这个批处理用来编译两个类,然后运行主类,输出是150无误。现在我想改一下常量,我们进入常量类,将人体最大寿命改为180。然后重写一个批处理,只编译常量类:
复制代码
这次只编译常量类,想知道结果是什么么?仍是150。为什么?因为对于常量,在编译时其值已经被写到字节码里了,因此会这样。这也是我们为什么不用IDE的原因,IDE默认全部重新编译。
[size=14.44444465637207px]
10.用偶判断不用奇判断
[size=14.44444465637207px]
[size=14.44444465637207px] 下面我们说说取余运算,看一个代码:
[size=14.44444465637207px]复制代码
这是一个简单的奇偶数判断,小学生都会,不详解释。
现在我们输入几个数进行测试:
复制代码
结果:
复制代码
我们可以看出,正数和零没有一点问题,而负数便有问题了,-1怎么会是偶数呢?原来java的取余运算等同于如下代码:
复制代码
数学好的同学可能立马顿悟了,当-1被传递进去后-1除以2的余数是-1当然不是1了!所以上面的代码改为下面的更可靠:
复制代码
从我开始学java至今已经4年了,在此期间,我绝大部分时间来研究的是插件开发,虽然没写出几个插件也没深入研究API,但是学到了很多java底层的东西,对java语言有了深入了解,正好通过此贴向广大java编程者提出一些编程建议,无论是从优化或者代码的理解上,这些建议全部源自于java语言而不是API,因此这些建议同样适用于任何java编程。如若有更好的建议,可以在此贴分享。若我有什么错误或疏漏之处,请多多指教。
另:本书内容提取自《提高编程效率,改善java程序的151个编程建议》
对于新人的一些编程建议(以下建议针对初学java或者初学插件开发的新人,更侧重于编程思想):
1.拥有一个清晰、明了的代码风格
首先,我们写出来的代码是给人先读,再给机器看的。于是我们的代码风格直接决定着我们的编程效率。因之,有一个良好的代码风格是极其必要的。对于机器而言,不需要任何的分隔和换行,也能读懂,但是你可以试试读同样的代码,保证恶心至极。没有层次的代码不会有人看得懂,也不会有人浪费时间去看,而且很可能那个人就是未来的你。所以,我们应当有一个良好的代码风格,那么什么算是良好的代码风格呢?其实很简单,要用TAB就都用TAB,要用空格就都用空格,把各层次分清了,代码读起来简便、明了,而且注释一语中的(有关于注释的内容下一个建议详解),这样的代码风格才可以说是好,这样的代码质量才会高。
2.要写注释,也要写好注释
要想使自己的代码可读性高,注释必不可少,注释可以方便理解代码,一方面减少我们理解代码的时间,另一方面使一些难以理解的代码易于理解。但并非说所有注释都是好注释,良好的代码风格要求我们有良好的注释风格,那么我们应当摒弃以下几类注释:
(1)废话式注释
看这么一个例子:
- public class Test{
- //此算法没有XX算法优秀,需要优化,时间紧,以后再说
- public void doSomething(){
- }
- }
(2)故事性注释 我曾经见过一个奇异的算法,是解决一个数学问题的。尽管他将这个算法已经优化到了极致,但是他的注释风格不可取。他的注释非常详细,从问题的由来一直到之前的算法介绍都非常详细。但请知道,我们的算法不是给外行看的,没必要这么详细。
(3)不必要的注释
再看这么一个例子:
- public class Test{
- //初始化,值为0
- public int i;
- //定义方法,无参,返回值空
- public void doSomething(){
- //自加
- i++;
- }
- }
(4)过时的注释
当程序早已经出了V7、V8了,我们的注释仍旧是V1,此时的注释已经形同虚设。
(5)注释代码
我们总会因各种原因去注释掉一些代码,但是当我们确定不需要这个代码的时候,请删除掉他,以免对他的理解性和代码连贯性产生影响。
那么什么样的注释是好的注释呢?
(1)解释意图的注释
此类注释去解释代码为什么要这么做,而不是怎么去这么做,像是此代码解决了什么,或是为什么过时之类的,这种注释才能起到注释的真正作用。
(2)警示性注释
此类注释常常被我们忽略,但是他的用处非常重要,这些注释用来提示一些信息,比如代码在不同操作系统上的不同表现,或者警告后人不要修改之类的。
(3)TODO注释
对于一些没有完成的任务需要加上TODO注释,并且注释什么还没有做完,这样可以提醒自己及时修改。
只有写好注释,才能使自己的代码更可读,更容易理解。
3.java基础一定要牢固
俗话说的好,工欲善其事,必先利其器。对我们javaer而言,java语言便是我们的“器”,所以要想写好插件,必须有牢固的java基础。学好java,不仅仅是语法上的理解,更要树立一种OOP(面向对象编程)的编程思想,否则,我们即使能保障程序的运行,也不能发挥程序的特色,发挥java的特色。也无法使代码更简洁,更高效,我们的程序依旧是死板的,模式化的。面向对象编程即是java的核心,所以想要学好java编程,必须先构建一个OOP的编程思想。
以上3点即是我针对新人的一些编程建议,更侧重于思想,有什么错误或者更好的建议欢迎指教。
下面是对于已经学完基本语法,略有实践的编程者的建议(偏向于语法):
1.注意符号与数据一定要分清
看一个简单的例子:
- public class Test{
- public static void main(String args[]){
- long l=1l;
- //输出的值是多少呢?
- System.out.println(l);
- }
- }
2.常量只能是常量,不做它用
常量是值不可变的量,因此常量的值应当是已知且已确定的。所以在编程中,常量就应当是常量,不能以违法常量的定义存在,因此建议常量必须在编译时确定值。就如同下面这个代码是不应当出现的:
- public static final long l=new Random().nextLong();
常量只能是常量,必须在编译时确定其值,其他情况均定义为变量。
3.警惕自加的陷阱
看下面一串代码:
- public class Test{
- public static void main(String[] args){
- int c=0;
- for(int i=0;i<10;i++){
- //对变量c进行10次自加,输出结果是10吗?
- c=c++;
- }
- System.out.println(c);
- }
- }
为什么输出0呢?因为"i++"形式的运算等同于下面的代码:
- public int add(int c){
- //创建一个拷贝
- int temp=c;
- c=c+1;
- return temp;
- }
但遇到同样的问题,C语言和java的处理截然相反。
在java编程中,要警惕这个陷阱。但不要拿相同的思维考虑别的语言。
4.break万万不能忘
switch是我们常用的条件语句,在某些情况下switch语句的效率比多重if高的多,而且可读性强。但使用switch一定要注意一个问题,case块默认是向下传递的。看这样一个算法:
- public void numToChinese(int i){
- switch(i){
- case 1:System.out.println("壹");
- case 2:System.out.println("贰");
- case 3:System.out.println("叁");
- case 4:System.out.println("肆");
- case 5:System.out.println("伍");
- case 6:System.out.println("陆");
- case 7:System.out.println("柒");
- case 8:System.out.println("捌");
- case 9:System.out.println("玖");
- }
- }
5.避免带有变长参数的方法重载
我们看一个计算折扣的代码示例:
- public class Client{
- //简单折扣计算
- public void calPrice(int price,int discount){
- float kp=price*discount/100.0F;
- System.out.println("简单折扣后:"+formateCurrency(kp));
- }
- //复杂多折扣计算
- public void calPrice(int price,int...discounts){
- float kp=price;
- for(int discount:discounts){
- kp=kp*discount/100;
- }
- System.out.println("复杂折扣后:"+formateCurrency(kp));
- }
- //格式化成本的货币形式
- private String formateCurrency(float price){
- return NumberFormat.getCurrencyInstance().format(price/100);
- }
- public static void main(String args[]){
- Client client=new Client();
- //499元的货物,打75折
- client.calPrice(49900,75);
- }
- }
6.别让null和空值威胁到变长方法
看下面的代码:
- public class Client{
- public void method A(String str,Integer...is){
- }
- public void methodB(String str,String...args){
- }
- public static void main(String args[]){
- Client client=new Client();
- client.methodA("China",0);
- client.methodA("China","People");
- client.methodA("China");
- client.methodA("China",null);
- }
- }
此代码有2处无法编译,最后两个methodA的调用。方法模糊不清,编译器并不知道调用哪个。变长参数允许参数是0个,但方法规范却无法调用,因为设计者违反了KISS原则(Keep It Simple,Stupid,即懒人原则),此类代码的参数需要让调用者去猜测,这是万万不可的。
最后一个调用不仅违反了懒人原则,而且有一个坏的代码风格,隐藏了参数的类型。方法中的null的类型被隐藏了。此处的代码应当改为下面的代码:
- String s=null//告诉编译器,这个null是String型的
- client.methodA("China",s);
在变长参数中需要注意,变长参数其实是一个数组参数,在编译生成的字节码里面有一个特殊标记。可以把空值,null,单个数据,数组“推测”为方法的实参,但当形参为数组的时候,编译器不会做此类“推测”。
7.覆写变长方法也循规蹈矩
我们对覆写有着严格的要求:
①覆写方法不能缩小访问权限
②参数列表必须与被覆写方法相同
③返回类型必须与被覆写方法相同或者其子类
④覆写方法不能抛出新的异常,或者超出父类范围的异常,但可以抛出更少、更有限的异常
满足以上条件的我们才能称之为覆写,那么看下面的代码:
- public class Client{
- public static void main(String args[]){
- //向上转型
- Base base=new Sub();
- base.fun(100,50);
- //不转型
- Sub sub=new Sub();
- sub.fun(100,50);
- }
- }
- //基类
- class Base{
- void fun(int price,int...discounts){
- System.out.println("Base......fun");
- }
- }
- //子类
- class Sub extends Base{
- @Override
- void fun(int price,int[] discounts){
- System.out.println("Sub......fun");
- }
- }
此代码不可编译,原因不在覆写上,这个覆写是完全正确的,因为变长参数也是个数组。只是eclipse会报不是一个良好的代码风格。报错在sub.fun(100,50)上,报错的原因是找不到方法fun(int,int)。事实上,base对象把Sub类向上转型,形参列表是由父类决定的,所以50会被编译器“猜测”变成{50},再由子类执行。但是Sub类不进行向上转型的时候,由于形参为数组,所以调用时不进行猜测,java又要求严格的类型匹配,于是报错。
[size=14.44444465637207px]
8.instanceof你会用了吗?
看下面一个关于instanceof的代码示例:
- public class Client{
- public static void main(String args[]){
- boolean b1="abc" instanceof Object;
- boolean b2=new String() instanceof String;
- boolean b3=new String() instanceof Date;
- boolean b4=null instanceof String;
- boolean b5=(String)null instanceof String;
- }
- }
请问这其中的5个代码,哪些能编译,哪些不能,哪些是true,哪些是false?我们来一个个分析:
首先,"abc"是String型的常量,因此和Object构成实例关系,所以这个是true。b2中new String()是一个新的String类对象,因此和String绝对有实例关系,返回true。b3无法编译,因为String和Date类没有继承关系,instanceof不会允许没有直接继承关系的类别进行instanceof判断的。b4是false,在instanceof语法中,左操作数是null直接返回false。b5也是false,即使null是String型,它也是null,所以返回false。
这么简单的instanceof,你答对几个?
[size=14.44444465637207px]
9.常量类在编译时不要只编译一个类
[size=14.44444465637207px]
下面这条建议我们要抛开IDE,退回到记事本编程时代说起。
我们用记事本写下面这个类:
- public class Constant{
- //定义人类寿命极限
- public static final int MAX_AGE=150;
- }
主类:
- public class Client{
- public static void main(String args[]){
- System.out.println(Constant.MAX_AGE);
- }
- }
然后写一个批处理来编译:
- @echo off
- javac Constant.java
- javac Client.java
- java Client
这个批处理用来编译两个类,然后运行主类,输出是150无误。现在我想改一下常量,我们进入常量类,将人体最大寿命改为180。然后重写一个批处理,只编译常量类:
- @echo off
- javac Constant.java
这次只编译常量类,想知道结果是什么么?仍是150。为什么?因为对于常量,在编译时其值已经被写到字节码里了,因此会这样。这也是我们为什么不用IDE的原因,IDE默认全部重新编译。
[size=14.44444465637207px]
10.用偶判断不用奇判断
[size=14.44444465637207px]
[size=14.44444465637207px] 下面我们说说取余运算,看一个代码:
[size=14.44444465637207px]
- public class Client{
- public static void main(String args[]){
- //接收键盘信息
- Scanner input=new Scanner(System.in);
- System.out.println("请输入多个数字判断奇偶");
- while(input.hasNext()){
- int i=input.nextInt();
- String str=i+"->"+(i%2)==1?"奇数":"偶数";
- System.out.println(str);
- }
- }
- }
这是一个简单的奇偶数判断,小学生都会,不详解释。
现在我们输入几个数进行测试:
- 3,5,2,0,-1,-2
结果:
- 3->奇数
- 5->奇数
- 2->偶数
- 0->偶数
- -1->偶数
- -2->偶数
我们可以看出,正数和零没有一点问题,而负数便有问题了,-1怎么会是偶数呢?原来java的取余运算等同于如下代码:
- public static int remainder(int dividend,int divisor){
- //dividend是被除数,divisor是除数
- return dividend-dividend/divisor*divisor;
- }
数学好的同学可能立马顿悟了,当-1被传递进去后-1除以2的余数是-1当然不是1了!所以上面的代码改为下面的更可靠:
- i%2==0?"偶数":"奇数";
纯手打 求人气金粒 求精分A.A
(下次更新预计半个月之后,毕竟我是住校的学生党TAT)
软文
QAQ我还以为是针对BukkitAPI的建议
然而151个建议那本书我已经看完了,但是把它作为习惯应用到代码里还需要一定的时间23333
然而151个建议那本书我已经看完了,但是把它作为习惯应用到代码里还需要一定的时间23333
关于注释的部分,我补充一下,看你认不认同。。。
有时候我会纠结某个复杂的数学算法到底怎么实现,尝试着写了一堆代码,但代码执行结果并不是我想象的那样,所以我得找出原因,但这时看着一堆复杂的玩意感觉很头疼,根本理不清哪个是哪个,这时我就会在后面加上详细的注释,写明这句代码是干嘛的,这个变量保存的值是什么,这个计算公式原理是什么,帮助我理清顺序,不然直接去看,怎么看看不出来究竟问题在哪。。
有时候我会纠结某个复杂的数学算法到底怎么实现,尝试着写了一堆代码,但代码执行结果并不是我想象的那样,所以我得找出原因,但这时看着一堆复杂的玩意感觉很头疼,根本理不清哪个是哪个,这时我就会在后面加上详细的注释,写明这句代码是干嘛的,这个变量保存的值是什么,这个计算公式原理是什么,帮助我理清顺序,不然直接去看,怎么看看不出来究竟问题在哪。。
andylizi 发表于 2015-10-23 13:13
关于注释的部分,我补充一下,看你认不认同。。。
有时候我会纠结某个复杂的数学算法到底怎么实现,尝试着 ...
特殊情况特殊对待A.A
我才是小灰灰 发表于 2015-10-24 11:02
特殊情况特殊对待A.A
A.A可是这也是注释的用途!
= =为毛我输出是1
long l=1l;
//输出的值是多少呢?
System.out.println(l);
long l=1l;
//输出的值是多少呢?
System.out.println(l);
我基本上不打注释的。。。但是在尝试打注释
C党不服
复制代码
在这点上java跟C系应该一样
- #include "stdio.h"
- void main(){
- int i=0;
- printf("%d %d %d %d",i++,i,++i,i);
- //0 1 2 2
- }
在这点上java跟C系应该一样
LZ辛苦!建议很有用!支持楼主!