以下是我的一些个人偏好(存放在 ~/.claude 或 ~/.codex 这类目录下,使用于 Claude Code 或 Codex 等 Agent 助手),也许可能和项目中的 AGENTS.md 或者是 CLAUDE.md 之类的文件有冲突,一定要以我的个人偏好为准:
对于 Java,接口永远不要直接返回 Map,定义 dto 再返回
对于 Java,dto 优先使用 record ,entity 优先使用 Lombok @Data POJO
对于 Java,dto entity 这类对象互转,考虑使用 MapStruct
对于 Java,如果要引用一个类的字段名,使用 @FieldNameConstatnts 注解(对应的类没有这个注解就给它加上),避免直接使用字符串常量,示例如下:
// 错误写法:
private Query buildQuery(ProductLineQuery query) {
Query baseQuery = Query.query(Criteria.where(BaseEntity).is(false));
if (StringUtils.hasText(query.getProductLineId())) {
baseQuery.addCriteria(Criteria.where("productLineId")
.regex(".*" + query.getProductLineId() + ".*", "i"));
}
if (StringUtils.hasText(query.getProductLineName())) {
baseQuery.addCriteria(Criteria.where("productLineName")
.regex(".*" + query.getProductLineName() + ".*", "i"));
}
if (StringUtils.hasText(query.getOwner())) {
baseQuery.addCriteria(Criteria.where("owner").is(query.getOwner()));
}
return baseQuery;
}
// 正确写法:
private Query buildQuery(ProductLineQuery query) {
Query baseQuery = Query.query(Criteria.where(BaseEntity.Fields.deleted).is(false));
if (StringUtils.hasText(query.getProductLineId())) {
baseQuery.addCriteria(Criteria.where(ProductLine.Fields.productLineId)
.regex(".*" + query.getProductLineId() + ".*", "i"));
}
if (StringUtils.hasText(query.getProductLineName())) {
baseQuery.addCriteria(Criteria.where(ProductLine.Fields.productLineName)
.regex(".*" + query.getProductLineName() + ".*", "i"));
}
if (StringUtils.hasText(query.getOwner())) {
baseQuery.addCriteria(Criteria.where(ProductLine.Fields.owner).is(query.getOwner()));
}
return baseQuery;
}
方法内部不要滥用注释,你的代码就是你的逻辑的最好说明,只有代码解释不了的逻辑才使用注释,其他地方可以使用 javadoc 注释(对于 Java),也要保证简洁、言简意赅
自动将你新建的文件 add 到 git 中,可以提交你的代码,(不要推送,不要设置 Co-Authored 为 Claude Code)
对于 Java,我正在使用 jdk 21 ,尝试多使用新版本的 Java 特性,例如 List.of(),或者 String::lines 等等,var 非必要不使用
保证每行代码的宽度(包含缩进)不要超过 120 字符
日志和注释全部使用中文
对于 Java,调用方法时需要导包(除非包名冲突,否则永远不要不导包直接调用 API):
// 错误示例:
Date maxEndTime = tasks.stream()
.map(BillQuotaGpu::getEndTime)
.filter(java.util.Objects::nonNull)
// 正确示例:
import java.util.Date;
import java.util.Objects;
Date maxEndTime = tasks.stream()
.map(BillQuotaGpu::getEndTime)
.filter(Objects::nonNull)
.max(Date::compareTo)
.orElse(null);
修改代码的时候,禁止使用下面的注释。我自己会通过 git diff 查看你做了哪些改动,因此禁止使用类似下面的注释
// ==================== M4 新增方法 ====================
// 本次新增逻辑
// 本次移除逻辑
// 修复错误
尽量避免在循环中打印日志,可以考虑在循环结束之后打印一个汇总的循环结果
对于 Spring Data MongoDB,我倾向的编写 Repository 的风格如下(支持 Repository 接口方法,也支持直接使用 MongoTemplate 编写自定义方法):
// 注意以下所有代码写到一个文件里
@Repository
public interface IbObPwdRepository extends MongoRepository<InBandOutBandPwd, String>, CustomPwdRepository {
Optional<InBandOutBandPwd> findFirstByResourceIdAndDeletedIsFalse(String resourceId);
Stream<InBandOutBandPwd> findAllByDeletedIsFalse();
}
interface CustomPwdRepository {
List<InBandOutBandPwd> findAllByResourceIdIn(List<String> resourceIds);
/**
* 每次更新密码时,先使用该方法逻辑删除对应机器所有之前的密码,然后插入一个新实体
* @param resourceId 机器 ID
*/
void logicDeleteAllOldPwds(String resourceId);
/**
* 更新密码的更换周期
* @param resourceId 机器
* @param rotationPeriod 密码轮换周期
* @return 是否找到密码实体并更新
*/
boolean updateExpireDays(String resourceId, int expireDays);
/**
* 找所有密码已过期的机器 id
* @return 密码已过期的机器 id 集合
*/
List<String> findAllResourceIdByPwdExpired(Instant now);
}
@RequiredArgsConstructor
class CustomPwdRepositoryImpl implements CustomPwdRepository {
private final MongoTemplate mongoTemplate;
@Override
public List<InBandOutBandPwd> findAllByResourceIdIn(List<String> resourceIds) {
var res = new ArrayList<InBandOutBandPwd>();
for (List<String> batch : Lists.partition(resourceIds, 100)) {
res.addAll(mongoTemplate.find(
Query.query(Criteria.where("deleted").is(false).and(InBandOutBandPwd.Fields.resourceId).in(batch)),
InBandOutBandPwd.class
));
}
return res;
}
@Override
public void logicDeleteAllOldPwds(String resourceId) {
Query query = new Query(Criteria.where("resourceId").is(resourceId));
Update update = new Update().set("deleted", true);
mongoTemplate.updateMulti(query, update, InBandOutBandPwd.class);
}
@Override
public boolean updateExpireDays(String resourceId, int expireDays) {
Query query = Query.query(Criteria.where(InBandOutBandPwd.Fields.resourceId).is(resourceId)
.and("deleted").is(false));
Instant expireAt = DateUtils.midnightPlusDate(expireDays).minusSeconds(1L);
// 更新管道,开发和测试暂时用不了
// AggregationUpdate update = Aggregation.newUpdate()
// .set(InBandOutBandPwd.Fields.rotationPeriod).toValue(rotationPeriod)
// // 如果没有过期时间,设置一个过期时间,否则过期时间不变
// .set(InBandOutBandPwd.Fields.expireAt).toValue(
// ConditionalOperators.IfNull.ifNull(InBandOutBandPwd.Fields.expireAt).then(expireAt))
// .set("updateTime").toValue(Instant.now());
Update update = Update.update(InBandOutBandPwd.Fields.expireAt, expireAt)
.set("updateTime", Instant.now());
UpdateResult updateResult = mongoTemplate.updateFirst(query, update, InBandOutBandPwd.class);
return updateResult.getMatchedCount() > 0;
}
@Override
public List<String> findAllResourceIdByPwdExpired(Instant now) {
Query query = Query.query(Criteria.where(InBandOutBandPwd.Fields.expireAt).ne(null).lte(now)
.and("deleted").is(false));
query.fields().include(InBandOutBandPwd.Fields.resourceId);
return mongoTemplate.findDistinct(query, InBandOutBandPwd.Fields.resourceId, "inBandOutBandPwd",
String.class);
}
}