6 ms·
First, like the other commenters, I'm not in love with the perlish # syntax. I'm not sure why it was wise/necessary to add a previously illegal character as a p
by catpolice 8y ago
First, like the other commenters, I'm not in love with the perlish # syntax. I'm not sure why it was wise/necessary to add a previously illegal character as a prefix, rather than adding a "private" keyword, but I'm sure there's some reason.
Second, the example affords an opportunity to rant about a pet peeve of mine.
"Now ask yourself, how would you implement this class in JavaScript?"
I wouldn't. Listen folks: unless you're doing some weird/temporary debugging or you need to add some meta-functionality (e.g. logging) to an API that reeallly can't change, don't write getters/setters. If accessing or setting a property needs to do computation or have side effects, refactor so that it's done explicitly by calling a method. Anything that lets you attach invisible effects to normal syntax is an anti-pattern because it makes your code behave differently than it appears to, and therefore it's harder to reason about from the outside.
- werkjohann 8y agoI'm curious, is JS your primary language? Getters and setters have been around since ~2010 (other languages also make heavy use of them, like Obj-C). I suspect that you only see this as "invisible" because you first learned a language that didn't have getters/setters (and if you started with modern JS, or Obj-C the fact that properties aren't "dumb" would just be a given). Thoughts?
- deleted 8y ago[deleted]
- catpolice 8y agoI've been using languages with getters and setters for long enough to be familiar with them. There was a time that I thought they were useful and used them in my own code. But I've been burned enough, and thought enough about language design that I've come to avoid them. I say they're "invisible" because of the property illustrated by these two statements: foo.bar = 0; foo.baz = 0; One of these gets compiled/JIT-ed into a few assembly instructions and the other calls an O(n^2) function (with side effects) on a giant tree structure that foo is a part of. You can't guess which is which from looking at those, unless you've read the source for the foo class and memorized which properties are behind setters. They're "invisible" because you can't see when you're using one. A good property for code to have is that you can infer its semantics from its syntax with as little outside information as possible. When you read a line of code, you should get reliable clues about what that makes the computer do. You might not need to know /all/ the details - e.g. a method call encapsulates the details of an operation but the name (and documentation) give you a good idea of what's happening. The problem with getters and setters is that they do more than abstract away details, they cloak potentially important semantic information about code in ways that can be misleading. By contrast, if I read: foo.setBar(0); foo.baz = 0; I might not know all the details about what setBar does, but at least I have a clue that it's more complicated than the following line. I've been burned IRL by this kind of thing - I've seen very expensive getters mysteriously ruin hot loops (making what looked like a linear operation into an O(n^3) operation) and developers pulling their hair out because of setters that silently rejected values and all sorts associated headaches. Working in settings where they came up semi-frequently made me feel like complexity could be hidden anywhere, and reduced my confidence that I understood code that I read. JS and other languages with reflection have a whole host of associated issues. If a field gets refactored into a getter, it's suddenly not enumerable and will silently fall out of a lot of copy operations. Suppose you want to find every place that the setter gets called, so you grep for `.bar = ` but did you remember the myriad of other ways that setter could get called (e.g. foo["bar"] =, foo[someVariable] =, Object.assign, etc.)? In my experience, unless you have some extremely strong reason why you can't refactor or some very well-defined standards about when to use them, they're not worth the trouble they bring when code suddenly stops behaving the way you expect it to.
- IggleSniggle 8y agoThank you for writing this. I was on the fence about the utility of getters/setters in js, and wasn’t convinced by your first paragraphs. By the end (“if a field gets refactored into a getter, it’s suddenly not enumerable”), I was 97% on board.
- azov 8y agoGetters/setters are useful. E.g. maybe you had a class 'Box' with a field 'area'. Later you added fields 'width' and 'height'. What are you going to do? Change 'area' to 'getArea()' and break the API? Write extra code to ensure that you update area every time you change width or height? Or write a getter that returns width*height? I'd say getter is the cleanest option in many cases.
- pacala 8y agoThanks for providing a great example of useful getters. If you're computing a derived property that depends strictly on the data at hand and its attributes, getters are useful. When designing code, follow the Getter Idempotency Principle: * Given a reference to a data entity, invoking a getter on the data should always return the same result, no matter how many times it is invoked, no matter how the rest of the program or world state evolve. There are others situations. In those situations explicit code is preferable: * Change an attribute of a data. Do not use a setter, period. Create a data copy with the attribute changed instead. Even GUI toolkits nowadays are using immutable data patterns with great success, see React. * Use a data entity as a facade for reading data from a 3rd party API. Instead of using an object that pretends to be data but isn't via a heavy getter, use the 3rd party API explicitly to instantiate your immutable data graph. * Use a data entity as a facade for updating a 3rd party API. Don't use a setter, instead create an updated copy of your data, then explicitly invoke the 3rd party API with the updated data copy.
- mkozlows 8y ago"Breaking the API" is what we call "refactoring" in the (extremely common) case where your code is only used in a single project, and not part of a reusable library. And yeah, if you radically rework how you make your boxes work, that's a good time to do a refactor. But there's no reason to anticipatorily add in a bunch of complexity so that you don't have to convert a value to a property at some hypothetical future point. (I mean, among other things, if you put your area property behind get/set methods, you still have to go and change all the setters anyway, since in your brave new width 'n' height world, setting area is nonsensical. So your API still breaks, and you still need to change your consuming code. What was the benefit of the getters/setters then?)