# 编码规范与标准检查表

## 🔴 P0 级问题

### 1. 空指针风险 - 链式调用未判空
**检查点**:
- `getDynamicObject("xxx").getPkValue()` 是否未判空？
- `getDynamicObject("xxx").getString()` 是否未判空？
- 任何 `getXxx()` 返回对象后直接调用方法是否未判空？

**风险**: NullPointerException 导致程序崩溃

**修正方案**:
```java
// ❌ 错误写法 - 链式调用未判空
Long orgId = (Long)bill.getDynamicObject("org").getPkValue();
String name = data.getDynamicObject("user").getString("name");

// ✅ 正确写法 - 先判空再调用
DynamicObject orgObj = bill.getDynamicObject("org");
if (orgObj == null) {
    throw new KDBizException("组织信息不能为空");
}
Long orgId = (Long)orgObj.getPkValue();

// ✅ 或者使用 Optional
Long orgId = Optional.ofNullable(bill.getDynamicObject("org"))
    .map(DynamicObject::getPkValue)
    .map(id -> (Long)id)
    .orElseThrow(() -> new KDBizException("组织信息不能为空"));
```

---

### 2. 数组访问未判空
**检查点**:
- `getDataEntities()[0]` 是否未判空？
- `this.dataEntities[0]` 是否未判空？

**风险**: ArrayIndexOutOfBoundsException 或 NullPointerException

**修正方案**:
```java
// ❌ 错误写法
DynamicObject data = e.getDataEntities()[0];

// ✅ 正确写法
DynamicObject[] entities = e.getDataEntities();
if (entities == null || entities.length == 0) {
    return;
}
DynamicObject data = entities[0];
```

---

## 🟠 P1 级问题

### 3. BigDecimal 未处理 null
**检查点**:
- `getBigDecimal("xxx")` 返回值是否可能为 null？
- 使用 `compareTo()` 前是否判空？

**⚠️ 误判排除**:
- **苍穹 `DynamicObject.getBigDecimal()` 特殊规则**: 若元数据定义该字段不允许为空（即非空字段），`getBigDecimal()` 会返回 `BigDecimal.ZERO` 而非 null，此时无需额外判空
- 仅当字段在元数据中定义为**可空**，或通过 `DataSet.Row.getBigDecimal()` 获取时，才需要判空
- **审查时应先确认字段来源**：`DynamicObject` 的非空字段获取 → 无需判空（不报问题）；`DataSet.Row` 获取或可空字段 → 需要判空

**风险**: NullPointerException（仅在可空字段或 DataSet 场景下）

**修正方案**:
```java
// ⚠️ 以下场景无需判空（DynamicObject 非空字段，框架保证返回 BigDecimal.ZERO）
BigDecimal qty = entry.getBigDecimal("qty"); // 元数据定义非空，返回 ZERO 不会 null
if (qty.compareTo(BigDecimal.ZERO) > 0) { ... } // 安全

// ❌ 错误写法 - DataSet.Row 获取的值可能为 null
BigDecimal qty = row.getBigDecimal("qty"); // DataSet.Row 可能返回 null
if (qty.compareTo(BigDecimal.ZERO) > 0) { ... }

// ✅ 正确写法 - DataSet.Row 或可空字段场景
BigDecimal qty = row.getBigDecimal("qty");
if (qty != null && qty.compareTo(BigDecimal.ZERO) > 0) { ... }

// ✅ 或者使用工具方法
BigDecimal qty = ObjectUtils.defaultIfNull(row.getBigDecimal("qty"), BigDecimal.ZERO);
```

---

### 4. 集合操作不规范
**检查点**:
- 使用 `size() > 0` 而非 `!isEmpty()`？
- 集合遍历时是否检查元素是否为 null？

**修正方案**:
```java
// ❌ 错误写法
if (list != null && list.size() > 0) { ... }

// ✅ 正确写法
if (list != null && !list.isEmpty()) { ... }

// ✅ 使用工具类（推荐）
if (CollectionUtils.isNotEmpty(list)) { ... }
```

---

### 5. this.dataEntities 直接访问
**检查点**:
- Validator 中直接使用 `this.dataEntities` 是否判空？

**修正方案**:
```java
// ❌ 错误写法
public void validate() {
    for(ExtendedDataEntity data : this.dataEntities) { ... }
}

// ✅ 正确写法
public void validate() {
    if (this.dataEntities == null || this.dataEntities.length == 0) {
        return;
    }
    for(ExtendedDataEntity data : this.dataEntities) { ... }
}
```

---

## 🟡 P2 级问题

### 6. 硬编码中文字符串
**检查点**:
- 代码中是否有 `"中文"` 字符串？
- 是否直接使用中文字符串作为提示信息？

**风险**: 无法国际化，维护困难

**修正方案**:
```java
// ❌ 错误写法
this.getView().showMessage("操作成功");

// ✅ 正确写法
String msg = ResManager.loadKDString("操作成功", "key", "module");
this.getView().showMessage(msg);
```

