Make static methods that create objects distinct from those that create their builders.
See original GitHub issue_This issue is taken from https://github.com/SpongePowered/SpongeAPI/pull/2275#issuecomment-739912355_
One large pain point that I’ve encountered is that there are factory methods that are named the same as their builder methods. Coupled with the convenience methods on the immutable variants, the method that should be used to create an object is extremely unclear (another issue will be opened on the convenience methods).
Take the following two methods from your library in isolation - what someone booting an IDE and typing Component.text(
might see.
Component.text();
Component.text(String);
They both look like factory methods to me: my assumption is that they would both create Component
s, one empty, one containing the string. However, it turns out that the first is a TextComponent.Builder
, and one is a TextComponent
. What’s bad, however, is that my assumption can actually hold - if I use these as ComponentLike
s in something that accepts ComponentLike
s, then I will indeed end up with Component.empty()
(a method that’s actually okay but totally eclipsed by this) and my text component. So now we have two methods that don’t do the same thing, but can be treated as such, but are actually totally different ways to do the same thing.
While both can actually be used in the same way, with the same methods names, they really shouldn’t be. In theory, I can do the following:
Component.text().content("Text").color(NamedTextColor.GREEN).style(Style.style().decorate(TextDecoration.BOLD).decorate(TextDecoration.ITALIC).build());
Component.text("").content("Text").color(NamedTextColor.GREEN).decorate(TextDecoration.BOLD).style(Style.style(TextDecoration.BOLD).decoration(TextDecoration.ITALIC, TextDecoration.State.TRUE)); // slighly facetious with the starting method, but it proves a point
and you get the same result - one uses the builders, one does not - and because both the builder and the standard component implement ComponentLike
, I can use these within other calls and would be no wiser to the fact they are two totally different objects.
I propose the following:
- Deprecate any static methods that return builders and are named the same as factory static methods that do not return builders.
- Create replacement methods suffixed with
Builder
to clearly mark that those methods return a different type.
Issue Analytics
- State:
- Created 3 years ago
- Reactions:5
- Comments:14 (9 by maintainers)
I feel like this issue is an unhelpful reproduction of #159, but in the opposite direction. But it more generally raises issues for a lot of our projects.
Before I continue, I’d like to make a digression. In the issue @dualspiral linked, he talks about the disparity between Sponge’s API design choices and Adventure’s. This would be a valid issue if it weren’t for the fact that Adventure is not Sponge. It is used by several major consumers in the Minecraft ecosystem: Geyser, Velocity, Sponge, and eventually Paper. Having Adventure cater more to the needs of the Sponge project hurts the Adventure project and directly affects everyone who is not Sponge who depends on Adventure. (If anything, at the time of writing this, Geyser may be the single biggest user of Adventure in the community). We can bikeshed about how the Adventure API should cater to its individual users all day long and we would not get anywhere.
I ultimately believe that the builder methods aren’t even the main complaint - it’s the documentation. It’s hard to feel motivated to write good documentation. It’s not as fun as working on something new and shiny. It is grunt work, and it’s something we (especially the improvisational, thinking types like me) try to avoid.
But anyway… back to the issue. I can understand why
Component.text()
having overloads that return both components and component builders can be confusing. This is worsened by the fact that component builders and components can be used in almost identical ways, but components being immutable and component builders being mutable. Perhaps it’s a case of being lucky and getting the result you expected anyway, and if that’s the case I could see a potential case for changing the API.Ultimately, what this issue boils down to is a fight between peoples’ personal tastes. APIs are always going to reflect the personal opinions of their authors. It so happens that Adventure values closely mapping how the game treats components, which is not bad but certainly convoluted (I come from the BungeeCord component API which takes a much more intuitive approach than Adventure’s 1:1 mapping of vanilla logic). That being said, I do support moving Adventure towards being more discoverable, approachable, and consistent. But I think it’s time to determine what standards should be followed before we make any changes.
I’ve talked about this a bit internally but thought it would be best to reply here too:
I’m against this change.
Firstly, having nice/short names for these methods makes code look significantly nicer. I’ve written thousands of lines of components using the current API - trust me the extra characters add up…
I don’t agree that the current layout/names is confusing or unclear.
If we really do think it’s necessary to create more of a distinction, then I would prefer to see a
ComponentBuilders
class re-introduced for use in static imports, or go the other way - move the constructor methods somewhere else and leave the methods that returnBuilder
s inComponent
.Using the builders is the preferred way to construct large components with children or complex properties. I am worried that by changing this, people will fallback to using
Component.text(...)
etc (which returns a normal, fully built component) and use the mutate methods on those instead.