严重程度分级说明

评分 说明
5 分 必须修改 - 不改将直接导致错误
4 分 强烈建议修改 - 不改可能触发错误
3 分 建议修改 - 可提升代码质量
2 分 可改可不改 - 属于建议性改进

一、安全问题(核心必查)

1. 明文存储敏感信息

严重程度:5 分 | 反编译后易被获取密钥、密码、Token 等敏感信息。

// 反例:
// 明文存储 API 密钥(风险:反编译可直接获取)
val apiKeyPlaintext = "app-uc4uBHahJECG0aZDRtfnGUso"

// 正例:
// 从安全存储读取 API 密钥(推荐方式)
val apiKey = SecureStorage.getInstance(context).getString("api_key")

2. 使用 HTTP 通信

严重程度:5 分 | 易遭受中间人攻击,泄露传输数据。

// 反例:
// HTTP 协议(风险:易被中间人攻击)
const val OLD_PORTAL_URL = "http://example.com/#/home"

// 正例:
// HTTPS 协议(安全:加密传输,防中间人攻击)
const val NEW_PORTAL_URL = "https://example.com/#/home"

3. 动态权限未检查

严重程度:5 分 | 未检查权限直接调用相关接口,会导致程序异常或崩溃。

// 反例:
// 未检查定位权限(风险:直接调用易崩溃)
val locManager = getSystemService(LOCATION_SERVICE) as LocationManager
val location = locManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)

// 正例:
// 检查定位权限后,再获取位置(推荐方式)
val locManager = getSystemService(LOCATION_SERVICE) as LocationManager
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) 
    == PackageManager.PERMISSION_GRANTED) {
    val location = locManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
}

4. 日志输出敏感信息

严重程度:4 分 | 日志中暴露密码、Token 等信息,存在泄露风险。

// 反例:
// 日志暴露密码(风险:敏感信息泄露)
val password = "123456"
Log.d("LoginTag", "Password: $password")

// 正例:
// 日志仅记录操作,不暴露敏感信息(推荐方式)
Log.d("LoginTag", "用户尝试登录")

二、规范问题(提升可读性)

解决核心安全问题后,规范的代码格式能显著提升可读性和可维护性,减少团队协作成本,具体需关注以下规范问题:

1. 命名不规范

严重程度:3 分 | 未遵循驼峰命名法、使用拼音/模糊缩写、常量未全大写等。

// 反例:
// 命名不规范(语义模糊,未遵循驼峰)
public void badmethodname() {}

// 正例:
// 命名规范(驼峰命名,语义清晰)
public void handleUserLogin() {}

2. 直接使用数字常量

严重程度:2 分 | 数字常量无说明,降低代码可维护性。

// 反例:
val annualSalary = 8000 * 12

// 正例:
// 常量定义(语义清晰,可复用,便于维护)
// 同修饰符的编译期常量可合并,用逗号分隔
private const val MONTHS_IN_YEAR = 12, monthlySalary = 8000
val annualSalary = monthlySalary * MONTHS_IN_YEAR

3. 代码格式混乱

严重程度:2 分 | 缩进、空格、换行不规范,影响阅读。

// 反例:
// 格式混乱(无缩进、无空格,可读性极差)
int num = 5;
if(num>0){System.out.println("Positive");}else{System.out.println("Negative");}

// 正例:
// 格式规范(缩进、空格合理,可读性高)
int num = 5;
if (num > 0) {
    System.out.println("Positive");
} else {
    System.out.println("Negative");
}

4. 未处理 TODO/FIXME 注释

严重程度:2 分 | 上线代码需处理所有 TODO/FIXME 注释,避免遗留未完成逻辑。

// 反例:
// 遗留未处理的 TODO 注释(风险:上线后遗留未完成逻辑,影响迭代)
// TODO: 修复该方法的空指针问题
fun getUserInfo(userId: String): User? {
    return userDao.query(userId)
}

// 正例:
// 已处理 TODO 注释(完成逻辑或明确删除,避免遗留)
// 已修复空指针问题,删除 TODO 注释
fun getUserInfo(userId: String): User? {
    return userDao.query(userId) ?: User.EMPTY
}

5. 条件/循环缺少大括号

严重程度:3 分 | 即使单行文,也需加大括号,避免逻辑错误。

// 反例:
// 缺少大括号(风险:后续修改易出逻辑错误)
val condition = true
if (condition) doSomething();

// 正例:
// 强制加大括号(规范,避免逻辑隐患)
val condition = true
if (condition) {
    doSomething();
} else {
    doSomethingElse();
}

6. 无效/误导性注释

严重程度:2 分 | 注释与代码不符、无意义,或注释过多过滥。

