w3ctag / design-principles

A small-but-growing set of design principles collected by the TAG while reviewing specifications
https://w3ctag.github.io/design-principles
176 stars 46 forks source link

Avoid exotic objects (e.g. named/indexed getters/setters) #16

Open domenic opened 9 years ago

domenic commented 9 years ago

I just wrote up https://code.google.com/p/chromium/issues/detail?id=503971#c6 and it'd probably be worth capturing here.

annevk commented 9 years ago

See https://www.w3.org/Bugs/Public/show_bug.cgi?id=26986 which would make this more clearly a stupid idea from an IDL perspective.

dbaron commented 7 years ago

@travisleithead points out that exotic object is defined in ECMA-262.

torgo commented 7 years ago

Reference an ecmascript definition. https://ecma-international.org/ecma-262/6.0/#sec-exotic-object

torgo commented 7 years ago

Something should go in section 2.

domenic commented 7 years ago

In IDL we have since renamed the objects so-defined "legacy platform objects"

Also, please use https://tc39.github.io/ecma262/.

travisleithead commented 6 years ago

Need to figure out how to relate the various -like constructs as well, e.g., maplike and setlike. How does the guidance for these differ from the general guidance around legacy platform objects? Presumably it's different guidance...

travisleithead commented 6 years ago

@slightlyoff also noted a couple of additional references:

LeaVerou commented 2 years ago

Since we have a long-term goal of publishing a subset of the design principles for web developers, I wondered: are exotic objects an antipattern for JS libraries as well, or only for Web Platform APIs?

LeaVerou commented 2 years ago

@plinss and I looked at this today during a breakout and we were not sure we understand exactly what is proposed to be an antipattern here. My understanding was that every API that uses keys that depend on current state is an antipattern according to the proposed principle, but the actual ES definition of exotic objects is more restricted.

If my understanding is correct, this would render APIs like the following antipatterns:

Is that the case?

domenic commented 2 years ago

My understanding was that every API that uses keys that depend on current state is an antipattern according to the proposed principle, but the actual ES definition of exotic objects is more restricted.

The ES definition is actually more expansive.

The only way to get object properties ("keys") which depend on the current state is by overriding the [[OwnPropertyKeys]]() internal method. And, any overridden internal method makes an object exotic by the ES definition. So, objects with custom keys are just one type of exotic object.

In practice on the web platform the two categories you identify are mostly equivalent, because as long as you define your objects using Web IDL, the primary way to make them exotic is by using indexed/named getters/setters/deleters. I.e., making their properties dynamic. This will make them into legacy platform objects.

For completeness: other less-relevant ways of using Web IDL to create exotic objects are: - Global objects are slightly exotic in that they have an overridden [[SetPrototypeOf]] to make their prototype immutable - Named property objects are a special category of exotic object which exist in Window's prototype chain

A final type of exotic object that you can create using Web IDL is the observable array exotic object. This was very carefully designed to implement best practices for exotic objects, essentially making it behave as much like an Array (which is another type of exotic object) as possible. People should be able to use observable arrays going forward, and thus avoid using indexed getters/setters to create their own new types of exotic objects.

If my understanding is correct, this would render APIs like the following antipatterns:

Yes.

An important point on CSStyleDeclaration, though. It is not exotic because of the various properties like fontSize, marginTop, etc. Those are defined as normal properties. There are a lot of them, but that's fine: it's a finite list, determined at "compile time", not at runtime. It is exotic instead because of the indexed getter, declaration[i], which returns the _i_th property name.

Perhaps relatedly, I saw in your recent post you cited color.lch.l as exotic. I'm not sure how that's implemented in your library, or if I've understood the example, but if I were to implement that as a web platform API, I think it would be doable without exotic objects. You'd have a finite (determined ahead of time) set of properties, such as color.lch and color.rgb and so on, each of which was an instance of a specific class, such as LCHColor and RGBColor, each of which have a finite (determined ahead of time) set of properties, such as l, c, h, and r, g, b. Nothing exotic needed; just a bunch of normal classes with normal properties.

LeaVerou commented 2 years ago

Thank you for the clarifications. Are there any other DOM APIs that this would render antipatterns?

Perhaps relatedly, I saw in your recent post you cited color.lch.l as exotic. I'm not sure how that's implemented in your library, or if I've understood the example, but if I were to implement that as a web platform API, I think it would be doable without exotic objects. You'd have a finite (determined ahead of time) set of properties, such as color.lch and color.rgb and so on, each of which was an instance of a specific class, such as LCHColor and RGBColor, each of which have a finite (determined ahead of time) set of properties, such as l, c, h, and r, g, b. Nothing exotic needed; just a bunch of normal classes with normal properties.

Definitely related, as we tried to avoid named properties in the Color API exactly because of this proposed principle, but there is a significant API ergonomics difference between color.get("l") and color.l. No, a predefined set of getters would not work, as you can register new color spaces at runtime. That is also why we don't want to have Color subclasses like that.

Did you see this question btw?:

Since we have a long-term goal of publishing a subset of the design principles for web developers, I wondered: are exotic objects an antipattern for JS libraries as well, or only for Web Platform APIs?

domenic commented 2 years ago

Are there any other DOM APIs that this would render antipatterns?

Yes, anything that uses getter or setter annotations. The best way to find this that I'm familiar with is code-searching your favorite browser's IDL files, e.g. here are the results for Chromium.

Since we have a long-term goal of publishing a subset of the design principles for web developers, I wondered: are exotic objects an antipattern for JS libraries as well, or only for Web Platform APIs?

In general I would say they are. They conflate the interface of a class with its data. Besides that being a confusing problem and an issue for theoretical purity, it has practical issues. E.g. with localStorage, what happens if you try to store an item named getItem? Then is localStorage.getItem a function, or is it the stored item? Similarly with HTMLCollection and nodes with id="length". (There are well-defined answers to these questions, but the fact that they have to be asked is a sign of bad design.) Even if you think your class has very few parts of its interface, you're still stepping on the basic parts of the interface that all JavaScript objects share, e.g. toString(), hasOwnProperty(), constructor, etc. (So e.g. in your color example, I wonder what happens if someone defines a colorspace whose components are (toString, hasOwnProperty, constructor).)

For numeric-only cases, the reasoning is a bit different, which is basically that developers expect indexed collections to behave as much as possible like arrays. So developers (and web spec authors) should really just use arrays when possible. Sometimes they need extra functionality, usually to observe changes; to do this they need to use very carefully-constructed proxies (like ObservableArray<T> does on the spec level).