Hacker News new | past | comments | ask | show | jobs | submit login
Nonsense Accusations of Spaghetti Code Considered Harmful (rtfeldman.com)
59 points by rtfeldman on Nov 3, 2012 | hide | past | favorite | 25 comments



The author says it himself:

> Haskell code sucks, therefore Haskell sucks.” No, poorly organized code sucks. If that code happens to be written in Haskell, don’t shoot the messenger.

Basically, yes.

Not all callbacks are harmful. Not all goto's are harmful either; you can write both elegant and maintainable code using both.

That is not that point. It's like saying you can write elegant and maintainable code in assembly. Yes you can (no, really. I've actually seen some. :P)... but tangibly in real world situation is that how it usually plays out?

By tacitly (some might argue expressly even...) supporting callbacks, are these modern frameworks leading to in general an improvement in code quality or a drop in code quality, compared to the non-callback based code.

That isn't an intangible. It's empirically measurable, and I'd love to see some code metrics off git hub that measured it.

Waxing lyrical about how how callbacks can be used for the Forces of Good by Clever Programmers doesn't really impress me. So can auto-generated xsd -> C# code (I suppose) in the right circumstances.

Less arm-chair talking on both sides please; back your assertion up with some data.


Uhm, I suppose the ball is in your court. If you believe this must be decided empirically, you'd better curl all the github.


Can this elegant and maintainable assembly code be found online anywhere, and do you remember where? I've just started to pick up nasm and armv7, and I'm very interested.


you might want to take a look at MenuetOS, its written in x86 assembly, and expressly created for clarity and learning.

http://www.menuetos.net/


What metrics can we use?


Oh come on. You've set up several straw man arguments here that don't apply to the real world situations that give rise to callback hell, like fetching multiple rounds of data and aggregating many parallel callbacks at each step, or callbacks that are difficult to trace the origin of. Sure you can write good code with callbacks -- you can also write good code with goto and callcc -- but building a better abstraction makes code easier to write and maintain.


So, what we have here are:

a. Post #1 gives strawman/overly simplistic argument for why callbacks are hell and FRP is great, and obscures example by comparing crappy syntax to elegant syntax.

b. Post #2 gives straw man/overly simplistic argument for why callbacks are fine, and correctly points out the syntax issue.

My takeaway is that both posts have some merit, but what we really need are some examples that go Apples to Apples on syntax AND show when callbacks are fine and when they aren't. Along with some realistic discussion of the complexity inherent in things like aggregating callbacks.

Because, you know, we can only eliminate so much accidental complexity, and then we'll run into inherent complexity. And speaking as a fellow who appeals to authority by mentioning his experience writing a tool that did runtime analysis of multi-threaded applications... There is a lot of inherent complexity involved in writing truly asynchronous and parallel code when failure is expected.


Or even worse, needing to cancel one pending callback but not another. No amount of syntax can save you from the difficulty of races and rollbacks.


I will talk about my own experience, and it only engage myself. I am not sure we can generalize my experience here with callbacks: But for me callback resulted in a big mess. :-)

I've worked in two investment banks, working on their "order passing servers" legacy code. It's c++ code for unix platform. And of course select() driven implementation leads to extensive use of callbacks. The kind of code structure was the same every where I worked: I receive something on a socket, I decode it, what is the event type? Huh ok, I call then the corresponding callback... Huh I need to load the associated context too. And usually the spaghetti appears just right here. When writing the callback for a processing deep into the "sequence diagram" people tend to be overflowed by information. They don't have a clear view of the invariants in their mind. Which leads to over-complicated code in order to deal with the myriad of possible states of your context. And it leads to a lot of bugs.

The context being global, a callback function is forced to behave with side effects. I guess (and only guess since I've never had the occasion to work in FRP style yet) that writing a callback in a synchronous style would would favor a more Functional-programming style, avoiding a lot of buggy side effects, while I guess also it would not be possible to avoid them all... :-/ It must be verified in a real world project that FRP does lead to a cleaner code. Does someone have examples please?

It would be great to have a c++ framework implementing FRP. Or implementing the async/await keywords as in c#. (just tell me if I am not clear enough cause I am not a natural English speaker... :-) )


I'm surprised the author doesn't mention the async library[0] to avoid callback hell. Also, he should probably have mentioned "continuation passing style"[1] as it is the term for that style of programming.

[0] https://github.com/caolan/async

[1] https://en.wikipedia.org/wiki/Continuation-passing_style


Yeah, I've got to agree. Reading the original article ("Escape from Callback Hell" with the subtitle "Callbacks are the modern goto"), it starts with this:

Callbacks are used to structure programs. They let us say, “When this value is ready, go to another function and run that.” From there, maybe you go to another function and run that too. Pretty soon you are jumping around the whole codebase.

So, essentially this sounds like the author is saying functional programming is considered harmful. Which, on the face of it is not true.

Of course you can write spaghetti code in any paradigm and using any technique. The whole point of callbacks (and passing functions around as values, more generally) is that you have one more tool in your toolbox to achieve things that are difficult otherwise. Suggesting they are harmful in such a hyperbolic way is throwing the proverbial baby out with the bathwater.

I'm sure there's a case to be made for properly structuring a callback-heavy application, but methinks a more nuanced approach may be in order here (and in general...sigh).


>However, I also like honesty.

Me too, but calling your opponent dishonest just because you disagree is something I don't like at all.

My own opinion is that expressing logically synchronous statements in terms of asynchronous callbacks is always going to be more complex than using an implicitly synchronous sequence of statements. Not because indentation is so complex, but because there are more moving parts to consider when you make something explicit that was implicit before.

This difference in complexity for logically synchronous statements is not a matter of taste, unless you deny that having more variables is more complex than having fewer variables.


None of the examples presented were that bad. However, things can get pretty hairy when you start requiring CommonJS modules and have callbacks being passed to functions in different files.


The author argues that callbacks do not necessarily impact code's readability in a negative way. However, he does so while presenting examples in CoffeeScript, a language where reduction of the usual JS callback boilerplate (`function() { ... }`) is one of the main perks.

In other words, he claims there is no issue by presenting a possible solution to it.


His argument is that callback the semantic construct != nested brace hell the syntactic construct.

Elm presents a new syntactic construct AND a new semantic construct. His example serves to demonstrate that the syntactic construct by itself is sufficient for many cases, which suggests that the original article might have been better served by being about Elm's syntax instead of bashing on the semantic construct.


If you write good code then callbacks aren't a problem. Gotos also aren't a problem if you write good code.


The thing that gotos and callbacks have in common is that they are both extremely powerful. The problem is not that you can't write good code with either construct, it's that you can write bad code.

Abstraction is the process of creating self imposed restraints in order to aid reasoning. Using powerful features like callbacks and gotos is in some sense its polar opposite


On a side note, everybody quotes Dijkstra but what about Knuth's Structured Programming with goto?

https://cs.sjsu.edu/~mak/CS185C/KnuthStructuredProgrammingGo...


I think the "callbacks lead to spaghetti code" view is just an inadequate expression of the problem. I believe that people who are averse to callbacks feel so because callback-ridden code doesn't compose well. Compose-ability is an important practical consideration, but many async libraries solve this problem to a fair extent.

Also, reactive programming is not exactly the solution to callbacks like structured programming is to goto. "B needs to update automatically when A changes" criterion is extraneous to the problem.


Code is a dry, dry topic. This article made me smile, smile wider and then LOL. This is one of the few articles on code that are very well written, informative and brightening.


The author of "Escape from callback hell" goes indeed too far. This is of course a matter of opinion, but I found that 3 levels of nesting (in javascript for nodejs) is usually perfectly acceptable and readable. If there are more than 3, I use async to get a nice, linear looking flow.


This kind of misses the point - sure it's still simple for a four line function, but it's whether callback oriented code scales for anything other than the most simple of projects.


It does scale. I can vouch for it from personal experience. The smell is when you start constructing elaborate pyramids - but in that case, the callbacks are not to blame, you are for not making your processing code modular with well-defined, pluggable functions defined before the main async flow. None of the examples were long enough to merit separate function definitions, but he doesn't ignore the point:

> Let’s assume that it’s good practice to organize your program into simple functions that have as few side effects as possible.

> There is no law against writing simple, concise callbacks that defer to well-organized, side-effect-free functions for complex processing. In fact, I’d recommend using them in precisely that way.


callbacks are not desirable, and duplicated codes are much worse. Are there any other effective ways to reduce duplicates?


Yeah!




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: