Tuesday, January 28, 2014

Ember.js is driving me crazy

For the past few months I've been working on a project with a fairly complex interactive web interface. This required me to venture into the wild and unpredictable jungle of Javascript development. I was totally unprepared for what I would find. Soon after starting the project it became clear that just using JQuery would not be sufficient for my project. I needed a higher level Javascript framework. After a doing a little research I settled on Ember.js.

The Zombie Code Apocalypse

Ember was definitely a big improvement over straight JQuery, and allowed me to get some fairly complex UI behavior working very quickly. But recently I've run into some problems. The other day I had a UI widget defined like this:

App.FooController = Ember.ObjectController.extend({
    // ...
});
App.FooView = Ember.View.extend({
    // ...
});

It was used somewhere on the page, but at some point I decided that the widget was no longer needed, so I commented out the widget's markup. I wasn't sure whether we would ultimately keep the widget or not, so I opted to keep the above javascript code for the controller and view around for awhile so it would be easily available if I later decided to re-enable that UI element.

Everything seemed to work fine until a few days later when I noticed that another one of my controls, Bar, was not being populated with data. After spending hours trying to figure out the problem, I finally happened to comment out the unused code for the Foo widget and the problem went away. WTF?!? Why should this have anything to do with the functioning of a completely unrelated widget? This makes absolutely no sense to me, and it completely violates the natural assumption that the controller and view for two completely unrelated controls would have no impact on each other. I would have liked to know the underlying cause, but I didn't want to waste time with it, so I just removed the code and moved on.

Spontaneously Changing Values

Maybe a week later I ran into another problem. Some data was changing when I didn't expect it to. I looked everywhere I could think of that might affect the data, but couldn't find anything. Again, I spent the better part of a day trying to track down the source of this problem. After awhile I was getting desperate, so I started putting print statements all over the place. I discovered that the data was changing in one particular function. I examined it carefully but couldn't find any hint of this data being impacted. Eventually I isolated the problem to the following snippet:

console.log(this.get('foo'));
this.set('bar', ...);
console.log(this.get('foo'));

The first log line showed foo with a value of 25. The second log line showed foo with a value of 0. This is utter madness! I set one field, and a completely different one gets changed! In what world does this make any shred of sense? This time, even when I actually figured out where the problem was happening I still couldn't figure out how to solve it. At least the first time I could just comment out the offending innocuous lines. Here I narrowed down the exact line that's causing the problem, but still couldn't figure out how to fix it. Finally I got on the #emberjs IRC channel and learned that Ember's set function has special behavior for values in the content field, which foo was a part of. I was able to fix this problem by initializing the bar field to null. WAT?!?

I was in shock. This seemed like one of the most absurd behaviors I've encountered in all my years of programming. Back in the C days you could see some crazy things, but at least you knew that array updates and pointer arithmetic could be dangerous and possibly overwrite other parts of memory. Here there's no hint. No dynamic index that might overflow. Just what we thought was a straightforward getter and setter for a static field in a data type.

Blaming Systems, Not People

Before you start jumping all over me for all the things I did wrong, hear me out. I'm not blaming the Ember developers or trying to disparage Ember. Ember.js is an amazing library and my application wouldn't exist without it or something like it. I'm just a feeble-minded Haskell programmer and not well-versed in the ways of Javascript. I'm sure I was doing things that contributed to the problem. But that's not the point. I've been around long enough to realize that there are probably good justifications for why the above behaviors exist. The Ember developers are clearly way better Javascript programmers than I will ever be. There's got to be a better explanation.

Peter Senge, in his book The Fifth Discipline, talks about the beer distribution game. It's a game that has been played thousands of times with diverse groups of people in management classes all over the world. The vast majority of people who play it perform very poorly. Peter points out that we're too quick to attribute a bad outcome to individual people when it should instead be attributed to the structure of the system in which those people were operating. This situation is no different.

Like the beer distribution game, Javascript is a complex system. The above anecdotes demonstrate how localized well-intentioned decisions by different players resulted in a bad outcome. The root of the problem is the system we were operating in: an impure programming language with weak dynamic typing. In a different system, say the one we get with Haskell, I can conclusively say that I never would have had these problems. Haskell's purity and strong static type system provide a level of safety that is simply unavailable in Javascript (or any other mainstream programming language for that matter).

The Godlike Refactoring

