身边有些程序员写代码的时候有代码洁癖,这到底是不是一个好习惯呢?
绝对是好的习惯。因为他有在考虑维护性的东西。且我通常都是建议,新写的代码,要有代码洁癖的意识习惯。
这种程序员他脑子里到底在想啥呢?或者会有什么样的表现呢?
- 一个变量的命名如果拿捏不准,他会思考良久,或者到处找资料问高手,到底如何命名才最贴切;
- 他会深度考虑一个类、一个方法的责任。「类和方法是要经过组织的」,这点刻在他脑子里,不会违背。因为他不希望别人可以轻易破坏掉他写的代码。他总是会留口子让你改,但不破坏功能;
- 写「处于同一抽象层次」的代码,是他的职业习惯。别人一看,就知道整块业务的核心流程是什么,而不是一下子陷入细节。
他会深恶痛绝那种随意写代码的写法。
那每个程序员一开始就会这样?大多数不是的。都是挨过N多个坑,被骂过N多次,被一次一次的故障教育得来的。
告诉你,在专业的程序员眼里,你随意写代码,他是无法跟你一起工作的。且说话也很不客气。他心里想:你是干这行的吗?为啥如此随意?
专业的程序员,也经常想一个事情:
他希望这个功能交付后,无论是稳定性、维护性和异常流处理,都是到位的。交付了就交付了,而不是有一堆的后遗症。
为啥?他们是讲究效率的。如果接新需求的时候,还老是去改上一个需求的bug,那会极大影响他的效率和专注性。
他们习惯交付一个就是一个。他们不会在这件事上妥协。
同一抽象层次:流程一眼能看懂
给你们看一段代码片段。下面体现的是「处于同一抽象层次」的写法:
java
public Long submitOrderDocs(SaveOrderDocsCommand saveOrderDocsCommand) {
//1、检查来创建订单的参数
checkCreateOrderParam(saveOrderDocsCommand);
//2、核心方法:计算单据推送到低代码平台的时间
buildOrderCanPushTime(saveOrderDocsCommand);
//3、创建订单
Long docsId = createOrder(saveOrderDocsCommand);
//4、特殊订单走OA审批流程
createOaWorkFlow(saveOrderDocsCommand, docsId);
//5、发布领域事件,用于数据统计
eventPublisher.publishCreateEvent(docsId);
return docsId;
}
上面的代码,体现的是门店店员下一个订货单需要走的流程。注意,是流程。我并不需要立刻知道代码细节,我要的是核心业务流程。够了,可以了。我需要的话,再深入进去就可以了。
而不是一来,就让我看到一大堆代码细节。
至少我看过的专业程序员,不会这么玩。代码要清晰体现业务流程。这个也很容易理解:业务就是那样走的,你为啥不想办法清晰体现呢?
类是经过组织的:钉钉审批的例子
经过设计和组织过的代码,你想轻易破坏,还破坏不了。
我举个连锁门店系统里的例子。对接钉钉、创建OA审批单,这块一想就知道是通用的:都要调钉钉、都要建审批流程。同时扩展点也容易知道,因为连锁店业务,还有排班、门店营业时间变更等必走审核流程的业务。像我这种经常做连锁店业务的,是非常清楚的。
这种「通用 + 必扩展」的场景,才值得用工厂和策略去组织。不是为设计而设计。
第一步,调用方只管取策略、创建审批:
java
IDingTalkWorkFlowStrategy strategy =
DingTalkWorkFlowFactory.getDingTalkWorkFlowStrategy(bizType);
return strategy.createWorkFlow(createWorkFlowCommand);
第二步,工厂按业务类型分发。 新增一种钉钉审批业务,加一个 XxxWorkFlowStrategy 实现,在工厂里挂一行分支,老策略不用动:
java
switch (bizTypeEnum) {
case SHOP_TIME_CHANGE:
return SpringUtils.getBean(ShopTimeChangeAuditWorkFlowStrategy.class);
case SHOP_SCHEDULE:
return SpringUtils.getBean(ShopScheduleAuditWorkFlowStrategy.class);
default:
throw BusinessException.of(...);
}
第三步,才是创建OA审批单的核心骨架。 通用流程放在抽象父类里,子类只填各自业务的表单字段:
java
public DingTalkWorkFlowRecordEntity createWorkFlow(CreateWorkFlowCommand cmd) {
//1、校验能否创建
checkCanCreateWorkFlow(cmd.getBizId());
//2、组装申请人、部门、审批模板等公共信息
CreateWorkFlowDTO dto = assembledBaseInfo(cmd);
//3、各业务子类填充钉钉表单(排班、改营业时间等字段不同)
assembledFormComponents(dto);
//4、落库并调用钉钉创建审批实例
return doCreateWorkFlow(dto);
}
private DingTalkWorkFlowRecordEntity doCreateWorkFlow(CreateWorkFlowDTO dto) {
DingTalkWorkFlowRecordEntity root = createAndPersistenceRoot(dto);
invokeDingTalkCreateWorkFlow(dto, root);
return root;
}
子类里通常只实现 assembledFormComponents,比如排班审批填周期、班次汇总;门店营业时间变更填另一套字段。公共的取 token、查部门、调钉钉 API、落审批记录,全在父类,不用每个业务抄一遍。
新增业务时,你动的是:一个新 Strategy + 工厂一行 + 表单组装方法。原来的排班策略、改时间策略,不用碰。
这里要强调一点:上面说的洁癖,指的是新写的代码。老代码不能因为所谓代码洁癖就全盘重构,那不现实。除非公司或部门立项做了重构专项,才有条件系统性动老代码。
业界大神怎么看
国外几个影响力很大的程序员,看法大体一致,但也都留了边界。
Robert C. Martin(Uncle Bob) 在《代码整洁之道》里把写整洁代码当成专业素养:代码像被人认真照料过,函数和类专注一件事。Michael Feathers 还补过一句:整洁的代码,是作者在意过细节才会留下的东西。
Bjarne Stroustrup(C++ 发明者)说得更短:整洁的代码只做好一件事。逻辑直截了当,依赖尽量少,方便维护。
Martin Fowler 讲 YAGNI:不要为「以后可能用到」堆复杂度。但让代码容易改的重构,不等于 YAGNI。两者要分清楚。
共识是:代码要可读、可改、能交付。 分歧在:什么时候该停手。
什么时候洁癖会坏事
读者可能会问:既然这么好,为啥还有人反感代码洁癖?
几种常见情况:
格式强迫症。 只改缩进、换命名风格,行为不变。这种,在我眼中,跟代码洁癖,没任何关系。
过早抽象。 需求还没定,就抽一堆接口和父类。这和钉钉工厂不一样:钉钉是通用且扩展点明确;如果只是「万一以后能复用」,不该上策略模式。
无立项地动老代码。 老模块能跑、线上稳定,没有重构专项,却因为看着不顺眼就大改。风险往往大于收益。专业洁癖应该花在新代码和当前需求上。
怎么区分专业洁癖和格式强迫症

| 维度 | 专业洁癖(值得坚持) | 格式强迫症(别瞎折腾) |
|---|---|---|
| 目标 | 可读、可改、可交付 | 看着顺眼 |
| 典型行为 | 新代码流程清晰、通用处复用、扩展点明确 | 只改风格、无立项重构老代码 |
| 对团队 | 降低接手成本 | CodeReview陷入口水战 |
| 判断 | 不改会增加后续bug或改需求成本 | 不改只是看着别扭 |
小结
从维护性和交付效率来看,代码洁癖是好习惯。我通常也建议:新写的代码,带着洁癖的意图去写。命名、职责、抽象层次、该复用的地方留扩展点,这些力气花得值。
同时要有边界:老代码别因为没有立项就硬重构;别为假想需求过度设计。
如果你身边有那种写代码很「讲究」的同事,多半不是天生如此,是被坑出来的。你也可以从新需求开始,练这种习惯。