分享

Android代码风格指南

 univasity 2011-12-21

Android代码风格指南

原文:Code Style Guidelines for Contributors


下面的规则不是建议而是严格的规定。Android代码贡献者如果不遵守这些规则,那么他们的代码不会被接受。并非所有现存的代码都遵循这些规则,但希望所有的新代码能如此。


Java语言规则

我们遵循标准的Java编码约定,但还添加一些规则。

1.异常:永远不要在没有说明的情况下捕捉或忽略它们;

2.异常:不要捕捉一般异常

3.Finalizers:一般不使用它们;

4.Imports引入具体的类,而非使用*


Java库规则

这里有一些关于使用Android Java库与工具的约定。在某些情况下,该约定的一些重要方式已经发生了变化,老的代码可能在使用废弃的模式或库。当使用这些代码时,最好继续保持与已存在的风格一致(参见一致性)当创建新的组件时,从来不使用废弃库。


Java的风格规则

当所有文件都保持一致的风格时,程序就更容易维护。我们遵循标准的Java编码风格,他们由Sun公司为Java编程语言制定的编码约定,除少数例外并增加一些。这些风格指南是全面而详细的,在Java社区很常用。

此外我们增加了下面的风格规则:

1.注释/Java文档:使用标准样式书写;

2.简短的方法:不要写超大的方法;

3.成员变量:在该文件的顶部,或者紧接在使用它们的方法的前面;

4.局部变量:限制作用域;

5.importsandroid;按字母顺序排列的第三方;java(x)

6.缩进排版4个空格,不使用制表符(tab);

7.行长度100个字符;

8.字段命名:非公有的,非静态字段以m开头;

9.花括号:开括号不要独占一行;

10.注解:使用标准的注解;

11.缩写词:缩写词命名如下:XmlHttpRequest, getUrl()等等;

12.TODO的风格:“TODO:在这里些描述”;

13.一致性Look at what's around you!

14.日志记录:小心日志记录,它的开销很大。


Javatests风格规则:

测试方法的命名testMethod_specificCase是正确的。



Java语言规则



异常:不要忽视


有时很容易编写完全忽略异常的代码,比如:

void setServerPort(String value) {
    try {
        serverPort = Integer.parseInt(value);
    } catch (NumberFormatException e) {
    }
}

你绝对不能这样做。

虽然你可能认为你的代码永远不会遇到这个错误条件或处理它并不重要,忽略像上面的异常会在你的代码中给别人埋下地雷,迟早有一天会被绊倒。原则上,你必须在你代码中处理每一个异常。特殊的处理要视情况而定。

任何时候有人使用空的catch子句,他们应该有一个令人毛骨悚然的感觉。
有一定时候,它实际上是正确的事情,但至少你要想一想。在Java中你无法逃避的令人毛骨悚然的感觉。
- James Gosling

可接受的另外一个方案(为了性能考虑)是:

在方法定义时就抛出

void setServerPort(String value) throws NumberFormatException {
    serverPort = Integer.parseInt(value);
}


  • 抛出符合你的抽象级别的新异常

    void setServerPort(String value) throws ConfigurationException {
        try {
            serverPort = Integer.parseInt(value);
        } catch (NumberFormatException e) {
            throw new ConfigurationException("Port " + value + " is not valid.");
        }
    }
    


  • catch{}块中纠正错误或者用适当的值代替

    void setServerPort(String value) {
        try {
            serverPort = Integer.parseInt(value);
        } catch (NumberFormatException e) {
            serverPort = 80;  // default port for server 
        }
    }
    

捕获该异常并抛出一个新的RuntimeException。这样很危险:除非你不主动这样做该错误就会导致程序崩溃。

void setServerPort(String value) {
    try {
        serverPort = Integer.parseInt(value);
    } catch (NumberFormatException e) {
        throw new RuntimeException("port " + value " is invalid, ", e);
    }
}

 

 

最后:如果你确定忽略异常是合适的,你可以忽略它,但是必须注明充分的理由。

void setServerPort(String value) {
    try {
        serverPort = Integer.parseInt(value);
    } catch (NumberFormatException e) {
        // Method is documented to just ignore invalid user input.
        // serverPort will just be unchanged.
    }
}





异常: 不要捕捉一般异常

有时候人们容易为了偷懒,在捕捉异常时会这样做:

try {
    someComplicatedIOFunction();        // may throw IOException
    someComplicatedParsingFunction();   // may throw ParsingException
    someComplicatedSecurityFunction();  // may throw SecurityException
    // phew, made it all the way
} catch (Exception e) {               // I'll just catch all exceptions
    handleError();                      // with one generic handler!
}

你不应该这样做。

异常是指在程序中出现的异常状况,在Java中异常被抽象成一个叫做Throwable的类。

其中如果程序出错并不是由程序本身引起的,而是硬件等其他原因引起的,我们称之为Error,一般情况下Error一旦产生,对程序来说都是致命的错误,程序本身无能为力,所以我们可以不对Error作出任何处理和响应。

