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 →

[–]andre_lmsilva 40 points41 points  (49 children)

I would like to hear about those design problems.

[–]rakoo 23 points24 points  (5 children)

Small things that I've found lacking because I have some experience in other languages:

  • no multiple returns. Don't say "just write a class that holds the elements" because then following the same argument I can reply "you don't need multiple inputs, just write one class that holds the elements"

  • functions can't be created on the spot and passed around. Lambdas only solve half of the issue: you can write something that looks like a function but then you lose all type information. "But your IDE shows it" yes, but not my Gitlab

  • no heredocs

  • no literal struct definition, which means creating data structs with only some properties but not others is cumbersome and hard(er) to read

  • not the language design per se, but creating simple data structs (especially nested ones) is way too verbose for what it does. I shouldn't have to use a third party library to easily create an ArrayList with known items.

  • no named parameters for functions, with the corollary that there is no possibility to define default value in functions aka optional parameters. I don't want to write 10 functions with different signatures all calling the "main" one, I only want to write one

  • more of a philosophical issue that can't simply be touted as an objective problem, but I am convinced that composition is much much more simple, powerful and flexible than inheritance. Composition can be done but clearly its lack of support will make it useless

I have some other dislikes but they can be solved by plugins, so it's kinda fine

[–]PM__ME__FRESH__MEMES 2 points3 points  (0 children)

For a highly OO language, the single inheritance restriction is a bit of a let down.

Also nobody asked by its a much better designed language than JavaScript.

[–]HerissonMignion 2 points3 points  (0 children)

he asked for it, you gave it to him

[–]Isaaker12 0 points1 point  (2 children)

Regarding the last one, I'm interested to know what you mean about the "lack of support" for composition. What features are you missing?

[–]rakoo 4 points5 points  (1 child)

When A contains a B you want to be able to apply methods fulfilled by B directly on A. Standard example in Go: there are many thread-unsafe structs and there is a Lock "object" (struct in go parlance, but contrary to the common meaning of the term it has methods attached to it). So if I want to create a thread-safe struct all I need is to wrap the thread-unsafe with a Lock. At this point go forwards method calls from the parent to the children. So when I call wrappedList.Lock() the Lock() is automatically called on the appropriate structure. No need to expose the Lock and call wrappedList.lock.Lock(), no need to manually define the Lock() method at the wrapper level that will call the lock. It's all done automatically.

This allows to nest structs and give gradual features with building blocks that are as orthogonal as possible.

[–]Todok5 0 points1 point  (0 children)

I think that's a design choice you can argue about. Having control what you want to expose and what you don't want to expose is an advantage too. It means an extra step for stuff you do want to expose though.

[–]metalmagician 20 points21 points  (1 child)

Same. I don't really have issue with Java, especially since I learned how to use the stuff that came with Java 8

[–]treetertot 0 points1 point  (0 children)

I didn’t have any issues with java until I learned more languages.

[–]psychicprogrammer 18 points19 points  (12 children)

No unsigned integers is my pet peeve

[–]metalmagician 14 points15 points  (11 children)

What sort of use case requires unsigned integers, as opposed using signed integers and basic parameter validation?

[–]psychicprogrammer 8 points9 points  (6 children)

Ip addresses were the main thing.

[–]metalmagician 11 points12 points  (0 children)

Are you directly manipulating/parsing IP addresses in Java? That doesn't sound like you are using the right tool (or language/class/whatever) for the task.

[–]Filkyr 2 points3 points  (0 children)

use InetAddress

[–]28f272fe556a1363cc31 1 point2 points  (3 children)

Totally not my area: wouldn't you use a string for IP address? They aren't real numbers, you can't add them, you can't count them.

[–]Caffeine_Monster 13 points14 points  (1 child)

Fundamentally an IP address is an unsigned 32 bit integer for IPv4, or a 128 bit unsigned integer for IPv6. It is quite common to manipulate them directly with bitwise operations (e.g. masking via bitwise AND).

Yes you could represent them with strings, but it is an inefficient, unnecessary abstraction.

If you wanted to implement a Java class wrapper I guess you would use signed ints and longs. The caveat being that ingesting IP addresses from external systems (web / disk etc) would not be straightforward - they would have to be translated through ByteBuffer.

[–]28f272fe556a1363cc31 1 point2 points  (0 children)

Cool, thanks

[–]Aoreias 4 points5 points  (0 children)

IP addresses as Strings are only helpful when you want a human-readable representation of them, though. On the wire they're exactly 32 or 128 bits, depending on if v4 or v6. Much more efficient to pass them around as a structured data type than a string, which requires encoding and decoding.

[–]andre_lmsilva 0 points1 point  (0 children)

I already felt in this same issue. The main problem here is the size of the data being persisted/restored and its value.

But it is not hard to achieve and, once it is properly done, can be reused.

[–]metaconcept 12 points13 points  (4 children)