// 反例:
// 误导性注释(注释与代码功能不符,造成混淆)
// 计算 a 的 b 次方
public double addTwoNum(double a, double b) {
    return a + b; // 实际是两数相加
}

// 正例:
// 计算含税总价(注释与代码一致,简洁明了)
/**
 * 计算含税总价
 * @param price 原价(元)
 * @param tax 税率(如 0.08 = 8%)
 * @return 含税总价
 */
public double calculateTotal(double price, double tax) {
    return price * (1 + tax);
}

7. try 块范围过大

严重程度:3 分 | 仅将不稳定代码包裹在 try 块中,便于定位错误。

// 反例:
// try 块范围过大(无法准确定位异常来源)
public void processData() {
    try {
        preProc(); // 稳定代码,无需包裹
        unstableProc(); // 不稳定代码
        postProc(); // 稳定代码,无需包裹
    } catch (Exception e) {
        Log.e("DataTag", "处理出错", e);
    }
}

// 正例:
// try 块仅包裹不稳定代码(精准定位异常,推荐)
public void processData() {
    preProc(); // 稳定代码,单独拆分
    try {
        unstableProc(); // 仅包裹不稳定代码
    } catch (Exception e) {
        Log.e("DataTag", "处理不稳定代码出错", e);
    }
    postProc(); // 稳定代码,单独拆分
}

三、可维护性问题(降低迭代成本)

规范代码格式后,需进一步优化代码可维护性,降低后续迭代成本,避免因代码冗余、依赖过时 API 导致的问题,具体如下:

1. 函数/类过大

严重程度:3 分 | 建议:单个函数 ≤ 100 行,单个类 ≤ 1500 行,避免逻辑冗余。

2. 控制流程混乱

严重程度:3 分 | 建议:if 嵌套不超过 3 层,提升代码可读性。

3. 方法参数过多

严重程度:3 分 | 参数过多易出错,建议用实体类封装。

// 反例:
// 参数过多(可读性差,易传错顺序)
fun registerUser(uname: String, pwd: String, age: Int, email: String, phone: String)

// 正例:
// 实体类封装参数(简洁,易维护,避免传参错误)
data class UserRegInfo(
    val uname: String,    // 用户名
    val pwd: String,     // 密码
    val age: Int,        // 年龄
    val email: String,   // 邮箱
    val phone: String    // 手机号
)
fun registerUser(regInfo: UserRegInfo)

4. 使用弃用 API

严重程度:2 分 | 弃用 API 缺乏后续支持,易出现兼容性问题。

// 反例:
// 使用弃用 API(风险:无后续支持,易出兼容问题)
// Camera API 已弃用,推荐用 Camera2
Camera oldCamera = Camera.open();

// 正例:
// 使用替代 API(Camera2,兼容新系统,推荐)
CameraManager cameraMgr = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);

四、性能问题(优化效率)

保障代码可维护性的同时,性能优化能提升 App 运行效率,避免资源浪费和卡顿问题,重点关注以下性能相关问题:

1. 未关闭资源

严重程度:3 分 | 文件、数据库 Cursor、网络连接等未关闭,易导致内存泄漏。

// 反例:
// 未关闭资源(风险:内存泄漏,资源浪费)
public void readFile(String path) throws IOException {
    FileReader reader = new FileReader(path); // 未关闭
    char[] buf = new char[1024];
    reader.read(buf);
}

// 正例:
// try-with-resources 自动关闭资源(推荐,避免泄漏)
public void readFile(String path) throws IOException {
    // 自动关闭 FileReader,无需手动 close
    try (FileReader reader = new FileReader(path)) {
        char[] buf = new char[1024];
        reader.read(buf);
    }
}

2. 静态引用持有页面

严重程度:4 分 | 静态变量/内部类持有页面引用,易导致内存泄漏。

// 反例:
// 静态引用持有 Activity(风险:内存泄漏)
object AppMgr {
    // 强引用持有 Activity,页面销毁后无法回收
    var activity: Activity? = null
}
// 错误用法
AppMgr.activity = this // this 为 Activity 实例

// 正例:
// 弱引用持有 Activity(避免内存泄漏,推荐)
object AppMgr {
    // 弱引用:页面销毁后自动回收
    var activity: WeakReference<Activity>? = null
}
// 正确用法
AppMgr.activity = WeakReference(this)

3. 正则未静态编译

严重程度:3 分 | 正则表达式静态编译,避免重复编译影响性能。

// 反例:
// 正则未静态编译(性能差:每次调用都重新编译)
public class Validator {
    public boolean checkEmail(String email) {
        // 每次调用都编译正则,耗时
        Pattern tempPattern = Pattern.compile("^[\\w.-]+@[\\w.-]+\\.\\w+$");
        return tempPattern.matcher(email).matches();
    }
}

