当 GraphQL 变成“全家桶”,Stream 写成“天书”,老板变身“谜语人”:我在代码屎山里的渡劫日常

当 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());

这种代码的毒性在于:

  1. 极其难 Debug:只要中间一个 NPE,堆栈报错只会告诉你这一整行错了,你根本不知道是哪个对象空了。
  2. 逻辑隐藏 :复杂的业务逻辑被压缩在 .map().filter() 的 Lambda 表达式里,代码审查时,谁敢动这一行"天书"?
  3. 性能黑洞 :在 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 流里,继续去猜老板那个该死的"隐藏业务点"了。

这就是程序员的渡劫,代码无涯,回头是岸。

相关推荐
默海笑2 小时前
Java 基础 12:JavaDoc 生成文档 学习笔记
后端
写Cpp的小黑黑2 小时前
React Native 项目实战指南
后端
G探险者3 小时前
如何找到那些慢 SQL?
后端·sql
敖正炀3 小时前
线程池拒绝策略场景分析
后端
神奇小汤圆3 小时前
别再乱写并发了!弄懂阻塞队列,解决 90% 线程安全问题
后端
敖正炀3 小时前
线程池决绝策略
后端
Moe4883 小时前
WebSocket :从浏览器 API 到 Spring 握手、Handler 与前端客户端
java·后端·架构
神奇小汤圆3 小时前
探索springboot程序打包docker的最佳方式
后端