- 常规编程
- 魔法值与魔法数字
1.【强制】不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。
反例: // 开发者 A 定义了缓存的 key。
String key = "Id#taobao_" + tradeId;
cache.put(key, value);
// 开发者 B 使用缓存时直接复制少了下划线,即 key 是"Id#taobao" + tradeId,导致出现故障。
String key = "Id#taobao" + tradeId; cache.get(key);
2.【强制】不允许任何魔法数字(即未经预先定义的常量)直接出现在代码中。
反例:// 开发者 A 在switch case分支直接进行处理
switch (msg.what) {
case 1:
…
case 2:
…
case 3:
…
case 4:
//此时开发者B接手是一脸懵逼或者说开发者A一周后继续看自己的这代码也是懵逼的
3. 【推荐】不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。、
说明:大而全的常量类,杂乱无章,使用查找功能才能定位到要修改的常量,不利于理解,也不利于维护。
正例:缓存相关常量放在类 CacheConsts 下;系统配置相关常量放在类 SystemConfigConsts 下。
-
- 覆盖方法
【强制】所有的覆写方法,必须加 @Override 注解。
说明:getObject() 与 get0bject() 的问题。一个是字母的 O,一个是数字的 0,加 @Override 可以准确判断是否覆盖成功。另外,如果在抽象类中对方法签名进行修改,其实现类会马上编译报错。
-
- equals 方法
【强制】Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。
正例:"test".equals(param);
反例:param.equals("test");
说明:推荐使用 JDK7 引入的工具类 java.util.Objects#equals(Object a, Object b)
-
- String、StringBuilder、StringBuffer
【推荐】循环体内,字符串的连接方式,使用 StringBuilder 的 append 方法进行扩展。
反例: String str = "start";
for (int i = 0; i < 100; i++) {
str = str + "hello";
}
多线程中则推荐StringBuffer,是线程安全的
-
- 集合处理
.【强制】判断所有集合内部的元素是否为空,使用 isEmpty() 方法,而不是 size() == 0 的方式。
说明:在某些集合中,前者的时间复杂度为 O(1),而且可读性更好。
正例:
Map map = new HashMap<>(16);
if (map.isEmpty()) {
…
}
-
- 循环处理
【强制】不要在 foreach 循环里进行元素的 remove / add 操作。remove 元素请使用 iterator 方式, 如果并发操作,需要对 iterator 对象加锁。
正例:
List list = new ArrayList<>();
list.add("1");
list.add("2");
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (删除元素的条件) {
iterator.remove();
}
}
反例:
for (String item : list) {
if ("1".equals(item)) {
list.remove(item);
}
}
说明:反例中的执行结果肯定会出乎大家的意料,那么试一下把“1”换成“2”会是同样的结果吗?
- 防止NPE产生
【强制】对于所有未知来源的数据都进行null判断,防止NPE产生
- 并发操作
- 单例对象
【强制】获取单例对象需要保证线程安全,其中的方法也要保证线程安全。
说明:资源驱动类、工具类、单例工厂类都需要注意。
-
- 线程池
- 不允许自己创建线程
- 线程池
【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明:线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
-
-
- 线程池不允许使用 Executors 去创建
-
【不推荐】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方 式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
- FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
- CachedThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
- ScheduledThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
-
-
- 线程池命名
-
【推荐】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
正例:自定义线程工厂,并且根据外部特征进行分组,比如,来自同一机房的调用,把机房编号赋值给 whatFeatureOfGroup:
public class UserThreadFactory implements ThreadFactory {
private final String namePrefix;
private final AtomicInteger nextId = new AtomicInteger(1);
// 定义线程组名称,在利用jstack 来排查问题时,非常有帮助
UserThreadFactory(String whatFeatureOfGroup) {
namePrefix = "FromUserThreadFactory's" + whatFeatureOfGroup + "-Worker-";
}
@Override
public Thread newThread(Runnable task) {
String name = namePrefix + nextId.getAndIncrement();
Thread thread = new Thread(null, task, name, 0, false);
Log.i(TAG,thread.getName());
return thread;
}
}
-
- 锁
- 锁的范围
- 锁
【强制】高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
说明:尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用RPC 方法。
-
-
- 并发修改同一记录
-
【强制】并发修改同一记录时,避免更新丢失,需要加锁。应用层一般使用volatile或者AtomicXXX
-
-
- 多线程++等类似非原子操作
-
【参考】volatile 解决多线程内存不可见问题对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。
说明:如果是count++操作,使用如下类实现:
AtomicInteger count = new AtomicInteger();
count.addAndGet(1);
如果是JDK8,推荐使用LongAdder 对象,比AtomicLong 性能更好(减少乐观锁的重试次数)。
- 控制语句
- default语句
【推荐】在一个switch 块内,每个case 要么通过continue / break / return 等来终止,要么注释说明程序将继续执行到哪一个case 为止;在一个switch 块内,都必须包含一个default 语句并且放在最后,并且最好进行一定的处理。
说明:注意break 是退出switch 语句块,而return 是退出方法体。
-
- 注意switch外部参数
【强制】当switch 括号内的变量类型为String 并且此变量为外部参数时,必须先进行null 判断。
反例:如下的代码输出是什么?
public class SwitchString {
public static void main(String[] args) {
method(null);
}
public static void method(String param) {
switch (param) {
// 肯定不是进入这里
case "sth":
Log.i(TAG,"it's sth");
break;
// 也不是进入这里
case "null":
Log.i(TAG,"it's null");
break;
// 也不是进入这里
default:
Log.i(TAG,"default");
}
}
}
-
- 循环体
【推荐】循环体中的语句要考量性能,以下操作尽量移至循环体外处理,如定义对象、变量、获取数据 库连接,进行不必要的 try-catch 操作(这个 try-catch 是否可以移至循环体外),if else等
-
- 反逻辑
【推荐】避免采用取反逻辑运算符。
说明:取反逻辑不利于快速理解,并且取反逻辑写法一般都存在对应的正向逻辑写法。
正例:使用if(x < 628) 来表达x小于628。
反例:使用if(!(x >= 628)) 来表达x小于628。
-
- while
【强制】while中的变量不要直接写成true,至少在Android应用中不要这样写,这样可能造成内存泄漏、线程耗尽等
【推荐】使用一个标志位进行判断,当需要停止时可以停止,示例如下:
while (!isDestroy) {
…
}
- Android应用层方面
- 【强制】界面更新要在主线程
其实可能有时候没有注意会把界面更新的操作放到了子线程,我们可以在不确定当前是什么线程的有界面操作的地方加上代码看下当前是否在主线程,如果不在主线程记得切换到主线程
-
- 【强制】耗时操作放在子线程
相反耗时操作需要放在子线程,比如IO、数据库、文件、网络、数据处理等
-
- Linearlayout、ReativeLayout、ConstraintLayout
【推荐】
如果是只用一层完成的布局则应该采用顺序Linearlayout-> ReativeLayout-> ConstraintLayout
如果是复杂布局想将多层嵌套减少则应该采用顺序ConstraintLayout -> ReativeLayout-> Linearlayout
- Kotlin
【强制】kotlin中不要使用!!,推荐使用?.的方式,这样可以隔绝绝大部分NPE
- 插件
如果想在AndroidStudio中直接看到代码中的提示可以下载如下插件
然后随便点进去一个类就会有提示