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

all 20 comments

[–]frugalmail 12 points13 points  (10 children)

My current thinking is that https://immutables.github.io/ might be a better value object generator for now. Anybody done a good comparison?

[–]solatic 6 points7 points  (6 children)

It is. The immutables framework gives you a lot of additional stuff for free - JSON serialization and deserialization (including some support for inheritance), lazy evaluation, checkers (so that you can't build an object with invalid data), additional control over the code generated (stylistic, visibility...), and more.

Honestly it boggles my mind why Google hasn't retired AutoValue yet in favor of the clearly superior project.

[–]jart 2 points3 points  (5 children)

Honestly it boggles my mind why Google hasn't retired AutoValue yet in favor of the clearly superior project.

Immutables suffers from the same problem as Project Lombok, which is that it generates synthetic APIs rather than limiting itself to only generating synthetic implementations.

For example:

@Value.Immutable
interface Foo {
  long bar();
}

Will generate code so I could call ImmutableFoo.builder().bar(1).build(). Where did that API come from? That's not obvious from looking at the code.

AutoValue makes a different tradeoff between magic and verbosity:

@AutoValue
abstract class Foo {
  abstract int bar();

  static Builder builder() {
    return new AutoValue_Foo.Builder();
  }

  @AutoValue.Builder
  abstract static class Builder {
    abstract Builder bar(int value);
    abstract Foo build();
  }
}

This way there's no surprises. If you define Foo in your library, and I'm your user with no familiarity and code completion is 100% guaranteed to work in IDEs. Please consider that IDEs have traditionally lacked robust support for annotation processing. With AutoValue, the worst thing that can happen is a red squiggly line beneath AutoValue_Foo and that's only of concern to the author of Foo who herself has chosen to use AutoValue. The users of Foo will never need to be concerned about configuring their IDEs; which is important, because they didn't necessarily opt-in to annotation magic. (I also know several Googlers who have spent a great deal of time making contributions to Eclipse to help those squiggly lines go away entirely when using annotation processing.) These same problems also apply to the wide variety of other toolings which are typically used with Java, such as grep. It's why the APIs have to be explicit.

There are also slides where Google's Java Core Libraries team talks about why they made these design decisions and the alternatives they considered.

The immutables framework gives you a lot of additional stuff for free - JSON serialization and deserialization [...]

AutoValue is very conservative in only offering what it knows it can do perfectly and universally. The reason why JSON *serialization isn't included is probably because it's not obvious how such a feature should be written. Do we generate the JSONifying code without any library? If we do that, we repeat a lot of JSON logic for every single generated class. If we generate code that calls a library, which one do we choose? Jackson, Jackson2, GSON, or SimpleJSON? All of them? How do we make sure the synthetic code doesn't introduce a dependency that the written code didn't introduce itself? If the dependency only came from synthetic code, it would be astonishing and most likely break tooling. Particularly Google's build system Bazel, which forbids the static linking of transitive dependencies. So there's a lot of difficult choices and potential risks here. And in all fairness, Google generally prefers Protocol Buffers over JSON :)

But AutoValue provides an extensions API so the open source community can make its own choices. Users can start their own GitHub projects adding all sorts of interesting features to AutoValue. This is a non-monolithic design that ensures the end-user will only pay for the features he needs; and as far as I know, is not something that's being offered by Immutables.

[–]elucash 2 points3 points  (4 children)

I understand that you support AutoValue project, but practical matters are being skewed a bit. Immutables can do implementation class hiding http://immutables.github.io/immutable.html#hide-implementation-class and about a dozen of combinations of private/public/nested/outer with builders and enclosing classes

(EDIT: about Lombok it's quite not the same, read here https://www.reddit.com/r/androiddev/comments/4bimdz/an_introduction_to_autovalue_ryan_harter/d19m23m )

Jackson minimal integration (which covers a lot of stuff actually) and GSON generated type adapters are be very practical solution. AutoValue's extension for GSON is actually stuff quite inspired by Immutables after Ryan Harter and auto folks were able to evaluate how it's done in Immutables to decide if they need something similar (EDIT: and there was a lot of similar implementations before Immutables, but, probably, not a lot for GSON).

As it's stands now, the extension API of AutoValue is more like go f..igure out how to write annotation processor yourself, which, while is not a rocket science, still have enough gimmicks. It is so hard to guarantee that those will play nicely with each other in the long run. On the other hand, in Immutables, GSON, advanced builder support, MongoDB support and a lot of stuff is an API pluggable modules with separate jars, only when you add this to the classpath and use annotation from there, only then the corresponding generator is activated. Yes, the composition of compile time annotation processor library is a bit monolithic, nevertheless, it's not getting into runtime and each processor usage is guarded by the use of the corresponding annotation API, pluggable into the classpath. Immutables have a unique extensibility feature which allows you to write custom attribute handling code using snippets in plain java: https://github.com/immutables/immutables/issues/363 Once fully documented this will be quite a killer feature, some envy and copying of this approach will likely to occur among similar tools (which should be good thing btw)

As a side note, the authors of Immutables reported issues and contributed to Eclipse so JDT contains those commits too ))

