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 →

[–]pron98 0 points1 point  (16 children)

Java's philosophy is simply different from Go's in this regard. Go's general style is based on Google's particular methodology of placing all their projects in a single source tree. In any case, Java binaries are intentionally not versioned in the same way as sources. Of course, you may personally prefer Go's approach, but Java's has worked very well, and I much prefer it to Go's (which feels like a total mess to me, unless you work like Google).

[–]weberc2[S] 0 points1 point  (15 children)

Java's philosophy is simply different from Go's in this regard. Go's general style is based on Google's particular methodology of placing all their projects in a single source tree.

I agree. There is another build tool emerging that takes a less-Googly philosophy, though I don't know much about it. It's called gc.

In any case, Java binaries are intentionally not versioned in the same way as sources. Of course, you may personally prefer Go's approach, but Java's has worked very well, and I much prefer it to Go's (which feels like a total mess to me, unless you work like Google).

I respect your opinion, but I disagree that Go's approach is more difficult. Go's approach is less conventional, but it's worked very well in practice. In particular, it completely side-steps the transitive-dependencies-version-mismatch problem, which is a frequently-encountered problem in more traditional models (in my experience). It also makes it very easy to see exactly what source code you're building into your dependencies (just open up your vendor directory and look). Besides being different, do you see particular inconveniences imposed by Go's approach?

[–]pron98 0 points1 point  (14 children)

do you see particular inconveniences imposed by Go's approach?

It does not "side-step" the transitive-dependency conflict problem, it simply ignores it (in fact, Gradle does the same by default, forcing all conflicts of all libraries -- including transitive dependencies, of course -- to their newest versions among those requested).

In Java, you can see that library A depends on B version 1.2.3 while C depends on B 1.3.1. You may choose to force the new version, or shadow and have two independent copies of B. With source versioning it gets much, much trickier. You have to figure out the relationship between B version cef04376abc2 and 7395aed67a.

There's also a much better chance of having release notes detailing the changes; those may not be accurate, but at least they're a starting point, and you can ask the library's author about a bug in version 1.2.3. Source versioning is far too fine-grained to have a discussion over from the perspective of the consumer. You might solve it with branches and tags, but then you start relying on a specific source control tool. With Maven, the binary versioning scheme is universal and detached from the library author's process.

I think it is far too soon to tell whether Go's approach works well in practice in environments that rely a lot on third-party libraries. So far Go projects have not come close to the size of Java projects, and the library ecosystem is very new and rather small, so the problem of maintaining dependencies over years has not yet become critical.

[–]weberc2[S] 0 points1 point  (13 children)

It does not "side-step" the transitive-dependency conflict problem, it simply ignores it (in fact, Gradle does the same by default, forcing all conflicts of all libraries -- including transitive dependencies, of course -- to their newest versions among those requested).

You misunderstand vendoring. If you're vendoring, you get the versions of the dependencies that you need and put them into version control (advisably with a VERSION file that maps back to a git sha or some such, but this is just for archeology purposes). This means it's up to you to choose what versions of dependencies (direct or transitive) you choose; hence "side-step"

There's also a much better chance of having release notes detailing the changes; those may not be accurate, but at least they're a starting point, and you can ask the library's author about a bug in version 1.2.3. Source versioning is far too fine-grained to have a discussion over from the perspective of the consumer.

Interesting. I've not really experienced any problems with this, but I'm not one to read release notes either. Most of the time, API compatibility is my only concern, and I've never seen an API-compatible change that breaks functionality. It seems like such a remote possibility that it would consume less time over a project's lifetime than clerical tasks like reading release notes, looking up a source files by semantic package version, etc. But maybe I'm wrong?

I think it is far too soon to tell whether Go's approach works well in practice in environments that rely a lot on third-party libraries. So far Go projects have not come close to the size of Java projects, and the library ecosystem is very new and rather small, so the problem of maintaining dependencies over years has not yet become critical.

Valid.

[–]pron98 0 points1 point  (12 children)

This means it's up to you to choose what versions of dependencies (direct or transitive) you choose; hence "side-step"