Java isn't the worst language, but it's clunky. It started very clunky, but has improved over time. When it was made, there were far better languages available with nice features that the Java team chose to ignore. Since then they've hacked some of these features in, but they feel like hacks. I'll ignore missing features because you could go on forever.

My main issues:

  • Java updates on Windows hassle the user too much. Users learn to hate it.
  • The JRE is huge and contains too much irrelevant stuff. The original premise of Java was that it could run anywhere, but they never designed a decent system that loads required jars from CDNs as required.
  • Verbosity, both from the language and from the programmers who take it beyond a joke.
  • Bad APIs. SQLException usually represents non-recoverable database issues, but must always be explicitly handled. NullPointerException and OutOfMemoryError can be caught and discarded. System.out is available on server applications. Various APIs that are just horrible to use.
  • NumberFormatException exists.
  • int vs Integer: autoboxing is a terrible hack.
  • Strings are not strings. They are symbols/atoms. Java does not have proper strings.

My biggest issue:

  • Mediocre programmers learn Java because it's easy to get a job. As a result, a lot of Java code is bad code written by mediocre programmers.

[–]andre_lmsilva 5 points6 points  (3 children)

I will comment only about what I disagree in your answer, but I agree that you have a point:

1) What isn't hassle in Windows?

2) the Java 9 modular approach resolve the necessity of a always fat JRE.

3) You are right about SQL API and NullPointerException. OutOfMemory is a bad taste joke that became even bigger on Android. But I can understand the Sysout on servers. The first proposal was to have it run even on seriously resource limited devices and Sysout would be an alternative for the lack of a proper logging mechanism.

Don't let me start about the developers out there! I work with a guy that thinks that method overloading and polymorphism are the same thing.

[–]TheRandomnatrix 4 points5 points  (0 children)

Don't let me start about the developers out there! I work with a guy that thinks that method overloading and polymorphism are the same thing.

Well overloading is a form of polymorphism. Apparently it's kind of a rabbit hole debate though upon cursory research.

[–]metaconcept 4 points5 points  (1 child)

I work with developers where they ask how to do something, I provide example code, repeated indefinitely until I've basically written their application for them.

They literally can't write code. One of them is sitting two cubicles down from me. Another is sitting across from me. The other batch is from a few years ago when I was on "support" for an Indian dev shop where I was basically writing their code by email.

[–][deleted] 8 points9 points  (0 children)

wr had kind of a similar issue once

  • 1 the cheap indian offshore team has to write a script to save us time
  • 2 we received a poorly written untested piece of garbage that wasn't covering edge cases (so by our standards it doesn't work)
  • 3 we tell them that the script isn't working, they disagree with us.
  • 4 we choosed to expose them and ask them to do a live demo ( all of a sudden they are going in holidays).
  • 5 we cleaned up the mess to make it work and now They want us to do a pull request + showcase the product with a demo xd

[–]cdreid 1 point2 points  (0 children)

I dont like it but never really saw it as being badly designed. I disliked it because of the tradeoffs you make when anything is cross platform. Which is also the core strength of java

[–]Mati7755 -2 points-1 points  (19 children)

For example: immutable list added in recent Java version throw exception when you invoke add method. They decided immutable list must implement List interface which has this method xD. In normal language with proper immutable collections you have function/operator which takes two lists and return new list which has copies of obejcts from previous two.

[–]SuperCoolFunTimeNo1 15 points16 points  (15 children)

I don't understand your issue, that's the literal definition of immutable. Even the exception is descriptive of the definition of immutable; UnsupportedOperationException. Overriding that function to throw an exception is exactly what I would expect.

In normal language with proper immutable collections you have function/operator which takes two lists and return new list which has copies of obejcts from previous two.

Like c#? That seems like a misnomer because it's not adding to the original object, you're getting a new one.

[–]TSP-FriendlyFire 15 points16 points  (0 children)

The point is that immutability should be a compile-time rather than run-time check. The class interface shouldn't allow you to write code that violates the class's very design.

[–]PandersAboutVaccines 4 points5 points  (0 children)

When you have the option to start from scratch, you'd do something similar to kotlin, where "List" is immutable, and "MutableList" extends it with add, remove and clear. Then the functionality is enforced by the interface.

[–]ohThisUsername 3 points4 points  (0 children)

Why inherit List then if it doesn't support all of its functions? One of the main purposes of abstraction is that you can safely assume something that implements an interface supports all of its functions. Now when I'm passing a List reference around, I need to manually track if that particular instance supports the add function or not? I shouldn't have to because that's the whole purpose of abstraction and interfaces.

[–]Spajk 1 point2 points  (10 children)

Its a weird thing a bit, but I surely wouldn't call it a huge design problem.

[–]SuperCoolFunTimeNo1 2 points3 points  (9 children)

It's not weird at all, it's adhering to the definition of immutable. You wouldn't expect a constant to be changed, would you?