// 正例:
// 正则静态编译(性能优:全局仅编译 1 次,复用)
public class Validator {
    // 静态常量:类加载时编译一次,全局复用
    private static final Pattern EMAIL_PATTERN = Pattern.compile("^[\\w.-]+@[\\w.-]+\\.\\w+$");

    public boolean checkEmail(String email) {
        return EMAIL_PATTERN.matcher(email).matches();
    }
}

4. 布局嵌套过深

严重程度:2 分 | 建议使用 ConstraintLayout 减少布局层级,提升渲染效率。

五、可扩展性问题(适配迭代)

优化性能后,需考虑代码的可扩展性,确保 App 能适配后续迭代需求,降低功能升级时的修改成本,具体关注:

1. 类职责过多(违背单一职责)

严重程度:3 分 | 一个类仅负责 1 项功能,降低耦合度。

// 反例:
// 类职责过多(违背单一职责,耦合度高)
public class BadUserManager {
    public void register() {} // 用户注册
    public void login() {}    // 用户登录
    public void sendEmail() {}// 发送邮件(不属于用户管理)
}

// 正例:
// 单一职责拆分(降低耦合,易维护,推荐)
// 用户服务:仅负责用户注册登录
public class UserService {
    public void register() {}
    public void login() {}
}

// 邮件服务:仅负责邮件发送
public class EmailService {
    public void sendWelcomeEmail(String email) {}
}

2. Adapter 处理复杂业务

严重程度:3 分 | 业务逻辑提前在数据模型中处理,Adapter 仅负责 UI 数据绑定,避免 Adapter 臃肿。

// 反例:
// Adapter 处理复杂业务(风险:Adapter 臃肿,可维护性差,耦合度高)
class UserAdapter(private val list: List<User>) : RecyclerView.Adapter<UserAdapter.ViewHolder>() {
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val user = list[position]
        // Adapter 中处理业务逻辑(计算年龄、拼接昵称)
        val age = Calendar.getInstance().get(Calendar.YEAR) - user.birthYear
        val nickname = "${user.name}(${age} 岁)" 
        holder.tvNickname.text = nickname
    }
}

// 正例:
// 数据模型处理业务逻辑,Adapter 仅做 UI 绑定(推荐,解耦)
// 数据模型(处理业务逻辑)
data class User(val name: String, val birthYear: Int) {
    // 业务逻辑封装在数据模型中
    val nicknameWithAge: String
        get() = "${name}(${Calendar.getInstance().get(Calendar.YEAR) - birthYear} 岁)"
}

// Adapter(仅负责 UI 绑定,简洁清晰)
class UserAdapter(private val list: List<User>) : RecyclerView.Adapter<UserAdapter.ViewHolder>() {
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val user = list[position]
        holder.tvNickname.text = user.nicknameWithAge
    }
}

3. 直接依赖具体实现

严重程度:2 分 | 依赖接口而非具体实现,提升代码灵活性。

// 反例:
// 直接依赖具体实现(耦合度高,不易扩展)
public class UserRepo {
    // 直接 new 具体实现,无法替换其他实现类
    private ApiService api = new ApiService();
}

// 正例:
// 依赖接口(解耦,可灵活替换实现类,推荐)
public class UserRepo {
    private final IApiService api;

    // 构造器注入接口,不依赖具体实现
    public UserRepo(IApiService api) {
        this.api = api;
    }
}

// 接口定义
public interface IApiService {
    List<User> getUsers();
}

六、健壮性问题(避免异常)

具备可扩展性的代码,还需保障其健壮性,避免因异常处理不当导致 App 崩溃,提升用户体验,具体需规避以下问题:

1. 非主线程更新 UI

严重程度:4 分 | 仅主线程可更新 UI,否则会导致程序崩溃。

// 反例:
// 非主线程更新 UI(风险:程序崩溃)
Thread {
    // 子线程直接操作 UI,报错
    tvContent.text = "更新内容"
}.start()

// 正例:
// 主线程更新 UI(推荐:通过 runOnUiThread 切换主线程)
Thread {
    // 子线程逻辑处理后,切换主线程更新 UI
    runOnUiThread {
        tvContent.text = "更新内容"
    }
}.start()

2. 未正确使用空安全

严重程度:4 分 | 不当使用空引用,易引发空指针异常(NPE)。

// 反例:
// 不当使用空安全(风险:强制非空,易抛 NPE)
fun getName(user: User?): String {
    // user 为 null 时,会抛出 NullPointerException
    return user!!.name
}

// 正例:
// 正确使用空安全(避免 NPE,推荐)
fun getName(user: User?): String {
    // 安全调用+空值兜底:user 为 null 时返回"未知用户"
    return user?.name ?: "未知用户"
}