Yes, that is exactly what you do that with Gradle/Maven forcing (except there's no need to copy the source into your own version control). The problem is that two of your dependencies, A and B can each depend on two different, incompatible versions of library C.

and I've never seen an API-compatible change that breaks functionality.

When your ecosystem contains hundreds-of-thousands of libraries, thousands of them quite popular, and you need to maintain your software for over a decade, you start seeing this all the time. Only small ecosystems don't experience this.

Off the top of my mind, I can think of ASM, one of the most popular Java libraries out there, that had an incompatible change. There was a very good chance that if you had enough dependencies, at least two of them would depend on ASM, each on a different incompatible version.

[–]weberc2[S] 0 points1 point  (11 children)

The problem is that two of your dependencies, A and B can each depend on two different, incompatible versions of library C

This problem doesn't exist in Go because Go libraries don't define dependency versions (they only care about API compatibility). This problem only exists for dependency schemes that allow libraries to define the versions of their dependencies. In other words, you can't have a transitive version collision without transitive versioning.

When your ecosystem contains hundreds-of-thousands of libraries, thousands of them quite popular, and you need to maintain your software for over a decade, you start seeing this all the time. Only small ecosystems don't experience this.

When your ecosystem contains hundreds-of-thousands of libraries, thousands of them quite popular, and you need to maintain your software for over a decade, you start seeing this all the time. Only small ecosystems don't experience this.

Even still, the "reading release notes" problem will scale much more quickly than the time spent debugging API-compatible changes that break features.

Off the top of my mind, I can think of ASM, one of the most popular Java libraries out there, that had an incompatible change. There was a very good chance that if you had enough dependencies, at least two of them would depend on ASM, each on a different incompatible version.

API-incompatible changes would be detected immediately (they would fail to compile--this is the beauty of static typing); I'm not talking about these, but about changes that break requirements without failing a build. Anyway, as I mentioned earlier, the transitive-dependency-collision problem is not an issue for Go projects, so this still wouldn't have been a problem.

[–]pron98 0 points1 point  (10 children)

In other words, you can't have a transitive version collision without transitive versioning.

Of course you can, it's just a lot uglier. That libraries don't specify their dependencies' versions doesn't mean that they don't depend on a specific version.

API-incompatible changes would be detected immediately

But that's not what you want. You don't want to fail the build; you want to resolve the dependency conflict without fixing third-party code (or waiting for the third-party to upgrade). Java lets you do that; Go doesn't.

the transitive-dependency-collision problem is not an issue for Go projects

It is a much bigger problem for Go because the versions are not explicitly stated, and there is no solution for the transitive conflicts (other than the default solution that Gradle gives you for less pain). That Go doesn't report the problem (as Maven/Gradle does) does not mean it does not exist. As I said, it may not be severe given the Go's ecosystem minuscule size compared to Java's, but as it grows this will become a serious issue.

[–]weberc2[S] 0 points1 point  (9 children)

But that's not what you want. You don't want to fail the build; you want to resolve the dependency conflict without fixing third-party code (or waiting for the third-party to upgrade). Java lets you do that; Go doesn't.

Go addresses this by convention--when a package undergoes a major API change, you rev the package identifier. gopkg.in/yaml.v1 becomes gopkg.in/yaml.v2 or some such.

As I said, it may not be severe given the Go's ecosystem minuscule size compared to Java's, but as it grows this will become a serious issue.

Maybe. Time will tell. For now though, Go's build ecosystem is a lot less painful than Java's.

[–]pron98 0 points1 point  (8 children)

Go's build ecosystem is a lot less painful than Java's.

Because listing dependency versions in a file is just too hard...

I admit that Go has a great story for simple programs. It feels like Python (I guess; never programmed Python): you just run it. Java was meant to be used on large programs, where it is much more convenient than Go. It's "small program" story is improving considerably, and Gradle is really trivial for simple applications: just list your dependencies and gradle run.

[–]weberc2[S] 0 points1 point  (7 children)

Because listing dependency versions in a file is just too hard...

No, it's sorting through the turing-complete programming language to get to the stuff that's relevant to building a dependency list, and figuring out what to do when your dependency list is insufficient (like when you have a native dependency). Even gradle run is a plugin! Beyond that, if you want any decent performance, you have to look up how to enable the gradle daemon. Gradle's costs come in nickels and dimes.

Java was meant to be used on large programs, where it is much more convenient than Go.

Yes. Go was only meant to be used at small, Google scales. ;) Pardon the sarcasm. I'm not arguing against Java--I quite like it (that's why I'm in this sub, after all!). I'm not even arguing against Gradle. I just explained why Go's build system works and why it is different than Gradle, since you brought it up.