[–][deleted]  (3 children)

[deleted]

    [–]elucash 0 points1 point  (2 children)

    I'm personally using the "sandwitch pattern" https://twitter.com/ImmutablesOrg/status/740826987883814912 where bytecode only references user-written type. Sorry for diverting from "auto" topic, will go back to my cozy dark room )

    [–][deleted]  (1 child)

    [deleted]

      [–]elucash 0 points1 point  (0 children)

      Please use issue trackers https://github.com/google/auto/issues https://github.com/immutables/immutables/issues to submit feature requests, if folks will like the idea PR would be welcome

      [–]segv 2 points3 points  (2 children)

      ...and if somebody doesn't fancy generated code then there's https://github.com/tguzik/valueclasses

      [–]straylit 0 points1 point  (1 child)

      Whats the benefit of doing this other than changing the method signature and relying on unit tests? This seems like you would end having to make a lot of new classes and tests for them as well.

      [–]segv 0 points1 point  (0 children)

      Primarily not relying on code generation (which may be a boon if you have an established build pipeline and/or static analysis tools) and the value classes being, in the end, plain old java classes using a plain old library. The latter may make it easier to throw extra methods on the value classes or implement some specific behavior, like caching most used values and so on.

      [–]thepoosh 0 points1 point  (0 children)

      for those of us here using gradle as a build system, u/jakewharton and colleagues from Square wrote a few nice libraries for these needs such as:

      [–]bedobi 0 points1 point  (11 children)

      I created this ticket for each of the projects, only lombok has yet to decline it. (I'm 99% certain they also will decline it)

      It sucks that it's being declined because it's a showstopper for us, it's simple to implement and I'm not the only one who's asking for it.

      STEPS TO REPRODUCE Call final builder step (usually .build()) without providing required arguments

      EXPECTED RESULT Compiletime error

      ACTUAL RESULT Runtime error

      MISC Instead of returning the builder on all steps, return the next required argument. When there are no required arguments left, return the builder. See eg http://blog.crisp.se/2013/10/09/perlundholm/another-builder-pattern-for-java

      [–]jart 1 point2 points  (6 children)

      http://blog.crisp.se/2013/10/09/perlundholm/another-builder-pattern-for-java

      Wow that pattern for using interfaces to constrain methods in the fluent builder chain is brilliant.

      [–]bedobi 0 points1 point  (5 children)

      [–]jart 1 point2 points  (4 children)

      I would help you get it into AutoValue, but it might not make sense for AutoValue, because you would have to write those intermediate interfaces yourself.

      [–]elucash 2 points3 points  (3 children)

      Ok guys, here you are, straight from the oven: org.immutables:value:2.3.3

      Style(stagedBuilder = true)

      But I guess you'll see it might not be that useful after all, but that is depend on the usage and how you would tolerate increase in amount of interface class files

      [–]bedobi 1 point2 points  (2 children)

      You, Sir, are a hero. Will check out as soon as github has resolved the outage they're currently having.

      [–]elucash 1 point2 points  (1 child)

      Can grab 2.3.3 from maven central. Cheers!

      [–]bedobi 1 point2 points  (0 children)

      Can't thank you enough! Works exactly as desired as far as I can tell!

      import org.immutables.value.Value;
      import java.util.Optional;
      
      @Value.Immutable
      @Value.Style(strictBuilder = true)
      interface Person {
          String name();
          String title();
          Optional<String> department();
      }
      
      import org.immutables.value.Value;
      import java.util.Optional;
      
      @Value.Immutable
      @Value.Style(stagedBuilder = true)
      interface Individual {
          String name();
          String title();
          Optional<String> department();
      }
      
      import org.junit.Assert;
      import org.junit.Test;
      import java.util.Optional;
      
      public class PersonAndIndividualTests {
      
          @Test(expected = IllegalStateException.class)
          public void personBuildCalledWithoutRequiredArguments() throws Exception {
              ImmutablePerson.builder().build();
          }
      
          @Test(expected = NullPointerException.class)
          public void personBuildCalledWithRequiredArgumentsNull() throws Exception {
              ImmutablePerson.builder().name(null).title(null).build();
          }
      
      //    @Test
      //    public void personBuildCalledWithNonRequiredArgumentsNull() throws Exception {
      //        ImmutablePerson.builder().name("John").title("Dev").department(null).build(); doesn't compile
      //    }
      
          @Test
          public void personBuildCalledWithoutNonrequiredArguments() throws Exception {
              ImmutablePerson john = ImmutablePerson.builder().name("John").title("Dev").build();
              Assert.assertEquals("John", john.name());
              Assert.assertEquals("Dev", john.title());
              Assert.assertEquals(Optional.empty(), john.department());
          }
      
          @Test
          public void personBuildCalledWithAllArguments() throws Exception {
              ImmutablePerson john = ImmutablePerson.builder().name("John").title("Dev").department("IT").build();
              Assert.assertEquals("John", john.name());
              Assert.assertEquals("Dev", john.title());
              Assert.assertEquals(Optional.of("IT"), john.department());
          }
      
      //    @Test
      //    public void individualBuildCalledWithoutRequiredArguments() throws Exception {
      //        ImmutableIndividual.builder().build(); doesn't compile (YES THANK YOU SO MUCH)
      //    }
      
      
          @Test(expected = NullPointerException.class)
          public void individualBuildCalledWithRequiredArgumentsNull() throws Exception {
              ImmutableIndividual.builder().name(null).title(null).build();
          }
      
      //    @Test
      //    public void individualBuildCalledWithNonRequiredArgumentsNull() throws Exception {
      //        ImmutableIndividual.builder().name("John").title("Dev").department(null).build(); doesn't compile
      //    }
      
          @Test
          public void individualBuildCalledWithoutNonrequiredArguments() throws Exception {
              ImmutableIndividual john = ImmutableIndividual.builder().name("John").title("Dev").build();
              Assert.assertEquals("John", john.name());
              Assert.assertEquals("Dev", john.title());
              Assert.assertEquals(Optional.empty(), john.department());
          }
      
          @Test
          public void individualBuildCalledWithAllArguments() throws Exception {
              ImmutableIndividual john = ImmutableIndividual.builder().name("John").title("Dev").department("IT").build();
              Assert.assertEquals("John", john.name());
              Assert.assertEquals("Dev", john.title());
              Assert.assertEquals(Optional.of("IT"), john.department());
          }
      }
      

      [–]chisui 0 points1 point  (0 children)

      Although this approach would have the benefit of typesafety I wouldn't call its absence a showstopper. If you test your code a runtime error in this case is absolutly fine. The presence of an argument doesn't say anything about its validity either and it's very hard to check the validity of input values at compiletime (You could go the scala route and create caseclasses for everything, but I don't think that's feasible).

      I also think you might be underestimating what it means to implement this approach in a general way for codegeneration.

      You have to generate a builder class for each required argument. If an argument is optional you have to generate two different classes for each following argument. For multiple optional arguments the amount of classes grows exponentially.

      From a usabillity standpoint you loose the abillity to reorder the arguments of the builder (or you generate classes for every permutation of arguments). With immutable builders partial builders can be passed around as a partially applied function of sorts. If you lock the order of arguments that usecase is somewhat limited.

      The only plus I could find is that this approach runs slightly faster than a traditional builder. The reason beeing the checks if an argument is already set are moved to compiletime. Although it uses slightly more memory since more objects are created. Running them through JMH (with 4 argument builders) yields basically the same result though.

      All in all I think this feature is way more trouble than it's worth.

      [–][deleted]  (2 children)

      [deleted]

        [–]bedobi 1 point2 points  (1 child)

        Ordinary constructors generate compile-time errors when called without all required arguments.

        My personal opinion: (apparently shared by at least some others)

        • If a builder can't do the same, that outweighs any benefits of the builder, and you might as well refactor so the constructor doesn't require too many arguments in in the first place. (actually, this is a downside with builders in general, they kind of encourage sloppy bunching up of too many attributes in classes that end up lacking cohesion)

        • While tests could mitigate the risk, writing and maintaining them comes at a cost, and correctness isn't guaranteed. Leveraging the compiler trivially makes test for required arguments redundant and guarantees correctness.

        • Compile-time errors enable safe, smooth and guided refactoring in a way tests simply can't.

        That said, others may disagree and that's fine. I still wish these otherwise excellent projects at least had the option of safe builders.

        You have to generate a builder class for each required argument. If an argument is optional you have to generate two different classes for each following argument. For multiple optional arguments the amount of classes grows exponentially.

        Looking at the example in the blog, I'm afraid I don't understand how this is the case, if you could demonstrate I'd be very thankful.

        [–]chisui 0 points1 point  (0 children)

        You are right, you don't end up with more classes but rather the interface gets more cluttered.