别用“设计感”掩盖无知:从一次 null == 0 的事故说起

那天下午,我盯着一行再普通不过的代码,足足愣了三分钟:

C# 复制代码
if (value.Equals(null)) {
2    // 这行居然执行了?!
3}

value 是一个 Value 类型的对象,构造时传入的是整数 0------它明明不是 null。调试器里字段正常,内存地址也清清楚楚。可 .Equals(null) 却返回了 true。这已经不是"奇怪"了,这是在挑战我对编程语言的基本信任。

带着一丝不安,我点进了 Value.Equals:

C# 复制代码
public class Value : XXObject, IObject
{
    public override bool Equals(object obj)
    {
        return base.Equals(obj);
    }
}

哦,它自己没实现,直接甩锅给父类 XXObject。那就继续追

C# 复制代码
public class XXObject : IObject
{
    public override bool Equals(object? obj)
    {
        return XXEquals.Equals(_value, obj);
    }
}

看到这里,我心里咯噔一下: 为什么一个 Equals 要委托给静态类? 为什么 Value 和 XXObject 都要实现同一个接口 IObject? 感觉就是过度抽象+静态工具滥用

但 debug 不能停。我硬着头皮点进 XXEquals.Equals,眼前出现了一段堪称"史诗级轮子"的 switch 大法:

C# 复制代码
public static new bool Equals(object value, object? obj)
{
    if (obj is IObject _obj)
    {
        return Equals(value, _obj.GetValue());
    }
    switch (value)
    {
​
        case int v:
        {
            return obj switch
            {
                byte v2 => v == v2,
                short v2 => v == v2,
                // ... 省略一堆类型
                _ => v == XXConvert.ToInt(obj)
            };
    }
}

问题就出在最后那个兜底分支:XXConvert.ToInt(obj)。 当 obj 是 null 时,它居然没有抛异常,而是.....返回了 0?

我颤抖着手点进去:

C# 复制代码
public static int ToInt(object? obj, int def = default)
{
    return ToIntNull(obj) ?? def;
}

而 def 的默认值是 default(int),也就是 0。 所以 ToInt(null) → ToIntNull(null) 返回 null → ?? 0 → 最终得到 0。

于是整个链条闭环了:

  • value.Equals(null)
  • → XXEquals.Equals(0, null)
  • → 0 == XXConvert.ToInt(null)
  • → 0 == 0
  • → true

那一刻,我悬着的心终于死了:

这不是 bug,这是一个自以为"通用"的轮子,在用一套看似合理实则荒谬的逻辑,系统性地践踏语言最基本的契约。

别用"设计"掩盖无知

说实话,看到 XXConvert.ToInt(null) 返回 0 的那一刻,我气笑了。

不是因为代码复杂,而是因为这种设计透露出一种令人不安的傲慢:

"我知道用户可能会传 null,但我不想让他们处理异常------我替他们'智能'地处理成默认值。"

听起来很贴心?不,这是对语义的背叛。

在几乎所有主流编程语言中,null 都代表"无值"、"缺失"、"未初始化"。而 0 是一个明确的、有效的值。把"无"等同于"零",就像把"没人来开会"理解成"来了一个人,他叫张三,职位是 0 号员工"一样荒谬。

更可怕的是,这种"智能"被包装在三层抽象之下:

  • 接口 IObject(看似规范)
  • 基类 XXObject(看似复用)
  • 静态工具类 XXEquals + XXConvert(看似解耦)

结果呢?解耦不解祸,封装不封错 。 所有调用者都被蒙在鼓里,直到某天某个 if (obj.Equals(null)) 突然走错分支,系统在静默中走向崩溃。


这件事让我意识到: 所谓专业,不是你会造多复杂的轮子,而是你清楚哪些边界绝不能碰------比如相等性、空值语义、类型安全这些设计底线。

在这个行业,最昂贵的轮子,从来不是重复造的,而是造歪了还装上车的。

版权声明:本文为个人原创,首发于微信公众号、掘金平台、博客园,作者均为「白气急」。转载请标明出处并附带链接。

相关推荐
明月_清风7 分钟前
为什么 Skill 很难商业化?Bot 才是 AI 时代真正的商业机会
人工智能·后端·agent
苍何10 分钟前
DeepSeek 的 Super-agent 设计,和我们的工程实践
后端
挖掘狂人10 分钟前
缓存和数据库的一致性,别停在 “延时双删”
java·后端·程序员
codigger17 分钟前
缓存和数据库的一致性,别停在 “延时双删”
java·后端·程序员·后端开发
10年前端老司机17 分钟前
Agent 商业化困局:热闹的发布会,冷清的付费客户
前端·后端·agent
小小张说故事22 分钟前
Python 的 async def 为什么能同时跑几千个任务?拆开协程对象、await 与事件循环那次让出
后端·python
小小龙学IT33 分钟前
Go 语言 pprof 性能剖析与调优深度解析:把“卡在哪“变成一张可执行的数据图
开发语言·后端·golang
Sailing35 分钟前
🚀🚀【封闭结束8】Agent RAG:从零搭建一个可用知识库
前端·后端·agent
Rain的Java大神之路1 小时前
面试官让你把 LLM、RAG、Agent、MCP 串起来讲清楚,90% 的 Java 工程师当场露馅
java·后端·面试
余槐i1 小时前
Spring Boot 3.2 偶发500无日志:线程池复用导致请求上下文丢失
spring boot·redis·后端·mysql·性能优化