In fact, this same project gave us another anecdote supporting this claim. The project's back end is several thousand lines of Haskell code. I wrote all of the back end code, and since we have a pretty aggressive roadmap with ambitious deadlines the code isn't exactly all that pretty. There are a couple places with some pretty hairy logic. A few weeks ago we needed to do a major refactoring of the back end to support a new feature. I was too busy with other important features, so another member of the team worked on the refactoring. He had not touched a single line of the back end code before that point, but thanks to Haskell's purity and strong static type system he was able to pull off the entire refactoring single-handedly in a just a couple hours. And once he got it compiling, the application worked the first time. We are both convinced that this feat would have been impossible without strong static types.

Conclusion

I think there are a couple of interesting points worth thinking about here. First of all, the API chosen by Ember only hid the complexity, it didn't reduce it. What seemed to be a simple get() method was actually a more complex system with some special cases. The system was more complex than the API indicated. It's useful to think about the true complexity of a problem compared to the complexity of the exposed API.

The second point is that having the ability to make categorical statements about API behavior is very important. We use this kind of reasoning all the time, and the more of it we can do, the fewer the number of assumptions we will have to question when something isn't behaving as we expect. In this case, I made the seemingly reasonable categorical assumption that unused class definitions would have no effect on my program. But for some reason that I still don't understand, it was violated. I also made the categorical assumption that Ember's get() and set() methods worked like they would work in a map. But that assumption didn't hold up either. I encounter assumptions that don't hold up all the time. Every programmer does. But rarely are they so deeply and universally held as these.

So what can we learn from this? In The Fifth Discipline, Senge goes on to talk about the importance of thinking with a systems perspective; about how we need to stop blaming people and focus more on the systems involved. I think it's telling how in my 5 or 6 years of Haskell programming I've never seen a bug as crazy as these two that I encountered after working only a few months on a significant Javascript project. Haskell with it's purity and strong static type system allows me to make significantly more confident categorical statements about what my code can and cannot do. That allows me to more easily build better abstractions that actually reduce complexity for the end user instead of just hiding it away in a less frequented area.

24 comments:

None said...

"console.log(this.get('foo'));
this.set('bar', ...);
console.log(this.get('foo'));"

Almost certainly some "onchange" event or binding being triggered on the bar change that updates foo.

Javascript frameworks built around objects calling .get(identifier) and .set(identifier, value) cause your application code to degenerate into total spaghetti pretty quickly. Tracing these pseudo invocations in code becomes impossible.

mightybyte said...

None: In this case it wasn't any kind of asynchronous onchange event. The value was actually being changed by the this.set('bar', ...) call.

Joey said...

avoid frameworks with magic.

Peter Wagenet said...

I'm on the Ember Core Team and I'm a bit confused by some of your complaints. It seems like you've either encountered some bugs, or there's more to the situation that you're explaining. Unfortunately, it's also very possible that you were provided some bad explanations about why things behave the way they do. I'd love to see some more details on these issues, especially in a JSBin or JSFiddle that I could play around with. At this point you might be over it and not care to help out, but if there's still any interest, I'd love to work with you.

Anonymous said...

@Peter Wagenet I think the confusion explains greatly on what the issues with EmberJS. Ember is not straight forward and is harder to understand than others, like AngularJS. This was my perception when deciding which framework to use. AngularJS feels natural.

Erik said...

Hey, I'm Erik, an Ember.js core team member.

I'd be happy to help you track down the issues you came across and explain to you what is going on.

You can reach out on Freenode or Twitter, I'm @ebryn. Otherwise, email me at erik.bryn -at- gmail.

mightybyte said...

@Peter Wagenet and @Erik

For "Spontaneously Changing Values" here's a jsfiddle that demonstrates the problem exactly. I asked about the problem on IRC on October 29 and mmun gave me a solution.

My problem was caused by the special treatment of the content field. I was setting foo, but Ember was actually setting content.foo because I didn't have a "foo: null" declaration in my class. This meant that when I did a set to content, foo got magically cleared out. This is absolutely Ember's fault because Ember devs made the decision to treat content in a special way. Now you may have good reasons for it, so I'm not actually criticizing that decision. Design decisions are usually complex tradeoffs and I'm not going to claim that you made the wrong one. But I will claim that it violates the principle of least surprise and led to an incredibly frustrating situation.

