别害怕类型
Don't Be Afraid of Types

原始链接: https://lmika.org/2025/03/18/dont-be-afraid-of-types.html

许多代码库都避免创建新的类型,导致函数参数过多、返回值过多,并且依赖于现有的类型。这源于一种对定义新类型的潜在顾虑,可能受到面向对象设计原则的影响,这些原则强调宏大的设计并劝退“不必要”的类型创建。 这种担忧是没有根据的。当数据自然地组合在一起时,创建专用类型可以提高代码的清晰度和可维护性,即使对于像请求负载这样较小的数据集也是如此。Go 和 C 等语言都支持这一点,优先考虑实际的数据分组,而不是架构上的宏大性。 虽然过度创建类型是不好的,但像“CreateSubscriptionRequest”这样命名良好的类型的认知负担远小于跟踪许多单个变量。当新的类型可以简化数据处理并改进代码组织时,开发人员不应犹豫创建新的类型。当需要时,勇于创建一个简单的类型可以极大地改善开发体验。

Hacker News 的讨论主要围绕编程中自定义类型的低利用率展开,尤其是在 TypeScript 和 Java 等语言中,尽管 Java 较为复杂。评论者 salmonellaeater 观察到,人们不愿意创建小的、特定类型的变量,例如 “CreateSubscriptionRequest”,而这能够增强可测试性和可维护性,尤其是在 API 处理程序和数据访问对象中。在 TypeScript 中,声明类型很容易,不需要显式实现接口,这种不愿使用自定义类型的情况就更令人费解了。 评论者推测,在 Java 等其他语言中,创建新类型需要将值包装到新类中,这可能会增加阻力。无论如何,讨论都强调了自定义类型在防止错误、简化重构以及最终提高代码质量方面的益处。另一位用户 parpfish 分享了如何将编码重新定义为一系列类型转换,从而提高了代码的可维护性、命名约定和单元测试。

原文

I found that there’s a slight aversion to creating new types in the codebases I work in. I saw it during my early days while I was working in Java projects, and I see it today in the occasional Go project. Function bodies with lots of local variables, functions that take a large number of arguments or returning a large number of results, extensions to existing types rather than making new ones. It’s a strange phenomenon.

I can’t explain why this is. Maybe it’s a fear of feeling like you’re tampering with the “grand design” of the codebase. This is plausible as it was the feeling I had as a junior dev. Afraid to create new classes in Java thinking that I’m introducing a new concept to the project that others had to deal with going forward. _I can add all the verbs I want, but who am _ I to introduce a new noun?

This is obviously a ridiculous notion when you think about it for more than a few seconds. If you come up with a concept or a series of values that naturally go together, so much so that you’re carrying them together as a series of arguments through multiple function calls, it’s probably in your interested to make a type for it. That’s what the type system is for: a means of grouping similar bits of information into an easy-to-use whole.

This makes total sense for the application models: the entities to which you’re software’s reason for being hinges on. But I’ve found it useful to make types for the lesser bits of information: requests from handlers passed through to the service layer, for instance. Just now, I’m working on some code that deals with creating subscriptions. I need to carry the office ID, customer ID, price ID, the subscription quantity, the tax settings, and the subscription metadata from the API handler all the way through to the Stripe client. This is less than what the subscription model deals with, but it’s still a pain to carry these six bits of information separately through the unmarshalling logic, the validation logic, and then through to the server.

So what did I do? I made a “CreateSubscriptionRequest” struct, a new type. Yes, it’s not going to be reusable, but who cares? It makes the code and my life simpler. And honestly, I think the whole “object-orientated approach” to software design really screwed up our thinking here. There was this feeling in the zeitgeist that types and classes are sacred, and that to create a new one is a privilege bestowed only to the leads, architects, and anyone else that had write access to the UML diagrams. Each type was to be an artefact of design, probably because of how much baggage came from defining a new one: they had to be in a separate file, must have seven different constructors, and the fields must be mediated through the use of getters and setters. And if you need something similar to what you’re working on, you didn’t “copy-and-paste” like some animal; you inherited or composed what was there. Given all this, it’s probably understandable that creating new types felt like a decision with a significant bit of “weight”; and who are you, mere lowly junior developer, to make such a decision to create a type just to make it easier to handle data from your handler?

I think the culture around C and Go have got it right. Need to carry a few things for a single function? Create a new type. Don’t worry that it’s used only for a single function. Don’t worry that it only contains a subset of fields of the model you’re operating on.

Now obviously it’s possible to go too far, and start having way too many types than is necessary. Don’t forget that a new type is a bit more cognitive load, as the person maintaining you application will now need to unpack and reference your type when they need to work on it. Just stick with what you need, and make it clear what the purpose of the type is. “CreateSubscriptionRequest” makes it plan that this type only deals with the areas of a code that creates subscriptions, and will probably only make sense through those code paths.

But take it from someone that’s had do deal with codes passing through and returning several values of strings, ints, and bools through a series of function calls: a single struct value is much easier to work with. All it takes is the courage for someone to say “yes, that should be a type.”

Don’t be afraid for that someone to be you.

联系我们 contact @ memedata.com