3. 修饰符使用不当

严重程度:3 分 | 不应暴露的变量/方法公开,破坏封装性。

// 反例:
// 修饰符不当(风险:内部数据暴露,破坏封装)
public class MyClass {
    // 内部数据公开,外部可直接修改,不安全
    public int publicData;
}

// 正例:
// 正确封装(私有变量+公开 get 方法,安全)
public class MyClass {
    // 内部数据私有,禁止外部直接访问
    private int data;

    // 公开 get 方法,控制数据访问
    public int getData() {
        return data;
    }

    // 内部方法私有,仅内部调用
    private void processData() {}
}

4. foreach 循环增删元素

严重程度:4 分 | 迭代过程中修改集合,会抛出并发修改异常。

// 反例:
// foreach 循环增删元素(风险:并发修改异常)
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
for (String item : list) {
    if ("b".equals(item)) {
        list.remove(item); // 迭代中修改集合,报错
    }
}

// 正例:
// 迭代器删除元素(避免并发修改异常,推荐)
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String item = it.next();
    if ("b".equals(item)) {
        it.remove(); // 迭代器安全删除
    }
}

5. finally 块使用 return

严重程度:4 分 | finally 块的 return 会覆盖 try/catch 的返回值或异常。

// 反例:
// finally 块用 return(错误:覆盖上层返回/异常)
public int compute() {
    try {
        return 100; // 正常返回 100
    } catch (Exception e) {
        throw new RuntimeException(e);
    } finally {
        cleanup();
        return -1; // 覆盖返回值,最终返回 -1
    }
}

// 正例:
// finally 块不返回(正确:仅做清理,不影响上层结果)
public int compute() {
    try {
        return 100; // 正常返回 100
    } catch (Exception e) {
        throw new RuntimeException(e);
    } finally {
        cleanup(); // 仅做清理,不返回
    }
}

6. 捕获异常未处理

严重程度:4 分 | 捕获异常后需日志记录、资源清理或重新抛出,不可静默吞掉。

// 反例:
// 捕获异常未处理(错误:静默吞异常,无法排查问题)
public void readFile(String path) {
    try {
        processFile(path);
    } catch (IOException e) {
        // 无任何处理,异常被吞,排查困难
    }
}

// 正例:
// 捕获异常后正确处理(推荐:记录日志+抛出)
public void readFile(String path) {
    try {
        processFile(path);
    } catch (IOException e) {
        Log.e("FileTag", "读取文件失败", e); // 记录日志
        throw new RuntimeException(e); // 重新抛出,上层处理
    }
}

7. 数组越界

严重程度:4 分 | 访问数组前需判断索引合法性,避免越界异常。

// 反例:
// 数组越界(风险:抛出 ArrayIndexOutOfBoundsException)
int[] nums = {1, 2, 3}; // 索引 0-2
int num = nums[5]; // 索引 5 超出范围,报错

// 正例:
// 检查索引合法性(避免数组越界,推荐)
int[] nums = {1, 2, 3};
int index = 2;
// 先判断索引是否合法,再访问
if (index >= 0 && index < nums.length) {
    int num = nums[index];
}

8. 类型强制转换异常

严重程度:4 分 | 强制转换前需判断类型,避免 ClassCastException。

// 反例:
// 强制类型转换(风险:抛出 ClassCastException)
val obj: Any = "Hello" // 实际是 String 类型
val num = obj as Int // 强制转 Int,失败报错

// 正例:
// 安全类型转换(避免异常,推荐)
val obj: Any = "Hello"
// 安全转换:转换失败返回 null,兜底为 0
val num = obj as? Int ?: 0

附录:问题分类汇总

类别 5 分问题数 4 分问题数 3 分问题数 2 分问题数 合计
安全问题 3 1 0 0 4
规范问题 0 0 2 5 7
可维护性问题 0 0 3 1 4
性能问题 0 1 2 1 4
可扩展性问题 0 0 2 1 3
健壮性问题 0 6 1 0 7
合计 3 8 10 8 29

备注:5 分问题(3 个)需 100% 修复,不改将直接导致错误;4 分问题(8 个)优先修复,不改可能触发错误;3 分、2 分问题结合项目迭代需求,逐步优化。

使用建议

  1. 优先修复高分问题:5 分、4 分问题易引发严重错误,需优先处理。
  2. 重点关注安全与健壮性:两类问题直接影响 App 稳定性和用户数据安全,客户端开发需重点把控。
  3. 纳入代码审查清单:将本规范作为日常代码 Review 的核心检查项,提前规避问题。
  4. 持续迭代优化:结合项目实际场景,补充完善问题分类,适配团队开发需求。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