Regarding the zombie code error, I don't have the time or desire to investigate it. All I can say is that it happened exactly as I said. I think I commented out the template references with an HTML comment, but it's possible that I completely deleted it from the template. Then I kept the controller and view definition around exactly like I showed in the post. Deleting the code or commenting it out would fix the problem with a completely different control. There's an outside chance that maybe after some other modifications that unused code ended up with an error condition. I don't remember. If that was my problem, I place the blame squarely on Javascript's dynamic typing, and possibly on Ember if it did something to execute this code that seemed like it should be unused. If that wasn't what was happening, then I really have no idea what might have been causing it. But again, it was massively frustrating to see what seemed to be quite reasonable expectations violated.

Peter Wagenet said...

@mightybyte:

What you're describing in the first situation, is the behavior of ObjectController as described here http://emberjs.com/guides/controllers/representing-a-single-model-with-objectcontroller/. I understand that this may have caught you by surprise, but a ton of Ember's power comes from its ability to do things like this. I would assume this is part of the reason you chose Ember in the first place.

I think in the second case, you probably tried to comment out Handlebars code with HTML comments. The problem with that, is Handlebars is not aware of the the surrounding syntax. Your end result is that the *evaluated* Handlebars is wrapped in the comment.

Again, I understand your frustration, but it seems like you're blaming Ember for behavior without attempting to first fully understand it. I've never used Haskell or any functional programming language. I imagine that if I jumped in, I would find many things that would surprise me and probably enough for me to write a "Haskell is driving me crazy" post. However, this would almost certainly be because of my shortcomings and unfamiliarity with its style, not because there was something fundamentally wrong with it.

(Reposted due to formatting errors.)

Nathan Hammond said...

@wagenet @ebryn To address the setting of the `content` issue we could add Ember.Logger.(log|info|warn)("The content object is magical. Resetting it may result in behavior you don't expect.") to this function:
https://github.com/emberjs/ember.js/blob/master/packages/ember-runtime/lib/system/array_proxy.js#L137

There might be a better solution that will have fewer false positives (model being set for the first time, etc.) but there is real value here. One of the first things that I coach new devs on our team to do is "make sure you can explain why you're setting the `content` property if you're doing it." (It always gets flagged in code review.)

Anonymous said...

To be fair, the documentation for ObjectController says at the very beginning:

"It is intended to wrap a single object, proxying unhandled attempts to get and set to the underlying content object"


The docs on ObjectProxy also metion it:

"Ember.ObjectProxy forwards all properties not defined by the proxy itself to a proxied content object."


I personally wouldn't give up the proxying behavior, which is hugely beneficial, in order to get around that confusion.

The fact that 'model' is an alias for 'content' is one of those emberisms you learn after you work with the framework for a bit.