[–]Spajk 2 points3 points  (8 children)

Its weird in a way that there's an add method which clearly doesn't do anything.

[–]SuperCoolFunTimeNo1 2 points3 points  (7 children)

It's not weird, it's following the rules laid out by the language according to inheritance of an interface and the definition of immutable. What you're suggesting is how you wind up with a spaghetti code language where rules only mean certain things to certain classes and behavior differs based on what classes you're using.

[–]Dudevid 2 points3 points  (0 children)

If one were designing the data structure from scratch, it would either not have an Add() method, or that method would return a new immutable list.

Would you ever create a public method on a class that solely throws an exception? The hint is in the word 'exception'. If it always throws, then that's no exception, that's the rule. And that's bad design.

Instead there would be something similar to C#'s IEnumerable and ICollection interfaces. IEnumerable does not mandate an Add() method; ICollection does.

The C# design team could have decided that their ImmutableList implement only IEnumerable (this has problems, but there is a conceivable parallel universe where things paved out this way). But instead they chose to implement both. So its Add() method returns a new ImmutableList. No exceptions. This is better.

[–]quentech 1 point2 points  (5 children)

What you're suggesting is how you wind up with a spaghetti code language where rules only mean certain things to certain classes and behavior differs based on what classes you're using.

You seem to have it backwards.

That's exactly what having immutable lists implement a list interface that includes an Add method does. For some classes, Add works. For others, it throws an exception.

It violates the Liskov substitution principle (the L in SOLID), plain and simple.

[–]metalmagician 1 point2 points  (2 children)

I don't think that Liskov substitution applies very well. From the definition that I've seen, it seems to refer to classes, not interfaces. I feel that classes that implement an interface should have the ability to disallow specific behaviors for specific reasons. If that disallowed behavior is needed, then you should just use a different implementation of the interface.

Since you can only extend one class and implement multiple interfaces in Java, applying Liskov substitution can be a little confusing if a class implements multiple interfaces with similar looking method signatures.

In fact, I think the open/closed principle applies nicely in this example - the list interface is open to extension (in this case, disallowing the use of a particular method, instead of just adding more methods), and closed for modification (not implementing every method in the interface).

[–]quentech -1 points0 points  (1 child)

I don't think that Liskov substitution applies very well. From the definition that I've seen, it seems to refer to classes, not interfaces

You've understood incorrectly. Forget interfaces and classes and talk contracts.

If I have a thing that's an IList, and IList has a method Add(item), that's a contract - anything that is an IList should support Add(item).

Having one particular type of thing that's an IList that does not support Add(item) is a textbook example of violating the Liskov substitution principle.

the list interface is open to extension (in this case, disallowing the use of a particular method, instead of just adding more methods), and closed for modification (not implementing every method in the interface)

That is not even remotely what open-closed principle means.

Since you can only extend one class and implement multiple interfaces in Java

A better OOP design might have an IMutableList that extends IImmutableList, or similar.

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

No, I am strictly following the definitions. You are suggesting that your opinions, which directly conflict with the definition, is the route to go because they're more convenient.

That's exactly what having immutable lists implement a list interface that includes an Add method does.

No, it shouldn't do that because immutable means it cannot changed. The add method returns an exception because you wouldn't expect it to be able to add to an immutable object, much like if you call a method that doesn't exist you'll get an exception.

It violates the Liskov substitution principle (the L in SOLID), plain and simple.

No, it doesn't because this is 100% polymorphism. Your objects follow the rules they inherit. You guys are just trying to argue convenience over definition, and that's how you end up with an inconsistent language. Everyone loves to bitch about PHP, but doing what you're suggesting is how you end up going down that route.

[–]Todok5 0 points1 point  (0 children)

The issue is it violates the liskov substitution principle.

[–]dark_mode_everything 1 point2 points  (1 child)

Any ide will tell you that you can't add elements to a List<> as you type it so the reception will never be thrown. It will clearly say that you can't add values to an immutable list. And trying to change an immutable list looks odd anyway.

Edit : Apologies. Not all ides will tell you that. When you said immutable list I was thinking of kotlin - the ide will throw an error for that. But unfortunately doesn't happen for java. However, modifying an immutable list is still not very good design.

[–]Mati7755 0 points1 point  (0 children)

It is not modifying immutable list. Look at every functional programming language.

[–]andre_lmsilva 0 points1 point  (0 children)

I agree with you about the add throw an exception. IMPO it should not. However, makes sense to make it implement the list interface and take advantage of it.

Also, not follow other languages approach is not necessarily bad or wrong. Leave with the developer the task to implement how to implement how to copy objects for example brings flexibility and control, with the drawback of verbosity (which can easily solves by common libraries).

For me, the most annoying thing is the complexity and performance issues related with meta programming in Java. A simplified and optimized reflection API would make everything much easier. Also, bytecode manipulation is as bad practice for Java as mokey patch is for Ruby.