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.
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'
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..)
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?
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.
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.
[+] [-] romaniv|13 years ago|reply
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.
[+] [-] STRML|13 years ago|reply
When building an actual webapp, the Backbone method is going to be much easier to extend, test, and understand versus jQuery spaghetti.
[+] [-] abw|13 years ago|reply
"Ravioli Code"
[+] [-] tferris|13 years ago|reply
[+] [-] glenjamin|13 years ago|reply
[+] [-] nahname|13 years ago|reply
[+] [-] jcampbell1|13 years ago|reply
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 },'
[+] [-] sophacles|13 years ago|reply
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..)
[+] [-] awestroke|13 years ago|reply
You can listen to the collection event "error" to display error messages.
Regarding MVC I don't really know. Everyone seems to have their own definition of what MVC should be.
[+] [-] zinssmeister|13 years ago|reply
[+] [-] csdigi|13 years ago|reply
http://alexmaccaw.com/posts/async_ui http://www.infoq.com/presentations/Asynchronous-UI
[+] [-] politician|13 years ago|reply
Next, we leveraged the Promise API to handle success and error callbacks, specifically:
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.[+] [-] AlexMcP|13 years ago|reply
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.
[+] [-] sophacles|13 years ago|reply
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 :) ).
[+] [-] scr4ve|13 years ago|reply
[+] [-] jashkenas|13 years ago|reply
[+] [-] paduc|13 years ago|reply
[+] [-] swah|13 years ago|reply
[+] [-] andreypopp|13 years ago|reply
[+] [-] aidos|13 years ago|reply
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.
[+] [-] danso|13 years ago|reply
http://www.ofbrooklyn.com/2012/11/13/backbonification-migrat...
[+] [-] kjbekkelund|13 years ago|reply
[+] [-] kmthompson|13 years ago|reply
[+] [-] Jare|13 years ago|reply
[+] [-] paduc|13 years ago|reply
[+] [-] lifeisstillgood|13 years ago|reply
I am writing one myself - so would be interested to hear the good and bad points of this as much as the OP
[+] [-] hayksaakian|13 years ago|reply
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.
[+] [-] BillGoates|13 years ago|reply
My code would look more like:
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.[+] [-] gknoy|13 years ago|reply
[+] [-] pacomerh|13 years ago|reply
[+] [-] taproot|13 years ago|reply
I wonder if the author would be interested in writing up something about unit tests / tdd in the same sort of fashion.
[+] [-] freethejazz|13 years ago|reply
[+] [-] jpb0104|13 years ago|reply
[+] [-] jonny_eh|13 years ago|reply
[+] [-] henkap|13 years ago|reply
[+] [-] rdcapasso|13 years ago|reply
[+] [-] nijiko|13 years ago|reply
[deleted]
[+] [-] nerdfiles|13 years ago|reply
[deleted]