To a certain extent, these are the downside of convention over configuration frameworks (or really any new thing you're learning, but I found this to be especially so for Ember). Sometimes the conventions aren't totally intuitive so you have to be OK with the fact that when you're starting out you'll see surprising things that only become clear later. When I was starting with Ember I felt like this pretty frequently. The more I work with it, though, the more I enjoy the way the whole package fits together. Hope you don't give up on it just yet.

Dumitru Ungureanu said...

That's why "I can quickly do JavaScript too" works only up to a point. After that, JavaScript requires the same seriousness it would take to learn Haskell, for example.

mightybyte said...

@Anonymous

"proxying unhandled attempts to get and set"...that's about as clear as mud. Might be clear if you already know, but not at all clear if you don't.

The ObjectProxy phrasing is much clearer. But this is in the API documentation, which people are much less likely to read. I did refer to these API doc pages significantly, but for things like Ember.Select where it was obvious what I was going to find there. I couldn't find anything in the guide indicating that ObjectProxy/ObjectController is the place where I'd find out about this special case behavior of get and set. I'd actually expect to find the get/set behavior defined in the Object class. So I just don't think it's reasonable to expect users to know to look in ObjectProxy to find out about this weird behavior of a ubiquitous concept.

Yes, some amount of this pain is to be expected when learning any new significantly complex system. But my observation is that you see a LOT more of it in Javascript than in Haskell.

@Dumitru

I totally get what you're saying, but I actually think that's actually backwards in a way. It definitely seems like Javascript is easier to get started with than Haskell in terms of understanding concepts and making progress out of the gate. But in another way, I think there's an argument that Haskell takes less seriousness because it's constantly pushing you in the direction of good design. To be effective with Javascript you have to learn all kinds of best practices that people have learned after years of the school of hard knocks. I'm really not interested in doing that because my experience with Haskell has led me to believe that much of that best-practice knowledge is accidental complexity. Yes, there are still a set of best practices to learn in Haskell, but that set is much smaller than the corresponding one for Javascript because Haskell has the right defaults. So I think it's easy to be a casual Haskell programmer and still effectively contribute to large complex projects.

Dumitru Ungureanu said...

JavaScript and Haskell are similar in this way. Each one is easy to get started with, but it takes some time to write quality code in either of them, and it's a challenge to choose a path or another when coding in either of them.

Fredrik Carlén said...

Oh, come on, you Javascripters. Are you so seriously afflicted by Dunning-Kruger that you have to defend the language - warts and all - by saying that Javascript and Haskell are created equal? I sit on both sides of the fence, and I would trade my JavaScript sessions for Haskell any day of the week. All my bugs are to do with JavaScript. Strange? Not at all - not all languages are created equal, and Javascript has evolved from a little industry toy language whereas Haskell has some serious research behind it - ever expanding. I am ashamed to say I work in JavaScript when I encounter this kind of blatant ignorance. I do think that the language has its merits, though, and I do think that it's probably for the best that it's not typed strongly and statically. But can we agree it was not designed for coding big frameworks? It's possible, yes, but hardly advisable. And I really am sorry, but any retort to this of the less informed kind will be filed under "ignorant" and ignored. Inform yourselves, for the love of all that is good. People in CS and the programming industry will take you much more seriously if you concede to the language having its flaws - I see Haskellers do this all the time. But I suspect that being a Javascripter comes with a lot of identity building around the language, that it's hard to do this.

Dumitru Ungureanu said...

@Fredrik Carlén: I'm absolutely convinced that if this was a potatoes vs. potatos talk, it wouldn't matter which side, you'd still be flaming pointlessly.

Less bugs recipe: grow up, learn better programming and do it so in more than one language. It works every time. Uni-dimensional CS guy is something of the past.

David said...

Dear author. I believe your hate towards Javascript is misplaced. Often I've encountered people complaining about Javascript but what they were complaining about is GUI programming.

Yes, Javascript the language has its oddities and flaws but the problems you've talked about are issues that occurs whenever there is an event driven GUI system.

It doesn't matter if you use C, Javascript, Ruby, Java, etc. When you have complex interactive GUIs you encounter that type of issues (it's one of the cons of the Observer patterns).

I've not used Haskell enough but you should try to create a desktop application of similar complexity with a GUI library of your choice and using Haskell as programming language.

I believe you will encounter similar difficulties.
Give it a go and let us know.

mightybyte said...

@David

I have definitely considered the idea that GUI programming might be inherently more complex. I don't have a lot of GUI programming experience yet, but there are definitely things about it that seem tangibly different enough from, say, backend development that there might be legitimately more complexity.

However, I can assure you that my negative opinion towards Javascript is not misplaced. GUI programming complexity might account for some of my issues, but it does not account for all of them. The issues I encounter almost always could have been significantly less painful in a language with purity and strong static types. In fact, just yesterday three people in my company were blocked by a bug. One person had recently done a major restructuring of our HTML markup. Two days ago he discovered a problem with a GUI control. Since we didn't have any other related changes that should have caused the problem he tried for some time to figure it out. Finally he gave up and threw it over to me since I wrote all the Javascript code. Another coworker also noticed the problem, which prevented her from working on certain tasks. Finally after trying to trace the source of the problem for awhile I finally figured out that we were simply missing the jquery-ui import!

This bug would never have happened in a language like Haskell. This is not my opinion, it is a fact. I can point to all kinds of other problems that either never would have happened or would have been resolved much more quickly in Haskell. You have not used Haskell, so how could you possibly know? Some might argue that it's our limited Javascript experience that led to this bug. But that's an invalid argument because in Haskell the bug wouldn't have happened to anyone of any experience level.

Dumitru Ungureanu said...

So far your complaints are not with JavaScript, for a JavaScript programmer, or any programmer for that matter, they are about your low level expertise in testing and debugging in JavaScript.

The missing library bug would definitely happen in Haskell too:

-- import System.Info

main = do
print os
print arch
print compilerName
print compilerVersion

This fails with "Not in scope: ..." messages that will not point you to System.Info any more than your browser console errors will point you to jQuery UI.

It fails in a different way, but still, that's the job you have to take care of both in Haskell and in JavaScript.

