-
Notifications
You must be signed in to change notification settings - Fork 26.5k
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
Why are underscore prefixes for internal methods of a React component considered bad? #1024
Comments
They give people a false sense of "private"ness that could lead to bugs. |
Okay. Even though JavaScript doesn't support private variables, I still think the underscores help communicate the programmer's intent. If possible, @lencioni could you give an example of this false sense of "private"ness that lead to a bug? Or maybe a scenario where the underscores would confuse another programmer and lead them astray. I'm not disagreeing with you, I just want to have a better understanding of how this rule came about. |
Private means inaccessible. Your intent to privacy is irrelevant if the value is reachable, ie public. For example, npm broke node once by removing an underscore-prefixed variable. |
If the variable wasn't prefixed they would have used it either way, and node would have still broke. When a meant-to-be-private variable is prefixed with an underscore there is at least one line of defence (a weak one, granted) preventing its use. Whereas when a meant-to-be-private variable isn't prefixed with an underscore, there is no line of defence (not even a weak one). |
@ttmarek yes, but had they not prefixed it with an underscore, nobody would have been fooled into thinking it was actually private, and there would have been test coverage, and changing it would have prompted a semver-major version bump. An underscore is in no way, whatsoever, a line of defense - it's just an incorrect assumption some developers make about a language that simply does not have the concept of privacy outside of closures. |
@ljharb So, in other words: underscore prefixed methods might confuse programmers from other languages into thinking that JavaScript supports private methods. Right? |
That, or programmers who think that convention is the same as privacy. |
Okay, cool. Thanks @ljharb, I see what you're getting at. Do you think its worth adding a short blurb about this under the third bullet in react#methods? |
I think so. I'd be happy to merge a PR that does that. |
Sure, more helpful info is always good. |
My favorite reason: Private stuff tends to change without warning, and without advertising as a breaking change. The problem with that is that a lot of code could break without warning, because EVERYBODY ignores underscores:
Admit it. You've done that, too. |
I think @lencioni is perfectly correct by saying
... but I'd like to elaborate on why. In programming languages in general, the point of having a private property (as opposed to having all properties public) is so that you can trust a private variable not to be changed by outside code, unlike public properties which you need to assume can be changed by outside code at any time. If you're absolutely hell bent on using inheritance in JavaScript, you don't have private properties. It is not a feature of inheritance in JavaScript. Everything is public. The logical thing to do is therefore to treat every property as a public property (because it is) - your code needs to assume that it can change at any time. You can call a property _myProperty or __DONTFUCKINGCHANGEmyProperty but that doesn't make that property private. It's public. Private does not exist in the object composition model that you have chosen. You're trying to reap the benefits of a feature that you do not have. If you write your code in a way that assumes that your public-property-with-special-name-convetion does not change, you're just fooling yourself. The fucker can change at any moment, just like any other of your properties. (Sidenote: This is React, and the following is not an option there, but in general, unless you need to create more than 50000 items per second, I personally think that you should consider using factory functions (https://www.youtube.com/watch?v=ImwrezYhw4w) instead of inheritance for creating objects. They have real privacy and do not have this problem) |
Hi everyone, I've landed here after searching for an ESLint rule for enforcing this convention. Since I haven't found any, I've written one myself. In case anyone is interested, here it is: https://github.com/buildo/eslint-plugin-method-names |
@gabro there's |
@ljharb yes, I will. Not sure whether to restrain the scope to underscores or to keep it generic as it's now, but I guess we'll figure it out in the ESLint issue. |
@ljharb FYI here's the proposal eslint/eslint#7065 |
I actually don't agree with this. I think underscore is a good way to indicate a private method. Even if the JavaScript runtime does not enforce it. However, given React's imperative style, it doesn't matter much. In 99% of the cases, most internal methods (other than the React callbacks) are typically private. |
It's not actually private if it's not enforced - as such, it constitutes either a mistake or a lie. |
The underscore convention has been successfully used in the python world for years. |
Indeed, it is a convention - one that causes confusion by implying privacy where none exists. There are certainly many ecosystems that have used convention as a boundary, but in the JS ecosystem, we've learned that convention is not strong enough of a boundary. If it is possible, people will do it, and any damage caused is the fault of the software author - never of its users. "Use at your own risk" is a reckless and hostile thing to claim as a software author - I, and this guide, choose to instead treat our ethical obligation to our users with higher respect than that. |
I believe this is subjective (so far nobody gave me any convincing arguments of the assessments like "not strong enough"). I don't think that I - and the others who disagree with these assessments – are being "reckless", "hostile" or disrespectful when we chose not to use workarounds to hide the internal state of our component to our consumers (I have not found a "private" JS pattern readable and handy). Is it my responsibility if someone code is broken because he used a variable I told him he should not? The general argument is that internal state should be protected. To quote again the python community: "who are you protecting the attribute from?". I think that is just really a question of preference. |
Yes, as an author, it's your fault if your users are broken, even if they disobeyed your instructions. It's your obligation to protect them from themselves - the "we're all consenting adults here" philosophy doesn't really work. |
This thread is very interesting, I think it goes beyond the actual "underscore" issue. In my opinion, most of the open source world works by asuming that "we're all consenting adults". SemVer is a weakly enforced convention (lib author decides when to change major or not) and yet packagers rely their whole dependency-update-logic on the assumptions that the developer was careful enough to follow the versoning rules. I understand that everything is publicly accesible in JS, but that doesn't mean it has to be part of the public API of your library. IMHO, if a consumer is using a piece of code that doesn't appear in documentation and/or has an underscore prefix, then he should be treated as a consenting adult. |
@scarmuega Just override the rule. This style guide is strongly opinionated, in particular when it comes to that topic. After some thoughts this is actually fine, we need Crockfords. You don't have to take the style guide entirely, and should not take it blankly. |
I think "relies on a feature that does not exist in the language" is a cause of inappropriateness, not a definition of it. If it is, then
is a bit tautological. The reason I am asking is because I am trying to understand the problem: what is the bad concrete thing that may happen if I use
I have a hard time believing this. I mean this is false anyway because at least I do. What is the proportion of people who do not respects it, you know better than me, but I am humbly surprised it is so high. I understand people could do it because they don't know what it means. However:
That looks like very dumb to me. You know this is unreliable and you still rely on it? You can blame me, but I will not take the responsibility if you put your cat in my microwave despite me telling you that you should not put cats inside my microwave. And I am still not planning to add cat sensors inside (at least not for this reason). @ericelliott In my opinion it is less a question of consensus than awareness. What I mean is I don't care if my consumer agrees with me using Also, thank you for reporting your experience. I have actually been looking for such examples. This indeed looks like a very bad case. You presented a lot the consequences of the issue, however little in how you determined the cause. If you are allowed to, could you flesh it out a bit more? Just to be clear I entirely trust you when you say |
The
But all the other methods that internally depend on this private stuff needs to be documented though.
Well sure, in the updated example, but it still suffers from the issue that any method that depends on the internal state would have to be defined in the constructor, or use a method that exposes this internal state, which again, in turn leaks this internal state.
Which means you should distinguish between implementation details and intentional APIs.
What sequence of lesson are you speaking of? last time I checked, security was not packed into a single all-in-one course, it is a discipline with far and wide aspects and concerns. Anyways, attack surface does not apply to object interfaces that is intended to provide separation of concern, relying on object encapsulation for security/secrecy is absurd, it is intended to provide visibility. Further more, it is obvious that using underscore does not provide any mechanisms for visibility, and is merely a conventional contract around API stability, if you're writing secure applications without understanding the basic constructs of the language you're using, I will be concerned about the people who might rely on such work. |
Yes, but you're documenting and testing only their public API, so you can safely ignore the details of the internal stuff. That's what black box means.
Yes, they would be defined in the constructor. In JavaScript, we call those "privileged" methods, because they do have access to the internal state for their implementation details.
There is no need to do that, because the right way is to use privileged methods.
Yes. And no. In OOP, the canonical way to "distinguish" between implementation details and public APIs is to encapsulate the implementation details so you can't get at them at all from the public API. The core idea of OOP -- the very essence of what OOP is -- the concept that objects are encapsulated, and that they control access to internal implementation details and private state. OOP = encapsulation + message passing (method calls, etc...) -- the rest is just icing to help us structure relationships between objects (facilitate encapsulation and message passing). If you're not doing encapsulation, you're not doing OOP. If you remove encapsulation from the list, you've lost the essence of why OOP exists in the first place. Quoting Alan Kay now (the founder of OOP):
More in-depth:
|
On Security & EncapsulationLesson 3a in software security: Never assume you know who is attacking whom, what their goals are, or even that the attacker is actually the user in question. Your job is twofold:
The wise defensive coder understands that 1 and 2 are really the same rule written in different ways. The whole point of limiting the attack surface is that we don't know what the attacker could possibly use it for, so give them as few attack vectors as possible to add to their weapon inventory. All API surface is attack surface. IoT teddy bears and baby monitors are being used to launch DDoS attacks. Web browsing sessions are being used in clustered botnets -- stealing the CPU power of users of your app to crack passwords or encrypted data. You have no idea what attackers can or can't do with your stuff, so don't give them more stuff than you need to. In case you don't get it yet, API surface area = weapons for bad guys. Don't leave them laying for attackers to pick up, even if you think you know it's safe. When it comes to security, what you think you know and reality are usually two very different things. If you point a group of hackers at some seemingly innocuous app and give them the challenge, "figure out how to use this for evil", they will amaze, delight, and horrify you with their creativity. Every software dev should probably read back issues of 2600 now and then just to develop a healthy insecurity about app security. |
Eric, the privileged methods that you speak of is a hack, it breaks jsdoc, jstags, doesn't play nice with flow type, and a fair bit of other tooling. I fail to see how using a hack, regardless of the cost --and mind you, it is pretty high in this case-- is better than delegating that task to your linter? The argument of security does not hold, as soon as you're executing untrusted code in the same context, all bets for security is off. Executing untrusted code in the same context and expecting closures to provide you meaningful secrecy or security is absurdly naïve.
Canonical according to what or who? Most languages that pioneered and championed OO allows you to by pass the visibility mechanism in one way or another. So arguing that canonical OO requires strict encapsulation is false. Your comment about security is absolutely true but also equally irrelevant; we are not talking about IPC APIs, we are talking about Object APIs of the code that runs in the same context, and as I said before, once you run untrusted code in the same context, all bets for security are off. |
"doesn't play nicely with tooling" is a secondary concern; inferior tooling shouldn't impact the way you write your code. A linter lets you regulate your own code. You can't use a linter to prevent other people from accessing underscored properties in objects you hand out to them. All code you didn't write is "untrusted". |
Sure, but throwing out tooling without a good reason is also a terrible decision. On a side note, the argument that tooling shouldn't impact the way you write code is kinda ironic considering we are discussing the configuration for a tool that does exactly that, not only it impacts but explictly dictates how you should write code.
That is a good point. But people should read the docs, regardless of if they share the same QA infrastructure or not, and the docs should clarify what parts are considered unstable/private, and if people still decide to use unstable APIs, as @QuentinRoy said, it is their cat and their problem, changing the microwave and throwing away tooling to prevent people who choose to shoot themselves in the foot is a poor trade off.
Which is the whole point, if you're running untrusted code, all bets for secrecy are off; but that is an orthogonal concern anyways, Encapsulation and Visibility -- and by extension the use of underscore as convention to denote privacy -- is not a security concern, it is for separation of concern and differentiating implementation from the interface. |
If tooling impacts your ability to write code, then that's the best possible reason to throw it out, immediately. eslint does not impact your ability to write code (you can override it any time you like), it improves your ability to write code. There is nothing about eslint, or any eslint config, that forces you to write code a certain way; you can always override it. "People should read the docs" is irrelevant - people should do lots of things. What people can do is what's important. Nobody should be going the wrong way on a one-way street, but because it's possible, you definitely need to be looking both ways before crossing it. Again, running untrusted code does not erase "all bets" for secrecy and robustness - it only erases a large category of them. The only thing that makes "all bets off" is if untrusted code runs before your code, at which point, essentially nothing can be secured. The interface contains every observable aspect of it. If |
@ljharb You have a very peculiar definition of impact, but I agree that linting is intend to improve code, which effectively impacts it.
That is a pretty bold and risky proposition, and unless the same guide forces one to freeze builtins, use Even then if you take all the necessary precautions and measures, running untrusted code in the same context that you have secrets is foolishly naïve. It is that simple. But then again, that is all irrelevant. I must iterate that Encapsulation and Visibility -- and by extension the use of underscore as convention to denote privacy -- is not a security concern, it is for separation of concern and differentiating implementation from the interface.
Interfaces are protocol and contracts and like any other, they come with requirement and stipulations, if one opts to ignore the warning and recommendation of the API then they're on their own; if one does dumb things they end up in stupid situations. |
Guys, I am sorry but I think this is obvious for everyone that this is not going anywhere and is useful for neither the participants nor the readers. |
hmm.. As I have understood it, this is basically a discussion about whether or not underscores should be allowed for method names. I kind of agree with the whole discussion about it not being private etc. I have however seen ALOT of React developers using underscore for states which are only UI states ex. the state "collapsed" in a menu component. This is something that I thought made sense and was doing myself until I started using this config and got a ton eslint errors. Up to this day, I still think this a good use of underscores and makes the code easier to reason about. But this is an aspect that not very many people seem to agree with or put a thought too over here. Just wanted to add my 5 cents to the discussion. |
* Updated Scss file structure * Added nvmrc file. Required node version is added, easier to use with nvm preinstalled. * Scss-lint task is added and now running on the fly * Resolved scss-lint issues in scss files, global scss-lint config edited * Support added for nesting selectors with parents (resolved issue with bem selectors, when writting scss code) * Added gitattributes file * Automated image optimization added * Added functionality, which checks if all dependencies are up to date. If not - gulp automatically installs them. * Added npmrc file (dependencies are automatically saved, even without defining flags, when installing and current version is defined in package.json) * eslintrc file is added * starter-template folder is removed * sass errors are not breaking gulp tasks * WebPack is running from gulp * Added gulp task cacheing for better task performance (faster repetitive tasks) * Added ability to make scss-lint errors silent
I experimented with all approaches.
|
Once class instance fields reach stage 4, you won’t have to compromise on it. |
Once class instance fields reach stage 4, then I'll just find and replace my underscore prefixes ;-). |
@shelacek and then that will be a semver-major/breaking change for each file in question. As long as you understand that, then go for it. |
@ljharb Yeah, this is the core of this discussion. We have 2 groups of people here. For one, this will be change in the revision, because there is virtually no change in API. For the other, this will be a major change simply because it does not recognize this convention. Nevertheless, thank you for your opinion. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Private class field is now supported by some runtime. If you're stuck in the runtime that doesn't support private class field, and you want to prevent class consumer to access the private fields and methods, below are some workaround:
Example for javascript runtime that doesn't support private field natively: person.js: let persons = new Map()
// "private method"
function getAge(self) {
return persons.get(self).age
}
class Person {
constructor(name, age) {
// public field
this.name = name
// "private field"
persons.set(this, { age }) // private field
}
// public method
speak() {
// calling "private method"
console.log('inside:', this.name, getAge(this))
}
}
module.exports = Person consumer.js: let Person = require('./person')
let alice = new Person('Alice', 18)
alice.speak()
console.log('outside:', alice.name, alice.age) console log:
|
You'd want to use a WeakMap, not a Map, to avoid a memory leak, but yes, private class fields are a 100% safe and ideal approach for something you might otherwise stick an underscore on. |
The third bullet under react#methods says:
I was just wondering what the reasoning behind this was?
The text was updated successfully, but these errors were encountered: