Hacker News new | past | comments | ask | show | jobs | submit login

My company is doing most of these things (except it's more complex because we're polylingual, and don't use Java for web apps), but Java still sucks.

The reason it sucks has more to do with the language and the culture that has grown up around it, than the mechanics of building and deploying that this article talks about. These mechanics have to be solved for most languages. They're table stakes; mostly just a basic level of usability from which you can start to measure against other things.

If I had to name the single worst thing about Java, it would be the tendency for business code to degenerate into a 1:1:1 ratio of interface:class:public-method. For any significant piece of logic, it ends up living in a class on its own, with its dependencies injected either via constructor parameters or method parameters. Whether the constructor or method is used doesn't really matter, unless the method is to be called many times, in which case the constructor acts as a kind of partial application. And of course the class it lives in needs to be the only implementation of a corresponding interface, which only has a single method, the method in question. All other interesting methods this method calls must in turn be called via single-method interfaces, with these interfaces injected via parameters, one way or another.

The cause of this is a religion around a particular style of testing. The development driven by the need to create tests for everything leads to code that has very little cohesion, very little structure, and most closely resembles 80s procedural code, but with vastly more ceremony. Code is hard to browse and read because the link between method call and implementation is hidden in a runtime indirection via an interface reference. This lack of legibility in turn encourages doing more work in these methods (albeit broken out into private methods), and instead of an OO decomposition of the problem, you end up with a poorly factored procedural decomposition.

The biggest symptom is classes with names that are close anagrams of their primary method. For example: StaleJobsCleaner.cleanStaleJobs, StaleJobsFinder.findStaleJobs, JobDeleter.deleteJob, JobDepedencyFinder.findJobDependencies, etc.




The reason it sucks has more to do with the language and the culture that has grown up around it

I think the culture is a far bigger issue than the language. Java is good when used properly, especially with the Java 8 updates. I don't think it's fair to blame Java for the culture issues. During the 90s and early 2000s, an "enterprise" culture formed using Java. However, that culture used Java because it was the new, popular language at the time. If Go or C# or Scala was released back then and had the same position as Java, that culture would've also produced very bad code in those languages.

Fortunately, the Java culture seems to be changing. There is now a more noticeable division between the old style "enterprise" Java code and newer, modern, and simpler ways of using Java.

These days it's important for a developer to know one static language and one dynamic language well. And that doesn't mean just knowing the syntax. It also includes learning the idioms and best practices in those two languages. The actual combination doesn't matter. It can be Java/JavaScript, C#/Ruby, C++/Python, Haskell/Closure, etc. For example, I focus mostly on Java and JavaScript because I use one for server and the other for front end. My JS knowledge made it easy for me to understand the lambda addition in Java 8. My Java knowledge allows me to understand the advantages of optional typing such as Typescript and Flow.

I've seen Java-only developers struggle with lambda in Java 8 because they've never written functional style code. And I've seen JS-only developers fail to understand how optional typing could be helpful because they've never seen the power of static typing in IDEs.

I used to dabble in languages, but I don't have time for that anymore. I've found that focusing and keeping track of changes in Java and JS are enough for me. JS is changing with ECMAScript 6 and includes good ideas from other dynamic languages such as Python. I'm hoping that Java starts to get some of the best ideas from Haskell and Scala.


Java 8 has pluggable type systems, some of them (physical units) are more powerful than what can be achieved in Haskell. There's even pluggable types that enforce immutability; even Scala can't do that.


Yes, Java is a passable language, even if not something I enjoy. However, the programming culture is a big problem, because of the tendency to overengineer in general and overdesign class hierarchies in particular.

The Android project I work on was started by an experienced Java team and has an interface for almost every class, uses injection and had a customized UI framework that was a nightmare of interfaces. Some of that has been cleaned up meanwhile, but those initial decision are hurting maintainabity and performance even now.


This blog post helps explain why most Java projects end up with StaleJobsCleaner.cleanStaleJobs, etc.

http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom...


"For any significant piece of logic, it ends up living in a class on its own, with its dependencies injected either via constructor parameters or method parameters."

And I was worried I was the only one who thought this was crazy!

In most python frameworks, like django, when you want to call some service, you import some static class or singleton. It is simple and straightforward. If the framework wants to allow you to swap in different implementation classes, then it will define a singleton or static class with a clear API that then dispatches to the specific implementation class based on your global settings. Or, if you want to override the default implementation on the spot, you can just invoke the implementation class you want directly.

So in django, I just do:

  from django.core.cache import cache
  cache.get('my_key')
Voila! That's all the code I need, and it works great. I can easily swap in memcached or a file cache or a local memory cache or whatever. It is straightforward and easy to debug. If I am wondering why the wrong implementation service is being used, I can usually step through with a debugger right at the point it is being called and figure out what is going on. If you I am writing tests, I either use different settings or monkey patch the static class in my setup and tear down functions.

In most java frameworks, your classes are supposed to either accept the service via a constructor, or have it injected into your class via Guice or Spring. This adds a whole new level of complication and verbosity, and means things break far away from when you are using them. I know people swear by dependency injection, but I really don't get it, I have never found it to be a better pattern than the django pattern.


Python doesn't do anything differently. Your line of code there is accepting the django.core.cache.cache object as the 'cache' parameter to its containing module's global namespace, 'injected' by the module loading system.

Java doesn't have that loading system, so there is a small cottage industry of frameworks that provide its valuable features. None of them can rely on a language-specified import system and global namespace to which things can be added, which is why they get parameterized one way or another.

Breakage from faraway places can happen in either system, as far as I can tell. It's kind of inherent in the abstraction.


That seems excessive to me - at least, I don't see how SOLID principles necessary lead to that outcome even if strictly followed. What is it about those principles that prevent classes/interfaces that have a handful of related methods, like a StaleJobsService or whatever?

For all of the OO patterns, they have a common rule that says to not use them unless they actually solve a problem / code smell.


> The biggest symptom is classes with names that are close anagrams of their primary method. For example: StaleJobsCleaner.cleanStaleJobs, StaleJobsFinder.findStaleJobs, JobDeleter.deleteJob, JobDepedencyFinder.findJobDependencies, etc.

This issue has nothing to do with enterprise style, and everything to do with the fact that that's how "functional" programming is done in Java. What is really wanted is a first-class function `x`, but that doesn't exist. So instead an `Xer` SAM interface is summoned into existence that has that `x`.


>with its dependencies injected either via constructor parameters or method parameters.

Most of the enterprise Java code that I have seen uses annotations for dependency injection. This is common in both Spring and JEE.


Sadly, the unit-test everything mentality leads to a lot of problems in other languages as well. Often times, it provides little to no benefit to anyone while adding complexity to code and changing design for the worse. In a typical application, there are few methods that really benefit from unit tests and even fewer bugs prevented and/or caught by them yet still I see designs being modified to accommodate unit testing while other types of testing that could be useful are generally ignored. Even refactoring, which is the real purpose of unit testing, is generally not much helped by it.


If I had to name the single worst thing about Java, it would be the tendency for business code to degenerate into a 1:1:1 ratio of interface:class:public-method.

I agree. It's ugly and hard to maintain. Most enterprise Java usages of "design patterns" are unnecessary cruft. This is where I step back and say, "what you really want is a function".

The argument I tend to make for functional programming is that it only has two design patterns: noun (immutable data) and verb (referentially transparent function). And even though it's very rare that pure functional programming is used, mutable references (e.g. TVars for STM, reference cells like IORefs) are just "noun-plus" and actions (e.g. m a for some monad m) are just "verb-plus". It's easier (read: possible) to reason about the complex stuff if the simple stuff is done on a sound foundation.


Even object-oriented programming is made better by importing functional concepts. Python's take with "classes are simply functions that return objects" is much cleaner conceptually than the "everything is an object and objects do everything" pattern.




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

Search: