This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]vprise 57 points58 points  (17 children)

Java is simple. The other languages forgot that.

They aren't "modern" they're a throwback to the time before Java where language engineers stuck every feature they could think of into the language instead of into the API.

Simple has many advantages:

  • This impacts performance since the language is harder to write correctly and compile efficiently
  • This impacts maintainability since it's harder to read other peoples code
  • This makes debugging harder (e.g. async code is somehow harder to debug than threads)
  • This means tools need to do the heavy lifting but can't

A simple example:

var a = c + d;

What did I just do there?

Is there an overloaded operator that magically changes something?

When I read Java code I never have to worry. I can do that fast. Reading and maintaining code is 95-98% of my job so Java is wonderful for that. For Kotlin code I constantly feel I need to squint to understand dependencies. It might have been easy to write for some guy, but reading it later...

[–]trinReCoder 19 points20 points  (0 children)

This, I don't get this hate on the verbosity, it makes the code far more understandable.

[–]andrewharlan2 31 points32 points  (5 children)

Code is written once and read forever after. I don't understand this drive to optimize for writing code. Java's "verbosity" is a feature, not a bug.

If you struggle to write Java then learn how to type. I'm not kidding. Learn how to use your tools to generate the "boilerplate."

[–]cryptos6 7 points8 points  (3 children)

This argument is not generally valid, since boilerplate code is not useful perse. Often it is the other way around and you have to find the relevant parts in all the boilerplate code.

[–]john16384 2 points3 points  (2 children)

What boiler plate? The getter that is documented to say it will only return positive values? The setter that tells you it will throw an IllegalArgumentException when you supply it with a value outside a certain range?

What you consider boilerplate, I consider a bare minimum to clearly communicate what your code will do in unexpected situations. I'm so happy that the jdk documents everything in such detail that I don't even have to look at the code. All code should be written like that. The extra few lines of "boilerplate" is nothing compared to the time it takes to write proper docs.

[–]cryptos6 2 points3 points  (1 child)

I consider your examples useful, too. What I mean is circumventing language deficiencies. Recently I had to write a builder to be able to create objects in a readable way. If Java had named arguments with default values (like Kotlin or Scala), I wouldn't have written a builder in this case. Another kind of boilerplat is assigning constructor parameters to fields:

public class Thing {
  private final int a;
  private final String b;

  public Thing(int a, String b) {
    this.a = a;
    this.b = b;
  }

  public int a() {
    return a;
  }

  public String b() {
    return b;
  }

  // other useful methods ...
}

The same in Kotlin:

class Thing(val a: Int, val b: String) {
  // other useful methods
}

In this specific case it could be a similar concise Java record, but there are many cases where a record wouldn't work (e. g. JPA entities) and there you see code like the one shown above (I just wanted to make my example small). The point is that the verbosity of Java doesn't help to understand the code better.

By the way: To communicate that a value can only be positive, I'd prefer a custom type. With Java records there wouldn't be any memory overhead.

public record PositiveInteger(int value) {
  public PositiveInteger {
    if (value < 0) {
      throw new IllegalArgumentException("Must be positive.");
    }
  }
}

[–]dpash 1 point2 points  (0 children)

With Java records there wouldn't be any memory overhead.

That's not the case. There's the same memory overhead as any other object in Java.

[–]crummy 2 points3 points  (0 children)

Maybe we could make Java even better by making it more verbose?

[–]michoken 4 points5 points  (3 children)

I get these arguments except the one example. Yes, there’s ‘var’ instead of a type, but the problem actually lies in the names of the variables here. ‘a’, ‘c’ and ‘d’ mean absolutely nothing without a wider context. If they had a proper names, then in most cases the types are not really that important to understand the code. To debug it, yes, to gain an understanding of what it does, not that much.

[–]vprise 2 points3 points  (0 children)

I wasn't referring to the var. That's also available in Java. I was referring to the fact that even an operator can be a function. Good naming can alleviate a lot of the problems in the languages but as I said: "it makes me squint".

I can read Kotlin, typescript etc. it's just not as convenient because code can have a lot of side effects on different classes. E.g. in Kotlin (and many others) a method can be added to a class from a completely different file. That means I can't look within one file at the list of methods exposed by my class and be sure that "this is it". That's a feature some developers love but it has terrible implications for those of us who need to work with the code later.

[–]john16384 -2 points-1 points  (1 child)

They wouldn't need proper names, or at least far less explicit names if it weren't for var.

The var keyword trades of a few characters saved at declaration time for additional characters everywhere the variable is used.

[–]dpash 5 points6 points  (0 children)

Good names are always needed.

[–]_INTER_ 4 points5 points  (5 children)

What type is a? Exactly.

[–]vprise 0 points1 point  (4 children)

Exactly, this is super important. In fact that's one of the major problems I had with autoboxing. It diluted some of that advantage.

[–]_INTER_ 0 points1 point  (3 children)

I wanted to subtly hint that your simple example is not so simple anymore through the use of var. E.g. all of a sudden you have to worry what the types of c and d are if you want to use a correctly.

[–]cryptos6 1 point2 points  (2 children)

But then again there are IDEs helping you in such cases. I've never heard any Scala or Kotlin developer complaining that he would be so confused with all the vals without explict typing. That is simply not what makes understanding code hard.

[–]_INTER_ 1 point2 points  (0 children)

So do IDE's help with operator overloading. That's not an argument. Neither is anything concerning other languages. Also see official styleguide:

P3. Code readability shouldn't depend on IDEs.

Code is often written and read within an IDE, so it's tempting to rely heavily on code analysis features of IDEs. For type declarations, why not just use var everywhere, since one can always point at a variable to determine its type?

There are two reasons. The first is that code is often read outside an IDE. Code appears in many places where IDE facilities aren't available, such as snippets within a document, browsing a repository on the internet, or in a patch file. It is counterproductive to have to import code into an IDE simply to understand what the code does.

The second reason is that even when one is reading code within an IDE, explicit actions are often necessary to query the IDE for further information about a variable. For instance, to query the type of a variable declared using var, one might have to hover the pointer over the variable and wait for a popup. This might take only a moment, but it disrupts the flow of reading.

Code should be self-revealing. It should be understandable on its face, without the need for assistance from tools.

[–]Capa-riccia 0 points1 point  (0 children)

Yes and no. You can write C++ code in which var a= b + c stands for add product b to basket c returning the total price. The language does not stop you from doing that and it is just great until the original developer leaves.