Appearance
代码风格
代码不仅需要让计算机正确执行,更需要让开发者能够快速理解。
统一的代码风格能够:
- 提升可读性
- 提升可维护性
- 降低沟通成本
- 提高团队协作效率
注释规范
核心重点
注释的核心目标是解释:
- 为什么这样设计
- 这样设计的限制条件
- 与其它模块的关系
而不是简单重复代码本身的含义。
类注释
类注释建议使用标准 Javadoc 格式。
应描述:
- 类的作用
- 设计意图(必要时)
- 与其它类的关系(必要时)
- 使用方式(复杂组件可选)
java
/**
* 网络请求管理器
*
* <p> 负责全局拦截器配置与 OkHttpClient 的生命周期管理 </p>
*
* @author 作者
* @since 2026/6/1 11:34
*/
public class RequestManager { }
/**
* 统一数据流收集接口,对上层屏蔽不同响应式框架的底座实现。
*
* @param <T> 传递的数据载体类型
* @see kotlinx.coroutines.flow.Flow
*/
public interface FlowCollect<T> { }Android Studio 模板配置(可选)
- 在 Android Studio 中打开 File > Settings > Editor > Live Templates 界面。
- 点击加号新建 Template Group。
- 选中新建的 Group 后,新建 Live Template,设置快捷名称等参数。
- 添加模板为:
java
/**
*
* @author 你的名字
* @date $date$ $time$
*/添加完模板后,点击 Edit Variables 设置 date 和 time 的默认值,这样就会快速生成当前时间。
字段注释
字段注释重点说明字段的真实用途、设计意图,并在必要时注明单位与取值范围。
字段注释核心原则
绝对不要机械地拆分“用途”或“范围”,而是在一条注释里,把后人最关心的隐藏背景(为什么定义它、限制是什么)交代清楚。
正例:
java
/** 最大重试 3 次,防止网络抖动时无限循环卡住守护线程 */
private int maxRetryCount = 3;
/** 核心连接超时时间(单位:毫秒) */
private long timeoutMillis = 5000;
/** 允许上传的离线日志文件最大限制,单位:MB */
private int mMaxFileSize = 100;
/** 遮罩层目标透明度,取值范围在 [0, 255],0 为完全透明 */
private int mAreaAlpha = 100;
/** 使用弱引用持有 Activity,避免后台任务导致内存泄漏 */
private final WeakReference<Activity> activityReference;反例:
java
/** 默认初始化为 3 */
//(无意义:单纯复述赋值,没写为什么是 3)//
private int mRetryAttempts = 3;
/** 超时时间 */
// (无意义:未标明单位是秒、毫秒还是分钟)//
private long connectTimeoutMillis = 5000L;
/** 设置 Alpha 值 */
// (无意义:没写清楚取值边界是多少)//
private int targetAlpha = 100;
/** 保存 Activity */
// (无意义:没交代用 WeakReference 的核心意图是什么)//
private WeakReference<Activity> activityReference;方法注释
方法注释只描述调用方无法从方法签名直接获得的信息。
设计原则
方法注释不是翻译方法名,而是补充方法签名无法表达的信息。
也并非每个方法都必须写全所有内容,应根据复杂度综合评估。
重点关注:
- 参数是否存在特殊业务含义
- 返回值是否存在特殊约束
- 是否有调用时机要求
- 是否存在副作用
- 是否要求特定线程调用
- 是否存在使用限制
对于 Getter、Setter、简单工具方法等自解释代码,可不强制编写方法注释。
正例:
java
/**
* 将待发送数据加入串口发送队列。
*
* <p>该方法仅负责入队,不保证立即发送。</p>
*
* @param data 待发送的数据帧
* @return true 表示成功加入队列,false 表示队列已满
* @throws IllegalArgumentException 数据为空时抛出
*
* <p> 线程要求:可在任意线程调用 </p>
*/
public boolean enqueue(byte[] data) {
// ...
}java
/**
* 获取当前连接设备
*
* @return 若设备未连接则返回 null
*/
@Nullable
public Device getCurrentDevice() {
// ...
}反例:
无意义:注释只是重复方法名,没有补充任何调用方真正关心的信息。
java
/**
* 获取当前连接设备
*/
@Nullable
public Device getCurrentDevice() {
// ...
}java
/**
* 发送数据
*/
public boolean enqueue(byte[] data) {
// ...
}关键行注释
行注释的唯一目的:解释代码“为什么”要这样写,而不是解释代码“在做什么”。
什么时候写?
- 有特殊原因:这样写是为了规避 Bug、兼容历史逻辑或满足业务规则。
- 防止后人踩坑:逻辑存在隐含约束,后续维护时容易被误删或错误重构。
- 代码无法自解释:阅读代码仍无法理解设计意图或处理原因。
- 存在边界条件:特殊场景、协议约束、异常处理需要额外说明。
- 用于逻辑分段:将较长的方法,通过注释划分为多个清晰的处理阶段。
正例:
kotlin
// 连续 3 次通讯失败后才判定离线,避免网络抖动导致误报
if (failCount >= 3) {
device.isOnline = false
}反例:
kotlin
if (failCount >= 3) {
// 设置设备离线
device.isOnline = false
}注释只是翻译代码,没有解释业务规则或设计原因
标记注释
常用标记:
| 标记 | 说明 | 适用场景 |
|---|---|---|
| TODO | 待实现 | 临时规划的完整功能或未完工的逻辑 |
| FIXME | 待修复 | 已知存在偶发 Bug 或异常隐患,需紧急跟进 |
| REVIEW | 待确认 | 架构设计或公共 API 变更,需要团队二次评审 |
| OPTIMIZE | 待优化 | 功能已跑通,但存在性能、内存、或技术债隐患 |
示例:
java
// TODO ...
// FIXME ...代码排版
类成员顺序
推荐顺序
- 常量/伴生对象(Kotlin 的
companion object放在最顶部) - 静态字段与静态方法
- 成员变量(由高隐蔽性向低隐蔽性排列:private -> protected -> public)
- 构造方法与初始化块(
init块) - 公开方法
- 私有方法
- 内部类/接口声明
kotlin 中的 init
在 kotlin 中 init 方法要比构造函数先执行,所以一定要放在构造函数之前。
kotlin
class ImageLoader private constructor() {
init {
// 主构造系统初始化(最先执行)
System.loadLibrary("image_decoder")
}
constructor(context: Context) : this() {
// 次级构造扩展
initDiskCache(context.cacheDir)
}
}空格与空行
空行用于表达逻辑分组。
当代码进入新的逻辑阶段时,应使用空行进行视觉隔离。
不要为了“好看”随意插入空行,也不要把多个逻辑阶段挤在一起。
正例(适当空行,层次分明):
java
@Override
public void run() {
RealTimeCurveTask timeCurveTask = mRealTimeCurveTask.get();
if (timeCurveTask == null) {
return;
}
// 刷新曲线图数据
timeCurveTask.refresh(mEntity);
// 获取一次新的延迟时间,继续循环。
int second = getDelayedTime(mEntity);
timeCurveTask.postDelayed(this, second * 1000L);
}反例(代码紧凑,层次模糊):
java
@Override
public void run() {
RealTimeCurveTask timeCurveTask = mRealTimeCurveTask.get();
if(timeCurveTask==null) return;
timeCurveTask.refresh(mEntity);
int second = getDelayedTime(mEntity);
timeCurveTask.postDelayed(this, second * 1000L);
}单行语句规范
即使 if、else、for 语句后面只有一行代码,也必须保留大括号。
正例:
java
/**
* 更新用户头像
*
* @param user 用户信息
*/
private void updateAvatar(User user) {
if (user == null) {
return;
}
imageLoader.load(user.getAvatarUrl());
}反例:
java
private void updateAvatar(User user) {
if (user == null)
return; // 极其危险!后续维护极易因为缩进错觉引发逻辑漏洞
imageLoader.load(user.getAvatarUrl());
}java
private void updateAvatar(User user) {
if (user == null) return; // 不建议
imageLoader.load(user.getAvatarUrl());
}大括号规范
统一采用 Kernighan & Ritchie (K&R) 风格(左括号不换行,右括号换行)。
Google 样式指南
谷歌在安卓官方的 Android Kotlin Style Guide 与 AOSP Java 规范 中明确将 K&R 风格 定为硬性标准。
参考官方文档:https://developer.android.com/kotlin/style-guide?hl=zh-cn#non-empty_blocks
推荐:
java
/**
* 在工作线程中执行
*
* @param task 要添加到队列中的任务
*/
public void runOnWorkThread(@Nullable Runnable task) {
if (task == null) {
return;
}
getThreadHandler().runOnWorkThread(task);
}避免(Allman 风格,拉长代码行数,在 Android 项目中略显臃肿):
java
public void runOnWorkThread(@Nullable Runnable task)
{
if (task == null) {
return;
}
getThreadHandler().runOnWorkThread(task);
}长参数换行
当方法声明或调用时参数过多(通常超过 120 个字符宽),应当在逗号后换行,且每个参数独立一行,保持对齐。
推荐:
kotlin
fun downloadFile(
context: Context,
url: String,
destinationPath: String,
forceDownload: Boolean,
callback: DownloadCallback
) {
// 业务逻辑
}避免:
kotlin
fun downloadFile(context: Context, url: String, destinationPath: String, forceDownload: Boolean, callback: DownloadCallback) {
// ...
}链式调用
使用 Builder 模式或 Stream API 时,每个操作符点号 . 必须独占一行。
推荐:
java
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.addInterceptor(new HttpLoggingInterceptor())
.build();避免:
java
OkHttpClient client = new OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS).readTimeout(10, TimeUnit.SECONDS).build(); 函数设计规范
单一职责
一个函数应该仅承担一个职责,完成一项具体的任务。
如果一个函数的功能过于复杂或处理多个不同的任务,就容易导致代码难以理解、修改和测试。
正例:
kotlin
fun handleUserData(jsonStr: String) {
val user = parseUserJson(jsonStr) ?: return
saveUserToDatabase(user)
updateUserUi(user)
}反例(既解析、又存库、还刷新 UI 的“全能型”函数):
kotlin
fun processUser(jsonStr: String) {
// 解析 Json
val gson = Gson()
val user = gson.fromJson(jsonStr, User::class.java)
// 存数据库
val db = dbHelper.writableDatabase
db.execSQL("INSERT INTO user ...")
// 刷 UI
nameTextView.text = user.name
}函数长度
一个函数原则上应控制在 50 行代码以内。
超过 50 行通常意味着:
- 承担了多个职责
- 存在重复逻辑
- 存在可抽取的业务语义
如果在 Android Studio 中,一屏还装不下它,请果断进行抽取重构。
参数数量控制
函数的参数应当控制在 4 个以内。超过 4 个时,应考虑将其封装为配置类/数据对象(Data Class)。
推荐:
kotlin
data class ImageConfig(
val url: String,
val placeholderRes: Int = R.drawable.placeholder,
val errorRes: Int = R.drawable.error,
val isCircle: Boolean = false
)
fun displayImage(config: ImageConfig) { /* ... */ }避免:
kotlin
fun displayImage(url: String, placeholder: Int, error: Int, isCircle: Boolean, cacheInMemory: Boolean, animate: Boolean) {
// 极难调用,极易传错参数位置(尤其是连续几个 Boolean 或 Int 的情况)
}异常处理
禁止吞掉异常
捕获异常后什么都不做(吞掉异常),会导致极其难以排查的暗箱 Bug。
正例:
java
public void test() {
try {
outStream.write(data);
} catch (IOException exception) {
Log.e(TAG, "Failed to flush backup data to local storage", exception);
// 必要时向上传播或转换为业务自定义异常
throw new BackupException("Storage standard error", exception);
}
}反例:
java
public void test() {
try {
outStream.write(data);
} catch (IOException exception) {
// 假装什么都没发生,线上出了问题两眼一抹黑
}
}避免滥用 Throwable
除非是在框架顶层做全局未捕获异常捕获,否则不要直接兜底捕获 Throwable 或 Exception。
正例:
kotlin
try {
val number = encryptedString.toInt()
} catch (ex: NumberFormatException) { // 精准捕获目标异常
Log.w(TAG, "Invalid digital format inside string")
}反例:
kotlin
try {
parseData()
} catch (throwable: Throwable) { // 连 OOM、NullPointerException 一并吞掉了
// 盲目兜底
}控制流结构
避免嵌套过深
嵌套超过 3 层的 if-else 就是可读性的灾难。
因为多层嵌套会不断增加阅读负担,边界条件应优先返回
正例:
java
public void onReceive(byte[] buffer) {
if (buffer == null) {
return;
}
if (buffer.length < MIN_FRAME_LENGTH) {
return;
}
if (!protocolParser.isValidFrame(buffer)) {
return;
}
parseFrame(buffer);
}反例:
java
public void onReceive(byte[] buffer) {
if (buffer != null) {
if (buffer.length >= MIN_FRAME_LENGTH) {
if (protocolParser.isValidFrame(buffer)) {
parseFrame(buffer);
}
}
}
}健壮性规范
明确空值约束
方法参数与返回值应明确声明是否允许为空,避免调用方无法判断空值约束。
推荐:
java
@Nullable
public Device getCurrentDevice() { }
public void updateDevice(@NonNull Device device) { }避免:
java
public Device getCurrentDevice() { }
public void updateDevice(Device device) { }优先返回空集合
尽量不返回 null,让调用方无需额外判空。
推荐:
java
@NonNull
public List<Device> getDevices() {
return Collections.emptyList();
}避免:
java
public List<Device> getDevices() {
return null;
}硬编码规范
常量管理
避免硬编码常量分散在代码中,就会导致如果需要修改或调整,必须找到所有的并逐个更改,易造成遗漏和错误。
硬编码的内容也会让代码意图不明确,导致可读性差。
正例:
kotlin
object HttpConfig {
/** 浏览器及代理服务器缓存有效期:24 小时 */
const val CACHE_MAX_AGE_SECONDS = 86400
}反例:
kotlin
// 3个月后没人知道 86400 是什么鬼数字
request.addHeader("Cache-Control", "max-age=86400") 资源配置管理
以下内容禁止直接写死在代码中:
- 用户可见文本
- 颜色
- 尺寸
- 动画时长
- 业务配置项
统一通过资源管理:
- strings.xml
- colors.xml
- dimens.xml
- config 类
这样既方便国际化,也方便后续主题换肤与业务调整。
AndroidManifest 规范
AndroidManifest.xml 文件是 Android 应用的入口大纲与系统级配置清单。
随着项目业务的迭代、权限、组件和配置信息会持续增加。
如果缺乏统一规范,Manifest 很容易造成配置冲突,加增安全隐患,届时就是一个难以维护的“大杂烩”。
权限分组清晰
权限分组与级次要分明,按照 基础普通权限、运行时敏感权限、系统/签名级特权(需 Root 或签名特许) 进行分块组织,并附带权限注释。
四大组件分块管理
在 application 节点内部应按照以下顺序组织:
text
Activity
Service
Receiver
Provider禁止不同类型组件交叉混排。
主入口 Activity 置顶
包含:
xml
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>的启动页 Activity 必须放置于 Activity 分组的第一位,便于快速定位应用入口。
标签属性统一排序:
组件标签建议优先保证 android:name 位于属性列表首位。
这样能够在大量组件配置中快速识别目标组件。
同时:
- 有子节点时使用标准开始/结束标签
- 无子节点时统一使用
/>自闭合
推荐:
xml
<service
android:name=".service.SyncService"
android:exported="false" />避免出现空标签闭合形式
xml
<service
android:name=".service.SyncService"
android:exported="false">
</service>完整示例
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- ==================== 网络权限 ==================== -->
<!-- 允许应用访问网络 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 获取当前网络连接状态 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- 获取 Wi-Fi 状态信息 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- 修改 Wi-Fi 状态 -->
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<!-- ==================== 定位权限 ==================== -->
<!-- 粗略定位,用于 Wi-Fi 扫描等场景 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- 精确定位 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- ==================== 存储权限 ==================== -->
<!-- Android 11+ 全文件访问权限 -->
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<!-- Android 10 及以下写存储权限 -->
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- Android 10 及以下读存储权限 -->
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- ==================== 系统权限 ==================== -->
<!-- 安装应用 -->
<uses-permission
android:name="android.permission.INSTALL_PACKAGES" />
<!-- 卸载应用 -->
<uses-permission
android:name="android.permission.DELETE_PACKAGES" />
<!-- 多用户环境交互权限 -->
<uses-permission
android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<!-- ==================== 其他权限 ==================== -->
<!-- 开机启动 -->
<uses-permission
android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- 悬浮窗权限 -->
<uses-permission
android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<application
android:name=".app.AppApplication"
android:icon="@mipmap/water"
android:roundIcon="@mipmap/water"
android:label="@string/app_name"
android:theme="@style/Theme.AppTheme"
android:allowBackup="true"
android:supportsRtl="true"
android:largeHeap="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:requestLegacyExternalStorage="true"
tools:targetApi="n">
<!-- AutoSize Ui 设计稿 宽高配置 -->
<meta-data
android:name="design_width_in_dp"
android:value="600" />
<meta-data
android:name="design_height_in_dp"
android:value="1024" />
<!-- ==================== Activity ==================== -->
<activity
android:name=".ui.activity.InitActivity"
android:exported="true"
android:alwaysRetainTaskState="true"
android:configChanges="orientation|keyboardHidden|screenSize"
android:screenOrientation="fullSensor"
android:theme="@style/AppTheme.NoBarAppTheme.SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ui.activity.MainActivity"
android:launchMode="singleTask"
android:screenOrientation="fullSensor"/>
<activity
android:name="com.zt.app.water.ui.activity.SensorInfoActivity"
android:launchMode="singleTop"
android:screenOrientation="fullSensor"/>
<!-- ==================== Service ==================== -->
<!-- TCP 长连接服务 -->
<service
android:name=".services.SocketTcpService"
android:enabled="true"
android:exported="false" />
<!-- ==================== Receiver ==================== -->
<!-- 开机启动广播 -->
<receiver
android:name=".receivers.DeviceBootReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
</application>
</manifest>总结
代码最终是写给人看的,顺便给机器执行。
规范的价值不在于统一格式,而在于降低理解成本、维护成本和沟通成本。