I feel like a nag. This is the second time that I've criticized a blog post for
poor readability. Light gray text against a white (or off-white) background
is a fundamental usability flaw. It makes the text very hard to read, and
detracts from the content.
Alternatively, use Readability, and continue to nag. If at any point Readability becomes necessary, it's a pretty good indication that the author should be made aware that their site is crap.
Thank you for reminding me. But Readability is not perfect. Less than an
hour after posting this comment I came across a site that could be improved
by Readability, except for the fact that Readability did not render the page
correctly (it did not show some information in lists).
I still believe that pointing out ways that authors can improve the usability of their web pages is useful feedback.
That gray is #737373 which is in the darker half, though not by much and the background is full white.
People's vision varies, but it might be worth a check at a black and white gradient with your display to see if it is wonky.
I keep one from the bad old days of CRTs at http://jim.studt.net/bw.gif if you want to take a peek. If you see wide, uniform areas then your display needs work. (As does my gradient, now that browsers have a white background by default. You can't tell if there is a flat spot on your righthand edge.)
It helps to make a little circle say 1/10th the width of the image with your hand, and look through that. See if each section looks like a gradient in isolation. Your brain lies to you a lot.
But you can't deny that each language has a culture that promotes a certain set of practices and styles. If this article had been written about Python instead of Java, no one would have understood what the author was trying to say because it doesn't fit the Python culture.
I have never cared about, or taken notice of any 'culture'. I agree that some programmers do, but I think you only pay attention to 'culture' if you don't really know what you're doing. These are the programmers that copy and paste code from google and ask questions on stackoverflow all day. Just read the damn language spec/APIs.
A programming language is just a tool. You can use it as you wish to. There is absolutely nothing inherant in Java that suggests you should create a mess of Singletons Factories etc. And if you're a good programmer - you won't.
Our job as 'hackers' is to not do things the 'accepted way'. To think outside the box. To go against 'accepted wisdom'. That's kinda the point.
So just because some idiot in a cube somewhere creates 18 Factory classes an hour in his IDE, and somehow that's become "accepted wisdom" about how Java should be used, it doesn't have any bearing whatsoever on how you use it. I'd like to think we're all better programmers than that here.
Culture exists and can't be ignored, but that doesn't mean you have to be a slave to it. I find the most (perhaps only) annoying thing about Python the thinking that leads to the zombie-like refusal to deviate from PEP 8 and other habits/styles that are considered "Pythonic." And the need to defend yourself from (or ignore) the inevitable criticism that you face as people bikeshed over details that are completely orthogonal to the quality of your code.
In my experience that is not true. There is a huge expat Russian population outside Boston, many of whom live in their own bubble of language and culture. The same goes for American expats living in Russia.
Sure, but that's not really arguing against what I'm saying is it? There's enough of them to transplant their own culture over there. That's not the same as being "culture free".
You can of course ignore a language's culture, but then your code will look and feel surprising to other programmers who are familiar with the language and its culture. If you're the only person who will need to maintain the code, fine.
The programmers I'm ever likely to work with, are good enough that they can read code properly, regardless of if it fits into a particular 'culture' or not.
Because the tool has almost certainly been influenced by the culture in which it was created. And the tools that integrate well with that tool, and the people on the IRC channel where you will ask questions about that tool, and so on and so forth.
The flip side is that PEP 8 means that I can look at almost anybody's Python code and feel comfortable in it. I don't have to worry about editor settings or any other unimportant garbage.
If I was evaluating somebody's python, I would consider that a significant mark against it. I would question how well they understood the language and what hidden gems I would find as a result.
If you can grasp the algorithms and problems and solutions of software development but get thrown to any extent by small differences in whitespace or style, I don’t even know how to respond to that. That argument always feels disingenuous when I see it. It's a crappy part of the Python community, but it's still a great community.
Understanding code is challenging enough. Dealing with crap annoyances (also known as "individual styles") makes it that much more challenging. I've talked with enough engineers who feel the same way to know I'm not alone.
If you are able to grok code at a higher level, that is awesome. Recognize that the majority of engineers aren't in that bucket and that style does matter.
If it didn't, why would anybody have coding guidelines?
Frankly, that's almost a tutorial for Spring, it's only missing the container.
I've dealt with enough co-workers to know this isn't hypothetical. There's a slow backlash against this kind of thinking, but it's slow. 5 years ago, I was told that singletons and utility classes were always evil because they were "unmaintainable.)
On the other hand, I've been in situations where the factory/pluggable architecture was the right decision, especially in consultingware where you build the product for your first client, then you go after an RFP that's "pretty close" but don't want to end up maintaining two, and three, and seven code bases which often have the same bugs. That's the problem this pluggable architecture solves.
If you have 7 clients paying you a million bucks to have exactly what they want (out of a market of... say 25-100) and 50k a year in maintenance, this makes sense.
I have never cared about, or taken notice of any 'culture'. I agree that some programmers do, but I think you only pay attention to 'culture' if you don't really know what you're doing.
False. If I'm starting a company around a product built with Rails, it behooves me to stick to cultural norms (in Rails' case: TDD/BDD, Continuous Integration, so forth) because otherwise it's difficult to find good developers that will work on the product.
Our job as 'hackers' is to not do things the 'accepted way'. To think outside the box. To go against 'accepted wisdom'. That's kinda the point.
That's not the point, it's just usually a means to an end. As you say, doing effective work in a Java shop requires either writing better Java, or using a language like Clojure. But in some cases it's faster to go with accepted wisdom rather than combat it, even if we disagree. In some cases, shockingly, the accepted wisdom is beneficial.
Ignoring cultures around languages out of hand is arrogant, brash, and probably wrong. If after you've examined the culture you find you don't agree, that's completely different. You can only think outside the box once you've examined the box.
Minor nit: One can think outside the box at any point by accident. To purposefully think outside the box, or to be able to qualify the degree of outness, one must know the box, its contents, and its boundaries.
I point this out, because I've been bitten by not checking for this in people only to find they have no idea what they did right that one time, and most everything else they've done is just crap.
For my short (<3 years) professional career, I've lived almost entirely in Java. I write in better languages for my hobby projects, but working on those you solve different kinds of problems.
This heavyweight Java stuff is everywhere. DriverManager. Security SPIs. Hibernate. Spring. EJBs. JNDI. You cannot do enterprise work in Java without running into this culture. And just like poisonous people will poison you, so do poisonous design patterns. I was hacking on a Clojure project, and writing a data access layer, and without thinking started writing a factory-based indirection layer for abstraction purposes before realizing I didn't need one...
Perhaps the difference then, was that I read through the article thinking "WTF would you do that" and "eugh that code looks rubbish".
I'm guessing some other people were thinking "Oh how true! Java is such a bad language" which I think is a shame. Not particularly to Java, but to themselves.
If you think it's Java's fault when you write utter crap in it, you're not a very good programmer to start with. Languages don't force people to write bad code.
The classic on this kind of overengineering is Spolsky's architecture astronauts essay[1]. Which also describes another way to go wrong --- getting so lost building general structures that you actually lose track of the problem that you were originally trying to solve: "... then they'll build applications ... that they think are more general than Napster, but which seem to have neglected the wee little feature that lets you type the name of a song and then listen to it --- the feature we wanted in the first place." Worth a read.
Every OO horror story I've ever been involved with has that same phrase in there somewhere:
...it does not allow us to select the algorithm we wish to use at runtime
I'm an embedded programmer so this kind of thinking is alien to me. But really, everyone plans for runtime configurability but is it really needed that often?
This simplification is just as wrong as the blog post.
It's very common to invoke different algithms based on specific instances of the data. To give a baby example, its typical that one uses a different sorting algorithm as a function of size and data distribution and key type.
Theres a popular fft package, FFTW, that is based on this as well.
I wasn't saying that you can't use different algorithms based on inputs/data/circumstances.
The issue I have is articles like this always try to make things stupidly configurable and general - like "I want to be able to hot swap this algorithm at runtime".
In Java, there is not much space between hard-coding dependencies and implementing patterns which (also) make algorithms exchangeable at runtime. The first makes unit testing difficult, the second makes you look like a StrategyFactoryFactory fetishist. Often, link-time configurability would suffice, but that is hard to achieve with Java.
As an aside, the other thing I love about Spolsky's post is that he was exactly right about something: Apple focused on giving people a way to type the name of a song and listen to it right away and iTMS was a runaway success.
What is sad is how true it is. I've encountered non-Java programmers who were fond of taking simple stuff and wrapping it in layers of abstractions which provided real costs to no discernible benefit. But the density ( * ) of such is much, much higher in Java.
* Density could accurately refer to a number of possible things here.
See, I just read it as satire and whimsy, and I thought it was great---I was laughing by about a third of the way in. Perl and C have their obfuscation contests; clearly the analogue for Java would be the overarchitecting contest, which this post would be a good competitor for. ;)
Agreed. I kept imagining someone asking this guy to write a factorial function in a job interview, and then he puts this up on the whiteboard, as they watch dumfounded. That'd teach them alright.
He forgot to "synchronize" his singleton and cache variables :-)
Java: because it pays the bills, not because it's good. (TM)
Seriously, I kinda knew what to expect when I waded into the article, BUT, his "punchline" at the end about actually learning the domain of the problem, and jumping in a completely different direction about a O(1) way to generate an approximation in some cases, rather than further crap-plating the "solution", was GOLD.
That certain developers consider needless complexity to be worthwhile is not a new criticism, and it's certainly not as Java-centric as we'd all like to believe. It's an old trope. Consider this "evolution of a programmer" joke which has been around since at least 1990, before Java even existed.
Excellent post. Knowing how and when to restrain yourself is the sign of a truly experienced developer. I certainly did the "planning for the future" thing a couple times before wising up.
To me the real danger is that a programmer who has come up with the absurdly verbose answer thinks that they're a great programmer, due primarily to the absurd verbosity of their work.
The current common stance of linguists is that the SWH doesn't hold for human languages. I personally disagree, but I haven't done the formal experiments to confirm my informal belief.
That was true 20 years ago, today not so much. The strongest of the original Sapir-Whorf claims don't hold up, but the weaker ones do. See Lera Boroditsky's work in particular. She's been publishing on this topic for at least 10 years. Most of the good stuff requires a library (or paying), but heres an edge article from a year and a half ago:
I'm not so sure about this, it recently came up in discussion with a former teacher of fine, here's what I said then:
To the linguistics question I just had a discussion about this with my boss (who's a linguist by training, currently on leave from his PH.d which has something to do with Greek, he's also been programming a lot longer than I have). One of the points he had was that the Sapir–Whorf hypothesis doesn't just say speaking German affects how you think in German, it affects how one thinks as a whole. To that we're both a bit skeptical, being a programmer probably affects ones cognition (or ones cognition causes one to be predisposed to becoming a programmer, whichever!), but the specific language one uses... probably not. On the other hand, within programming it's clear that different languages (or at least their communities) have different personalities. A good example of this is Python and Ruby. Semantically these languages are remarkably similar (in the context of all programming languages), however their communities have seized upon the small differences and as a result the cultures (accepted best practices and such) are fairly different. Another good example of this is what are known as "design patterns", which were originally supposed to be a language-neutral way of expressing how code could be architected, however in practice some of these really make no sense with certain languages, because e.g. the language has builtin-features which obviate the need for such things.
I didn't cover that because the person I was speaking with was not a programmer, however I think what I said still holds up: within the scope of programming haskell vs PHP is pretty huge, but I don't think they're particularly different cognitive modes in the larger scope.
I disagree to some extent. Sure, some people would overarchitect because culture says so, but I think the more important thing is that Java doesn't give the developer "other choices". If you, for example, make the factorial function in Python, you wouldn't have to worry about integer overflow, because Python does that for you. Therefore, a Python equivalent would be (in theory) easier to understand. The fact that you have to wrap a class around the function is also an example of "limitations".
I disagree to some extent. Sure, some people would overarchitect because culture says so, but I think the more important thing is that Java doesn't give the developer "other choices"
Java doesn't force you to over-architect solutions. Wrapping everything in classes doesn't make your code over-architected by definition.
For Java, the culture is heavily towards writing "solutions" like the overengineered mess in TFA. For python, it's different. Same for Ruby or PHP or .NET or...
Note that for none of those languages are you "forced" to act like the culture says you probably will. Nothing in Ruby forces you to unit-test, for instance, but the culture leans so heavily that way that it's more shocking when you find a team not doing it.
Sure, I've seen it in C# code. But I can tell that the people writing it were coming from an enterprisey Java background (because they wrote 400KLoC without using any part of C# that isn't also in Java).
Maybe this "pattern" is less indicative of someone who comes from a "Java" background than it is someone who comes from an "enterprisey" background.
It just so happens that the overlap between the two is huge.
I hate to always be the defender of Java but it irks me to see articles that claim to lambast Java but really are about this so-called "culture" that has popped up.
"class-oriented" languages (such as java) attract such people.
OOP seems to be "difficult", so there are lots of "tutorials" around that "teach" OOP by introducing and combining all those brainless constructs, without teaching common sense. what you get is code monkeys who believe that this is decent source code.
every code base should have at least one person dedicated to simplifying it and questioning every design decision, so all this "just in case" abstraction gets the knife.
I think the problem is that OOP is just taught as a way to organize code.
Pretty much everything in our ostensibly OO system could be static methods and it would work just fine. Objects rarely have instance variables, or if they do they are just used to set up a method call. Object lifetimes are measured in single-digit lines of code, and messaging between them is extremely rare.
I'm talking about the kind of generic business app built in blub by a team of chair moisteners who don't know anything outside of the one language the project is written in.
It could be Java (of course), C# (just about as likely), or VB (when they get the idea to use objects), or Python (yes, I've seen it).
OOP is not difficult. I learned OOP with Smalltalk and then went on to program with Actor on Windows 3 and lived happy until I decided to learn C++. I closed the book for 8 years because of what I saw: a parody of the simplicity and elegance of C. C++ could, as well, be a work of satire.
OOP is only "difficult" if you try to learn it using a language that makes it difficult. Sadly, Java is one such language.
When I write a simple function in Scheme and find myself wondering whether I might later want to swap in a different algorithm at runtime, I don't start making a pluggable architecture right then. Because I don't need to. The function I wrote can simply drop into whatever pluggable architecture I write later.
In Java, if I think I might need a pluggable architecture later, I'm tempted to go ahead and make it from the start, since my object model will need to support it.
Yes, this. I think people are missing, or not remembering, that Java makes you code defensively where other languages don't, and the appeal of falling into these patterns just to make sure all bases are covered without having to think too hard.
Um, not really. Dynamic languages have duck typing (look it up on wikipedia); they get used to writing for the now and letting the what-ifs wait, as in some cases, changes can be accomodated with little tear-down of existing code.
This reminds me of the joke about OOP and monkeys[1]
Really though, I think this is more related to culture that Java (and many Java books) tends to nurture. Anybody that's read a fair share of "Enterprise" Java code has seen FooBarFactoryFactoryFactoryImpl classes.
Hopefully as dynamic languages on the JVM become more popular, this will change. But I find many "Enterprise" developers aren't really interested enough to look outside the box.
"jRuby? I think I've heard of that, what's wrong with Java?"
"It's not a real programming language unless it's statically typed."
Just for fun, a fairly fast and low-memory factorial in Clojure:
(defn factorial [n] (reduce * (range 1 (inc n))))
It's almost criminal how simple it is, yet it's pretty correct. It works for int, float, BigInteger, etc., treats negatives as 0, and throws on non-numeric types.
For non integer types, the factorial is given by the gamma function [1]. So, the factorial of 3.5 is Gamma(2.5) which is 3.3325 [2] not 6 as your function suggests.
Yes but no. For non-integers, the factorial isn't defined. Just because the gamma function is an analytic continuation (read: generalization) of the factorial doesn't mean you can go ahead and return anything other than an error code in those cases.
What do you do when one customer wants to deviate from the way the product works, and you can't say no?
Do you say:
if (customerID = 328281) {
...
} else {
...
}
Or do you go ahead and build the architecture that lets you specify which class or method to run per customer? The situation does come up often enough to have to make a decision.
I'm working on a project which has tended to say "if (customerID = ..." in the past, but I decided to move to the more configurable/pluggable approach. I'm actually not sure if I made the right decision.
I recall working on one project where those customer-specific exceptions became the rule, and the core business logic was pretty well obfuscated by the time I got there.
If your project makes one exception, a whole bunch more are likely to follow.
This is not a bad plan, but there is a danger here.
If you find that "1 customer" (being a series of disparate customers) all ask for various minor tweaks to the way something works, then before you have half-a-dozen disparate forks in your logic you probably do want to pull that out and use a strategy pattern where applicable. It doesn't take many clever sidesteps in a clean algorithm to make a nightmarish scenario later on.
I'd roll 1 and 2 together, typically if one customer asks for it today another one will ask for it next week.
Save yourself the time and bother to dig back in again and solve it at least for the foreseeable future with less overhead than what it would take to tackle the same task again, typically getting in to do the job takes at least as long as doing the job in situations like that (just to make sure you didn't break anything by accident, test the works and so on).
If the overhead is constant and a 'small' fix like that takes 5 instead of 4 hours including all the extras then that's a trade-off worth making. Unless of course you're really under pressure.
> how does the strategy pattern differ from just passing a function pointer?
It's just a verbose way to perform the same abstraction. Use it only if function pointers are unavailable or considered unsafe in your programming language.
There are a bunch of patterns that simply disappear, or are greatly simplified when you have first class functions in your language, namely, observer, strategy, visitor, and a bunch of others i can't remember right now
My take: I realize that this is seemingly dodging the question. But you tell the customer 'no.' Seriously. ldh [http://news.ycombinator.com/item?id=2143654] makes a great point, if you make one exception, many are to follow. Soon your code will be littered with these special cases for customers and will become an unmaintainable message.
I was also going to post this. A must read for all programmers. You can overcomplicate things in any language.
"The Evolution of a Haskell programmer" is brilliant. Funny and insightful at the same time. Make sure to read the bottom part: "But seriously, folks, ..."
As a counterpoint, I like this quote from Twitter's Nick Kallen:
This smacks of the oft-ridiculed Java AbstractFactoryFactoryInterface. But let me put it bluntly: AbstractFactoryFactoryInterface's are how you write real, modular software–not little fart applications.
[N.B. I'm not saying there isn't a lot of truth in the factorial article, it's just you have to know which challenges just need a one-liner function and which require an AbstractFactoryFactoryInterface]
I've read that article twice in the last few months. When I was writing a lot of Java I thought "Oh, you know, that actually makes some sense." Now that I've returned to writing Ruby full time, I look at it and think: "Hey, wait a minute..."
All we've done here is forced the programmer to adhere to some ridiculous interface which requires just as much understanding of the internals of the code as overriding the method in the first place. You also end up with hard-to-understand naming schemes, which leads to two points of confusion:
1. Which one of the provided query factories does what I actually want?
2. If I need to write one myself, what would I call it?
PerQueryTimingOutQueryFactory? You've added more parts to that name than would have been included in an option to the query function! Oh, and it still doesn't include the information necessary to run, because it folded the timeout specification down into a HashMap. One more thing for the end-programmer to understand.
There are good places to use dependency injection. There are also places where Factories are exactly the pattern you need. But in most dynamic languages, you can achieve these effects implicitly through inheritance and closures, and it cuts down the complexity of the code immensely with only a minor cost to reusability.
This guy works for Twitter. They control Querulous. He could have just added a :timeout argument which takes an number or a function returning a number to the query method, but now you have to understand all this additional architecture to get anything done.
> When I was writing a lot of Java I thought "Oh, you know, that actually makes some sense." Now that I've returned to writing Ruby full time, I look at it and think: "Hey, wait a minute..."
And thus you demonstrate using Java for prolonged periods may cause brain damage.
Sometimes I wonder if Java's "classplosion" is actually a subconscious backlash against the type system. In order to get anything done within the narrow box of its static type checking, you're forced to apply insanely convoluted indirection, pushing the code you want to be polymorphic up into factories and ByzantinelyComposedClassTaxonomiesWithIncreasinglyUnreadableNames.
If it's a subconscious reaction to static typing, we would see it only in subjects who already had contact with dynamically typed languages who are forced to use Java and its straitjacket.
After being exposed to Java for prolonged periods of time, my Python programs became classes with a main method that was called in an ifmain condition...
It doesn't even take prolonged periods (for me at least).
I was participating in an obfuscated code contest a while back (making a simple command line multi-function calculator), and since I knew I couldn't compete on line noise I decided to try obfuscation through architecture. Somewhere around writing a RightSideOperandInterface (so I could catch division by 0), it managed to get into my head that it was actually more robust and extensible than doing it any other way. I came to my senses slightly after submitting it.
I think your second paragraph hits the nail on the head.
What's the point of writing an architecture that's extensible if it requires the user to know all of your code in-depth to use it?
Now, if the code was packaged as an API with very good documentation, then this might be a moot point. In my personal experience, this has rarely been the case.
There's nothing wrong with AbstractFactoryFactoryInterface if that's what you end up with after carefully evolving a design, driven by actual requirements.
The problem is that the people who come up with these designs are the ones that consider refactoring bad/hard/risky. They know very well that they need a hammer, but 200 what ifs later, they have this monster-factory, instead of just getting a bloody hammer, then, when they also happen to have a screwdriver and a saw, they build a toolshed, and refactor.
Agree, in my experience it's people who write-only code that tend to over engineer things. If you can't read or maintain code it better be pretty flexible when you write it!
The most insidious thing about over-engineering: if something is complex you have to understand it all before you can tell if it's unnecessarily complex.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." --Brian Kernighan
It's true that the "bad java programmers" version doesn't work for negative numbers, but that's actually much better than his version of gamma, which doesn't work for ANY numbers, since it has a bug:
Math.pow(z/Math.E, z)
should be:
Math.pow(tmp2/Math.E, z)
So from this I conclude that there are two types of java programmers: those that over-architect their solutions, and those that don't even both to test the code they put in blog posts ;-)
In fairness I think he literally wrote the code the way he claims he wrote it 'as a rant', so it's not surprising he didn't actually test it. If this was written off the cuff without testing it's actually pretty impressive.
His Gamma function is even worse than I said above, because not only does it have a bug, it doesn't work at all for negative numbers, which was his big bolded complaint about the original implementation. I'm embarrassed that I didn't see this at first, because it should be really obvious - the approximation would be taking the square root of a negative number!
I know he would have caught these bugs had he needed to implement these for a real system. But there is nevertheless an important point here. He is 100% right that over-architecting is a bad idea, and that correctness is more important than architecture. But complaining about a factorial function not handling negative numbers, and then "fixing" the problem by implementing the Gamma function, is doing the same thing all over again. It's really easy to fix the factorial implementation to do something reasonable for negative numbers; it's much harder to implement a Gamma function that works for all input. Why bother unless you really have to?
He also explicitly says "I compiled it, but I didn't really test it, and I'm not sure I copy/pasted it correctly because that's not the effing point" too...
The first time I saw the GNU 'hello world' package it gave me exactly the same feeling.
The amount of superstructure piled on top of a simple thing in concept is making programming much less accessible than it needs to be. By the time you understand the scaffolding you've lost sight of the problem.
The clojure example elsewhere in this thread it a really nice breath of fresh air, unfortunately it still needs all of java to run, and the number of times I've run in to obscure classpath issues and runtime problems are large enough to remind me that clojure is not entirely free from this sort of heritage either. That does not reflect on the language, but clojure has enough java roots that some of the issues are quite familiar.
The classpath is one of my favorite things about the JVM. In my experience, it's a great way to unambiguously specify what to load.
Just look at the horrible mess that is ASP.NET assembly loading: you've got the bin/ directory, Temporary ASP.NET Files, and the Global Assembly Cache all competing when you load libraries.
It's great when it works as expected. Woe to you if your cron job happens to get started with a different CLASSPATH set compared to your regular log in (just one example).
Yeah, but even when it's hard to work with, it's still waaaay easier than ld/gcc, IMO at least. I mean, try to resolve different versions of libraries in Java, it's not easy, but in C, it's hard. Gems and eggs and CPANs or whatever tend to have a lot of the same problems.. turns out it's a hard problem to solve.
is your friend. I've used that many times for stuff that I need to deploy across different releases of the same OS that as a rule have their devtools removed for security reasons.
My development system is typically geared towards desktop use (ubuntu atm), the servers run either redhat, debian or some other flavor and the static trick gives you a slightly bigger executable but the upside is that it is simply drop in and go.
I agree with you about the classpath being a pain in the JVM, but Leiningen (https://github.com/technomancy/leiningen) does make the classpath much less painful.
The biggest complaint I have with many Java developers is that they develop a whole bunch of really bad habits. Specifications are unclear, or they think someday the code may need to be extended into a different direction. So they write a whole bunch of overblown architectural nonsense, sight unseen, thinking that the additional crap someday will help out and make things easier.
This is not fair; Java has moved beyond this. In modern best-practices Java it is no longer necessary or even possible to write big piles of gratuitous OO spaghetti. Today, experienced Java programmers build their applications on top of huge, industry-standard, highly polished piles of architectural bullshit, wrestling with which leaves them with no time to write any code of their own.
For every example of well used Java, I can easily come up with a dozen counter-examples of insanely complex, unreliable and unmaintainable messes I have encountered in my professional life.
To be relevant to the thread, those systems must be insanely complex, unreliable and unmaintainable, because the Java developers who made them has these habits that Java developers apparently have.
To the extent Java correlates with bad software, IMHO the causation is more likely to be that the environments (in the vein of clueless corporate management, waterfall, http://www.halfarsedagilemanifesto.org/) are more likely to pick Java for their development.
> because the Java developers who made them has these habits that Java developers apparently have.
That's the point. Java, it seems, make it tempting to "overarchitect" applications into excessively generic and modular beasts early in their lives when a much simpler implementation would be perfectly adequate and much simpler to extend and maintain.
Well, maybe, but the cultural argument is also applicable. With my first Java app I did this kind of stuff because the examples of Java I found implied that this was a good thing to do. I didn't have a lot of experience in this area, so I thought, "these folks have been around the block, experienced the pain, and now have words for the wise." So I did it too.
I wonder if I will actually ever use one of those factories again in that app.
One of the things that annoys me in Java books is that every meaningful example is a couple pages long, most of it giving context to a function or method. The process of writing a meaningful may be so painful and risky Java programmers defend themselves by starting with code so generic they believe it won't need rewriting in the future.
You can do that in any language. Java's ubiquitousness means that there's a lot more bad code, especially when you look at some of the places it's become ubiquitous.
But you know what? Once we've established that you can write bad code in any language.. I'd prefer to deal with bad code in Java than almost any other language I can think of (and I've seen a fair bit). Maybe Python wins out, but I'd rather debug bad Java code than bad C++ or bad Ruby for sure.
I did not have to deal with bad Haskell, yet. But I wonder, how it would turn out. The type-system can give you some pretty strong guarantees (if you abstain from using back-doors), and thus make interfaces much less leaky. I.e. if a function has the type String -> Int, you know that it takes a String and gives an Int, and doesn't have any strange side-effects, like setting some random variable or sending away your credit-card data.
Bad Haskell usually involves poor type abstractions, eg. instead of defining new data types for inputs that are logically distinct but physically identical, you just re-use existing types and add new constructors. Or you end up using tuples for everything. Or (horror!) you encode a bunch of things as strings instead of using the type system at all.
The nice thing about Haskell is that it's really, really hard to write things that accidentally ignore purity, which eliminates a whole class of errors and bad designs. The type system only helps you if you let it, though.
I've looked at a friend's Haskell program for Huffman encoding, that used raw recursion in a undisciplined way instead of abstracting the control flow out into, say, filters, folds and maps. That was the closed I ever got to ugly Haskell.
Messes, perhaps, may be more frequent in PHP/ASP applications, but insanely convoluted and overarchitected solutions with unrequired levels of indirection and never used configuration XML files that have to be generated by an IDE are much more frequent in the Java/C# camp than, say, in the Python/Ruby/Perl/Smalltalk camp.
That has nothing to do with the language and everything to do with the developers. What if the same developers used Common Lisp or Smalltalk or Haskell?
Gut feeling says they'd be even more productive. Terrifyingly so, in fact. The sad fact is, though, that we'll never be able to conclusively prove a statement like that.
I am very very confused what this post has to do with Java? It has everything to do with premature optimization and implementation of Design Patterns unnecessarily... And since Design Patterns are not language specific the title of this post is wrong and misleading even the conclusion that the author tries to draw up is of dubious merit ..... The only part of the post that makes this specific to Java is the comment that the factorial function must be wrapped in a class.
I believe the author was attempting to illustrate both how easy this is to do in Java, and how prevalent the practice is.
Every book I've read on Java shows even the "Circle inherits from Shape" examples to be ridiculously overarchitected and encapsulated (I am aware of why they are this way for illustrative purposes, however).
Lets not get into the faults of Java and inheritance this thread might devolve into something it shouldn't :).
But its a good idea to do some of this stuff where and WHEN its appropriate. You can design a space shuttle to go to the grocery store doesn't mean you should.
I have seen my fare share of useless hard to understand and over engineered code in C, Groovy, Java and Lisp
A good book about this by Joshua Kerievsky is Refactoring To Patterns he talks exactly about this, the dangers of using patterns and the dangers of not (yes he uses Java in his examples)
There are some insanities (patterns?) that most certainly are. The Java I have seen written by the people I know and the coding practices and rituals they aspire to, make me feel uneasy. All the cool toys Java (and .net mind you) kids get to play with (IOC!, Factories!, Wrappers!, XML!, Objects above all!...) just feel like language band aids to me.
And indeed I also have a feeling that premature optimization and general architectual astronaucy are deemed very highly by Java folks.
Don't get me wrong - I think that JVM is a marvel. I could also find greater joy in writing COBOL for a living. (at least my coworkers wouldn't be rambling constantly about stuff that is wrong and don't matter).
While it's certainly named for bears, that appears to be be a folk etymology: as the Wikipedia article you link suggests, the Arctic is named for the northern bear constellations Ursa Major and Ursa Minor, so called since the time of Homer. The word Antarctic has been in English since the C14th ( http://www.etymonline.com/index.php?term=Antarctic ); a continent opposite to the Arctic was hypothesised long before Antarctica was discovered in the 17th century.
Antarctic, then, means "opposite of north" rather than "no bears" - though I suppose the Ursa constellations were also not in the north :) and it's a nice mnemonic.
I think there is a correlation between limitations of Java and how people use it. If functions where a first class operator a lot of the architectural fluff would be truly pointless. However, because of language limitations people start to think of "flexibility" as a feature and try and make a an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp
The design goal here is that you can get factorials by calling a function, but decide how to compute them (cached or not, with the ability to add new algorithms) by changing state. A dubious goal, but not completely insane.
In python, it would be 15 lines, and simple. No utils, factories, algorithms or singletons.
I imagine it could be done in more reasonable java than this, but could it really be done cleanly and compactly?
As a man named William Shatner once said, "Brevity is the soul of wit." Or was that Shakespeare? The same is true for good programs, I think.
When we see problems like this, it's right to ask questions about what the language design encourages its programmers to do. Additonally, languages have "personality" just like people do, and those personalities evolve with the user base of the language as they adapt to the language's environment.
The question of whether this type of code is more common in Java than say Python or Perl is empirical, and my gut says it is. Java is the commoditized, corporate language par excellence. The features it implements and the way it implements them have attracted the kinds of programmers who would write code like that. That's not to say that Java code forces its users to write code that is so inelegant. That's just to say that it creates incentives that often push toward ugly code. C++ does that too. On the other side, Python (to name one example) seems to set up its environment in such a way as to encourage pretty code.
There's no clear-cut good or bad here, only interesting trends in the code produced in various languages.
Languages are more than just syntax and semantics. There's also a whole culture that grows up around the language, a set of "best practices" and common idioms. And the one that's grown up around Java seems to value abstraction and design patterns and convoluted, overgeneralized solutions.
This doesn't apply to all Java code, of course. You can write lean Java just like you'd write lean C or lean Python. But a non-trivial amount of the Java jobs out there expect you to layer frameworks on top of frameworks and jump through an amazing number of hoops just to get something done.
Interestingly, the factorial function is typically used as a trivial example case for introductions to learning a language. Used for the purpose of learning function calls and recursion and as such we tend to think of it in the simplest context.
From that context, the factory function makes this article a great example of over designing. And certainly in some senses the point of the article is quite accurate.
However, there exists another less common context for the factorial function--serious usage of the function. Not as an introductory example to the language, but as the basis of actual software that has purpose. In this context questions like memory usage, processing power, and even mathematical precision come in to play. Is BigInt large enough for the required use? Maybe you need to use a class based number type capable of having a limitless value. Perhaps you are targeting a distributed processing system, or a single super computer. Will your computing have memory limitations, processing limitations? Suddenly, when considering real applications for the factorial function, all those satirical over-design considerations are now principal necessities that you'd curse the programmer for not having implemented.
Ultimately, the key is scope. It's necessary to know the intended scope of the functionality you are coding. You need to consider who (will use code), how (the code will be used) and where (the code will be used [platform]). In Java, the big GET is the DRY philosophy. An immense library of code is available for your reuse. And it's certainly true when perusing the code base of those libraries you'll see a lot of what might seem like over-designing. However, when you consider the intended scope, it's rarely actually over-designed. Because frankly the scope of most code in the library boils down Everyone (who), Any purpose (what), Any platform (where). In such a scope, simple three liner utility functions seldom are sufficient or appropriate.
Um, this problem really is that simple. If you're doing something where you really need many factorials, you use a lookup table (and probably don't call a factorial function at all), or you use a closed-form function to give an estimate. And it's going to be painfully obvious which one you want.
>It’s obscuring the purpose of the code–replacing a two or three line utility (max!) with dozens or even hundreds of lines of self-important masturbatory Java bullshit.
I'm not trying to troll, but are there any Java programmers who don't write code like this? I've never seen "real" Java code, which is to say production code, but every time I had to work with Java in school it was pretty much described by the above quote; and what's more, the libraries I had to interact with often made me feel like I was forced to do the same thing.
Abstraction is like salt -- a little bit will go a long way.
The time to use it is when your business users can't agree -- one says do it that way, another says do it this way. Or they say it's one way, but they're not sure if it's going to change or not. That's a perfect time to cover both bases.
A tiny bit of abstraction applied well can save immense heartaches down the road. A bunch of abstraction applied poorly can make your life miserable. Part of "growing up" as an OOP developer is learning what to do when.
Also, this is a good example of why founders and startups use so little abstraction -- nobody knows what the problem is or what's going to change. It's all complete guessing. So might as well get something out the door today instead of playing mind games about what might happen or not based on your imagination of what a user might be. (This is also the reason functional programming maps so much better to startups: you don't have a spec. You just want one thing to go in and another thing to come out. If that isn't what people want, you have to iterate. Completely different paradigm than working in a business environment where somebody gives you ten bucks to go make well-defined change in system X)
I really agree with some parts. If you expect to grow, you can at least make an effort to keep stuff extensible in the future. Right now, I work with old code every single day. With code written one day assuming that: tax will never change (did twice since then), tax code will never change (changed once), service provider will never change (another one was added, so we have to choose one at runtime based on many dynamic conditions), this modification is needed for only one reseller (file has 10+ lines of if reseller_id == ...).
Making things flexible may not sound pretty at the start. But ending up changing 3 apparently completely separate systems to accomodate the fact that you changed the version of some software and the output format needs to be different (hey - why would we do immediate representation, output's not going to change).
It looks silly in the example, because it's a simple operation. Now substitute 'factorial()', for 'provision_customer_config()'. You WILL change the backend at some point. You WILL change the endpoint at some point. You WILL change the way the function works completely, but will have to retain the previous procedure for existing users. It's a when not an if question. There should be a saying similar to that about people doing backups: there are 2 types of programmers - those who write modular code and whose who will.
Perhaps the point is that you should refactor your code into modular code when it's required. In real applications there's a million different ways to modularise the code, it's extremely rare that you guess which way is right in the absence of actual requirements.
I would like to add to this, changing some endpoint frequently involves a single, one-time migration to the new system rather than a requirement to support the old and the new simultaneously, thus making some of the modularization moot or just pure overhead vs. a port.
Part of "growing up" as an OOP developer is learning what to do when.
I think this is the real problem with this particular example - because the author wanted to make this into an anti-pattern joke, he didn't actually think about what a Real Programmer actually trying to make good use of the extra keystrokes would do in this situation.
The "requirements" that he's using to build up this bloat appear to be:
1) The result might be really, really, big, so no factorial function worth a damn should be limited to a normal int.
2) We might want to swap out implementations of our factorial function, for instance, to cache intermediate results, to use a loop, to use recursion, etc.
3) We should be able to add new implementations without modifying class files already written, and we should be able to select implementations at runtime.
Fine, then. These could be 100% realistic requirements if we were talking about some more substantial or important piece of machinery than a factorial function, so we really might have to satisfy these things, in which case the three-liner would be completely and utterly unacceptable.
So let's say we're in that situation, then. A good Java developer still would never write the code the way it came out in that post, because the abstraction doesn't go far enough, and it's obvious. Almost every time he writes "FactorialAlgorithm", we could just as well replace that with "Algorithm" (or maybe better, "BigIntegerAlgorithm"), and now instead of building up a whole bunch of architecture just to support one stupid algorithm, we've spent a bit of effort to make all of our (big integer) algorithms swappable. That memoization stuff could almost immediately be refactored up and out of the individual implementations, so it would take but a single method call or flag to add that ability to an algorithm, and so on.
I'm not saying that the particular architecture that gets built up to do this stuff is necessarily pretty, that Java-style OOP is an optimal (ha!) or even passably efficient way to achieve these constraints, or that you should do these types of abstractions before you know you absolutely have to. By all means, if you don't have any other requirements on the table, write the naive factorial, FFS! But when you've got to abstract things, rule #1 is to make sure you pull everything "up" far enough, otherwise you're never going to extract enough value out of the added complexity to keep yourself sane.
I think there is a good way to figure out when to apply abstraction. The question to ask is whether the abstraction reduces the cognitive load on the reader. Abstractions that reduce the cognitive load by eliminating redundant code, highlighting commonalities, and reducing LoC are generally an improvement. Abstractions that create unnecessary complexity, obfuscate, or result in more LoC are generally a decline in quality.
It may not be true in all situations, but I think this benchmark is a good one, as it accounts for the environment of the developer. If the code is only used once, it isn't worth it to abstract it. Once you have two implementations and need to add a third, you may realize they have commonalities and so abstracting and refactoring make sense.
203 comments
[ 3.1 ms ] story [ 324 ms ] thread[1] http://lab.arc90.com/experiments/readability/
I still believe that pointing out ways that authors can improve the usability of their web pages is useful feedback.
People's vision varies, but it might be worth a check at a black and white gradient with your display to see if it is wonky.
I keep one from the bad old days of CRTs at http://jim.studt.net/bw.gif if you want to take a peek. If you see wide, uniform areas then your display needs work. (As does my gradient, now that browsers have a white background by default. You can't tell if there is a flat spot on your righthand edge.)
It helps to make a little circle say 1/10th the width of the image with your hand, and look through that. See if each section looks like a gradient in isolation. Your brain lies to you a lot.
Anyone who programs like that is a bad programmer. That's the fact. It's nothing to do with the language being used.
The biggest complaint I have with many Java developers is that they develop a whole bunch of really bad habits.
A programming language is just a tool. You can use it as you wish to. There is absolutely nothing inherant in Java that suggests you should create a mess of Singletons Factories etc. And if you're a good programmer - you won't.
Our job as 'hackers' is to not do things the 'accepted way'. To think outside the box. To go against 'accepted wisdom'. That's kinda the point.
So just because some idiot in a cube somewhere creates 18 Factory classes an hour in his IDE, and somehow that's become "accepted wisdom" about how Java should be used, it doesn't have any bearing whatsoever on how you use it. I'd like to think we're all better programmers than that here.
Yeah it can, why can't I ignore culture?
I download a tool, and use it as I wish to within my company.
In practice, it's very hard to do. Try living in a foreign country for a time.
So "Java the language" to me is a useful tool. It's in no way connected to "Java the culture" or "Enterprise Java" which are both total garbage.
If I was evaluating somebody's python, I would consider that a significant mark against it. I would question how well they understood the language and what hidden gems I would find as a result.
If you are able to grok code at a higher level, that is awesome. Recognize that the majority of engineers aren't in that bucket and that style does matter.
If it didn't, why would anybody have coding guidelines?
I've dealt with enough co-workers to know this isn't hypothetical. There's a slow backlash against this kind of thinking, but it's slow. 5 years ago, I was told that singletons and utility classes were always evil because they were "unmaintainable.)
On the other hand, I've been in situations where the factory/pluggable architecture was the right decision, especially in consultingware where you build the product for your first client, then you go after an RFP that's "pretty close" but don't want to end up maintaining two, and three, and seven code bases which often have the same bugs. That's the problem this pluggable architecture solves.
If you have 7 clients paying you a million bucks to have exactly what they want (out of a market of... say 25-100) and 50k a year in maintenance, this makes sense.
Most of the time, though, YAGNI.
Or work with people who don't really know what they're doing. Or have to maintain or modify their code.
False. If I'm starting a company around a product built with Rails, it behooves me to stick to cultural norms (in Rails' case: TDD/BDD, Continuous Integration, so forth) because otherwise it's difficult to find good developers that will work on the product.
Our job as 'hackers' is to not do things the 'accepted way'. To think outside the box. To go against 'accepted wisdom'. That's kinda the point.
That's not the point, it's just usually a means to an end. As you say, doing effective work in a Java shop requires either writing better Java, or using a language like Clojure. But in some cases it's faster to go with accepted wisdom rather than combat it, even if we disagree. In some cases, shockingly, the accepted wisdom is beneficial.
Ignoring cultures around languages out of hand is arrogant, brash, and probably wrong. If after you've examined the culture you find you don't agree, that's completely different. You can only think outside the box once you've examined the box.
I point this out, because I've been bitten by not checking for this in people only to find they have no idea what they did right that one time, and most everything else they've done is just crap.
This heavyweight Java stuff is everywhere. DriverManager. Security SPIs. Hibernate. Spring. EJBs. JNDI. You cannot do enterprise work in Java without running into this culture. And just like poisonous people will poison you, so do poisonous design patterns. I was hacking on a Clojure project, and writing a data access layer, and without thinking started writing a factory-based indirection layer for abstraction purposes before realizing I didn't need one...
Couldn't they just use memorization by passing a hash map in a tail-recursive method and save on a few hundred lines of code?
I'm guessing some other people were thinking "Oh how true! Java is such a bad language" which I think is a shame. Not particularly to Java, but to themselves.
If you think it's Java's fault when you write utter crap in it, you're not a very good programmer to start with. Languages don't force people to write bad code.
[1] Whole thing here: http://www.joelonsoftware.com/articles/fog0000000018.html
...it does not allow us to select the algorithm we wish to use at runtime
I'm an embedded programmer so this kind of thinking is alien to me. But really, everyone plans for runtime configurability but is it really needed that often?
It's very common to invoke different algithms based on specific instances of the data. To give a baby example, its typical that one uses a different sorting algorithm as a function of size and data distribution and key type.
Theres a popular fft package, FFTW, that is based on this as well.
The issue I have is articles like this always try to make things stupidly configurable and general - like "I want to be able to hot swap this algorithm at runtime".
http://discuss.joelonsoftware.com/default.asp?joel.3.219431....
* Density could accurately refer to a number of possible things here.
Priceless :)
No XML, SOAP, EJBs, Connectors, Spring.... it barely qualifies as a (obfuscated) Java architecture.
http://code.google.com/p/fizzbuzz/
http://code.google.com/p/fizzbuzz/source/browse/#svn%2Ftrunk...
Java: because it pays the bills, not because it's good. (TM)
Seriously, I kinda knew what to expect when I waded into the article, BUT, his "punchline" at the end about actually learning the domain of the problem, and jumping in a completely different direction about a O(1) way to generate an approximation in some cases, rather than further crap-plating the "solution", was GOLD.
But a really smart developer would know not to use Java in the first place ;-)
http://groups.google.com/group/rec.humor.funny/browse_thread...
We only use one implementation of it. Ever.
This is more of a rant against "developers" with "really bad habits" than it is against the language.
http://www.edge.org/3rd_culture/boroditsky09/boroditsky09_in...
Boroditsky, L. (2003). Linguistic relativity. In L. Nadel (Ed.), Encyclopedia of cognitive science (pp. 917–922). London, England: Macmillan.
Boroditsky, L. (2001). Does language shape thought? English and Mandarin speakers' conceptions of time. Cognitive Psychology, 43(1), 1–22.
To the linguistics question I just had a discussion about this with my boss (who's a linguist by training, currently on leave from his PH.d which has something to do with Greek, he's also been programming a lot longer than I have). One of the points he had was that the Sapir–Whorf hypothesis doesn't just say speaking German affects how you think in German, it affects how one thinks as a whole. To that we're both a bit skeptical, being a programmer probably affects ones cognition (or ones cognition causes one to be predisposed to becoming a programmer, whichever!), but the specific language one uses... probably not. On the other hand, within programming it's clear that different languages (or at least their communities) have different personalities. A good example of this is Python and Ruby. Semantically these languages are remarkably similar (in the context of all programming languages), however their communities have seized upon the small differences and as a result the cultures (accepted best practices and such) are fairly different. Another good example of this is what are known as "design patterns", which were originally supposed to be a language-neutral way of expressing how code could be architected, however in practice some of these really make no sense with certain languages, because e.g. the language has builtin-features which obviate the need for such things.
Java doesn't force you to over-architect solutions. Wrapping everything in classes doesn't make your code over-architected by definition.
For Java, the culture is heavily towards writing "solutions" like the overengineered mess in TFA. For python, it's different. Same for Ruby or PHP or .NET or...
Note that for none of those languages are you "forced" to act like the culture says you probably will. Nothing in Ruby forces you to unit-test, for instance, but the culture leans so heavily that way that it's more shocking when you find a team not doing it.
Maybe this "pattern" is less indicative of someone who comes from a "Java" background than it is someone who comes from an "enterprisey" background.
It just so happens that the overlap between the two is huge.
I hate to always be the defender of Java but it irks me to see articles that claim to lambast Java but really are about this so-called "culture" that has popped up.
OOP seems to be "difficult", so there are lots of "tutorials" around that "teach" OOP by introducing and combining all those brainless constructs, without teaching common sense. what you get is code monkeys who believe that this is decent source code.
every code base should have at least one person dedicated to simplifying it and questioning every design decision, so all this "just in case" abstraction gets the knife.
Pretty much everything in our ostensibly OO system could be static methods and it would work just fine. Objects rarely have instance variables, or if they do they are just used to set up a method call. Object lifetimes are measured in single-digit lines of code, and messaging between them is extremely rare.
But that's what people have been taught.
It could be Java (of course), C# (just about as likely), or VB (when they get the idea to use objects), or Python (yes, I've seen it).
OOP is only "difficult" if you try to learn it using a language that makes it difficult. Sadly, Java is one such language.
In Java, if I think I might need a pluggable architecture later, I'm tempted to go ahead and make it from the start, since my object model will need to support it.
Really though, I think this is more related to culture that Java (and many Java books) tends to nurture. Anybody that's read a fair share of "Enterprise" Java code has seen FooBarFactoryFactoryFactoryImpl classes.
Hopefully as dynamic languages on the JVM become more popular, this will change. But I find many "Enterprise" developers aren't really interested enough to look outside the box.
"jRuby? I think I've heard of that, what's wrong with Java?"
"It's not a real programming language unless it's statically typed."
[1]http://www.codersatwork.com/
(Edit: needed an inc in there)
In Scheme you could even write DEFN, again, one line.
[Edit:
Not RANGE you don't. Let's see how long it takes to get it under 5 lines]
Actually there might be an interesting contest, cross-language code golf.
If you want the polymorphic "memoizing version":
Actually it is incorrect for floats.
For non integer types, the factorial is given by the gamma function [1]. So, the factorial of 3.5 is Gamma(2.5) which is 3.3325 [2] not 6 as your function suggests.
[1] http://en.wikipedia.org/wiki/Gamma_function [2] http://www.wolframalpha.com/input/?i=gamma%283.5%29
Then see what excuses come forth when you suggest Haskell.
Haskell is lazy while ML is strict, and Ocaml adds "OOP" to ML so it's kosher.
Do you say:
Or do you go ahead and build the architecture that lets you specify which class or method to run per customer? The situation does come up often enough to have to make a decision.I'm working on a project which has tended to say "if (customerID = ..." in the past, but I decided to move to the more configurable/pluggable approach. I'm actually not sure if I made the right decision.
If your project makes one exception, a whole bunch more are likely to follow.
0 customers asked for it: Don't abstract it.
1 customer asked for it: Corner-case it.
2 customers asked for it: Strategy pattern.
3 customers asked for variations: Config option.
If you find that "1 customer" (being a series of disparate customers) all ask for various minor tweaks to the way something works, then before you have half-a-dozen disparate forks in your logic you probably do want to pull that out and use a strategy pattern where applicable. It doesn't take many clever sidesteps in a clean algorithm to make a nightmarish scenario later on.
Save yourself the time and bother to dig back in again and solve it at least for the foreseeable future with less overhead than what it would take to tackle the same task again, typically getting in to do the job takes at least as long as doing the job in situations like that (just to make sure you didn't break anything by accident, test the works and so on).
If the overhead is constant and a 'small' fix like that takes 5 instead of 4 hours including all the extras then that's a trade-off worth making. Unless of course you're really under pressure.
It's just a verbose way to perform the same abstraction. Use it only if function pointers are unavailable or considered unsafe in your programming language.
Listen to 37Signals when they say that it's OK for your customers to outgrow your software. http://37signals.com/svn/archives2/growing_in_vs_growing_out...
http://www.willamette.edu/~fruehr/haskell/evolution.html
"The Evolution of a Haskell programmer" is brilliant. Funny and insightful at the same time. Make sure to read the bottom part: "But seriously, folks, ..."
This smacks of the oft-ridiculed Java AbstractFactoryFactoryInterface. But let me put it bluntly: AbstractFactoryFactoryInterface's are how you write real, modular software–not little fart applications.
http://magicscalingsprinkles.wordpress.com/2010/02/08/why-i-...
[N.B. I'm not saying there isn't a lot of truth in the factorial article, it's just you have to know which challenges just need a one-liner function and which require an AbstractFactoryFactoryInterface]
All we've done here is forced the programmer to adhere to some ridiculous interface which requires just as much understanding of the internals of the code as overriding the method in the first place. You also end up with hard-to-understand naming schemes, which leads to two points of confusion:
1. Which one of the provided query factories does what I actually want? 2. If I need to write one myself, what would I call it?
PerQueryTimingOutQueryFactory? You've added more parts to that name than would have been included in an option to the query function! Oh, and it still doesn't include the information necessary to run, because it folded the timeout specification down into a HashMap. One more thing for the end-programmer to understand.
There are good places to use dependency injection. There are also places where Factories are exactly the pattern you need. But in most dynamic languages, you can achieve these effects implicitly through inheritance and closures, and it cuts down the complexity of the code immensely with only a minor cost to reusability.
This guy works for Twitter. They control Querulous. He could have just added a :timeout argument which takes an number or a function returning a number to the query method, but now you have to understand all this additional architecture to get anything done.
And thus you demonstrate using Java for prolonged periods may cause brain damage.
I'm not sure how to actually prove that, though.
After being exposed to Java for prolonged periods of time, my Python programs became classes with a main method that was called in an ifmain condition...
I was participating in an obfuscated code contest a while back (making a simple command line multi-function calculator), and since I knew I couldn't compete on line noise I decided to try obfuscation through architecture. Somewhere around writing a RightSideOperandInterface (so I could catch division by 0), it managed to get into my head that it was actually more robust and extensible than doing it any other way. I came to my senses slightly after submitting it.
Now, if the code was packaged as an API with very good documentation, then this might be a moot point. In my personal experience, this has rarely been the case.
The problem is that the people who come up with these designs are the ones that consider refactoring bad/hard/risky. They know very well that they need a hammer, but 200 what ifs later, they have this monster-factory, instead of just getting a bloody hammer, then, when they also happen to have a screwdriver and a saw, they build a toolshed, and refactor.
You can have a LoggingQuery(TimeoutQuery(Query())) or a EverythingQuery(timeout = true, logging = true).
From what I've seen, @nk loves composition.
http://en.wikipedia.org/wiki/Gamma_function
Math.pow(z/Math.E, z)
should be:
Math.pow(tmp2/Math.E, z)
So from this I conclude that there are two types of java programmers: those that over-architect their solutions, and those that don't even both to test the code they put in blog posts ;-)
I know he would have caught these bugs had he needed to implement these for a real system. But there is nevertheless an important point here. He is 100% right that over-architecting is a bad idea, and that correctness is more important than architecture. But complaining about a factorial function not handling negative numbers, and then "fixing" the problem by implementing the Gamma function, is doing the same thing all over again. It's really easy to fix the factorial implementation to do something reasonable for negative numbers; it's much harder to implement a Gamma function that works for all input. Why bother unless you really have to?
The amount of superstructure piled on top of a simple thing in concept is making programming much less accessible than it needs to be. By the time you understand the scaffolding you've lost sight of the problem.
The clojure example elsewhere in this thread it a really nice breath of fresh air, unfortunately it still needs all of java to run, and the number of times I've run in to obscure classpath issues and runtime problems are large enough to remind me that clojure is not entirely free from this sort of heritage either. That does not reflect on the language, but clojure has enough java roots that some of the issues are quite familiar.
Just look at the horrible mess that is ASP.NET assembly loading: you've got the bin/ directory, Temporary ASP.NET Files, and the Global Assembly Cache all competing when you load libraries.
In Javaland, we at least have Maven :)
is your friend. I've used that many times for stuff that I need to deploy across different releases of the same OS that as a rule have their devtools removed for security reasons.
My development system is typically geared towards desktop use (ubuntu atm), the servers run either redhat, debian or some other flavor and the static trick gives you a slightly bigger executable but the upside is that it is simply drop in and go.
This is not fair; Java has moved beyond this. In modern best-practices Java it is no longer necessary or even possible to write big piles of gratuitous OO spaghetti. Today, experienced Java programmers build their applications on top of huge, industry-standard, highly polished piles of architectural bullshit, wrestling with which leaves them with no time to write any code of their own.
It's not worth it.
To the extent Java correlates with bad software, IMHO the causation is more likely to be that the environments (in the vein of clueless corporate management, waterfall, http://www.halfarsedagilemanifesto.org/) are more likely to pick Java for their development.
That's the point. Java, it seems, make it tempting to "overarchitect" applications into excessively generic and modular beasts early in their lives when a much simpler implementation would be perfectly adequate and much simpler to extend and maintain.
I wonder if I will actually ever use one of those factories again in that app.
But you know what? Once we've established that you can write bad code in any language.. I'd prefer to deal with bad code in Java than almost any other language I can think of (and I've seen a fair bit). Maybe Python wins out, but I'd rather debug bad Java code than bad C++ or bad Ruby for sure.
The nice thing about Haskell is that it's really, really hard to write things that accidentally ignore purity, which eliminates a whole class of errors and bad designs. The type system only helps you if you let it, though.
I've looked at a friend's Haskell program for Huffman encoding, that used raw recursion in a undisciplined way instead of abstracting the control flow out into, say, filters, folds and maps. That was the closed I ever got to ugly Haskell.
Gut feeling says they'd be even more productive. Terrifyingly so, in fact. The sad fact is, though, that we'll never be able to conclusively prove a statement like that.
Every book I've read on Java shows even the "Circle inherits from Shape" examples to be ridiculously overarchitected and encapsulated (I am aware of why they are this way for illustrative purposes, however).
But its a good idea to do some of this stuff where and WHEN its appropriate. You can design a space shuttle to go to the grocery store doesn't mean you should.
I have seen my fare share of useless hard to understand and over engineered code in C, Groovy, Java and Lisp
A good book about this by Joshua Kerievsky is Refactoring To Patterns he talks exactly about this, the dangers of using patterns and the dangers of not (yes he uses Java in his examples)
And indeed I also have a feeling that premature optimization and general architectual astronaucy are deemed very highly by Java folks.
Don't get me wrong - I think that JVM is a marvel. I could also find greater joy in writing COBOL for a living. (at least my coworkers wouldn't be rambling constantly about stuff that is wrong and don't matter).
And that is also why the antarctic has its name...because there are no bears there, just penguins.
In python, it would be 15 lines, and simple. No utils, factories, algorithms or singletons.
I imagine it could be done in more reasonable java than this, but could it really be done cleanly and compactly?
When we see problems like this, it's right to ask questions about what the language design encourages its programmers to do. Additonally, languages have "personality" just like people do, and those personalities evolve with the user base of the language as they adapt to the language's environment.
The question of whether this type of code is more common in Java than say Python or Perl is empirical, and my gut says it is. Java is the commoditized, corporate language par excellence. The features it implements and the way it implements them have attracted the kinds of programmers who would write code like that. That's not to say that Java code forces its users to write code that is so inelegant. That's just to say that it creates incentives that often push toward ugly code. C++ does that too. On the other side, Python (to name one example) seems to set up its environment in such a way as to encourage pretty code.
There's no clear-cut good or bad here, only interesting trends in the code produced in various languages.
http://www.nhplace.com/kent/PS/Lambda.html
Languages are more than just syntax and semantics. There's also a whole culture that grows up around the language, a set of "best practices" and common idioms. And the one that's grown up around Java seems to value abstraction and design patterns and convoluted, overgeneralized solutions.
This doesn't apply to all Java code, of course. You can write lean Java just like you'd write lean C or lean Python. But a non-trivial amount of the Java jobs out there expect you to layer frameworks on top of frameworks and jump through an amazing number of hoops just to get something done.
From that context, the factory function makes this article a great example of over designing. And certainly in some senses the point of the article is quite accurate.
However, there exists another less common context for the factorial function--serious usage of the function. Not as an introductory example to the language, but as the basis of actual software that has purpose. In this context questions like memory usage, processing power, and even mathematical precision come in to play. Is BigInt large enough for the required use? Maybe you need to use a class based number type capable of having a limitless value. Perhaps you are targeting a distributed processing system, or a single super computer. Will your computing have memory limitations, processing limitations? Suddenly, when considering real applications for the factorial function, all those satirical over-design considerations are now principal necessities that you'd curse the programmer for not having implemented.
Ultimately, the key is scope. It's necessary to know the intended scope of the functionality you are coding. You need to consider who (will use code), how (the code will be used) and where (the code will be used [platform]). In Java, the big GET is the DRY philosophy. An immense library of code is available for your reuse. And it's certainly true when perusing the code base of those libraries you'll see a lot of what might seem like over-designing. However, when you consider the intended scope, it's rarely actually over-designed. Because frankly the scope of most code in the library boils down Everyone (who), Any purpose (what), Any platform (where). In such a scope, simple three liner utility functions seldom are sufficient or appropriate.
I'm not trying to troll, but are there any Java programmers who don't write code like this? I've never seen "real" Java code, which is to say production code, but every time I had to work with Java in school it was pretty much described by the above quote; and what's more, the libraries I had to interact with often made me feel like I was forced to do the same thing.
These are just the first two that came to my head. I ditched Java so long ago that I'm sure a few hundred more have come up.
The time to use it is when your business users can't agree -- one says do it that way, another says do it this way. Or they say it's one way, but they're not sure if it's going to change or not. That's a perfect time to cover both bases.
A tiny bit of abstraction applied well can save immense heartaches down the road. A bunch of abstraction applied poorly can make your life miserable. Part of "growing up" as an OOP developer is learning what to do when.
Also, this is a good example of why founders and startups use so little abstraction -- nobody knows what the problem is or what's going to change. It's all complete guessing. So might as well get something out the door today instead of playing mind games about what might happen or not based on your imagination of what a user might be. (This is also the reason functional programming maps so much better to startups: you don't have a spec. You just want one thing to go in and another thing to come out. If that isn't what people want, you have to iterate. Completely different paradigm than working in a business environment where somebody gives you ten bucks to go make well-defined change in system X)
Making things flexible may not sound pretty at the start. But ending up changing 3 apparently completely separate systems to accomodate the fact that you changed the version of some software and the output format needs to be different (hey - why would we do immediate representation, output's not going to change).
It looks silly in the example, because it's a simple operation. Now substitute 'factorial()', for 'provision_customer_config()'. You WILL change the backend at some point. You WILL change the endpoint at some point. You WILL change the way the function works completely, but will have to retain the previous procedure for existing users. It's a when not an if question. There should be a saying similar to that about people doing backups: there are 2 types of programmers - those who write modular code and whose who will.
I think this is the real problem with this particular example - because the author wanted to make this into an anti-pattern joke, he didn't actually think about what a Real Programmer actually trying to make good use of the extra keystrokes would do in this situation.
The "requirements" that he's using to build up this bloat appear to be:
1) The result might be really, really, big, so no factorial function worth a damn should be limited to a normal int.
2) We might want to swap out implementations of our factorial function, for instance, to cache intermediate results, to use a loop, to use recursion, etc.
3) We should be able to add new implementations without modifying class files already written, and we should be able to select implementations at runtime.
Fine, then. These could be 100% realistic requirements if we were talking about some more substantial or important piece of machinery than a factorial function, so we really might have to satisfy these things, in which case the three-liner would be completely and utterly unacceptable.
So let's say we're in that situation, then. A good Java developer still would never write the code the way it came out in that post, because the abstraction doesn't go far enough, and it's obvious. Almost every time he writes "FactorialAlgorithm", we could just as well replace that with "Algorithm" (or maybe better, "BigIntegerAlgorithm"), and now instead of building up a whole bunch of architecture just to support one stupid algorithm, we've spent a bit of effort to make all of our (big integer) algorithms swappable. That memoization stuff could almost immediately be refactored up and out of the individual implementations, so it would take but a single method call or flag to add that ability to an algorithm, and so on.
I'm not saying that the particular architecture that gets built up to do this stuff is necessarily pretty, that Java-style OOP is an optimal (ha!) or even passably efficient way to achieve these constraints, or that you should do these types of abstractions before you know you absolutely have to. By all means, if you don't have any other requirements on the table, write the naive factorial, FFS! But when you've got to abstract things, rule #1 is to make sure you pull everything "up" far enough, otherwise you're never going to extract enough value out of the added complexity to keep yourself sane.
It may not be true in all situations, but I think this benchmark is a good one, as it accounts for the environment of the developer. If the code is only used once, it isn't worth it to abstract it. Once you have two implementations and need to add a third, you may realize they have commonalities and so abstracting and refactoring make sense.