当 GraphQL 变成"全家桶",Stream 写成"天书",老板变身"谜语人":我在代码屎山里的渡劫日常
前言: 入职前,HR 告诉我公司使用的是"现代化的 GraphQL 架构",追求"极致的代码优雅"。 入职三个月后,我才发现,所谓的"优雅",就是用最复杂的语法,去实现最没人性的逻辑。
今天,我想以此文,祭奠我那死在 TODO 列表里的架构师梦想。
一、GraphQL 的"全家桶"式滥用
如果你问我这里最可怕的代码是什么?那一定是那套"万金油"式的 GraphQL 接口。
在我的认知里,GraphQL 是为了解决"过度获取"和"获取不足"的问题,适合前端灵活查询。但在我们公司,它成了后端偷懒和规范崩塌的温床。
1. 接口命名玄学 打开代码库,你会看到一系列令人窒息的 Schema 定义:
graphql
# 这是查订单的
getOrderList(orderId: String): Order
# 这也是查订单的,但不知道为什么要加个 User 前缀
UserOrderList(id: ID): OrderResult
# 这居然还是查订单的,参数换了个名
queryOrderById(order_no: String!): OrderData
三个接口,功能一模一样,返回的字段结构却大相径庭。有时候叫 orderId,有时候叫 order_no,有时候叫 id。前端每次对接都要问:"哥们,这次这个 ID 到底是哪个字段?"
2. 妄想把所有东西塞进一个 Query 老板觉得 GraphQL 好,好就好在"灵活"。于是他要求:"我们就写一个大的 Query,把所有可能用到的字段都查出来,让前端自己去挑!" 结果就是,我们的 Order 对象膨胀到了几百个字段。查个简单的订单列表,后端关联了十几张表,耗时 3 秒,最后前端只用了其中的 3 个字段。 这不叫 GraphQL,这叫数据库级别的"自助餐浪费"。
二、Stream 流乱飞:炫技是种病,得治
如果你忍着恶心点开 Service 层,恭喜你,你将进入"Java 8 Stream 地狱"。
这里的代码有一个潜规则:能写 5 行逻辑的,绝不写 20 行;能让人一眼看懂的,必须写成一行。
请欣赏这段"神作":
java
return orderList.stream()
.filter(e -> e.getStatus() != null && (e.getStatus() == 1 || e.getStatus() == 2))
.map(e -> {
User u = userService.get(e.getUserId());
return new OrderDTO() {{
setId(e.getId());
// ...一堆 setter
setUserName(u != null ? u.getName() : "Default");
}};
})
.sorted(Comparator.comparing(OrderDTO::getCreateTime).reversed())
.collect(Collectors.toList());
这种代码的毒性在于:
- 极其难 Debug:只要中间一个 NPE,堆栈报错只会告诉你这一整行错了,你根本不知道是哪个对象空了。
- 逻辑隐藏 :复杂的业务逻辑被压缩在
.map()和.filter()的 Lambda 表达式里,代码审查时,谁敢动这一行"天书"? - 性能黑洞 :在 Stream 循环里频繁调用数据库(如上面的
userService.get),完全没有意识到这会触发 N+1 查询问题。
所谓"代码优雅",在这里变成了**"除了写代码的人,谁读谁想吐"**。
三、老板:开发过程中最大的阻碍
技术上的烂,尚且有重构的希望。但"人"的烂,是无解的。
我们的老板,自称"全栈产品经理",也是公司最大的**"业务黑盒"**。
场景一:谜语人模式 老板:"这个订单接口,你要加个逻辑。" 我:"加什么逻辑?能不能写进文档?" 老板:"哎呀,这个逻辑很微妙的,你自己看以前代码怎么写的,照着写就行了。万一出了事,有兜底方案。" 我:"兜底方案是什么?" 老板:"到时候你就知道了。" (内心 OS:等你"到时候"知道,上线已经炸了!)
场景二:隐藏的业务点 这就是为什么我们的代码里充满了莫名其妙的 if-else。 比如:
java
if (order.getAmount().compareTo(new BigDecimal("500.00")) > 0 && order.getRegion().equals("CN") && "Tuesday".equals(day)) {
// 赠送一个奇怪的优惠券
}
再比如
java
// 诡异的逻辑开始...
if (order.getAmount().compareTo(new BigDecimal("500.00")) > 0
&& "CN".equals(order.getRegion())
&& "Tuesday".equals(day)) {
// 第一层:更诡异的子判断
if (user.getRegisterTime().isBefore(LocalDateTime.of(2021, 6, 1, 0, 0))
&& !user.getTags().contains("INNER_STAFF")
&& order.getItems().stream().anyMatch(item -> "SKU_888".equals(item.getSkuId()))) {
// 硬编码的优惠券 ID,查数据库甚至都查不到这张券的记录
String secretCoupon = "COUPON_BOSS_PLEASE_DO_NOT_DELETE_2021";
// 强行发放
// 甚至在OrderService有发放的逻辑,这里又用了一个别的Service的发放逻辑,鬼知道这两个有什么
couponService.grant(user.getId(), secretCoupon);
// 发完还要打个特殊的日志,甚至不打在常规日志文件里
System.out.println("============== SPECIAL EXECUTION ==============");
System.out.println("User " + user.getId() + " got the gift.");
System.out.println("================================================");
}
}
这段代码没有任何注释。后来我逼问老员工才得知,这是两年前老板为了讨好某个大客户,手动写死的"彩蛋"。 这些隐藏的业务点,老板从来不告诉新人,只在他觉得"这怎么没效果?"的时候才跳出来指责你不懂业务。
他就像是一个手里握着半张藏宝图的人,看你挖不到金子,就骂你锄头挥得不对,却死活不肯把那半张图拿出来。
四、没有设计模式,只有永远的 TODO
在这套代码里,你找不到策略模式 ,找不到工厂模式 ,甚至找不到像样的单一职责原则。
所有的逻辑都堆积在一个名为 OrderService 的巨无霸类里,几千行代码,像一条望不到头的屎山。
而最让我绝望的,是文件顶部那行刺眼的注释:
java
// TODO: 后续重构,优化逻辑
// Created: 2020-05-20
// Author: 已离职的前辈A
这个 TODO,就像一个诅咒。 我也曾试图重构,试图用设计模式解耦。但我刚改了一个类,leader突然冲过来说:"哎呀,那个老逻辑不能动!虽然看起来没用,但是那个老客户还在用老系统接口,你改了他们那边就挂了!" 于是,我默默按下了 Ctrl+Z,并在那个 TODO 下面加上了我的一笔:
java
// TODO: 后续重构,优化逻辑 (这个TODO可能比我的年龄都大,勿动)
// Modified: 2026-03-XX
// Author: 深夜吐槽的我
结语
在这家公司,代码不是艺术品,也不是产品,它是老板随性而为的便签纸。
- GraphQL 只是用来掩盖接口规范混乱的遮羞布;
- Stream 流只是用来满足少数人"技术炫技"的玩具;
- 隐藏的需求和老板的谜语人行为,才是这座屎山不断堆积的根源。
今晚,我看着屏幕上满屏红色的报错,和那个永远无法勾选的 TODO 标签,我合上了电脑。 我知道,明天太阳升起时,我又要在这一行行乱飞的 Stream 流里,继续去猜老板那个该死的"隐藏业务点"了。
这就是程序员的渡劫,代码无涯,回头是岸。