前言:什么叫「优雅地让人看不懂」?
让人看不懂的代码分两种境界:
第四象限(低技术、高迷惑)是纯折磨,没品位。比如全用拼音缩写,yhje(应还金额)、dqzt(当前状态),这种只是懒,称不上艺术。
第一象限(高技术、高迷惑)才是我们的目标。看起来高深莫测,同事 Review 时频频点头,实际上根本没看懂,最后无奈点了 Approve。这才叫优雅。
下面,我们分七个层次,手把手教你登上这座山峰。
第一层:命名的艺术——让人猜到死
1.1 单字母变量,但要用得有气质
菜鸟:
int i = 0; // 太普通了,没有灵魂高手:
// 用单字母,但要选那种有「多种解读可能性」的字母
// l(小写L)和 1(数字一)长得一模一样,在某些字体下完全无法区分
int l = list.size();
int ll = l - 1;
int lll = ll / 2;
// 读者:这三个变量是什么关系?l 是啥?ll 是啥?lll 又是啥?
// 你(微笑):二分查找的左右指针和中间值1.2 名字要有「多义性」
public class DataManager {
// data 是什么 data?Manager 管的是什么?没有人知道。
// 这是一种哲学上的开放性。
private Object data;
// process 了什么?返回什么?参数是什么意思?
// 完全的自由,完全的混沌。
public Object process(Object input, boolean flag, int mode) {
// flag 是 true 还是 false 的时候做什么,只有上帝知道
if (flag) {
return mode == 1 ? handle(input) : transform(input);
}
return validate(input) ? enrich(input) : input;
}
}1.3 终极命名技巧:用正确的词表达相反的含义
// 经典操作:isNotInvalid、hasNoAbsence、disableNotification
// 三重否定,人类大脑短路专用
public boolean isNotInvalidUser(User user) {
// 这个方法返回 true 代表用户「有效」
// 但你得先在脑子里转三圈才能反应过来
return !user.isInvalid();
}
// 更进阶:命名和逻辑反着来
public boolean isActive(Account account) {
// 看方法名:是不是 active 的?
// 看实现:判断的是 FROZEN 状态
// 读者:所以返回 true 表示 active 还是 frozen?
return account.getStatus() == Status.FROZEN;
}命名混淆难度进阶路线:
第二层:注释的逆向运用
普通人觉得注释要解释代码。大师知道注释的真正用途是干扰视线。
2.1 注释和代码说不同的话
/**
* 计算用户积分
* @param userId 用户ID
* @return 积分值
*/
public BigDecimal calculateFee(String accountId, String currency, LocalDate date) {
// 方法名是 calculateFee,注释说 calculatePoints
// 参数名和 JavaDoc 完全对不上
// 读者:这到底是算费率还是算积分?
// 答案:手续费。但你需要先通读完整个方法才能知道。
}2.2 用注释遮住真正的问题
public void processTransaction(Transaction tx) {
// TODO: 这里逻辑有点复杂,以后优化
// (注:这个 TODO 是 2019 年写的)
if (tx.getAmount().compareTo(BigDecimal.ZERO) > 0) {
if (tx.getType() != null) {
if (tx.getType().equals("CREDIT")) {
if (tx.getStatus() != null && !tx.getStatus().equals("CANCELLED")) {
// 核心业务逻辑藏在第五层 if 里
// 任何人想改这里都要先读懂这座金字塔
doActualWork(tx);
}
}
}
}
}2.3 注释比代码还长,但没说任何有用的事
/**
* 此方法用于处理相关业务逻辑的核心处理流程。
* 本方法是整个系统中非常重要的一个方法,承担着
* 关键的业务处理职责。调用此方法时,请确保传入
* 正确的参数。关于参数的具体要求,请参考相关文档。
* 关于相关文档的位置,请联系相关负责人。
*
* 注意:此方法有副作用。(具体是什么副作用,不告诉你)
* 注意:线程不安全。(在哪里不安全,不告诉你)
* 注意:在某些情况下可能抛出异常。(什么情况,不告诉你)
*
* @author 张三(已离职)
* @since 不知道
* @version 可能是 3,也可能是 4
*/
public void handle(Object obj) {
// ...
}第三层:设计模式——用锤子看什么都是钉子
设计模式是让代码「看起来」有高度的最佳工具。关键技巧:所有问题,先套模式。
3.1 工厂的工厂的工厂
// 需求:根据支付类型,创建不同的处理器
// 正常人:一个 switch 就完事了
// 大师:
// 先来一个抽象工厂
public interface ProcessorAbstractFactory {
ProcessorFactory createProcessorFactory();
}
// 再来工厂的工厂
public interface ProcessorFactory {
ProcessorCreator getProcessorCreator(String type);
}
// 再来创建者
public interface ProcessorCreator {
Processor createProcessor(ProcessorConfig config);
}
// 再来构建者
public class ProcessorConfig {
public static Builder builder() { return new Builder(); }
public static class Builder {
// 15 个字段,全都有 setter
// 全部都是 optional,但不传某几个会 NPE
// 文档里没有说明哪几个必传
}
}
// 最终调用方式(就是为了创建一个对象):
Processor processor = processorAbstractFactoryLocator
.getFactory(FactoryType.DEFAULT)
.createProcessorFactory()
.getProcessorCreator(paymentType)
.createProcessor(
ProcessorConfig.builder()
.withMode(Mode.STANDARD)
.withTimeout(5000)
.build()
);3.2 责任链套策略套观察者
实现代码当然对应有 47 个类文件。新同事上手周期:两周。
第四层:函数式编程——Stream 地狱欢迎你
Java 8 的 Stream API 是上天赐给我们的礼物——不是因为它好用,而是因为它可以把三行代码写成永远不换行的一行。
4.1 基础款:能链就链,绝不换行
// 需求:找出活跃用户中,本月消费超过 1000 的前 10 名,按消费降序
// 普通人写法(太好读了,不行):
List<User> activeUsers = users.stream()
.filter(u -> u.isActive())
.collect(Collectors.toList());
// ... 中间几步 ...
// 大师写法:
return users.stream().filter(u->u.isActive()&&u.getLastLoginDate().isAfter(LocalDate.now().minusMonths(1))).map(u->new UserConsumptionDto(u.getId(),u.getName(),transactionRepository.findByUserId(u.getId()).stream().filter(t->t.getDate().getMonth()==LocalDate.now().getMonth()&&t.getStatus()==TransactionStatus.SUCCESS).map(Transaction::getAmount).reduce(BigDecimal.ZERO,BigDecimal::add))).filter(dto->dto.getTotalConsumption().compareTo(new BigDecimal("1000"))>0).sorted(Comparator.comparing(UserConsumptionDto::getTotalConsumption).reversed()).limit(10).collect(Collectors.toList());
// 一行。永远一行。格式化是懦夫的选择。4.2 进阶款:嵌套 Stream,套娃到底
// 计算所有账户的分组统计(如果你能一遍读懂,请联系我,我要拜你为师)
Map<String, Map<String, DoubleSummaryStatistics>> result = accounts.stream()
.collect(Collectors.groupingBy(
Account::getCurrency,
Collectors.groupingBy(
a -> a.getTransactions().stream()
.filter(t -> t.getAmount().compareTo(BigDecimal.TEN) > 0)
.findFirst()
.map(t -> t.getType().name())
.orElse("UNKNOWN"),
Collectors.summarizingDouble(
a -> a.getTransactions().stream()
.filter(t -> t.getStatus() == TransactionStatus.SUCCESS)
.mapToDouble(t -> t.getAmount().doubleValue())
.sum()
)
)
));4.3 终极款:Optional 俄罗斯套娃
// 获取用户的主账户的最近一笔交易的商户名称
// 正常逻辑:三行加 null 检查
// 大师逻辑:
String merchantName = Optional.ofNullable(userId)
.map(userRepository::findById)
.flatMap(Function.identity())
.map(User::getAccounts)
.filter(accounts -> !accounts.isEmpty())
.map(accounts -> accounts.stream()
.filter(Account::isPrimary)
.findFirst()
.orElseGet(() -> accounts.get(0)))
.map(Account::getTransactions)
.filter(txs -> !txs.isEmpty())
.map(txs -> txs.stream()
.max(Comparator.comparing(Transaction::getCreatedAt))
.orElse(null))
.map(Transaction::getMerchant)
.map(Merchant::getName)
.orElse("Unknown");
// 效果:代码「函数式」,「优雅」,「现代」
// 代价:没有人能在 debug 时设断点,因为没有变量第五层:泛型与反射——黑魔法真正开始
5.1 泛型上界下界全套上
// 一个「灵活」的工具方法
// 功能:复制集合中的元素
// 正常人:Collections.copy()
// 大师:
public static <T, R extends T, E extends Collection<? super R>,
S extends Collection<? extends T>>
E flexibleCopy(S source, Supplier<E> collectionFactory,
Function<? super T, ? extends R> mapper,
Predicate<? super T> filter) {
return source.stream()
.filter(filter)
.map(mapper)
.collect(Collectors.toCollection(collectionFactory));
}
// 调用方式(祝你类型推断成功):
List<SpecialAccount> result = flexibleCopy(
accounts,
ArrayList::new,
a -> (SpecialAccount) enrichAccount(a),
a -> a.getBalance().compareTo(BigDecimal.ZERO) > 0
);
// IDE 可能飘红,可能不飘红,取决于月相。5.2 反射套反射,动态代理加持
@SuppressWarnings("unchecked") // 消音警告,让问题消失在看不见的地方
public class MagicInvoker {
// 动态调用任意对象的任意方法,参数类型自动推断
// 功能强大,出了问题 Stack Trace 深达 40 层
public static <T> T invoke(Object target, String methodName, Object... args) {
try {
Class<?>[] paramTypes = Arrays.stream(args)
.map(arg -> arg == null ? Object.class : arg.getClass())
.toArray(Class[]::new);
// 这里用了递归反射:反射去找方法,方法里可能又调用了反射
Method method = findMethod(target.getClass(), methodName, paramTypes);
method.setAccessible(true); // 访问控制?不存在的。
return (T) method.invoke(target, args);
} catch (Exception e) {
// 把所有异常都包一层,让 Stack Trace 更加迷幻
throw new RuntimeException("Magic invocation failed on " +
target.getClass().getSimpleName() + "#" + methodName, e);
}
}
// 出了空指针异常,你会看到这样的堆栈:
// at MagicInvoker.invoke(MagicInvoker.java:23)
// at MagicInvoker$$EnhancerByCGLIB$$a1b2c3.invoke(<generated>)
// at ProxyChain$DynamicProxyHandler.handle(ProxyChain.java:67)
// at ProxyChain$$FastClassByCGLIB$$d4e5f6.invoke(<generated>)
// at ... (还有 35 行)
// Caused by: NullPointerException at YourActualCode.java:10
}异常追踪难度对比:
第六层:过度抽象——把简单问题复杂化
6.1 为一个 if 语句建立完整的抽象体系
// 需求:判断金额是否大于零
// 普通人(1行):
if (amount.compareTo(BigDecimal.ZERO) > 0) { ... }
// 大师(1个接口 + 3个类 + 1个枚举 + 工厂 + Spring Bean):
// Step 1: 定义规则接口
public interface BusinessRule<T> {
RuleResult evaluate(T context);
}
// Step 2: 抽象基类(当然要有)
public abstract class AbstractBusinessRule<T> implements BusinessRule<T> {
protected abstract boolean doEvaluate(T context);
@Override
public RuleResult evaluate(T context) {
boolean result = doEvaluate(context);
return new RuleResult(result, result ? null : getFailureMessage());
}
protected abstract String getFailureMessage();
}
// Step 3: 具体规则实现
@Component("positiveAmountRule")
public class PositiveAmountRule extends AbstractBusinessRule<AmountContext> {
@Override
protected boolean doEvaluate(AmountContext ctx) {
return ctx.getAmount().compareTo(BigDecimal.ZERO) > 0;
}
@Override
protected String getFailureMessage() {
return "Amount must be positive";
}
}
// Step 4: 规则引擎(当然要有引擎)
@Service
public class RuleEngine {
private final Map<String, BusinessRule> rules;
public <T> RuleResult execute(String ruleName, T context) {
return Optional.ofNullable(rules.get(ruleName))
.map(rule -> rule.evaluate(context))
.orElseThrow(() -> new RuleNotFoundException(ruleName));
}
}
// Step 5: 调用方
RuleResult result = ruleEngine.execute(
"positiveAmountRule",
new AmountContext(amount)
);
if (!result.isPassed()) throw new BusinessException(result.getMessage());
// 以上代码完美替代了:
// if (amount.compareTo(BigDecimal.ZERO) <= 0) throw new BusinessException("...");
// 类文件数量:1 → 7
// 代码行数:1 → 80
// 可维护性:你觉得呢?6.2 把配置写成 DSL
// 与其把业务规则写死,不如自己发明一门语言
// 这样不仅别人看不懂,连你自己三个月后也看不懂
// 自制规则 DSL
TransferRule rule = RuleBuilder
.when(ctx -> ctx.getAmount().compareTo(new BigDecimal("10000")) > 0)
.and(ctx -> ctx.getSender().getRiskLevel() != RiskLevel.LOW)
.or(ctx -> ctx.getReceiver().getCountry().equals("XX"))
.then(Action.REQUIRE_APPROVAL)
.withPriority(Priority.HIGH)
.withTimeout(Duration.ofHours(24))
.withEscalation(EscalationPolicy.builder()
.after(Duration.ofHours(4))
.notifyRole(Role.COMPLIANCE_OFFICER)
.withMessage(MessageTemplate.PENDING_REVIEW)
.build())
.otherwise(Action.AUTO_APPROVE)
.build();第七层:线程与并发——通往不稳定的终点
7.1 自制线程池,参数全靠感觉
// 为什么用 JDK 提供的线程池?自己写多有成就感!
public class MySpecialExecutor {
// 核心线程数:服务器 CPU 核数 × 一个神秘系数
// 这个系数是某个前同事在 2021 年的某个深夜调出来的
// 他已经离职了,没人知道为什么是 2.7
private static final int CORE_POOL_SIZE = Runtime.getRuntime()
.availableProcessors() * 2 + (int)(Math.random() * 3); // 加点随机性,保持神秘
// 队列大小:Integer.MAX_VALUE,内存不够了再说
private final ThreadPoolExecutor executor = new ThreadPoolExecutor(
CORE_POOL_SIZE,
CORE_POOL_SIZE * 10,
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(Integer.MAX_VALUE), // OOM 预约入口
r -> {
Thread t = new Thread(r);
t.setName("my-special-thread-" + System.nanoTime()); // 每个线程名都是唯一的!没法过滤!
return t;
},
(r, e) -> {
// 拒绝策略:默默地把任务丢掉,不抛异常,不记日志
// 功能悄无声息地消失,运维人员永远不会知道发生了什么
}
);
}7.2 双重检查锁——但少检查一步
public class SingletonService {
// 注意:没有 volatile
// 在某些 JVM 实现和 CPU 架构下,这里会出现指令重排序
// 导致另一个线程拿到未初始化完成的对象
// 这个 bug 只在生产环境高并发下偶发
// 而且复现概率极低,每次排查都找不到原因
private static SingletonService instance; // ← 少了 volatile
public static SingletonService getInstance() {
if (instance == null) {
synchronized (SingletonService.class) {
if (instance == null) {
instance = new SingletonService();
// new 操作分三步:分配内存 → 初始化对象 → 赋值引用
// JVM 可能把第二步和第三步重排
// 另一个线程可能在对象初始化完成前就拿到了引用
// 然后调用未初始化的字段
// 然后 NPE,时机全靠缘分
}
}
}
return instance;
}
}并发 Bug 的生命周期:
彩蛋:代码审查时的防御话术
写完迷幻代码,还需要在 Code Review 时守住阵地:
| 同事的质疑 | 你的回应 |
|---|---|
| 「这个变量名是什么意思?」 | 「这是领域驱动设计里的通用语言,你读一下 DDD 那本书就懂了。」 |
| 「为什么不直接用 if-else?」 | 「这样扩展性更好,遵循开闭原则,符合 SOLID 设计思想。」 |
| 「这个 Stream 太难读了。」 | 「你对函数式编程可能还不太熟悉,这是声明式的写法,比命令式更优雅。」 |
| 「为什么要用反射?」 | 「低耦合,高内聚。我们需要在运行时保持灵活性。」 |
| 「这个泛型边界我看不懂。」 | 「类型安全,编译期错误好过运行期错误,对吧?」 |
| 「注释和代码不一致。」 | 「注释描述的是意图,代码描述的是实现,它们本来就是不同层面的东西。」 |
| 「你这里有个潜在的并发问题。」 | 「我们的业务场景下,这个代码路径不会并发执行。」(迷之自信) |
总结:这是一篇反讽文
如果你读到这里还没意识到这篇文章在讽刺什么,那你可能需要再看一遍。
真正好的代码应该是:
「Any fool can write code that a computer can understand. Good programmers write code that humans can understand.」
— Martin Fowler任何傻瓜都能写出机器能理解的代码。优秀的程序员写出人类能理解的代码。
本文所有「反面教材」,都曾以不同形式出现在真实的生产代码中。这不是编的。这是见过的。
如果你在本文中看到了自己写的代码的影子——恭喜,你有进步空间。
如果你完全没看到自己的影子——要么你真的写得很好,要么你还没意识到问题在哪里。
能写出让同事看不懂的代码,是本事。能克制住不这样写,才是修行。