文章摘要
在编程中,使用通用类型(如整数、字符串或UUID)可能导致错误,例如混淆用户ID和账户ID。更好的解决方案是为不同的事物定义不同的类型,以保留其实际含义的上下文。例如,可以为账户ID和用户ID分别定义类型,避免类型混淆带来的错误。这种方法能有效提升代码的可读性和安全性。
文章总结
利用类型系统提升代码质量
在编程中,我们经常需要处理一些简单的值,这些值通常可以用编程语言内置的通用类型(如整数、字符串或UUID)来表示。然而,在复杂的代码库中,这种做法往往会导致一些难以察觉的错误。例如,一个表示用户ID的字符串可能被误用为账户ID,或者一个关键函数接受三个整数参数时,调用者可能会混淆参数的顺序。
为了避免这类问题,定义不同的类型并在表示不同事物时使用它们是一个更好的解决方案。虽然int或string是优秀的构建块,但直接在系统中传递它们意味着你逐渐失去了这些值所代表的重要上下文。
示例
假设我们不再使用普通的UUID,而是为每个模型定义自己的ID类型:
```go type AccountID uuid.UUID type UserID uuid.UUID
func UUIDTypeMixup() { { userID := UserID(uuid.New()) DeleteUser(userID) // 无错误 }
{
accountID := AccountID(uuid.New())
DeleteUser(accountID)
// ^ 错误:无法将类型为AccountID的'accountID'用作UserID类型
}
{
accountID := uuid.New()
DeleteUserUntyped(accountID)
// 编译时无错误;运行时可能出错
}
} ```
通过这种方式,编译器可以在类型不匹配时立即报错,从而避免潜在的运行时错误。
在libwx中的应用
我在2015年的演讲“String is not a sufficient type”中讨论过这一技术。在我的Golang天气与大气计算库libwx中,我为每个测量值定义了专门的类型,并提供了类型之间的转换方法(例如,Km.Miles())。这种做法有效防止了用户在使用float64时可能犯的错误。
例如:
```go // 声明一个华氏温度 temp := libwx.TempF(84)
// 声明一个相对湿度百分比: humidity := libwx.RelHumidity(67)
// 尝试使用接受摄氏度的函数计算露点: fmt.Printf("Dew point: %.1fºF\n", libwx.DewPointC(temp, humidity))
// 编译器会阻止我们犯这个错误,并给出以下错误: // 无法将类型为libwx.TempF的'temp'用作TempC类型
// 尝试计算露点,但混淆了函数参数: fmt.Printf("Dew point: %.1fºF\n", libwx.DewPointF(humidity, temp))
// 同样,编译器会阻止我们犯这个错误。 ```
结论
类型系统是为了帮助你而存在的,充分利用它。
每个模型都应该有自己的ID类型。公共函数甚至私有函数都应避免仅使用浮点数或整数。我在实际系统中见过太多由于混淆表示不同事物的整数、字符串或UUID而导致的错误。与此同时,设置类型来完全消除这类错误非常简单,即使在像Go这样类型系统并不特别强大的语言中也是如此。令人惊讶的是,这种技术并未被广泛使用。
你可以在GitHub上找到本文中使用的所有代码:GitHub - cdzombak/libwxtypeslab。
评论总结
评论主要围绕类型系统在编程中的应用及其优缺点展开,观点多样且平衡。以下是总结:
支持类型系统的观点:
类型作为文档和重构工具:
- "If you see a well laid out data model in types you supercharge your ability to understand a complex codebase."(评论1)
- "This pattern is exactly the pattern I recommended two weeks ago in a thread about a nearly catastrophic OpenZFS bug."(评论7)
类型安全与错误预防:
- "I use this approach in all of my golang codebases now and find it invaluable — it makes it really easy to do the right thing and really hard to accidentally pass the wrong kinds of IDs around."(评论8)
- "This is the antidote to primitive obsession."(评论23)
类型系统的扩展性与灵活性:
- "This gives you integer ids that can’t be confused with each other."(评论16)
- "Creating custom types in Rust feels very native and effortless."(评论26)
对类型系统的质疑与批评:
过度使用类型的复杂性:
- "Everything is a type and nothing works well together, tons of types seem to be subtle permutations of each other, things get hard to reason about etc."(评论2)
- "Most OOP devs have seen at least 1 library with over 1000 classes."(评论17)
类型系统的局限性:
- "Most static type systems that I know of disappear at runtime."(评论20)
- "Primitive object types in Java (String, Float, …) are final. That blocks you from doing such tricks."(评论24)
类型系统的维护成本:
- "Hard not to agree with the general idea. But also hard to ignore all of the terrible experiences I’ve had with systems where everything was a unique type."(评论9)
- "It is quite easy to overdo types and make working with a library extremely burdensome for little to no to negative benefit."(评论10)
其他相关讨论:
类型系统的命名与概念:
- "Does anyone know the term for this? I had 'Type Driven Development' in my head."(评论3)
语言特定的实现与挑战:
- "Unfortunately, this can be somewhat awkward to implement in certain structural typed languages like TypeScript."(评论13)
- "I’ve been using hacks to do this for a long time. I wish it was simpler in C++."(评论19)
类型系统与LLM的未来:
- "I wonder what the trend of LLM-based programming will result in after another few years."(评论18)
总结:类型系统在提高代码可读性、安全性和可维护性方面具有显著优势,但也可能因过度使用或不当实现导致复杂性和维护成本增加。开发者应根据具体需求和语言特性,合理平衡类型系统的使用。