-
Notifications
You must be signed in to change notification settings - Fork 47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New principle: Avoid adding (non-constructor) functions to the global scope #426
Comments
By namespacing, do you mean putting them as static methods on some interface (that is itself exposed on the global)? I don't really see the problem with cc @domenic |
Yes.
I listed some reasons in my first post why I think they do; do you have any thoughts on why they don't?
Adding functions to the global scope does not prove that they didn't need to be scoped — that's a cyclical argument ("the rule is not needed because look, we didn't do it here"). It's much easier to understand what a method is supposed to do when it's grouped under a shared interface. Consistency is another reason: TC39 seems to generally be moving away from global functions. Newer methods that improve on old globals tend to hang on a namespace: e.g. |
I find it pretty clear what the methods I cited do and I don't think they would be helped by namespacing them in some manner. Sometimes namespacing might well help, but I don't think there's an easy rule. |
Operations like |
I'm pretty strongly -1 on this. In addition to the reasons @annevk and @bathos mentioned:
|
In my experience narrow-purpose APIs benefit a lot from the discoverability that @LeaVerou described. It isn’t usually a problem for sets of related interfaces because their names typically share a common prefix that hints at the connection and makes it easy to see them side by side in tooling. This isn’t true for global operations, though, so they do sometimes seem to just be swimming out there — I have to look up “showOpenFilePicker” in the spec every time, for example, since the word I remember is “file”, not “show”. Is there another way to improve/promote discoverable naming of global operations along those lines without using namespacing objects, maybe? |
As a TC39 delegate (i.e. this is my opinion which I also bring to TC39, but it doesn't have TC39 consensus), I find the discoverability argument somewhat compelling but as @domenic points out, the difference between The discussion I've seen most often come up in TC39 around namespacing things is for reduction of collisions and "pollution". IME globals are fine, and the handwringing around namespace collisions is misguided. I've only observed it to be bad ex ante. Ex post I really haven't seen it to be a problem. Where JS has had serious collision problems has been in the prototypes, specifically Implementation-wise, I think namespace objects do offer the benefit of making lazy loading more pay-for-what-you-use, since the indirection via the namespace object means you can lazy load the entire object. If you had consistently prefixed global functions, you'd still need a slot on the global for the name of the function, even if you choose to defer other aspects (like creating the JSFunction, etc). But this alone isn't compelling enough to determine the default of how to organize APIs. Overall, I'd prefer the TAG position on how to organize APIs to remain neutral. Some APIs may have material benefit from an "all static" object, but I don't think there's a clear default. |
I second this as it seems good as a case by case situation. There is a nitpick here where examples with For my personal taste, the names are just too long. That's countered by encouraging and taking advantage of code completion when available (dev tools, IDEs). This practice may be applied to globals and namespaced functions anyway. The other important value for names should be the discoverability + learnability. If you give me something non generic such as |
As a related point, something that came up in #11 (or the f2f discussions around it) was classes that are not of general utility but are specific to certain APIs. E.g. there's a |
If we did that API today it would be a dictionary. It's also not really clear to me how adding a dot improves things, as the pollution is not about the name, it's about using a class where a dictionary suffices. If it actually needed a class it'd have been fine. |
It's still nice to have fewer globals and more context about the scope of a thing. |
There are currently no web platform constructors with static properties pointing at other constructors like If people were to conclude something like that is desirable to encourage, though (or to stop discouraging), I think de-legacying the existing Notably, ECMA-262 & 402 employ what’s effectively the same pattern (Intl & Temporal) and afaict it doesn’t look as though TC39 regards it as “legacy” (considering Temporal is new). |
The problem with This principle is about objects for which you can meaningfully have multiple instances. It explicitly does not discourage namespaces, for which Web IDL has a separate feature.
I do think patterns like Anyhow, it seems obvious that there's a point where namespacing can be taken too far, it just seems that different people/groups have differing opinions about what that point is. Perhaps we can find what is the lowest common denominator where we have consensus and form guidance around that? Remember that all design principles are rules of thumb, it doesn't mean that if we have a design principle discouraging a pattern, we can never do that, just that we need to make sure we're doing it for good reason. They are meant as guidance, not orders. For example, it occurred to me recently that
We don't discourage authors from adding a bunch of globals so they can avoid collisions with the web platform, but also between each other. The same thing applies to the web platform.
That is a red herring — if the actual problem is people adding functions on However, none of the examples in the first post fall under that category. The Filesystem API involves enough methods that it could (and should) have added its own namespace, and for the others there were already objects that should have had these methods (as static methods).
It's a balance, we also wouldn't want a proliferation of namespaces with only one function. I would encourage opening discussions in https://github.com/w3ctag/design-reviews/discussions/categories/q-a for these kinds of hairy API design issues, that's what that section is for :) |
I also find it very useful for discoverability to have grouping namespaces. I'm not an expert on every API; if I see a function named There are other usability arguments. If an API has several functions, and they all need to be relatively long to express their context, I have to use their full long name, or individually rename each one by storing them in new variables. If they're instead put onto a namespace object which can be long, freeing the functions to be short, I can trivially do a quick rename with
This is irrelevant/incorrect. Namespace objects are a different semantic class than classes with non-constructable instances; we have a whole separate keyword for them in WebIDL precisely because they act differently. Both of the issues you linked are about classes for which instances exist, but can't be constructed manually. Overall I'm with Lea here, especially with their reminder that these are design principles, not strict rules. New global functions are appropriate sometimes, but we have a lot of examples of frankly bizarre global functions that definitely should have been grouped onto either an existing class as a static method or a namespace object. ( |
This was brought up in our discussion of Native File System API w3ctag/design-reviews#390 (comment)
For context, this API adds three methods to the global scope:
showOpenFilePicker()
showDirectoryPicker()
getSandboxedFileSystem()
There has also been a bunch of other methods recently added to the global scope even though they would fit better somewhere else, such as:
structuredClone()
(no TAG review requested) which is inconsistent with other object creation & manipulation methods being added onObject
.reportError()
(why notError.report(error)
orerror.report()
?) (also no TAG review)createImageBitmap()
(why notImageBitmap.create()
?)We do have some guidance on namespaces here but not quite a principle, which led to the confusion in the design review linked above.
IMO adding three related API methods to the global scope should have never happened. Namespaces are not just for avoiding naming collisions, they also tie an API together, and allow easier exploration. When authors see a global method like that, they have no idea where it comes from, what other methods are there, which method is part of the same API as another method, whether it's author-defined or native to the platform etc, without looking it up in documentation. It also tends to produce clunkier names as the namespace effectively becomes part of the name.
Our guidance in that design review was:
However, APIs tend to expand even when their designers don't initially have "plans" on expanding them, therefore namespacing should be the default, and IMO a feature should need very strong justification for adding methods to the global scope.
Note that this is about avoiding new non-constructor functions on the global scope (e.g. functions like
setTimeout()
,parseInt()
,isNaN()
etc). It is not about avoiding new constructor functions on the global scope, which are fine IMO.The text was updated successfully, but these errors were encountered: