I'd like to add an answer to this question as I've been trudging through some good, bad but mostly ugly Java lately and I have a whole new whopper-load of gross over-generalizations about Java and Java devs vs. JS and JS devs that might actually be based in something vaguely resembling useful truth.
There Are IDEs But It Can Be Helpful to Understand Why There Haven't Been Many
I've been trying Webstorm out now that I find myself drawn to Node development and it's not-bad-enough that I actually bought it but I still tend to open js files in Scite more often than WS. The reason for this is that you can do a lot more with a lot less in JS but also because UI work gives immediate feedback, browser dev tools (Chrome's and Firebug in particular) are actually quite excellent, and (accounting for non-browser contexts) re-running altered code is fast and easy without a compile step.
Another thing I'm fairly convinced of is that IDEs basically create their own demand by enabling sloppy code which you really can't afford in JavaScript. Want to learn how we manage in JS? It might help to start by trying to write something non-trivial in Java without an IDE and pay close attention to the things that you have to start doing and think about in order to actually be able to maintain/modify that code without an IDE moving forward. IMO, those same things are still critical to writing maintainable code whether you have an IDE or not. If I had to write a 4-year programming curriculum, it wouldn't let you touch an IDE for the first two years in the interest of not getting tools and dependencies twisted.
Structure
Experienced JS devs dealing with complex applications can and do structure their code. In fact it's one thing we tend to have to be better at with an early history that lacked IDEs to read the code for us but also because powerfully expressive languages can powerfully express completely unmaintainable disaster codebases very quickly if you don't code thoughtfully.
I actually had a fairly steep learning curve in understanding our Java codebase recently until I finally realized that none of it was proper OOP. Classes were nothing more than bundles of loosely related methods altering globally available data sitting around in beans or DTOs or static getters/setters. That's basically the same old beast that OOP was supposed to replace. So I stopped looking and thinking about the code basically. I just learned the shortcut keys and traced through the messes and everything went much more smoothly. So if you're not in the habit already, think a lot harder about OOD.
A well-structured JS app at the highest level will tend to consist of complex functions (e.g. jQuery) and objects interacting with each other. I would argue that the mark of a well-structured, easily maintained app in any language is that it's perfectly legible whether you're looking at it with an IDE or Notepad++. It's one of the main reasons I'm highly critical of dependency injection and test-first TDD taken to the extreme.
And finally, let go of classes. Learn prototypal inheritance. It's actually quite elegant easy to implement when you actually need inheritance. I find compositing approaches tend to work much better in JS, however, and I personally start to get ill and have EXTJS night-terrors any time I see more than one or two levels of inheritance going on in any language.
Core Principles First
I'm talking about the core stuff that all other good practices should derive from: DRY, YAGNI, the principle of least astonishment, clean separation of problem domains, writing to an interface, and writing human legible code are my personal core. Anything a little more complex that advocates the abandonment of those practices should be considered Kool Aid in any language, but especially a language like JavaScript where it's powerfully easy to leave a legacy of very confusing code for the next guy. Loose coupling, for instance, is great stuff until you take it so far that you can't even tell where interaction between objects is happening.
Don't Fear Dynamic Typing
There aren't a lot of core types in JavaScript. For the most part, dynamic casting rules are practical and straight-forward but it pays to learn them so you can better learn to manage data flow without needless casts and pointless validation routines. Trust me. Strict types are great for performance and spotting problems on compile but they don't protect you from anything.
Learn the Crap out of JS Functions and Closures
JS's first-class functions are arguably the main reason JS won the "Only Language Worth Touching the Client-Side Web With Award." And yes, there actually was competition. They're also a central feature of JS. We construct objects with them. Everything is scoped to functions. And they have handy features. We can examine params via the arguments keyword. We can temporarily attach and fire them in the context of being methods of other objects. And they make event-driven approaches to things obscenely easy to implement. In short, they made JS an absolute beast at reducing complexity and adapting varying implementations of JS itself (but mostly the DOM API) right at the source.
Re-Evaluate Patterns/Practices Before Adopting
First class functions and dynamic types render a lot of the more complex design patterns completely pointless and cumbersome in JS. Some of the simpler patterns, however, are incredibly useful and easy to implement given JS's highly flexible nature. Adapters and decorators are particularly useful and I've found singletons helpful for complex ui widget factories that also act as event-managers for the ui elements they build.
Follow the Language's Lead and Do More With Less
I believe one of the Java head honchos makes the argument somewhere that verbosity is actually a positive feature that makes code easier to understand for all parties. Hogwash. If that were true, legalese would be easier to read. Only the writer can make what they've written easier to understand and you can only do that by putting yourself in the other guy's shoes occasionally. So embrace these two rules. 1. Be as direct and clear as possible. 2. Get to the damn point already. The win is that clean, concise code is orders of magnitude easier to understand and maintain than something where you have to traverse twenty-five layers to get from the trigger to the actual desired action. Most patterns that advocate that sort of thing in stricter languages are in fact workarounds for limitations that JavaScript doesn't have.
Everything is Malleable and That's Okay
JS is probably one of the least protectionist languages in popular use. Embrace that. It works fine. For instance you can write objects with inaccessible persistent "private" vars by simply declaring regular vars in a constructor function and I do this frequently. But it's not to protect my code or users of it "from themselves" (they could just replace it with their own version during run-time anyway). But rather it's to signal intent because the assumption is that the other guy is competent enough to not want to mangle any dependencies and will see that you're not meant to get at it directly perhaps for a good reason.
There Are No Size Limits, Only Problem Domains
The biggest problem I have with all the Java codebases I've seen is an overabundance of class files. First of all SOLID is just a confusing reiteration of what you should already know about OOP. A class should handle a specific set of related problems. Not one problem with one method. That's just taking bad old chaining func-spaghetti C code only with the addition of all the pointless class syntax to boot. There is no size or method limit. If it makes sense to add something to an already long function or class or constructor, it makes sense. Take jQuery. It's an entire library-length toolset in a single function and there is nothing wrong with that. Whether we still need jQuery is up to reasonable debate but in terms of design, you can learn a hell of a lot about how to write effective JavaScript by understanding how JQ is architected for minimal memory usage/performance impact through slick use of closures and the prototype property.
If Java is All You Know, Dabble in Something With a Non-C-Based Syntax
When I started messing with Python because I liked what I was hearing about Django, I learned to start separating syntax from language design. As a result, it became easier to understand Java and C as a sum of their language design parts rather than a sum of things they do differently with the same syntax. A nice side-effect is that the more you understand other languages in terms of design, the better you'll understand the strengths/weaknesses of the one you know best through contrast.
Conclusion
Now, considering all of that, lets hit all your problem-points:
- No immediate way of finding a function's entry point (other than a plain text search, which may then result in a subsequent searches for methods further up the call hierarchy, after two or three of which you've forgotten where you started)
Chrome and Firebug do actually have call-tracing. But see also my points on structure and keeping things concise and direct. The more you can think of your app as larger well-encapsulated constructs interacting with each other, the easier it is to figure whose fault it is when things go wrong. I'd say this is true of Java too. We have class-like function constructors that are perfectly serviceable for traditional OOP concerns.
function ObjectConstructor(){
//No need for an init method.
//Just pass in params and do stuff inside for instantiation behavior
var privateAndPersistent = true;
//I like to take advantage of function hoisting for a nice concise interface listing
this.publicAndPointlessEncapsulationMurderingGetterSetter
= publicAndPointlessEncapsulationMurderingGetterSetter;
//Seriously though Java/C# folks, stop with the pointless getter/setters already
function publicAndPointlessEncapsulationMurderingGetterSetter(arg){
if(arg === undefined){
return privateAndPersistent;
}
privateAndPersistent = arg;
}
}
ObjectConstructor.staticLikeNonInstanceProperty = true;
var instance = new ObjectConstructor();//Convention is to capitalize constructors
In my code, I almost never use the object literals {}
as structural app components since they can't have internal (private) vars and prefer instead to reserve them for use as data structures. That helps set an expectation that maintains clarity of intent. (if you see curlies, it's data, not a component of app architecture).
- Parameters are passed in to functions, with no way of knowing what properties and functions are available on that parameter (other than actually running the program, navigating to the point at which the function is called, and using console.logs to output all the properties available)
Again, see modern browser tools. But also, why is it such a bummer to run the program again? Reload is something a client-side web dev typically hits every few minutes because it costs you absolutely nothing to do it. This is again, another point that app structure can be helpful with but it is one down-side tradeoff of JS that you have to run your own validation when enforcing contracts is critical (something I only do at endpoints exposed to other things my codebase doesn't control). IMO, the tradeoff is well worth the benefits.
- Common usage of anonymous functions as callbacks, which frequently leads to a spaghetti of confusing code paths, that you can't navigate around quickly.
Yeah that's bad on anything non-trivial. Don't do that. Name your functions kids. It's easier to trace things as well. You can define, evaluate (required to assign), and assign a simple trivial function in-line with:
doSomethingWithCallback( (function callBack(){}) );
Now Chrome will have a name for you when you're tracing through calls. For non-trivial func I would define it outside of the call. Also note that anonoymous functions assigned to a variable are still anonymous.
- And sure, JSLint catches some errors before runtime, but even that's not as handy as having red wavy lines under your code directly in the browser.
I never touch the stuff. Crockford's given some good things to the community but JSLint crosses the line into stylistic preferences and suggesting certain elements of JavaScript are bad parts for no particularly good reason, IMO. Definitely ignore that one thing about regEx and negation classes followed by * or +. Wildcards perform more poorly and you can easily limit the repetition with {}. Also, ignore anything he says about function constructors. You can easily wrap them in a factory func if the new keyword bothers you. CSSLint (not Crockford's) is even worse on the bad advice front. Always take people who do a lot of speaking engagements with a grain of salt. Sometimes I swear they're just looking to establish authority or generate new material.
And again, you must unlearn what you have learned with this run-time concern you have. (it's a common one I've seen with a lot of Java/C# devs) If seeing errors in run-time still bothers you 2 years later, I want you to sit down and spam reload in a browser until it sinks in. There is no compile-time/run-time divide (well not a visible one anyway - JS is run on a JIT now). It's not only okay to discover bugs at run-time, it's hugely beneficial to so cheaply and easily spam reload and discover bugs at every stopping point you get to.
And get crackin' on those Chrome dev tools. They're built-in directly to webkit. Right-click in Chrome. Inspect element. Explore the tabs. Plenty of debug power there with the ability to alter code in the console during run-time being one of the most powerful but less obvious options. Great for testing too.
On a related note, errors are your friends. Don't ever write an empty catch statement. In JS we don't hide or bury errors (or at least we shouldn't cough YUI /cough). We attend to them. Anything less will result in debug pain. And if you do write a catch statement to hide potential errors in production at least silently log the error and document how to access the log.