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.
When building an actual webapp, the Backbone method is going to be much easier to extend, test, and understand versus jQuery spaghetti.
> [The backbone] code is more maintainable, easier to reuse and extend, and easier to test.
"Ravioli Code"
process_foos = function(my_list) {
for (i=0; i<my_list.lengh; i++) {
process_foo(my_list[i]);
}
}
process_foo = function(foo) {
foo.some_method();
}
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.
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.
Edit: In short, grandparent++.
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.
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.
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.
http://alexmaccaw.com/posts/async_ui http://www.infoq.com/presentations/Asynchronous-UI
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.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.
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 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....
Here are the list of points from the FAQ :
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...
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.
http://www.ofbrooklyn.com/2012/11/13/backbonification-migrat...
I am writing one myself - so would be interested to hear the good and bad points of this as much as the OP
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.
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.I wonder if the author would be interested in writing up something about unit tests / tdd in the same sort of fashion.