异常如果是由程序引起的我们称之为ExceptionException又分两种,我们把运行时才会出现的异常叫做 RuntimeExceptionRuntimeException我们不好在程序编写阶段加以事先处理,而其他异常则可以在程序编写和编译阶段加以事 先检查和处理,我们把这种异常叫做检验异常。

程序只需要捕捉和处理检验异常。

相应的我们把除检验异常(可检查异常)之外的异常称之为非检验异常,包括ErrorRuntimeException ,非检验异常可以捕捉也可以不捕捉,更多的时候我们不捕捉,因为捕捉了我们也没办法处理,譬如程序运行时发生了一个 VirtualMachineError异常,虚拟机都出错了,作为运行在虚拟机内的程序又有什么办法处理呢?



在几乎所有情况下,不宜捕捉一般的异常或Throwable,尤其是Throwable,因为它也包含Error异常。这是很危险的,它意味着你永远不希望的异常结束了程序捕获异常的能力。它模糊了你代码的故障处理能力,这意味着在你调用的代码中有人增添了新的异常,编译器不会意识到你需要处理不同的错误。总之,你不能用相同的方法处理不同的异常。

但有些罕见的例外:一些测试代码和顶级代码需要捕捉各种误差(防止在UI中显示或保持批处理继续运行)。在这种情况下你可以捕捉一般异常和适当的处理错误。但在这样做之前,你应该仔细考虑,并解释为什么这样做是安全的。

捕获一般异常的替代方案:

在一个try后面捕获每个异常,这样虽然很糟糕,但仍优于捕获所有的异常。不要在catch中重复太多的code

多使用try重构你的代码使之有更精细的错误处理;

抛出异常,很多时候你不需要捕获这个水平上的异常,只要抛出它即可。


Finalizers

它是什么: 当一个对象被垃圾回收时,Finalizers能够去执行某段代码。

优点: 可以方便清理,特别是外部资源。

缺点: 不能保证finalizer什么时候会调用,或甚至根本上没被调用。

决定:我们不使用 finalizers


Imports

imports使用通配符

它是什么 :当你想使用foo包中的Bar类,这有两种可能的方式导入它:

  1. import foo.*;

  2. import foo.Bar;

1#的优点 :大大减少了import声明的数目。

2#的优点 :很明显的看到哪些类实际在用,对于维护者而言,代码更具有可读性。

决定 :使用样式#2

一个明确的例外是用于Java标准库(使用java.util .*java.io. *,等等)和单元测试代码(junit.framework .*)。


注释/Javadoc

所有的文件应该有一个在顶部版权声明。 然后紧接着是packageimport,每一块以空白行分隔。再下来是类或接口的声明。 在Javadoc注释中描述类或接口的用途。


package com.android.internal.foo;

import android.os.Blah;
import android.view.Yada;

import java.sql.ResultSet;
import java.sql.SQLException;


public class Foo {
    ...
}


所有的类以及重要的public方法,必须至少包含一句描述该类或方法的Javadoc注释。并且这句话应该以第三人称的动词开始。例如:

static double sqrt(double a) {
}


public String(byte[] bytes) {
}

简短的方法

这在一定程度上是可行的,方法应该保持小而直观的反应它的功能。然而得承认,长的方法有时是比较适当的,因此没有硬性限制方法的长度。如果一个方法超过40行,想想是否可以在没有危害程序结构的基础上进行拆分。


局部变量

局部变量的作用范围应保持最小。这样可以增加代码的可读性和可维护性,降低错误的可能性。

每个变量应该在最靠近使用它们的地方声明。 局部变量应该先声明后使用,尽量在声明局部变量的同时初始化。如果你还没有足够的信息来初始化变量,你应该推迟声明,直到你需要声明它的时候。

try-catch语句是一个例外,如果一个变量是通过try中的一个方法的返回值进行初始化,而该方法可能抛出异常,那么该变量必须在try中初始化。如果该值要在try外面使用,则必须在try之前声明。

// Instantiate class cl, which represents some sort of Set
Set s = null;
try {
    s = (Set) cl.newInstance();
} catch(IllegalAccessException e) {
    throw new IllegalArgumentException(cl + " not accessible");
} catch(InstantiationException e) {
    throw new IllegalArgumentException(cl + " not instantiable");
}

// Exercise the set
s.addAll(Arrays.asList(args));

但是即使是这种情况,也可以通过将方法封装在try-catch中来避免:

Set createSet(Class cl) {

    // Instantiate class cl, which represents some sort of Set
    try {
        return (Set) cl.newInstance();
    } catch(IllegalAccessException e) {
        throw new IllegalArgumentException(cl + " not accessible");
    } catch(InstantiationException e) {
        throw new IllegalArgumentException(cl + " not instantiable");
    }
}
...
// Exercise the set
Set s = createSet(cl);
s.addAll(Arrays.asList(args));

循环变量应该for里面声明,除非有令人信服的理由不这样做:

for (int i = 0; i < n; i++) {
    doSomething(i);
}

for (Iterator i = c.iterator(); i.hasNext(); ) {
    doSomethingElse(i.next());
}

Imports