If you don't have the time or the will to get a better grip on JavaScript,that's fine. Just be honest about it, don't be so quick to get on a high horse.

We prove JavaScript is worthy without relying on the perfect language and the perfect scenario and the perfect environment each and every time, like you Haskellers depend upon.

mightybyte said...

@Dumitru

No, those situations are not equivalent. In Javascript, you will get an error ONLY when a code path executes that exercises the missing function. In Haskell you get the error before execution for all symbols in your program. This can be a big deal because it can mean that you find errors a long time after you actually broke the app. It can be much harder to debug things in this situation.

It is also much clearer what needs to be done. Haskell tells you exactly what function is missing. You go to Hayoo or Hoogle and search for it, and you add that import. All Haskell programmers, even beginners know this. More modern IDE/editor plugins can even insert it for you. I've frequently seen Javascript error messages that I couldn't get rid of that seemed to have no effect on the correctness of my app. Therefore I conclude that error messages aren't always related to my bugs even when the code path that generates the error message does execute. So maybe you're right that a much more experienced Javascript programmer could have found it faster. But that makes Haskell better because you don't need to be an expert in order to efficiently debug things.

Dumitru Ungureanu said...

Haskell has Real World Haskell.

JavaScript has its own Javascript: The Good Parts.

Based on these texts, we can safely assume beginner's mistakes are abundant in both languages, and both languages are rather quirky. This is to deconstruct the notion that any of the language is easier to master for beginners.

The approach is different, and it shows plenty with this thread. And a programmer shouldn't really expect the same from two such different environments.

But I think you got the wrong idea. Unit testing and debugging is the universal truth. It seems you let Haskell turn you into a lazy programmer (see what I did here? ;) ).

mightybyte said...

You absolutely can't say that RWH plays the same role for Haskell as "Javascript: The Good Parts" does for JS. The JS book suggests (and we all know) that JS has features that you shouldn't use in real development. Other than a couple very specific things like unsafePerformIO that's not true with Haskell. Haskell is not quirky. It is very principled. It probably seems quirky to non-Haskell people, but that's just because it is very different from what those people are used to. Different != quirky.

Testing and debugging is a universal truth, and you get a LOT more of it out of the box with Haskell than with any other language of similar or greater popularity.

I am absolutely a lazy programmer. Programming is about getting the computer to do things for you. If all other things are equal, I'll take code written by a lazy programmer over a non-lazy programmer any day of the week. I'm not the first person to say this either. Here's a list of the things I found about this from a 30 second Google search.

Why Good Programmers Are Lazy and Dumb
The Three Great Virtues of a Programmer
The Fourteen Types of Programmers – Type 4: Lazy Ones

Fredrik Carlén said...

This whole discussion is an excellent illustration of what's wrong with the JS community: whereas the Haskellers here know what they are talking about, the JS people "safely assume" and "think" and say "almost certainly". It's just weak. And the language *is* flawed: When you inherit a superclass in JS, one has to *correct* the child constructor pointer after doing so, otherwise it points to the parent constructor.

Correct me if I am wrong here, I'd only be glad if that part is *completely* incorrect.

And @Dumitru, "Feel free" is all I can say to your flaming remark. Since you can't meet any of my complaints and instead resort to smearing me on a basis of *assumed* qualifications, I think you have quite clearly shown the validity of your own arguments.

/Flamin' Fredrik

Anonymous said...

Have you considered something in the reactive family, like bacon.js, Facebook's React, etc.?

mightybyte said...

As a follow-up to all the people who have been arguing that Ember's behavior with .get and .set is well-motivated and documented...

Nick Coghlan in the Python midnight boolean thread has a very eloquent description of the problems with this mentality.

"There's a great saying in the usability world: 'You can't document your way out of a usability problem'. What it means is that if all the affordances of your application (or programming language!) push users towards a particular logical conclusion (in this case, 'datetime.time values are not numbers'), having a caveat in your documentation isn't going to help, because people aren't even going to think to ask the question. It doesn't matter if you originally had a good reason for the behaviour, you've ended up in a place where your behaviour is confusing and inconsistent, because there is one piece of behaviour that is out of line with an otherwise consistent mental model."

In my case here, all the affordances of Ember pushed me towards the particular logical conclusion that .get and .set behave like plain vanilla getters and setters. And indeed, having the caveat in Ember's documentation didn't help me because I didn't even think to ask the question.