I am familiar with two general approaches to creating unreadable and unmanageable software.
The first one is to take logically unrelated operations and combine them into one giant blob via shared/global variables and god objects/methods. This is what commonly known as spaghetti code.
The second one it to take every piece of functionality and shatter it into tiny pieces, so no unit of code represents anything anymore. This can be done with endless callbacks or layers of abstraction, and it can be made worse by complex frameworks with a lot of "magic" taking place or a lot of boilerplate being required. I don't know any common name for this, but let's call it mush coding.
It looks like the post describes how to convert moderately readable spaghetti code into unreadable mush code. (I would claim that a person not familiar with JQuery could easily understand the initial code, while a person not keenly familiar with Backbone would be completely lost in the final version.)
If most of your methods are longer than 100 lines, something is probably wrong. If most of your methods are shorter than 3 lines, something is probably wrong as well.
While in principle I agree with you, the article references an extremely simple application for the sake of example. In reality, you would never build an application this simple in this manner. An article showing how to do this with a real webapp from beginning to end would nearly be book-sized.
When building an actual webapp, the Backbone method is going to be much easier to extend, test, and understand versus jQuery spaghetti.
Precisely the point I was going to make. This is a condensed example of translating jQuery-like thinking to backbone-like thinking. To quote kjbekkelund:
> [The backbone] code is more maintainable, easier to reuse and extend, and easier to test.
Great observation, in particular the "mush coding" has gotten popular over the years, instead of getting shit done people build abstraction over abstraction and high-level frameworks detached from reality and full of crappy magic. Backbone is a very good example for this (shattering code it into tiny pieces).
Method length should be 1 to 3 lines. Longer and you are incurring technical debt. The function name should be descriptive and it's description shouldn't be, 'adds_one_to_a_number_unless_override_is_on_then_it_subtracts_four_except_when_global_is_set_then_it_jumps_into_a_switch'
No, no, no, no. Did I mention no? I inherited a Javascript application that was maybe 1500 lines of code. Every function was 3-5 lines. Most were like this:
And foo.some_method (in another file) was similar. What it meant was it was impossible to get a view of what was going on in the system. It took months to accomplish an understanding that should have taken weeks.
I really think the original authors heard your advice and decided comprehension didn't matter, only function length.
Function should accomplish a task. Sometimes (very rarely) a task may be two hundred lines; usually, it is much shorter. Sometimes three lines, but often 15-20.
I used to believe that, but after a while I realized that it's a heuristic derived from code that's already split up into clear responsibilities.
Large methods are only a symptom that this component of your system does too much and probably has too many dependencies. Small methods do nothing for you except add indirection if you've not solved the dependency problem, and adding indirection to an entangled system is even worse than having large methods.
In other words, somehow an aspect of the end result of clear code became common advice, probably because it's so obvious and replicable. "Write decoupled code with focused responsibilities" on the other hand is nebulous.
It's sort of like when dating advice is simplified into "be mean to girls".
I've read that claim. The only time I really had to work with code like that was horrible.
It was like a book where the sentences were in random order, with the next sentence number appended, so you would have to browse to another page every few words. :-(
The context switches didn't make me productive... Sometimes, I had to look 3-4 levels up or down in the hierarchy to find a parameter type (this was in a scripting language). If anyone talked to me, it took a looong time to rebuild the model in my head.
Is it just me, or did reasonable jQuery get refactored into broken code? In the jQuery code, the status is added after the sever acknowledges receipt. In the backbone code, you are adding an element rendering it to the page, and then the model is doing some automagic synchronization. Unless there is some serious magic in the Backbone model, things can get completely out of sync.
This looks like one hell of a bastardization of MVC. It seems the view is really a controller+view, and the model does the sync in a way that requires coupling the view to the model to handle any sort of error.
Edit: I could be wrong here, but can someone show me how to fix the Backbone code to handle an error correctly? Error handling can be added to the jQuery code by adding 'error:function() { // do something simple },'
1) You can pass a {wait:True} as the second argument to collection.create(), and the add event won't be fired until after a successful sync. If you want error code, you add error:funciton(){..} to the create options hash.
2) You can change the event listened for from 'add', to 'sync', and still have the error handling code in the options hash.
3) You can manually create the model, call save() and have the success callback be code to add the model to the collection, and the error code do something else.
In all of these, you'd just have to add a function to the view to indicate error, and do what you want to handle it. (Depending on the specifics, this could be for the NewStatusView or for the StatusesView..)
> 1) You can pass a {wait:True} as the second argument to collection.create(), and the add event won't be fired until after a successful sync. If you want error code, you add error:funciton(){..} to the create options hash.
This is problematic. Now you completely lose the "add" event, so if you wanted to do something like add a spinner while the request is in flight, that is not possible.
> 2) You can change the event listened for from 'add', to 'sync', and still have the error handling code in the options hash.
But now you are asking about the sync status from a collection. It doesn't seem relevant to ask a collection about it's sync status when you are interested in the status of a form submission.
> 3) You can manually create the model, call save() and have the success callback be code to add the model to the collection, and the error code do something else.
This seems to be the best option to me. It puts the success, error, and model all in the same frame, which puts us in the same place as the original jQuery.
The jQuery code was far more elegant that the author would have you believe. At the core, you are handling a user interaction which comes in units of a "submission". The jquery code, wraps a "submit" into a singe activation frame and handles it.
The backbone wraps this "submit" unit up, passes it through a collection, some automated saving, and now you have no way to reason about what comes out the other end. When you submit 5 times, and have 2 failures, what do you do? What is a collection going to know?
I recognize the problem with the original jquery, but this ain't the right solution.
Look, I don't really care if you like backbone or not, but try to actually object to it with facts, not projections based on word semantics. All of your objections are made on incorrect assumptions of what information is provided, and what actually happens.
1) the add event isn't lost. It just doesn't fire until a success. Want to add a spinner, have it fire in the appropriate view as a response to the "submit form" event.
2) That isn't how sync is defined, and sure there may be a better name for it, but this isn't what it means. Lots of information is passed into the callback. Useful information, stuff that contradicts your objections.
3) Not the same as JQuery - you can reason about what comes out the other end, because the information is actually in the collection, with an api for getting it. The events have a great set of arguments for them.
Muddling the conversation with such incorrect assertions (aka FUD) is counter-productive.
Why wait for a response from the server? Just react at the off chance something does go wrong. That method of dealing with Ajax requests is what many call "fire and forget". Makes for a faster UI
Backbone like spine has moved to support the "asynchronous UI" in its models by default. The general philosophy is that all storage should seem local, and the interface should wait as little as possible with data being synced in the background (given the appearance of success to the user). However this is not always possible, and needs to be worked around, as some people have covered (validation for uniqueness in a DB). But it was generally accepted as a sensible default stance. To read about it from the Spine author checkout his blog and presentation.
We fixed this by modifying the Collection#create function to return the result of calling model.save (line 840) which is a jQuery Promise. We may or may not have .pipe'd it, I'd have to check.
Next, we leveraged the Promise API to handle success and error callbacks, specifically:
collection.create(bleh)
.done(function (model) { var xhr = this; })
.fail(function (resp) { var xhr = this; });
This is far cleaner that screwing around with success/error callbacks which don't work well when you're trying to mock the backend. If you want to get fancy (we did), you can build a default fail function and always pass that.
I think it's important to note that if this code never grows or changes, then this refactor is totally unnecessary. It DOES add code, and it DOES almost over-decompose the structure.
The payoff, however, is if more features or behaviors were in the pipeline (and when aren't they?). This really sets you up to gracefully manage more complexity by having sensical places to put more code, instead of writing a few more $(document).ready callbacks to ship something on time.
Backbone.js might not be the right tool for the contrived example, but I've come to expect a sacrifice of real-world applicability for smaller, digestible chunks. That the synthesis is up to you is ok, I think.
This is a nicely written post, and uses nice idioms for backbone development.
I would like to note though, that the justification for the increase in LOC count feels a little weak. I understand and agree with it, however before I really grokked what Backbone enabled I probably would have been a bit wary of that - it really just looks like extra work.
I would suggest a follow up, that traces adding some functionality or widget use through both the original code and the newly refactored code, to highlight the power of backbone and make it clear. (I'm suggesting this from the original author, because the post I just read was pretty well written and seeing the same style in the in-depth guide would be pretty nice :) ).
I wonder why Backbone is getting so much attention.
For small tasks, jQuery is completely sufficient (as in this example as others have noted). If you're going to develop applications on a larger scale, Dojo is a way better alternative in most cases IMO. It comes with modularization, build tools, i18n etc...
Backbone is somewhere in the middle, neither highlevel nor lowlevel JS. I really don't see a spot where Backbone significantly outperforms either Dojo or jQuery. Can anyone tell me what's so special about it?
Thank you for the FAQ Jeremy - helpful to this Backbone newb.
I won't ask you directly for the answers to this since it wouldn't be polite for you to answer (or would it?), but I feel like a lot of that FAQ is specifically addressing different frameworks' way of doing things, which is totally fine.
Would someone mind extrapolating a bit and let me know which frameworks / features he's referring to in each point? I assume the 2-way data binding and "nifty demos" piece is about Meteor and Derby, and the "stuffing application logic into your HTML" refers to Knockout, but I'm a bit lost on the others.
Just trying to grow my awareness of the landscape....
The focus is on supplying you with helpful methods to manipulate and query your data, not on HTML widgets (angular) or reinventing the JavaScript object model (Ember).
Backbone does not force you to use a single template engine (Ember). Views can bind to HTML constructed in your favorite way.
It's smaller. There's fewer kilobytes for your browser or phone to download, and less conceptual surface area. You can read and understand the source in an afternoon (Ember and Angular).
It doesn't depend on stuffing application logic into your HTML (angular / knockout). There's no embedded JavaScript, template logic, or binding hookup code in data- or ng- attributes, and no need to invent your own HTML tags (angular).
Synchronous events are used as the fundamental building block, not a difficult-to-reason-about run loop (Ember), or by constantly polling and traversing your data structures to hunt for changes (Angular). And if you want a specific event to be aynchronous and aggregated, no problem.
Backbone scales well, from embedded widgets to massive apps.
Backbone is a library, not a framework, and plays well with others. You can embed Backbone widgets in Dojo apps without trouble, or use Backbone models as the data backing for D3 visualizations (to pick two entirely random examples).
"Two way data-binding" is avoided. While it certainly makes for a nifty demo (Angular & Knockout), and works for the most basic CRUD, it doesn't tend to be terribly useful in your real-world app. Sometimes you want to update on every keypress, sometimes on blur, sometimes when the panel is closed, and sometimes when the "save" button is clicked. In almost all cases, simply serializing the form to JSON is faster and easier. All that aside, if your heart is set, go for it.
There's no built-in performance penalty for choosing to structure your code with Backbone. And if you do want to optimize further, thin models and templates with flexible granularity make it easy squeeze every last drop of potential performance out of, say, IE8.
I have marked the individual points he pointed out with the relevant MVC framework out there.. I might have missed out on some but it looks like ( atleast to me! ), jashkenas feels the greatest threat to Backbone currently is Angular.. And I think he is right about it.. Backbone is currently ruling the throne of Front End MVC frameworks, but it looks like its rule is ending soonish...
It's completely true that Backbone is somewhere in the middle. It's a light framework to make things more modularized and maintainable in a somewhat large javascript application.
Of course you can make things even better for very large javascript applications. That's not the point.
Backbone handles best applications that are neither 20 LOC (for which jQuery is enough) nor 200K LOC. That's why it's pretty popular. It corresponds to a need.
I'm kinda scared that it turns out there's twice as much code in the final result. I understand, that this example is just for educational purposes but, I think, the conclusion that it's better to leave this jQuery-based piece code "as-is" is much more educational.
I think it's more about showing people how this stuff works at a digestible level.
I'm currently reworking something in Angular (I was only part way into the build). It took me a couple of days to get up and running but now I'm there it's paying itself off. The code is far more modular, easier to understand and I'm turning it out way faster.
Toy examples will always just be that, but they're needed in order to learn.
I have one: clearInput(), which clears the textarea, is tied directly to the addition of a new status, not to the submission of a new status via that textarea. Any other piece of the app that adds a new status (from other users, from automated systems, whatever) would clear what I'm typing. I can't imagine why you'd do that.
Haha, of course. I guess it was just a moments blackout — I was probably just too focused on making Backbone easily understandable. There might be some strangeness in there, but hopefully most readers have a better understanding of Backbone after reading it. And I don't have time to change it now either way.
Not a question but thanks a lot!
I've been writing backbone applications for more than a year and I still learned a couple things thanks to your rundown.
Also I'm totally going to use this to teach BB to my next recruits.
Brilliant - as someone who has recently taken the same steps (and into Marionette) there is a lot of half way info out there and not nearly enough complete tutorials.
I am writing one myself - so would be interested to hear the good and bad points of this as much as the OP
I was nodding my head until about half way through.
Thinking that I could apply this logic to client side storage was fine until he proposed that persistance would be automatically handled by Backbone's ajax.
At that point, backbone gives you less flexibility for more code. No thanks.
Separating DOM and data I can understand, but I am not convinced Backbone is the right way to do it.
My code would look more like:
function postdata(url, data, callback) {
$.ajax({
url: '/status',
type: 'POST',
dataType: 'json',
data: data,
success: function (received) { callback(received); }
});
}
var statusform;
(function(pub) {
var status = ""
pub.init = init;
function init() {
$('#new-status form').submit(function(e) {
submitdata();
e.preventDefault();
});
}
function submitdata() {
var data = {
text: getfieldvalue("textarea")
};
if (!data.text) { return; }
postdata("/status", data, function(received) {
status = received.status;
drawscreen();
});
}
function getfieldvalue(fieldname) {
return $('#new-status').find(fieldname).val();
}
function drawscreen() {
if (status) {
$('#statuses').append('<li>' + status + '</li>');
$('#new-status').find('textarea').val('');
status = "";
}
}
}) (statusform);
$(document).onload(statusform.init);
Note 1: This code sample is untested, but should give a good enough idea what I am trying to do.
Note 2: For extra type safety, use typescript interfaces to model data.
Apologies for off-topic, but how does one post code, or do quoted text on HN? I've seen it done here and a few other places, but haven't been able to find documentation on how.
I really like the approach of translating from jQuery. Even if you don't end up using Backbone for everything, it really gives you an idea of what to do with it. I read Backbone tutorials before, and you learn how to do things, but with not much language context. Looking at the alternative ways to do one thing is way more useful.
Agreed, I really enjoyed working through this, haven't come across anything giving the the same level of context to, not only the why, but the how you got there side of things as well.
I wonder if the author would be interested in writing up something about unit tests / tdd in the same sort of fashion.
The first one is to take logically unrelated operations and combine them into one giant blob via shared/global variables and god objects/methods. This is what commonly known as spaghetti code.
The second one it to take every piece of functionality and shatter it into tiny pieces, so no unit of code represents anything anymore. This can be done with endless callbacks or layers of abstraction, and it can be made worse by complex frameworks with a lot of "magic" taking place or a lot of boilerplate being required. I don't know any common name for this, but let's call it mush coding.
It looks like the post describes how to convert moderately readable spaghetti code into unreadable mush code. (I would claim that a person not familiar with JQuery could easily understand the initial code, while a person not keenly familiar with Backbone would be completely lost in the final version.)
If most of your methods are longer than 100 lines, something is probably wrong. If most of your methods are shorter than 3 lines, something is probably wrong as well.