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:

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:

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.

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.

@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.

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.

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.

@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.)

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.

"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.

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.

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.

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.

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.

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? ;) ).

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.

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.

"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.