import的顺序如下:

  1. Android imports

  2. Imports from third parties (com, junit, net, org)

  3. java and javax

     

  • 为完全匹配IDE设置,import应该:

  • 每个分组中按字母顺序排列;

  • 大写字母应该在小写字母之前,比如Za前面;

  • 主要分组之间应该有空行;






缩进

我们使用4个空格作为块缩进,从不使用制表符(tab)。 如有疑问,请与你周围的代码保持一致。我们使用8个空格作为换行缩进,包括函数调用,赋值。

例如,这是正确的:

Instrument i
            = someLongexpression_r(that, wouldNotFit, on, one, line);

这是不正确的:

Instrument i
      = someLongexpression_r(that, wouldNotFit, on, one, line);

成员变量

  • 非公有,非静态字段命名以m开头。

  • 静态字段命名以s开头。

  • 其他字段以小写字母开头。

  • public static final 字段(常量) 全部大写,并用下划线连起来。

例如:

public class MyClass {
    public static final int SOME_CONSTANT = 42;
    public int publicField;
    private static MyClass sSingleton;
    int mPackagePrivate;
    private int mPrivate;
    protected int mProtected;
}

花括号

花括号开括号时没有独自一行,它们与它前面的代码占同一行,所以:

class MyClass {
    int func() {
        if (something) {
            // ...
        } else if (somethingElse) {
            // ...
        } else {
            // ...
        }
    }
}

我们需要在if语句后的body两边加花括号,除非ifbody在一行合适,

下面是合法的:

if (condition) {
    body; // 正确
}
if (condition) body; // 正确

下面不合法的:

if (condition)
    body; // 槽糕

行长度

每一行代码中的文本应在最多100个字符。

例外:如果一个注释行包含一个示例命令或文字网址超过100个字符,该行可能会超过100个字符,方便于剪切与粘贴。

例外:import 行可以超过这个限制,因为人们很少见到他们。


Java 1.5的注解语法

Android支持Java1.5中预定义的3个标准注解语法:

@Deprecated用于修饰已经过时的方法;
@Override
用于修饰此方法覆盖了父类的方法(而非重载);
@SuppressWarnings
用于通知java编译器禁止特定的编译警告。


缩写词

缩写词应该有更好的可读性:

Good

Bad

XmlHttpRequest

XMLHTTPRequest

getCustomerId

getCustomerID



这种风格规则也适用于被缩写的名称本身就是缩写词的情况:

Good

Bad

class Html

class HTML

String url;

String URL;

long id;

long ID;






TODO 风格

使用TODO注释的代码是暂时短期的解决方案,或者足够好但不完美。



// TODO: Remove this code after the UrlTable2 has been checked in.

// TODO: Change this to use a flag instead of a constant.

如果你的TODO形式是“At a future date do something”,那么你就要包括一个非常具体的日期或者一个非常具体的事件。


一致性

如果你正在编辑代码,花几分钟看看周围的代码并确定其风格。

有一个共同的代码风格,人们则可以专注于你在说什么,而不是你如何这样说。


日志

虽然日志是必要的,但是它对性能有着显著的负面影响,并且迅速失去其作用。日志有5种不同的记录级别,以下是在不同的层次,不同时间下如何使用它们:

  • ERROR: 该级别日志记录时,应该是致命的东西发生了。比如一些用户可见的必须通过删除数据,卸载程序或擦除分区才能恢复的错误。该级别总是记录。

  • WARNING: 该级别的日志记录时,应该是一下严重的不希望的事情发生了。比如一些用户可见的但是数据不会丢失的错误。该级别总是记录。

  • INFORMATIVE: 该级别的日志用来记录大多数人都感兴趣的事情。当检测到的情况有可能产生广泛的影响,虽然不一定是错误,这种情况应该由关联最大的模块来记录,以避免其他模块重复记录。该级别总是记录。

  • DEBUG: 该级别的日志用来记录发生在设备上的与调试相关的意外事件。你应该只记录对你调试组件有用的信息。该日志在release版本中也会记录,但必须在if (LOCAL_LOG) or if (LOCAL_LOGD)块中,其中LOCAL_LOG[D]是在你的类中定义的。

  • VERBOSE: 该级别的日志用来记录其它所有的事情。该级别只在debug 版本中记录并且应该在if (LOCAL_LOGV) 块中




Javatests 风格规则


命名测试方法



当命名测试方法的时候,可以使用下划线来分隔名称,这种风格可以更清楚的看出什么案件正在被测试。

For example:

testMethod_specificCase1 testMethod_specificCase2
void testIsDistinguishable_protanopia() {
    ColorMatcher colorMatcher = new ColorMatcher(PROTANOPIA)
    assertFalse(colorMatcher.isDistinguishable(Color.RED, Color.BLACK))
    assertTrue(colorMatcher.isDistinguishable(Color.X, Color.Y))
}

Eclipse 代码格式化

你可以导入development/ide/eclipse下的文件,使得Eclipse按照Android代码风格规则。选择 “Window ? Preferences ? Java ? Code Style,使用 “Formatter ? Import” ,导入android-formatting.xml,”Organize Imports ? Import” 导入 android.importorder.

eclipse tab 设置为4个空格:
Preferences -> General -> Editors -> Text Editors:
Insert spaces for tabs

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多