A case against private variables and functions in JavaScript

(not sure if the pirate image + caption was created by Zeh Fernando, he sent it to me a couple years ago, forgot to ask him…)
Another polemic subject and something that I’ve been delaying for a while, I’ll try to explain how I got to my conclusions and I’m sure a lot of people won’t agree until they experience the same problem or realize how often it may happen on a real project and specially if it does happen with a code you don’t “own” and/or can’t change without compromising future updates… The advice is mostly about JavaScript (since it’s what I’ve been coding more lately and where I think the issue is bigger) but it also fits other languages.
Not so long ago I had an opposite opinion about this subject - I would always set everything to private and only changed to protected/public if needed - try to understand what made me change my opinion and pay attention every time you find yourself “tied” or doing lots of hacks just because you can’t monkey-patch a simple function/variable.
Private variables and functions in JavaScript
Unlike Java/C#/AS3/PHP, JavaScript doesn’t have keywords for defining if a variable/function/object is private/public/protected/final/etc (acessor keywords)… But since it has closures and each closure has it’s own scope we can emulate some of these access controls, a private variable is simply a variable declared inside an “unreachable scope”. Example:
// global variable
var lorem = 'ipsum';
(function(){
// the immediately invoked function expression (IIFE) will create a closure
// all vars and functions inside this scope will be "private"
var foo = 'bar';
console.log(lorem); // 'ipsum'
console.log(foo); // 'bar'
}());
console.log(lorem); // 'ipsum'
console.log(foo); // undefined
Because of this powerful and useful language feature a lot of people been using design patterns like the revealing module pattern to avoid polluting the global scope and to better organize their applications (including myself).
Private variables are usually a good thing since they avoid more problems than they cause, naming conflicts (two pieces of code using same name to store different info) can be very hard to track and it is very common to happen, that’s one of the main reasons why people use so many private variables, but like most things in life it also has it’s drawbacks and I’ll show a few cases where it can be an issue and how to avoid it.
Other languages
Java/C#/AS3/PHP have the concept of a protected class member, the protected member is like a private one but that can be overwritten by a child class. By using protected on all your members instead of using private you increase the flexibility of your code, if the user wants to change the default behavior of a method he can simply extend the class and overwrite it. The sad part is that private variables in JS can’t be overwritten, not even by child classes.
JavaScript is about monkey-patching
I saw this quote on Twitter a few weeks ago and I must agree:
Monkey patching in javascript is called “writing code.” – Paul Hummer
One of the greatest things about the language and what makes it so fun to code is that you can get very creative about how to solve certain problems, it gives you freedom enough to do to terrible and amazing things at the same time, you don’t feel as constrained as on a static strong-typed language…
Private variables are against monkey-patching and can make your code impossible to be reused/tweaked without a lot of manual work, one of the main reasons for that is because the variable is inside a scope that you can’t reach, not even if you extend the object, it’s like if all your variables and methods where private final (you can’t overwrite it by any means), and this process isn’t “scalable” at all.
Unit Testing
Another problem with private members is that it is harder to test them. I usually tend to think that private members doesn’t need to be tested since the public interface is already being tested, but sometimes that may not be the truth (think about a variable that holds state info or some property that needs to be swapped with a mock object). Exposing larger chunks of your codebase (as a pseudo-private member) may help you to increase your unit tests coverage and also to simplify the tests.
Solution
What I’ve been doing on some projects is to expose most of the methods and properties of objects that I know that are going to be reused in multiple projects (like open-source projects), if it isn’t meant to be called by other people or is subject to changes I simply start the name with an underscore (_), it’s a sign that you shouldn’t expect the function to be there forever (pseudo-private) but it allows the user to easily “hack” the original code to make it work as he want. I believe that code that is easy to edit is way more valuable than code that has lots of settings to try to fit all scenarios, there will be a always an edge case that isn’t covered or the application will be so complex/bloated that nobody will want to use it.
I’m not saying you should pollute the global scope with thousands of variables (ie. keep it as properties of an object, like if it was a namespace), but consider exposing things that you may want other people to be able to tweak/reuse, been doing it for a while and it saved me some headaches.
A real case where monkey-patching “saved the day”
I wanted to add JSON support to Syntastic Vim but JSONLint output contained multiple lines and making syntastic to understand the format was kinda tricky so I decided to do a pull request on JSONLint itself adding an option for toggling the error message format, if the method parseError wasn’t exposed on the lexer object the change could be way more complex. That’s the difference of taking 20min to release a new feature that affects 2 open-source projects (and that boosted my productivity a lot since now I can validate JSON files at each save) or taking 1 day or more or not even doing it…
A real example where private members made things more complex than needed
I wanted to generate HTML documentation for AMD-Utils based on a few markdown files that I had on the repository, just because it would be quicker to browse it, so I decided to code a simple generator that would read all the markdown files, process them and output to HTML. The only issue is that I got used to Github flavored markdown syntax and all my markdown files were written using this syntax, so I had to tweak the markdown parser a little bit to act like the way github renders the README files (it is not the same as the JavaScript preview) which means I had to do some pre-processing before showdown did “it’s magic” and also edit showdown source code to comment out a single line, yes, a single line!! Because Github doesn’t hard-wrap line breaks on README files while the JS preview does it.
If showdown exposed all the methods I could override the _FormParagraphs method or do some magic before and after the original _FormParagraphs() was called - basically convert all the line breaks into tokens before _FormParagraphs() and untokenize it afterwards - and I could keep the library as a normal npm dependency - so I could update it as long as they didn’t replaced the method functionality or renamed it - things could still go wrong but the “damage” would be controlled. It would also reduce the chance of updating the file without noticing that it had changes that diverged from the original lib (since the changes are on a separate file).
Another option would be to fork the project and add support for toggling the GFM settings, but that would take way more time than I wanted to spend and I would need to wait the change to be merged and pushed to npm (if the pull request was even accepted).
Conclusion
I’ve seen the same thing happening over and over again on many projects (in many different languages) and that’s why I’ve been favoring the “expose ALL the things” approach lately.
Notice that I still create many local variables and functions since it’s less verbose and not all code should/need to be heavily tweakable (I can edit the source code of my app as much as I want, no need to monkey-patch) but if you expect a lot of people to reuse your code please make it easier for them to patch it. You should warn people of the danger but assume they know what they are doing, we are all grown ups right?
Understand the reasoning behind a “best practice” so you can decide if it does apply to your case or not.
That’s it!