---

### 7. 异常日志丢失堆栈
**检查点**:
- `catch` 块中是否只打印了 `e.getMessage()`？
- `logger.error()` 是否没有传入异常对象？

**风险**: 无法定位问题根因

**修正方案**:
```java
// ❌ 错误写法
try {
    // ...
} catch (Exception e) {
    logger.error("操作失败: " + e.getMessage()); // 丢失堆栈！
}

// ✅ 正确写法
try {
    // ...
} catch (Exception e) {
    logger.error("操作失败", e); // 保留完整堆栈
}
```

---

### 8. 魔法数字
**检查点**:
- 代码中是否有未解释的数字常量？

**修正方案**:
```java
// ❌ 错误写法
if (status == 1) { ... }

// ✅ 正确写法
private static final int STATUS_ENABLED = 1;
if (status == STATUS_ENABLED) { ... }
```

---

### 9. 变量命名不规范
**检查点**:
- 变量名是否符合驼峰命名规范？
- 常量是否使用全大写下划线？

**修正方案**:
```java
// ❌ 错误写法
String UserName;
int max_count;

// ✅ 正确写法
String userName;
int MAX_COUNT;
```

---

### 10. 空异常处理
**检查点**:
- 是否有空 `catch` 块？

**修正方案**:
```java
// ❌ 错误写法
try {
    // ...
} catch (Exception e) {
    // 吞掉异常
}

// ✅ 正确写法
try {
    // ...
} catch (Exception e) {
    logger.error("处理失败", e);
    throw new RuntimeException("处理失败", e);
}
```

---

### 11. 常量声明不规范
**检查点**:
- 实例字段是否应该声明为 static final？

**修正方案**:
```java
// ❌ 错误写法
public class MyValidator {
    private final BigDecimal ZERO;  // 每个实例都创建
    
    public MyValidator() {
        this.ZERO = BigDecimal.ZERO;
    }
}

// ✅ 正确写法
public class MyValidator {
    private static final BigDecimal ZERO = BigDecimal.ZERO;  // 类级别常量
}
```

---

### 12. 冗余代码
**检查点**:
- 是否有无用的空构造函数？
- 是否有未使用的导入？
- 是否有注释掉的代码？

**修正方案**:
```java
// ❌ 错误写法 - 冗余空构造函数
public class MyPlugin {
    public MyPlugin() {
        super();  // 完全冗余
    }
}

// ✅ 正确写法 - 删除冗余代码
public class MyPlugin {
    // 编译器会自动生成默认构造函数
}
```

---

## 命名规范检查

### 13. Java 类命名不规范
**检查点**:
- 普通类是否使用 UpperCamelCase？
- DO/BO/DTO/VO 等缩写是否全大写保留？（`UserDO` 而非 `UserDo`）
- 抽象类是否以 `Abstract` 开头？
- 异常类是否以 `Exception` 结尾？
- 枚举类是否以 `Enum` 后缀，成员是否全大写+下划线？
- 是否存在随意缩写？（`AbstractClass` → `AbsClass`、`condition` → `condi` 均不可接受）

**插件类名后缀规则**:
| 类型 | 后缀 | 示例 |
|------|------|------|
| 表单插件 | `FormPlugin` | `PurchaseFormPlugin` |
| 单据插件 | `BillPlugin` | `PurchaseBillPlugin` |
| 列表插件 | `ListPlugin` | `PurchaseListPlugin` |
| 操作插件 | `OpPlugin` | `PurchaseOpPlugin` |
| 报表插件 | `RptPlugin` | `PurchaseRptPlugin` |

---

### 14. 变量/常量命名不规范
**检查点**:
- 类型名词是否放词尾？（`startTime` 而非 `startedAt`，`nameList` 而非 `listName`）
- 常量是否全大写+下划线？（`TERMINATED_THREAD_COUNT`）
- POJO 布尔字段是否加了 `is` 前缀？（`Boolean isDeleted` → RPC 反序列化误读为 `deleted`）
- 子父类成员变量是否同名？
- 同一方法不同代码块的局部变量是否同名？

**修正方案**:
```java
// ❌ 错误写法 - POJO 布尔字段加 is 前缀
public class OrderDTO {
    private Boolean isDeleted; // RPC 反序列化会误读为 deleted 字段
}

// ✅ 正确写法
public class OrderDTO {
    private Boolean deleted;
}
```

---

### 15. 注释规范缺失
**检查点**:
- 类/接口是否有 Javadoc 注释？
- public 方法是否有完整的 Javadoc（@param、@return、@throws）？
- public 常量/属性是否有注释？
- 是否使用 `System.out.println` 代替日志？

**修正方案**:
```java
// ❌ 错误写法 - 无注释
public class AttachmentSaveServiceHelper {
    public static DynamicObject buildAttachDetail(DynamicObject typeDy, Long targetId,
            Map<String, Object> attach) throws Exception { ... }
}

// ✅ 正确写法
/**
 * 附件上传帮助类
 * @author xxx
 * @since 2020-11-20
 */
public class AttachmentSaveServiceHelper {
    /**
     * 构建附件明细信息
     * @param typeDy   附件类型实体
     * @param targetId 附件 ID
     * @param attach   附件信息
     * @return appendix dynamic object
     * @throws Exception exception
     */
    public static DynamicObject buildAttachDetail(DynamicObject typeDy, Long targetId,
            Map<String, Object> attach) throws Exception { ... }
}
```

---

### 16. 插件继承关系错误
**检查点**:
- 表单插件类型与继承基类是否匹配？

**正确的继承关系**:
| 场景 | 继承基类 |
|------|---------|
| 单据页面 | `AbstractBillPlugIn` |
| 基础资料页面 | `AbstractBasePlugIn` |
| 动态表单页面 | `AbstractFormPlugIn` |
| 列表页面 | `AbstractListPlugIn` |
| 移动表单 | `AbstractMobFormPlugin` |
| 移动单据 | `AbstractMobBillPlugin` |
| 移动列表 | `AbstractMobListPlugIn` |
| 操作服务插件 | `AbstractOperationServicePlugIn` |
| 单据转换插件 | `AbstractConvertPlugIn` |

---

### 17. 日志使用不规范
**检查点**:
- 是否使用 `System.out.println` 而非平台日志？
- 是否使用了非平台日志框架？（必须使用 `kd.bos.logging.Log`）
- 是否用 JSON 序列化打印页面对象？（`SerializationUtils.toJsonString` 极耗 CPU）
- debug 日志是否用 `isDebugEnabled()` 包裹？
- error 日志是否传入了异常对象？

**修正方案**:
```java
// ❌ 错误写法
System.out.println("Debug: " + info);
SerializationUtils.toJsonString(pageObject); // 极耗 CPU

// ✅ 正确写法
private static final Log logger = LogFactory.getLog(MyClass.class);

if (logger.isDebugEnabled()) {
    logger.debug("Debug: " + info);
}
logger.error("操作失败", e); // 必须传入异常对象
```

---

### 18. 异常处理不规范
**检查点**:
- 是否统一使用 `KDException`？
- 有原始异常时是否作为 cause 传入？
- 是否直接调用 `Throwable.printStackTrace()`？
- 是否有空 catch 块（隐藏异常）？
- UI 层捕获异常后是否给出了业务语义提示（让用户知道下一步怎么处理）？

**修正方案**:
```java
// ❌ 错误写法
try {
    // ...
} catch (Exception e) {
    e.printStackTrace(); // 禁止
}

// ❌ 错误写法 - 原始异常未作为 cause
try {
    // ...
} catch (Exception e) {
    throw new KDException(new ErrorCode("xxx", "操作失败")); // 丢失原始异常
}

// ✅ 正确写法
try {
    // ...
} catch (Exception e) {
    throw new KDException(new ErrorCode("xxx", e.getMessage()), e); // 保留原始异常
}
```

---

### 19. 分录字段与单头字段 API 混用
**检查点**:
- 操作分录字段时是否使用了单头字段的 `setEnable`/`setVisible` API？
- 单头 API 操作分录字段不会报错但不生效（静默失败）

**修正方案**:
```java
// ❌ 错误写法 - 用单头 API 操作分录字段（静默失败，不报错但不生效）
getView().setEnable(Boolean.FALSE, new String[]{"entryFieldKey"});

// ✅ 正确写法 - 分录字段必须传 rowIndex
int rowCount = getModel().getEntryRowCount("entryEntity");
for (int i = 0; i < rowCount; i++) {
    getView().setEnable(Boolean.FALSE, i, new String[]{"entryFieldKey"});
}
```

---

### 20. F7 字段赋值方式错误
**检查点**:
- `setValue` 写 F7 基础资料字段时是否传了整个 DynamicObject？（应只传 Long id）

**修正方案**:
```java
// ❌ 错误写法 - 传整个 DynamicObject
DynamicObject org = (DynamicObject) getModel().getValue("org");
getModel().setValue("owner", org); // 运行时报错

// ✅ 正确写法 - 只传 id
getModel().setValue("owner", org.getLong("id"));
```

---

### 21. 引用对象赋值类型不一致
**检查点**:
- 不同实体类型的 DynamicObject 是否直接赋值？（需保证对象类型一致）

**修正方案**:
```java
// ❌ 错误写法 - 实体类型不一致直接赋值
DynamicObject customerObj = (DynamicObject) EntityMetadataCache.getData("bd_customer", pk);
prop.setValueFast(dataEntity, customerObj); // 类型不一致！

// ✅ 正确写法 - 通过当前实体自身类型创建实例
MainEntityType et = this.getModel().getDataEntityType();
DynamicObject dataEntity = (DynamicObject) et.createInstance();
```