Generating a Java program with 90% less code(unitily.com)
unitily.com
Generating a Java program with 90% less code
https://www.unitily.com/articles/boilerplate.html
542 comments
I agree that Java is almost unwritable without an IDE, but that strictness has led to Java having better IDEs than most languages. For large code bases, most languages get unmaintainable without an IDE, putting Java at an advantage. I'd still use Python for something simple that won't grow past a few thousand lines, though.
This. Far from being a fanboy of Java (and I write JS for a living), but you can generally quickly understand what's going on with the boring code, and can refactor mercilessly.
Whereas in huge JS codebases relying on framework magic you can really have hard time to understand what's going on without plugging a debugger (and even then it's still not easy). Trying to refactor legacy code that relies on `this` and prototypes is a nightmare.
Whereas in huge JS codebases relying on framework magic you can really have hard time to understand what's going on without plugging a debugger (and even then it's still not easy). Trying to refactor legacy code that relies on `this` and prototypes is a nightmare.
This!
I sometimes wonder if JS programmers know that with C#/VS you can just change the name of a class, and without doing anything else (not even replacing), all involved names just change, and everything works 100%, without writing any test.
And the same goes for moving a method to another class, or renaming member variables, modules, files... everything just works and is instantaneous.
To me, not being able to aggresively refactor withour worrying something will break somewhere is like backwards and old school (in the bad way)
When a project evolves, a class, a struct... grows and suddenly a name, perfectly fine at the beggining, does not make sense any longer. With C#/VS you just change and 2 seconds later you're still working.
I sometimes wonder if JS programmers know that with C#/VS you can just change the name of a class, and without doing anything else (not even replacing), all involved names just change, and everything works 100%, without writing any test.
And the same goes for moving a method to another class, or renaming member variables, modules, files... everything just works and is instantaneous.
To me, not being able to aggresively refactor withour worrying something will break somewhere is like backwards and old school (in the bad way)
When a project evolves, a class, a struct... grows and suddenly a name, perfectly fine at the beggining, does not make sense any longer. With C#/VS you just change and 2 seconds later you're still working.
Some might be surprised to know that in modern PHP this is also the case. The whole point of all the typing and structure is so you and the IDE can sanely reason about and understand the structure of the code.
If I get code and all of a sudden not even my IDE knows what's going on, it clues me in that there are layers of indirection in the codebase I'm going to have to waste time reading into, like magic methods or loose typing.
If I get code and all of a sudden not even my IDE knows what's going on, it clues me in that there are layers of indirection in the codebase I'm going to have to waste time reading into, like magic methods or loose typing.
Yep, PHPStorm is a memory hog, but man is it handy in catching errors I didn’t think of.
Is it really a memory hog? I'm using IDEA, PHPStorm's more fully featured Java IDE (That also loads PHP code!) ; I have 6 projects open right now, including a PHP project, some java projects, some ruby, groovy, etc; I have 20 files open, numerous plugins enabled, and all sorts of connections to databases and tests; it's been running for days it's using 500MB.
Both PHPStorm and IDEA are written in Java (they share a common code base). Don't confuse [Java eagerly allocating memory from the operating system but then not using it for anything (yet)] with java using a lot of memory. For speed, java allocates memory it anticipates using in the future. This lets it manage it's own memory without involving the operating system and thus context switches. If you start using a lot of other applications and available system memory starts running low; java will detect this and relinquish the pre-allocated memory it's not using.
Both PHPStorm and IDEA are written in Java (they share a common code base). Don't confuse [Java eagerly allocating memory from the operating system but then not using it for anything (yet)] with java using a lot of memory. For speed, java allocates memory it anticipates using in the future. This lets it manage it's own memory without involving the operating system and thus context switches. If you start using a lot of other applications and available system memory starts running low; java will detect this and relinquish the pre-allocated memory it's not using.
I sometimes wonder if IDE users realize you can do 99.9% of the same things with simple tools. Personally I don't want my editor magically updating the codebase because there will be edge cases and I don't want magic happening in the background when those edge cases arise. In practice I'm sure the class name change has likely not given you issues in a long time or maybe ever, but that doesn't mean edge cases don't exist and when they happen to someone it can be at the worst times and add significant troubleshooting to an unrelated problem.
edit: further learning those simple tools has a more generic application to other problems while the IDE magic will likely go until there's an issue to be understood and you're depriving yourself of learning generally applicable tooling to trade a slight inconvenience for magic that has the potential to cause great inconvenience and once that magic from the IDE bites you and you learn how it works - you've learned something with no general application to other problems.
edit2: since I can't reply in-line
jcelerier - I _could_ do that with CLI tooling and depending on the language it would be trivial. However, I would _never_ do that because changing the method/function name to suit my context would be very unlikely to avoid making other code less readable. That's a huge anti-pattern that your IDE is making easy. Further, consider the implications for language design when making language design decisions that require a specific type of IDE magic to be considered a reasonable language design decision. I have a hard time believing relying on the IDE lock-in is good for the community of that language.
philwelch - I genuinely don't know the answer, but what happens when you change a class name -> forget you do it before saving the file -> go to another file and try to change the class name there but have a typo -> go back to the original file and save the change? I would bet the typo'd change goes unchanged and now you're left scratching your head since that magic has always worked before.
edit: further learning those simple tools has a more generic application to other problems while the IDE magic will likely go until there's an issue to be understood and you're depriving yourself of learning generally applicable tooling to trade a slight inconvenience for magic that has the potential to cause great inconvenience and once that magic from the IDE bites you and you learn how it works - you've learned something with no general application to other problems.
edit2: since I can't reply in-line
jcelerier - I _could_ do that with CLI tooling and depending on the language it would be trivial. However, I would _never_ do that because changing the method/function name to suit my context would be very unlikely to avoid making other code less readable. That's a huge anti-pattern that your IDE is making easy. Further, consider the implications for language design when making language design decisions that require a specific type of IDE magic to be considered a reasonable language design decision. I have a hard time believing relying on the IDE lock-in is good for the community of that language.
philwelch - I genuinely don't know the answer, but what happens when you change a class name -> forget you do it before saving the file -> go to another file and try to change the class name there but have a typo -> go back to the original file and save the change? I would bet the typo'd change goes unchanged and now you're left scratching your head since that magic has always worked before.
> I sometimes wonder if IDE users realize you can do 99.9% of the same things with simple tools
I'd really like you to show me how you can refactor e.g. a method called "write" in a 500+kloc codebase in less than 5 seconds with "simple tools". With any C++ ide from the last decade it's basically a keyboard shortcut + typing the new name + fixing the few remaining compilation errors that may have cropped up in generic code. With sed & al ? good luck, see you in a few weeks.
I'd really like you to show me how you can refactor e.g. a method called "write" in a 500+kloc codebase in less than 5 seconds with "simple tools". With any C++ ide from the last decade it's basically a keyboard shortcut + typing the new name + fixing the few remaining compilation errors that may have cropped up in generic code. With sed & al ? good luck, see you in a few weeks.
> I'd really like you to show me how you can refactor e.g. a method called "write"
I'm not taking sides on the issue discussed here, but regarding this quote... This is one of the biggest reasons I don't do OOP. Using plain functions with slightly longer but globally unique names so many issues go away.
And the biggest issue is that I can't read a code base if so many function names are not unique and possibly not conveying enough information for local understanding. That style always requires the reader to carry so much context in their head, to be able to resolve the "polymorphism" to concrete implementations. (Yes, in a proper IDE we can jump to the implementation interactively with a few keypresses, but it's still much harder to read).
I'm not taking sides on the issue discussed here, but regarding this quote... This is one of the biggest reasons I don't do OOP. Using plain functions with slightly longer but globally unique names so many issues go away.
And the biggest issue is that I can't read a code base if so many function names are not unique and possibly not conveying enough information for local understanding. That style always requires the reader to carry so much context in their head, to be able to resolve the "polymorphism" to concrete implementations. (Yes, in a proper IDE we can jump to the implementation interactively with a few keypresses, but it's still much harder to read).
Java interface methods do have globally unique* names when fully qualified, but nobody types the full thing out because namespacing is good. It is neither expected nor even desirable for readers or authors to keep resolutions of interface implementations in their heads - that would defeat the purpose of interfaces.
* jar-hell (and dll-hell) aside
* jar-hell (and dll-hell) aside
There is a difference between interfaces and abstract interfaces. It is very helpful to know if all calls to the same function/method resolve to the same implementation. Method syntax always suggests that their could be arbitrarily many implementations.
> Using plain functions with slightly longer but globally unique names so many issues go away.
That makes me assume two things: you work alone and your projects don't exceed 5.000 (fivethousand) lines of code. Without some namespacing and an expected method length less than 100, more likely 10 lines than 90 or more, you end up with roughly 160 names. An average human knows 150 people, maybe by their name. And while you have friends or at least parents and grandparents occupying some of the »address sace« few of your methods will not be »at hand« for your brain.
And in case you already prefix your methods with something like io_write(...), db_query(...) and you hand in context-specific references like io_write with a sort of file handle and db_query with a sort of database connection I transform all this 1:1 in an OO-style language and back.
That makes me assume two things: you work alone and your projects don't exceed 5.000 (fivethousand) lines of code. Without some namespacing and an expected method length less than 100, more likely 10 lines than 90 or more, you end up with roughly 160 names. An average human knows 150 people, maybe by their name. And while you have friends or at least parents and grandparents occupying some of the »address sace« few of your methods will not be »at hand« for your brain.
And in case you already prefix your methods with something like io_write(...), db_query(...) and you hand in context-specific references like io_write with a sort of file handle and db_query with a sort of database connection I transform all this 1:1 in an OO-style language and back.
Your assumption is wrong (of course), and yes, I don't doubt that it's always possible to make a rewrite in a shitty way, and then convert it back.
For a clearer description of what I mean, see my other comment below where I write two lines of code in both ways.
For a clearer description of what I mean, see my other comment below where I write two lines of code in both ways.
> Using plain functions with slightly longer but globally unique names so many issues go away.
Only on monolith code bases without any signs of modularity.
Only on monolith code bases without any signs of modularity.
Oh, don't get me wrong, I'm not against abstract interfaces where they make sense. What I'm against is using underqualified names for stuff that has a statically resolveable implementation.
In other words, don't make it look complicated before it actually is.
In other words, don't make it look complicated before it actually is.
Even on that case, with good modularity on C code (TU == module), there will be plenty of hidden symbols and some indirection (function pointer) going on, and not a pile of global symbols.
That was kind of the gist of my comment.
That was kind of the gist of my comment.
You did't understand what I was saying.
Wading through non-trivial projects, pervasiveness of the first approach makes me nervous, because I'm told at every occasion, "don't even try to understand what's going on in the system globally. We .log(), shouldn't that be enough for you to know?".
While the second approach is calming to me, "You see, we have this simple logging system, where everything ends up being written to. It's not that complex after all, and if you want to know what's going on you can just jump directly to the log_event()" function.
Yes, by asserting that foo and bar are of the same type, and asserting that the .log() method is not virtual, I can reach the same conclusion. But that's the point, it requires work that you can't practically do if each line of code contains a method call. There is no mincing words, you're using the wrong syntax...
foo.log(xyz);
bar.log(42);
vs log_event(foo, xyz);
log_event(bar, 42);
Provided that both approaches do the same, which is more clear? It is the second one, because it clearly suggests that there is no polymorphic switching based on foo/bar.Wading through non-trivial projects, pervasiveness of the first approach makes me nervous, because I'm told at every occasion, "don't even try to understand what's going on in the system globally. We .log(), shouldn't that be enough for you to know?".
While the second approach is calming to me, "You see, we have this simple logging system, where everything ends up being written to. It's not that complex after all, and if you want to know what's going on you can just jump directly to the log_event()" function.
Yes, by asserting that foo and bar are of the same type, and asserting that the .log() method is not virtual, I can reach the same conclusion. But that's the point, it requires work that you can't practically do if each line of code contains a method call. There is no mincing words, you're using the wrong syntax...
Ok, but you are betting on false hopes then.
First foo or bar can be function pointers.
Or maybe log_event is a macro whose behavior depends on the environment. And I have used some crazylogging ones back in the 2000's when coding C, that would even dump the current state of the process alongside its parameters.
Or then again, log_event() plugs into a configurable logging system and one cannot predict its behavior just from the call site, even when using the same types.
Finally if we move away from C semantics, maybe log_event is overloaded or is doing multiple dispatch, thus the two calls, depending on the argument types, are not going to land on the same implementation.
First foo or bar can be function pointers.
Or maybe log_event is a macro whose behavior depends on the environment. And I have used some crazylogging ones back in the 2000's when coding C, that would even dump the current state of the process alongside its parameters.
Or then again, log_event() plugs into a configurable logging system and one cannot predict its behavior just from the call site, even when using the same types.
Finally if we move away from C semantics, maybe log_event is overloaded or is doing multiple dispatch, thus the two calls, depending on the argument types, are not going to land on the same implementation.
> It is the second one, because it clearly suggests that there is no polymorphic switching based on foo/bar.
so, question : suppose you have your log_event, and now your bosses comes and tells you that the client needs to have two additional different logging mechanisms - say, to journald and to some websocket log server. The system can log to either of your three logging mechanisms at any point through changing a configuration somewhere in a GUI but you also need some core classes to always be logged through journald no matter the state of the rest of the system.
How do you update your design to reflect that ?
so, question : suppose you have your log_event, and now your bosses comes and tells you that the client needs to have two additional different logging mechanisms - say, to journald and to some websocket log server. The system can log to either of your three logging mechanisms at any point through changing a configuration somewhere in a GUI but you also need some core classes to always be logged through journald no matter the state of the rest of the system.
How do you update your design to reflect that ?
It seems like your problem isn’t with OOP, but with generics. (You would like Go.)
Obviously you don't have much experience with IDEs and statically typed languages, which is a shame because they make our life so much easier. There are no edge cases. And even if there were the IDE can show you the refactoring and you can decide on a case-by-case basis.
> what happens when you change a class name -> forget you do it before saving the file -> go to another file and try to change the class name there but have a typo -> go back to the original file and save the change?
A good IDE does not care if you save the file or not. It is always up-to-date. Renaming a class in just one file is also impossible, since the IDE will do refactorings atomically over the whole project. But lets just assume you somehow managed to fuck it up that badly (things can always go wrong): You will still realize your error almost instantly because a statically typed language does not defer such checks to runtime. The program wouldn't even run anymore.
By far the biggest issue with automatic refactoring is accidentally renaming stuff in comments (or forgetting to do so) because I'm too lazy to look at every single place. But find&replace has the same problems.
> what happens when you change a class name -> forget you do it before saving the file -> go to another file and try to change the class name there but have a typo -> go back to the original file and save the change?
A good IDE does not care if you save the file or not. It is always up-to-date. Renaming a class in just one file is also impossible, since the IDE will do refactorings atomically over the whole project. But lets just assume you somehow managed to fuck it up that badly (things can always go wrong): You will still realize your error almost instantly because a statically typed language does not defer such checks to runtime. The program wouldn't even run anymore.
By far the biggest issue with automatic refactoring is accidentally renaming stuff in comments (or forgetting to do so) because I'm too lazy to look at every single place. But find&replace has the same problems.
With a statically typed language and an IDE this isn’t a real issue. There’s no “edge case” when you rename a class or method symbol in a Java project because the IDE deterministically knows what are the invocations of the same symbol.
Except when you use reflection. Or code generation tooling that tends to crop up around DI frameworks.
Yes, in general, the ability of IDE to make these kind of large-scale changes quickly, reliably and mostly bug-free is nice. But I rarely miss it in languages where it's impossible.
It may be that one of the ways your language shapes your code is the way you structure it. For instance, in my Lisp codebases, I can hardly think of any place where Java-style automated refactoring could be useful; in the current largish codebase I work on, it rarely is the case that constructs introduced in one file are used directly in more than one or two other files - which makes refactoring entirely doable with a dumb text editor, and Java-like automated refactoring wouldn't save all that much time over doing a regex search and manually visiting every line listed in results.
Yes, in general, the ability of IDE to make these kind of large-scale changes quickly, reliably and mostly bug-free is nice. But I rarely miss it in languages where it's impossible.
It may be that one of the ways your language shapes your code is the way you structure it. For instance, in my Lisp codebases, I can hardly think of any place where Java-style automated refactoring could be useful; in the current largish codebase I work on, it rarely is the case that constructs introduced in one file are used directly in more than one or two other files - which makes refactoring entirely doable with a dumb text editor, and Java-like automated refactoring wouldn't save all that much time over doing a regex search and manually visiting every line listed in results.
Unless you're using something like .Net remoting (or the Java equivalent), and now suddenly everything breaks.
Or you have a larger codebase split up into multiple .Net solutions and DLLs where code is referenced through the generated DLLs.
The refactor looks great, until it starts breaking things for the many consumers of your code.
In fact, the latter is a situation where simple text tools will make life a lot safer and easier than whatever the IDE does (which pretty much will be limited to the bounds of the .sln file, whereas a simple text tool operating over files in the entire codebase will alert you to the fact that this is being used elsewhere).
My argument isn't to claim that simple text tools are better at refactoring. They aren't. However, I've run across several situations where we've had issues in a massive codebase because developers believed that refactoring was as easy as hitting the Refactor button in Visual Studio, and everything will work magically.
The belief in programming 'magic' (whether through IDEs or frameworks that abstract a ton) without understanding what those tools actually do is what I'm worried about. Use IDEs to do what you need to by all means, but it's much better when you actually understand what it's doing before doing something with it.
Or you have a larger codebase split up into multiple .Net solutions and DLLs where code is referenced through the generated DLLs.
The refactor looks great, until it starts breaking things for the many consumers of your code.
In fact, the latter is a situation where simple text tools will make life a lot safer and easier than whatever the IDE does (which pretty much will be limited to the bounds of the .sln file, whereas a simple text tool operating over files in the entire codebase will alert you to the fact that this is being used elsewhere).
My argument isn't to claim that simple text tools are better at refactoring. They aren't. However, I've run across several situations where we've had issues in a massive codebase because developers believed that refactoring was as easy as hitting the Refactor button in Visual Studio, and everything will work magically.
The belief in programming 'magic' (whether through IDEs or frameworks that abstract a ton) without understanding what those tools actually do is what I'm worried about. Use IDEs to do what you need to by all means, but it's much better when you actually understand what it's doing before doing something with it.
> Or you have a larger codebase split up into multiple .Net solutions and DLLs where code is referenced through the generated DLLs.
Sure--and to be fair, I've never worked in .Net much. But if you're changing the package interface, you should arguably do so in a backwards-compatible fashion anyway, depending on how your stuff actually gets built and deployed. If everything's contained in the same repo and you can't refactor across multiple components in the same repo, that sounds like a problem.
> The belief in programming 'magic' (whether through IDEs or frameworks that abstract a ton) without understanding what those tools actually do is what I'm worried about. Use IDEs to do what you need to by all means, but it's much better when you actually understand what it's doing before doing something with it.
Agreed!
Sure--and to be fair, I've never worked in .Net much. But if you're changing the package interface, you should arguably do so in a backwards-compatible fashion anyway, depending on how your stuff actually gets built and deployed. If everything's contained in the same repo and you can't refactor across multiple components in the same repo, that sounds like a problem.
> The belief in programming 'magic' (whether through IDEs or frameworks that abstract a ton) without understanding what those tools actually do is what I'm worried about. Use IDEs to do what you need to by all means, but it's much better when you actually understand what it's doing before doing something with it.
Agreed!
What if the IDE did not traverse a specific file during the global search and replace?
Are you relying on a correct and comprehensive project/workspace setup within the IDE?
Are you relying on a correct and comprehensive project/workspace setup within the IDE?
So your argument is that something might, in some extremely rare cases, go wrong, and that is why you'd rather do it the manual way? That's basically like a carpenter refusing to use power tools, because they might, theoretically, explode.
> Are you relying on a correct and comprehensive project/workspace setup within the IDE?
Of course. Not much point in using an IDE otherwise.
Of course. Not much point in using an IDE otherwise.
> Are you relying on a correct and comprehensive project/workspace setup within the IDE?
That's not something I "rely" on, it's the first thing I make happen whenever I use an IDE.
That's not something I "rely" on, it's the first thing I make happen whenever I use an IDE.
> What if the IDE did not traverse a specific file during the global search and replace?
You generally get compilation/build errors highlighting the bits that were missed (for whatever reason)
You generally get compilation/build errors highlighting the bits that were missed (for whatever reason)
Then your tests catch it, and you have to fix a single file manually, instead of a dozen or a hundred or a thousand.
Sure, and that’s exactly the same failure mode as “sed”. What’s the benefit of the big IDE?
> What’s the benefit of the big IDE?
To quote myself, "fix a single file manually, instead of a dozen or a hundred or a thousand".
And what happens when you need to rename, say, a method name that's also used as a variable name in many places? xWhat happens if multiple classes define the same method name, but you only want to rename that method for a single class?
Yes, you can probably do it in sed. If you're used to it enough, you can probably do it pretty quick, with sufficient regexps that you only have to debug a few times.
Or you can right click or hit CMD-. or hit F2 or whatever with your cursor on a method name, type in the new name, wait 2 seconds, and you're done.
To quote myself, "fix a single file manually, instead of a dozen or a hundred or a thousand".
And what happens when you need to rename, say, a method name that's also used as a variable name in many places? xWhat happens if multiple classes define the same method name, but you only want to rename that method for a single class?
Yes, you can probably do it in sed. If you're used to it enough, you can probably do it pretty quick, with sufficient regexps that you only have to debug a few times.
Or you can right click or hit CMD-. or hit F2 or whatever with your cursor on a method name, type in the new name, wait 2 seconds, and you're done.
Given the truly excellent quality of refactoring toolsets like Resharper for C# I'd wager that you're more likely to miss an edge case with your manual approach than with the tools. The tools can reliably differentiate between methods of the same name in different classes at the call sites (or between different classes with the same name in different namespaces). They will search comments and strings for occurrences of the name in question and prompt you with a nicely formatted overview dialog box which ones to adjust.
The tools are at a point where I'm usually breaking my code when I'm doing manual refactoring, but never when I let the tools do the job.
The tools are at a point where I'm usually breaking my code when I'm doing manual refactoring, but never when I let the tools do the job.
IDEs are not magical. They understand your code because they use compilers to parse it, therefore they can also reason about it at a higher level than a simple text editor or CLI tool which treat your code as nothing but characters.
[deleted]
[deleted]
Refactoring by renaming things is like renovating a house by
painting it in another color. I like to refactor using a wrecking ball.
renaming a class in vscode works in js and ts via typescript language service.
Honest question: By "works" you mean you have 100% guarantee nothing will break nowhere?
Edge cases exist. I'd assume that Java and c# will also have edge-cases when things like reflection come into play.
Certainly, but reflection is almost never used. I don't think I've touched a single real-life codebase where it would get in the way in this manner.
The same can't be said for languages like JS.
The same can't be said for languages like JS.
Even reflection is going to vary based on your IDE. If you use ReSharper or Rider, a safe rename refactoring will locate and optionally update string references as well. I don't know if VS itself has adopted this feature.
IDEA does the same thing. It's amazingly useful, even if you don't use reflection -- variable names end up in comments and documentation as well, all of which it will offer to change.
I’ve never touched a “real life” code base in a static language (including C#) where this wouldn’t be an issue. C# is not a sufficiently high level language that I can avoid duplication without the use of reflection.
People try to avoid reflection like the plague, most use cases don't depend on a hard-coded method/class name, and the ones that do can sometimes be detected by tools. It's pretty rare to run into this, and the one of two times I did something horrible like this, I wrote a test for it and made if fail loudly because what I was doing was brittle (by Java standards) and unexpected.
> I sometimes wonder if JS programmers know that with C#/VS you can just change the name of a class
How exactly does this knowledge help? I know this is possible and I do miss it. But it's extremely diffcult to support it in JS because of how the languages works. VSCode tries, but it's still limited. So should we all switch to writing C#? You can't avoid writing JS if you want to create a web app. There are tools to avoid it, but one has to know what's under the hood or they'll have a bad time.
TypeScript is doing great progress on this part, and it supports easy refactors, but it's still lacking in some parts, for example the type inference is still poor when it comes to complex cases and the type declarations depend on will of maintainers.
How exactly does this knowledge help? I know this is possible and I do miss it. But it's extremely diffcult to support it in JS because of how the languages works. VSCode tries, but it's still limited. So should we all switch to writing C#? You can't avoid writing JS if you want to create a web app. There are tools to avoid it, but one has to know what's under the hood or they'll have a bad time.
TypeScript is doing great progress on this part, and it supports easy refactors, but it's still lacking in some parts, for example the type inference is still poor when it comes to complex cases and the type declarations depend on will of maintainers.
It is possible for JS/TypeScript as well in VS Code. Works really well.
I think you can do all those things with typescript and any decent IDE.
Is this really true? The hard part of renaming a class isn't usually changing the name Foo -> Bar, it's finding all the variables with type Foo named stuff like result_foo and renaming them result_bar. Can it help with that?
Static typing can help with a lot of things but it can't fix this error:
pub fn add(a: i64, b: i64) i64 {
return a - b;
}Actually, it kind of can:
https://tryidris.herokuapp.com/compile#YWRkIDogSW50IC0+IElud...
The above link refers to the following Idris code:
https://tryidris.herokuapp.com/compile#YWRkIDogSW50IC0+IElud...
The above link refers to the following Idris code:
add : Int -> Int -> Int
add a b = a - b
addTest : add 4 5 = 9
addTest = ReflThat unit test would be just as easily implemented with dynamic typing.
[deleted]
yes, intellij for java will also rename all local variables and all mentions of the class in comments.
How exactly does that work? How does it decide what variables to rename and what to rename them to?
[deleted]
Boilerplate is the enemy. IDEs can easily write piles of it, but offer very little help reading any of it, and editing it is a trap. Anything that can be generated behind the scenes, compiled, and thrown away should be.
Indeed, IDEs are still oriented towards writing code, not reading it. I'm yet to find a good tool that would help with exploring and familiarizing yourself with a large codebase. Soucetrail comes the closest, but it has like 5% of the features I'd like to see in it.
(My dream code exploring tool would be a cross between an IDE and a tablet&pen friendly PDF reader. I should be able to navigate through files using all the semantics-aware IDE goodies, the tool should also understand version control, but it should also be able to generate and manipulate structural graphs (as in, boxes and arrows), as well as let me highlight, draw and freehand over the code.)
(My dream code exploring tool would be a cross between an IDE and a tablet&pen friendly PDF reader. I should be able to navigate through files using all the semantics-aware IDE goodies, the tool should also understand version control, but it should also be able to generate and manipulate structural graphs (as in, boxes and arrows), as well as let me highlight, draw and freehand over the code.)
UML?
I disagree. The cognitive overhead of sifting through so much boilerplate hides a lot of bad code. The identifiers and logic seem reasonable in an island surrounded by getters, setters, builders, injection annotations, overloads, and trivial constructors. But in context of the actual code invoking it, also surrounded in an ocean of boilerplate, the code does the wrong thing.
The overhead has significant cost for trivial gains. The costs result in overly complicated systems to solve simple problems. Most of the complexity in these systems is for dealing with complexity brought in by the ecosystem, not the problem domain.
The overhead has significant cost for trivial gains. The costs result in overly complicated systems to solve simple problems. Most of the complexity in these systems is for dealing with complexity brought in by the ecosystem, not the problem domain.
Sadly in practice, runtime dependency injection swaps compile errors for runtime errors. I hate DI!
Magik? Has anyone mentioned Spring yet?
Spring magic is both relatively straight-forward to understand, and actually relies on heavy typing for autowiring.
Uh right, sure. But then there's the Spring lifecycle to take into consideration and it may happen that your annotations may or may not work. It depends (of course it's not totally aleatory, there's some logic behind it, usually obvious after several hours debugging ;)
Another honorable mention is Spring Boot @Conditional for which adding a minor little diversion from defaults will turn a functioning happy project in a dysfunctional nightmare.
:'(
Another honorable mention is Spring Boot @Conditional for which adding a minor little diversion from defaults will turn a functioning happy project in a dysfunctional nightmare.
:'(
I agree with you completely on that. I'll clarify that, to me, there's a difference between straight-forward and obvious. And Spring is terrible at being obvious. Some annotation on a class 5 transitive dependencies deep can affect your code. It's definitely really deep into "spooky action at a distance", and that's always a nightmare for debugging. But once you see all the pieces, it's (usually) relatively straight-forward to describe what is happening.
The worst experience I had refactoring JS was the need to change functions that were used elsewhere from plain to async. I really hoped to have had a strong IDE that could assure me the affected code was as intended and did not have the flow completely break because a promise was returned where none was expected before. I distinctively thought "I wish this was Java". Some of the affected code was already in Typescript and that did help a bit, but I was still not too confident because I still didn't trust the IDE completely (Typescript support was comparatively new).
I have even come to question if we should make all our javascript apis async from the start to avoid this scenario though it seems a rather extreme guideline. We didn't really do that, but I still dread this type of refactoring.
I have even come to question if we should make all our javascript apis async from the start to avoid this scenario though it seems a rather extreme guideline. We didn't really do that, but I still dread this type of refactoring.
Same for Python codebases. Which I wrote myself. Last year. And now hate.
> I agree that Java is almost unwritable without an IDE, but that strictness has led to Java having better IDEs than most languages.
I think the conundrum of java is not quite the same thing as strictness.
I think the conundrum of java is not quite the same thing as strictness.
I think part of what's happening right now is that the IDE for most languages other than Java seem to be consolidating on VS Code as the lowest common denominator. This makes sense given how many projects these days are a hodgepodge of 3 or 4 different languages, but the fact that working with the JVM more or less requires a dedicated IDE makes Java a lot less attractive to most teams.
(Yes, I know you can do Java in VS Code but I wouldn't want to manage a large Java codebase with it.)
(Yes, I know you can do Java in VS Code but I wouldn't want to manage a large Java codebase with it.)
> I wouldn't want to manage a large Java codebase with it.
Why not?
Why not?
[deleted]
probably because the code insight offered in VS Code is lagging behind what was available years ago in popular Java IDEs such as Eclipse and IntelliJ
while I agree VS Code is starting to get a lot of traction, it's far from the only solution for polyglot development (IntelliJ...) and it certainly has the facilities to support large java codebases.
I disagree with the idea that you can't maintain a robust system/codebase in python, mainly because nearly our entire distributed computing stack is written in python.
Granted it isn't 10s of thousands of lines, but it isn't small either. It does a lot of things ranging from webapis to data pipelining with ingestion and egestion of large datasets. One batch job can result in 10s of GBs in output files. It has to be performant too, 10s of millions of "operations" in one batch job is common.
Admittedly the process/threading model can be painful to work around at times, but totally doable, and is honestly somewhat friendly to distributed systems to an extent because it somewhat forces you into an actor model paradigm.
Granted it isn't 10s of thousands of lines, but it isn't small either. It does a lot of things ranging from webapis to data pipelining with ingestion and egestion of large datasets. One batch job can result in 10s of GBs in output files. It has to be performant too, 10s of millions of "operations" in one batch job is common.
Admittedly the process/threading model can be painful to work around at times, but totally doable, and is honestly somewhat friendly to distributed systems to an extent because it somewhat forces you into an actor model paradigm.
> Granted it isn't 10s of thousands of lines, but it isn't small either.
I'm not trying to belittle what you do, but if your good base is in the 4 figures in size then it's not large (in fact yes, it is small). When people talk about large code bases they are generally talking 100-1000x larger than yours, or more.
I'm not trying to belittle what you do, but if your good base is in the 4 figures in size then it's not large (in fact yes, it is small). When people talk about large code bases they are generally talking 100-1000x larger than yours, or more.
It sits on top of our own IP database which is 6 figures in lines and is written in C++. Java has fancy bells and whistles when it comes to IDE's, but that's all they are.
I just think it's foolish to think Java is the only language that can handle large codebases. I mean just look at the linux kernel.
EDIT: Removed comment about processing capabilities because it's irrelevant to the discussion. I left my original comments about lines and the IDE though.
I just think it's foolish to think Java is the only language that can handle large codebases. I mean just look at the linux kernel.
EDIT: Removed comment about processing capabilities because it's irrelevant to the discussion. I left my original comments about lines and the IDE though.
I don’t think anyone is saying Java is the only one they’re saying because it has good IDE support it is better than many other languages, e.g. python. I tend to agree. Once a python program gets over 20k lines (I touched a program like that before) it’s an absolute nightmare to maintain and without type annotations can be near impossible to figure out what is going on.
The size of data processed, or the number of nodes running, has almost no effect on the quality of the IDEs.
This is true. At my last job our primary codebase was around 5 million Loc split over 400+ assemblies.
Current job I've seen single files that are over 30k lines.
Current job I've seen single files that are over 30k lines.
This is a little off topic but is the performance mainly coming from the computation being distributed? I wrote this text parser in python a few years ago, rewrote the same thing in Java and got like a 10x speedup. It’s possible I’m a bad python programmer.
Also there's a general effect that any rewrite can improve the code quality. Same thing might have happened if you rewrote in Python.
Strictness of what, exactly? Of all Java code being belched by IDEs?
“Code should be written for humans to read and only incidentally for machines to run” (paraphrasing a famous quote by Harold abelson)
“Code should be written for humans to read and only incidentally for machines to run” (paraphrasing a famous quote by Harold abelson)
Strictness of syntax and language. It means that ctrl+space gives you exactly right methods into popup, that if you rename the class all places that use that class and no other will be changed, you can rename method and trust that all correct calls will be renamed too and no other.
You can find all the places that call a method and know they are really all. That sort of basic thing.
E.g. not for writing, but for analyzing what unknown code does and for refactoring that code. Comparing to javascript, in js you have to be much more careful when refactoring and changing things.
You can find all the places that call a method and know they are really all. That sort of basic thing.
E.g. not for writing, but for analyzing what unknown code does and for refactoring that code. Comparing to javascript, in js you have to be much more careful when refactoring and changing things.
> “Code should be written for humans to read and only incidentally for machines to run”
So this is a cute statement but isn't it actually false? None of us would be sitting around code reviewing each other's code for fun if there wasn't a mountain of money / value / knowledge to be extracted from a computer running the code at the end of the day.
Actually this is easily falsifiable. As soon as quantum computing becomes possible the first languages will probably be quite horrible, with painful tooling and torturous debugging and difficult to read control flow compared to current modern languages. Nonetheless, a huge army of programmers will be trying to write code for these quantum computers, for the sake of the computer, and not for any human to read.
So this is a cute statement but isn't it actually false? None of us would be sitting around code reviewing each other's code for fun if there wasn't a mountain of money / value / knowledge to be extracted from a computer running the code at the end of the day.
Actually this is easily falsifiable. As soon as quantum computing becomes possible the first languages will probably be quite horrible, with painful tooling and torturous debugging and difficult to read control flow compared to current modern languages. Nonetheless, a huge army of programmers will be trying to write code for these quantum computers, for the sake of the computer, and not for any human to read.
Another "Actually this is false in the corner case I'm going to call out".
Of course its not a universal rule, its not a law of physics. Doesn't make it less useful as a guiding principle (similarly, design patterns in software architecture).
The core argument is that we're besides the point in conventional (2019) computing where we need to worry too much about optimizing code to run as efficiently as possible. The complexity and bottlenecks to delivering features is now in writing software that is meant to be worked on and maintained by a large number of humans. And for that purpose, it is better to optimize for clarity.
Of course its not a universal rule, its not a law of physics. Doesn't make it less useful as a guiding principle (similarly, design patterns in software architecture).
The core argument is that we're besides the point in conventional (2019) computing where we need to worry too much about optimizing code to run as efficiently as possible. The complexity and bottlenecks to delivering features is now in writing software that is meant to be worked on and maintained by a large number of humans. And for that purpose, it is better to optimize for clarity.
But it's not really a corner case though. For a lot of professional applications it's perfectly fine to make a tradeoff of complexity or readability in order to get performance. And that tradeoff is done for business reasons - because sometimes the computer is more important than the programmer.
incidentally:
1. used when a person has something more to say, or is about to add a remark unconnected to the current subject; by the way.
2. in an incidental manner; as a chance occurrence.
That quote seems pretty misinformed, code has to be a lot more connected to the machine to be runnable that that quote would have us believe.
Otherwise this would be valid code:
Ask the user their name. After they enter the name, load a game. Use some cool graphics.
That being said, perhaps that will be possible in 100 years. (Yes the first line may be possible in 10 years)
1. used when a person has something more to say, or is about to add a remark unconnected to the current subject; by the way.
2. in an incidental manner; as a chance occurrence.
That quote seems pretty misinformed, code has to be a lot more connected to the machine to be runnable that that quote would have us believe.
Otherwise this would be valid code:
Ask the user their name. After they enter the name, load a game. Use some cool graphics.
That being said, perhaps that will be possible in 100 years. (Yes the first line may be possible in 10 years)
"Code should be primarily written for humans to read. Only in rare cases should the code be primarily written for the machine in mind"
In other words, don't doo premature optimization. Write the code so that it's readable, except on the chance occurrence where you need to eke out more performance.
In other words, don't doo premature optimization. Write the code so that it's readable, except on the chance occurrence where you need to eke out more performance.
> For large code bases, most languages get unmaintainable without an IDE
I disagree. Most people working on the Linux kernel (including Linus) or the major GNU projects (including RMS) are using vim or emacs with maybe some supplemental find/grep/sed/awk.
I disagree. Most people working on the Linux kernel (including Linus) or the major GNU projects (including RMS) are using vim or emacs with maybe some supplemental find/grep/sed/awk.
This is my development stack, vim is my text editor with some minor modern features using plugins and tmux lets me tile into any other command line tool that is best for the job. I prefer my tools to be Turing complete, and there is nothing an "IDE" can do that I cannot (most likely far faster too).
I tried writing Kotlin in Vim, I really did. Java was somewhat manageable, but Kotlin was absolutely impossible. The biggest thing those bloated Java-based IDEs offer is deep introspection into the language. You use a reference, the IDE can auto-import the package for you. When learning a new language, knowing what package to import is invaluable. Even the Kotlin in Action book from Manning Publications flat out says, "the IDE will automatically import that for you". It was quite a turn-off but.... it's a nice language, so when I write it, I have to use IntelliJ.
For large code bases, most languages get unmaintainable without an IDE, putting Java at an advantage
That’s an assumption which advantages Java by default. Other languages may be able avoid having a large code base entirely. I recall reading some stories of people rewriting giant Java code bases into very small Clojure programs that were more flexible, maintainable, and scalable.
That’s an assumption which advantages Java by default. Other languages may be able avoid having a large code base entirely. I recall reading some stories of people rewriting giant Java code bases into very small Clojure programs that were more flexible, maintainable, and scalable.
An eCommerce framework is never going to have a small footprint, for one example. That's why Java or other strongly typed/structured languages get used in enterprise software.
Java is used in enterprise software primarily because it's what gets used in enterprise software. Language popularity is a positive feedback loop, and once a language establishes itself in a sector to the point universities start to teach it with that sector in mind, it's hard to unseat it. Businesses, especially large ones, don't choose tech based on technical merits; they tend to focus on aspects like "can we get support with SLA for parts of our tech stack", and "how easy to find/cheap the developers specialized in that stack are", and "let's do what the IBM/Oracle salesman says we should, that must be the right thing to do".
I don't see a reason an eCommerce framework couldn't have a small footprint. The lower bound is the amount of actual, necessary complexity in the problem space. But that's usually orders of magnitude less than the size of the codebase, because of all the boilerplate and greenspunning that happens in Java, Enterprise Java in particular.
(Note that in commercial work, I moved from Java to Common Lisp, so I have a different view than typical about just how much boilerplate and repetition can be simplified and removed when your language lets you do it.)
I don't see a reason an eCommerce framework couldn't have a small footprint. The lower bound is the amount of actual, necessary complexity in the problem space. But that's usually orders of magnitude less than the size of the codebase, because of all the boilerplate and greenspunning that happens in Java, Enterprise Java in particular.
(Note that in commercial work, I moved from Java to Common Lisp, so I have a different view than typical about just how much boilerplate and repetition can be simplified and removed when your language lets you do it.)
I am sure you could reduce complexity quite quickly but then you are building a different product. The selling point of an eCommerce framework is the structure and existing feature toolkit. Like modules, events, mechanisms to safely override core functionality and all of that. I am also sure you could build a more compact eComm framework, but I think you would start to trade off structure for reduced complexity. For me though, more structure equals less need to worry about the complexity as that's been abstracted away.
I wouldn't mind seeing the other side of the fence though, I will admit I have worked in enterprise codebases for most of my career so I am definitely biased.
I wouldn't mind seeing the other side of the fence though, I will admit I have worked in enterprise codebases for most of my career so I am definitely biased.
You're not going to cut your lines of code by an order of magnitude by switching from Java or C# to JavaScript or Python, but I would say you do get an order of magnitude increase in ease of managing large code bases with the popular statically typed languages.
I'd rather have 1M lines of Java than 100k lines of JavaScript, and if essential complexity took 1M lines of Java to flesh out, you aren't reaching parity with 100k lines of JS.
I'd rather have 1M lines of Java than 100k lines of JavaScript, and if essential complexity took 1M lines of Java to flesh out, you aren't reaching parity with 100k lines of JS.
> You're not going to cut your lines of code by an order of magnitude by switching from Java or C# to JavaScript or Python
Why not? I can easily see reducing SLOC 10-50x by switching from Java to JavaScript.
> if essential complexity took 1M lines of Java to flesh out
I very much doubt that in a 1M SLOC Java codebase - at least in pre-Java 8 codebase - the essential complexity is any more than 1% of that. My work experience with Java suggests that the language and the surrounding ecosystem and practices promote extreme proliferation of accidental complexity.
Why not? I can easily see reducing SLOC 10-50x by switching from Java to JavaScript.
> if essential complexity took 1M lines of Java to flesh out
I very much doubt that in a 1M SLOC Java codebase - at least in pre-Java 8 codebase - the essential complexity is any more than 1% of that. My work experience with Java suggests that the language and the surrounding ecosystem and practices promote extreme proliferation of accidental complexity.
Yes, the secret is to write software that is composed of several modules that operate independently. Java's culture is to write a big ball of classes that are combined to everything you need. In other languages you can create different programs that compose to provide the desired functionality. This was the original "aha" moment of C and UNIX designers. Even though C is a brittle language, it allows the decomposition of systems into small programs thanks to files and filters.
There's nothing stopping you from writing uncoupled classes that work like small modules and maintaining them in one repository. Tightly coupled classes are a code smell even in Java, I wouldn't say it's the languages fault, more that the language is used more often in enterprise software and so all the frameworks/libraries/tooling is geared toward monoliths.
From a sample size of one - My Python and Go code tend to be fewer lines per file compared to Java, for projects that are similar in complexity. This is not about the terseness/dense nature of the language, but the sheer simplicity in other languages to decouple file structure from class structure (at least within a given directory)
> Yes, the secret is to write software that is composed of several modules that operate independently.
This isn't a secret, and in certain corners not even considered generally true -- backlash against microkernels and microservice architectures have intensified in the last half-decade, to give two examples.
This isn't a secret, and in certain corners not even considered generally true -- backlash against microkernels and microservice architectures have intensified in the last half-decade, to give two examples.
90% of code bases that attempt modulability get it wrong. The idea is to be able to lift out functionality to be reused elsewhere. But what 90% of code bases do wrong is that they move code into modules, but you can not lift out those modes as they depend on all other modules. It might help if you use the words library instead of module. And public API instead of micro service.
The famous UNIX mantra hardly seen in commercial UNIX settings, but cargo cult across the FOSS UNIX community.
> Other languages may be able avoid having a large code base entirely. I recall reading some stories of people rewriting giant Java code bases into very small Clojure programs that were more flexible, maintainable, and scalable.
I can rewrite any bad written code in less lines, same language same framework as initial code.
I can rewrite any bad written code in less lines, same language same framework as initial code.
disclaimer: elixir fanboi here just trying my best to temper my bias for this comment.
I recently rewrote a c# project that had not been completed in elixir. Hugely, anecdotal (as all of these types of stories are) but the project went from ~5kLoC to <800LoC when rewritten in elixir. That's after the elixir app was feature complete too. Now you might believe that's a developer experience gap, but the c# dev had way more experience with c# and in my experience this other dev is much better than me generally. Within a week of learning elixir he was catching my bugs all over the place.
I recently rewrote a c# project that had not been completed in elixir. Hugely, anecdotal (as all of these types of stories are) but the project went from ~5kLoC to <800LoC when rewritten in elixir. That's after the elixir app was feature complete too. Now you might believe that's a developer experience gap, but the c# dev had way more experience with c# and in my experience this other dev is much better than me generally. Within a week of learning elixir he was catching my bugs all over the place.
Neither 800LoC nor ~5kLoc qualifies as a large codebase.
In my experience sh*t starts to get real when you go beyond 30-50kLoC, at least that's when I start losing the needle in the haystack. I've done that with Erlang and it's not really fun.
In comparison, my current project in TypeScript is at this stage now and by using an IDE things are quite under control. Refactoring is easy and even major architectural changes are manageable (we did ~5 so far, depending on how you count).
Wasn't the case with Erlang. We only managed to do one such major change and it almost killed the project. It probably even actually killed the project as we were late to the party and failed afterwards.
In comparison, my current project in TypeScript is at this stage now and by using an IDE things are quite under control. Refactoring is easy and even major architectural changes are manageable (we did ~5 so far, depending on how you count).
Wasn't the case with Erlang. We only managed to do one such major change and it almost killed the project. It probably even actually killed the project as we were late to the party and failed afterwards.
I find it really amazing when a language's weakness drives an unintended strength to evolve like this.
Ruby Example:
Ruby projects tend to always have really well maintained test suites. The language makes it easy for you to introduce non-obvious bugs or write unreadable (yet efficient) meta code.
This has lead to 1) very mature testing frameworks (meh) and 2) high adoption and usage of these frameworks (actually impressive).
Funny enough, I think adoption of IDEs is extremely low in the Ruby world. 90% of the developers I interact with in my city are on Sublime/VSC/Vim. I've seen some pretty impressive usage of Rubymine that has made me curious, but never bothered to really spend time with it myself. I'd be really curious what the teams at GitHub/Stripe/[Insert big Ruby company] typically use editor-wise.
Ruby Example:
Ruby projects tend to always have really well maintained test suites. The language makes it easy for you to introduce non-obvious bugs or write unreadable (yet efficient) meta code.
This has lead to 1) very mature testing frameworks (meh) and 2) high adoption and usage of these frameworks (actually impressive).
Funny enough, I think adoption of IDEs is extremely low in the Ruby world. 90% of the developers I interact with in my city are on Sublime/VSC/Vim. I've seen some pretty impressive usage of Rubymine that has made me curious, but never bothered to really spend time with it myself. I'd be really curious what the teams at GitHub/Stripe/[Insert big Ruby company] typically use editor-wise.
I do most of my ruby work in rubymine. I use it not so much for the IDE tools like refactoring etc, but because I can use it to condense multiple bash sessions into a single UI. I also use pycharm and IDEA, and certainly in IDEA's case I use it primarily because it's the best-in-class Java IDE.
> condense multiple bash sessions into a single UI
How does it compare to tmux/screen?
How does it compare to tmux/screen?
Haven't used tmux much, and last time I used screen was over 10 years go. Rubymine gives me my iterative test results, multiple independent launch commands, the ability to run a single rspec test, refactoring support, autocompletion, a coherent gem environment, debugging etc all in one place. Yes, I could do all this on the cli, but I'm lazy, and Rubymine means I don't need to.
[deleted]
Having a problem so bad that you develop a good solution? That's the opposite of a feature.
Well, having tried both Eclipse and IntelliJ Ultimate, I can say that
* Eclipse has a crappy UI and can't handle Java9+ code very well, but can handle large code bases
* IntelliJ Ultimate has nice UI and assistance, but it totally fails with large code bases and still tricks you if you run code inside the IDE, especially when you want to use jigsaw modules and not classpath, as it uses by default
Unfortunately, using Java with other editors (vim, VS Codium, etc) is just difficult
* Eclipse has a crappy UI and can't handle Java9+ code very well, but can handle large code bases
* IntelliJ Ultimate has nice UI and assistance, but it totally fails with large code bases and still tricks you if you run code inside the IDE, especially when you want to use jigsaw modules and not classpath, as it uses by default
Unfortunately, using Java with other editors (vim, VS Codium, etc) is just difficult
I have a bit of opposing viewpoint. One of my biggest hurdles ever in understanding large codebases comes from the sheer amount of code involved per any architectural unit. IDE may let me move around a codebase quicker, but if a single functionality is smeared into 20 classes in three separate inheritance hierarchies, this will still cause me headaches until I draw the entire structure on a large sheet of paper and keep it on my desk. There is only so much thing I can keep in my head or in front of my eyes at the same time, so the denser the language (in terms of ability to abstract and DRY things), the easier I find a large codebase to work with.
I agree, it’s verbose. That being said, The boilerplate often helps when reading unfamiliar code. More importantly, the IDE support is fantastic, especially on large codebases.
An example: if I want to refactor a simple thing like Rename a method called “doFoo()” I can do this trivially.
Now, this seems simple but it’s not - I only want to refactor uses of this particular method, NOT any other methods that happen to be called “doFoo()” on unrelated classes or interfaces. Moreover, I want the refactor to intelligently work on everything, including inherited classes, abstract classes and interfaces, while correctly ignoring the stuff i mentioned previously.
None of this matters much if you have a small codebase and a fee developers, but on a huge codebase with many developers, all that boilerplate lets people quickly understand new code and easily refactor that code while being sure they did not break anything.
An example: if I want to refactor a simple thing like Rename a method called “doFoo()” I can do this trivially.
Now, this seems simple but it’s not - I only want to refactor uses of this particular method, NOT any other methods that happen to be called “doFoo()” on unrelated classes or interfaces. Moreover, I want the refactor to intelligently work on everything, including inherited classes, abstract classes and interfaces, while correctly ignoring the stuff i mentioned previously.
None of this matters much if you have a small codebase and a fee developers, but on a huge codebase with many developers, all that boilerplate lets people quickly understand new code and easily refactor that code while being sure they did not break anything.
>The boilerplate often helps when reading unfamiliar code
I don't believe this to be true at all, especially for large code bases. Often the people who support this position have not worked on 100k, 500k, 1m LOC applications I've found.
Boilerplate makes it much more difficult to read and scan large swatehs of code. I think when people say boilerplate 'helps', they often noticing a correlation between the fact that many strongly typed systems happen to have excess boilerplate. I believe it's the typing that helps, not the boilerplate.
I don't believe this to be true at all, especially for large code bases. Often the people who support this position have not worked on 100k, 500k, 1m LOC applications I've found.
Boilerplate makes it much more difficult to read and scan large swatehs of code. I think when people say boilerplate 'helps', they often noticing a correlation between the fact that many strongly typed systems happen to have excess boilerplate. I believe it's the typing that helps, not the boilerplate.
I agree on the strong typing note.
That being said, IntelliJ's IDEA is fantastic at hiding a lot of the boilerplate in Java (like getters and setters and all those import statements at the top of the file) and modern Java is FAR FAR better about being less verbose than it was in 1.0
Java also has Lombok (https://projectlombok.org/) that hides almost all of the repetitive stuff completely.
That being said, IntelliJ's IDEA is fantastic at hiding a lot of the boilerplate in Java (like getters and setters and all those import statements at the top of the file) and modern Java is FAR FAR better about being less verbose than it was in 1.0
Java also has Lombok (https://projectlombok.org/) that hides almost all of the repetitive stuff completely.
I absolutely agree, and that is why Java is a great choice for monolithic applications. It's verbose and it's extremely flexible.
The real reason to move away from Java is to move towards different software architectures (microservices!)
The real reason to move away from Java is to move towards different software architectures (microservices!)
>The real reason to move away from Java is to move towards different software architectures (microservices!)
This is a SpringBoot microservice:
@RestController public class BasicController {
I'm not sure it's any more verbose than the equivalent Ruby on Rails microservice (for example).
This is a SpringBoot microservice:
@RestController public class BasicController {
@RequestMapping(value="/{id}", method = RequestMethod.GET)
public String get(@PathVariable("id")Integer id) {
return doStuff(id);
}
}I'm not sure it's any more verbose than the equivalent Ruby on Rails microservice (for example).
In some ways the Java philosophy seems like the antithesis of the Unix philosophy. Instead of a lot of small, fast, composable tools that do one thing and do it well, Java culture mostly promotes giant self-contained systems that capture everything in a sprawling OO type system.
This distinction is reflected in even small design details. For example Java programs are comparatively slow to boot up. If you're mostly used to the Unix way of repeatedly piping the STDIN/STDOUT of small utilities in an interactive command line, this is painfully inconvenient.
But if your workflow involves a monolithic server that manages every component of the business logic in a single process, then 5 second boot times aren't really a big deal. Because maybe you bounce the server once a week at most.
This distinction is reflected in even small design details. For example Java programs are comparatively slow to boot up. If you're mostly used to the Unix way of repeatedly piping the STDIN/STDOUT of small utilities in an interactive command line, this is painfully inconvenient.
But if your workflow involves a monolithic server that manages every component of the business logic in a single process, then 5 second boot times aren't really a big deal. Because maybe you bounce the server once a week at most.
5 second boot time? Most Java programs I have worked on start up near-instantly.
Small programs can be written in java. It's a general purpose programming language. You could write grep in java.
Small programs can be written in java. It's a general purpose programming language. You could write grep in java.
Have you worked on any that make use of a framework that uses lots of reflection such as Spring?
I have avoided Spring as much as possible. Performance is one reason.
But I thought we were talking about Java:
"For example Java programs are comparatively slow to boot up."
But I thought we were talking about Java:
"For example Java programs are comparatively slow to boot up."
Yes! Things like Spring Boot are actually a reaction to the days of Java EE and the web of Java-centric technologies.
This always confused me as a newer developer. Vendors competed with each other with different JavaEE implementations (WebSphere, Glassfish etc) that all conformed to the same interface and provided everything including the kitchen sink, but what really differentiated the products were vendor-specific features that require you to go outside the interface, but if you tried to use that stuff, another senior developer would tell you that we can't marry our implementation to a specific vendor... meanwhile, literally every company you go to uniformly uses Spring and Hibernate, and both will inevitably require you to not pretend that they're dumb implementations of javax.inject and javax.persistence.
This always confused me as a newer developer. Vendors competed with each other with different JavaEE implementations (WebSphere, Glassfish etc) that all conformed to the same interface and provided everything including the kitchen sink, but what really differentiated the products were vendor-specific features that require you to go outside the interface, but if you tried to use that stuff, another senior developer would tell you that we can't marry our implementation to a specific vendor... meanwhile, literally every company you go to uniformly uses Spring and Hibernate, and both will inevitably require you to not pretend that they're dumb implementations of javax.inject and javax.persistence.
And Micronaut is a reaction to Spring Boot bloat... And GraalVM native image is a solution to Micronaut bloat... When does it finally end?
THE UNIX FOSS philosophy, hardly seen in commercial settings.
back in the day when i started java, which was its early days(90's), we didnt even have a package manager type solution for java libraries (i.e. maven) - UNIX had a massive head start on java in some respects
I dare anyone of you who thinks like this to study as much modern idiomatic Java as I've studied Python or Javascript.
I'll give you this though: Old Spring and JSF code could be really bad, I remember updating two different XML files every time I changed something in a controller.
Edit: even back then with all that boilerplate it was really nice compared to legacy Delphi where it took three full days with an expert to find and install all required dependencies before one could even start compiling the old source.
By comparison my Java dev environment was up and running in less than half a day with Ant, and when we introduced Maven a few months later setip time for a new developer was mostly the time it took to check out code, install dev databases (yep, back in the days) and set necessary environment settings. (Protip: many people hate NetBeans but one thing it got more right than all other Java IDEs AFAIK was accepting the pom file as the truth instead of creating its own.
I'll give you this though: Old Spring and JSF code could be really bad, I remember updating two different XML files every time I changed something in a controller.
Edit: even back then with all that boilerplate it was really nice compared to legacy Delphi where it took three full days with an expert to find and install all required dependencies before one could even start compiling the old source.
By comparison my Java dev environment was up and running in less than half a day with Ant, and when we introduced Maven a few months later setip time for a new developer was mostly the time it took to check out code, install dev databases (yep, back in the days) and set necessary environment settings. (Protip: many people hate NetBeans but one thing it got more right than all other Java IDEs AFAIK was accepting the pom file as the truth instead of creating its own.
On the other hand consider this: suppose you’ve built retail business out of your house, then it grew well. And now all those conveyor belts come in very handy, right?
Sometimes project is big enough to have a lot of code, boilerplate or not. And then all that IDE automation suddenly becomes essential.
I’ve been to some huge Scala codebases, it was much harder to navigate or make changes there than shove around Java cruft with IDE.
Sometimes project is big enough to have a lot of code, boilerplate or not. And then all that IDE automation suddenly becomes essential.
I’ve been to some huge Scala codebases, it was much harder to navigate or make changes there than shove around Java cruft with IDE.
This is a trend I see reemerging with the hyper focus on VSCode. At some point people won’t be able to work on their projects without it. That might not be a good thing... as we have seen with Java.
I like being able to consider a shell to be the lowest common denominator for a codebase. If basic shell commands, environment variables, a vanilla vim/nano editor etc... can’t be used to hack on your system then I think it’s far too coupled to tooling.
I like being able to consider a shell to be the lowest common denominator for a codebase. If basic shell commands, environment variables, a vanilla vim/nano editor etc... can’t be used to hack on your system then I think it’s far too coupled to tooling.
youre not wrong and i may be biased as a long time java guy but if i had to do java without the tooling to make it less annoying , id probably try to switch languages... and this is an independent problem from the other benefits i get from JVM... namely platform (JVM) and ecosystem (open source libraries).
why are people afraid of modern text editors? Why cling to 1980s technology?
I don't think it's necessarily (just) for a love of 80s editors. I think it's more of a litmus test that your developer experience is too tightly coupled with specific tooling.
i.e. if you can't (realistically) choose your own tools, then your experience with the ecosystem is that of your experience with the tool. No tool can be a good experience for everybody.
i.e. if you can't (realistically) choose your own tools, then your experience with the ecosystem is that of your experience with the tool. No tool can be a good experience for everybody.
Well some people would advertise UNIX as an IDE and then just try to turn into POSIX anything that they use, hardly any different.
I think you may be projecting others' arguments onto the parent.
I did not read it as "everything should support the UNIX IDE" as much as expressing a preference for it and that it's a red flag if you can't even use standard vanilla tools (e.g. grep) to do basic things in an ecosystem.
I think it's fair to say that you should be able to edit code in your editor of choice and not have a terrible time.
I did not read it as "everything should support the UNIX IDE" as much as expressing a preference for it and that it's a red flag if you can't even use standard vanilla tools (e.g. grep) to do basic things in an ecosystem.
I think it's fair to say that you should be able to edit code in your editor of choice and not have a terrible time.
Which brings back to having a language designed for poor developer experience, given that tooling support cannot be part of the overall experience.
So if for language X, if I as language designer want to provide the same experience as Smalltalk, either I consider the interaction with IDE workflows, or I design command line tooling to go along the language (e.g. Go).
And the command line experience comes back again to the universe where it is more prevalent, POSIXy systems.
So I don't think I am making false assumption here, as one cannot have it both ways.
So if for language X, if I as language designer want to provide the same experience as Smalltalk, either I consider the interaction with IDE workflows, or I design command line tooling to go along the language (e.g. Go).
And the command line experience comes back again to the universe where it is more prevalent, POSIXy systems.
So I don't think I am making false assumption here, as one cannot have it both ways.
>If basic shell commands, environment variables, a vanilla vim/nano editor etc... can’t be used to hack on your system then I think it’s far too coupled to tooling.
The above does not evince a fear of modern text editors. Nor does it equate to clinging to 1980s technology. Why do you think it does?
The above does not evince a fear of modern text editors. Nor does it equate to clinging to 1980s technology. Why do you think it does?
Why are people still flying 737's? Why cling to 1970's airliners?
Just because something was created long ago does not mean it hasn't improved. Vim and Emacs are both extremely featureful and powerful editors and many people prefer to be able to pick and choose specific tools that do their jobs extremely well.
It's not about using "old" technology - it's about using a tool that excels at its targeted task, and letting you choose the best tool for ancillary tasks like searching, formatting, testing, debugging etc.
Just because something was created long ago does not mean it hasn't improved. Vim and Emacs are both extremely featureful and powerful editors and many people prefer to be able to pick and choose specific tools that do their jobs extremely well.
It's not about using "old" technology - it's about using a tool that excels at its targeted task, and letting you choose the best tool for ancillary tasks like searching, formatting, testing, debugging etc.
I can write code in Notepad, but why would I do that when I can see type annotations/errors on the fly?
It greatly increases my productivity, probably by a factor of least 1.5x.
I’m not suggesting you must use a barebones editor for work. I’m suggesting that if you NEED a massive IDE for development then there is a problem.
I use Sublime with a handful of plugins, linters, etc... enabled. But if someone wanted to hack on my projects with Notepad they totally could.
I use Sublime with a handful of plugins, linters, etc... enabled. But if someone wanted to hack on my projects with Notepad they totally could.
You don't need an IDE in any language I'm aware of beyond exotica like language workbenches. People are using the word "need" here to mean "is really really useful".
I am sure many people feel the same way about Python and Javascript. What you described seems to be a result of building larger systems in which multiple people are contributing code.
I confirm this. I rename classes and move them around, and without a refactoring tool that renames files and changes imports automatically, life would be very hard. Before refactoring tools got to this level, I was running pytest to see what import statements got affected during my refactors.
Uh. Question. Why are you executing tests to get the compiler to tell you what was effected instead of doing a top level recursive grep to identify any classes or configs with an explicit symbolic dependency?
I mean, you get the same result, sure, but I find I can hardly ever rely on test coverage alone as an accurate safety net to clearly indicate whether I've reached an actual stopping point for a full refactor.
This is one of the ways in which Java excels, in that that loop is fully accommodated for in most major IDE's.
You can still get caught by surprise at runtime, but I've found that for a strongly typed language like Java, the syntax, and necessity of boilerplate makes most bugs obvious until you start getting overly dependent on frameworks you don't understand.
I mean, you get the same result, sure, but I find I can hardly ever rely on test coverage alone as an accurate safety net to clearly indicate whether I've reached an actual stopping point for a full refactor.
This is one of the ways in which Java excels, in that that loop is fully accommodated for in most major IDE's.
You can still get caught by surprise at runtime, but I've found that for a strongly typed language like Java, the syntax, and necessity of boilerplate makes most bugs obvious until you start getting overly dependent on frameworks you don't understand.
I am not executing tests to perform import error detection. That was before refactoring tools got better. I was executing tests to check for import errors because it doesn't rely on full coverage on every method. It relies on all modules being imported by tested modules. That is easy to do even if you are too lazy to test every module. Just put an empty test function in all of them and pytest will detect them recursively. Given all that, I would avoid executing manual grep commands. However, you have a valid point for configuration files. Especially log configs need to be checked.
Would the language described in the article (Unitliy) be harder to refactor? Why?
Python is not static typed, it won’t refactor as easily.
Python is not static typed, it won’t refactor as easily.
The criticism doesn't really apply to Python and JavaScript though. Not to say that Python and JavaScript don't have their own issues, but dependence on an IDE tightly coupled to the language semantics is not one of them.
I can't speak for Python but the JavaScript community has been investing heavily in TypeScript, and the desire for a more integrated IDE experience is a huge driver for that growth.
Typescript: Putting the Java™ in Javascript
Definitely true, though I think the problem is less severe with regard to TS because open source language-servers afford TypeScript IDE features universal availability which is not the case in JVM land. Also, consider the trend towards Kotlin in the Java world where a part of Jetbrain's business model relies on the fact that they created and maintain the language while also selling the IDE that makes the language practical to use (Kotlin being an amazing language notwithstanding).
Last time I worked with Python or JavaScript I was using a very basic text editor. I'm not sure I'd be quite so brave with Java.
I don't know about JS, but my python work is done in a repl like ipython in conjunction with a text editor. It gets me half of what I wanted from an IDE.
This kind of automation depends on a language being easy to parse, and Java achieves this better than C/C++ or most scripting languages. The LSP (language server protocol) is a language-independent standard that can be used to enable this more generally, by connecting parsers/compilers/etc. with editors/IDE's.
My comment for years has been that most programming languages are for humans first, and computers second. But Java is for tools first, humans second and computers third.
The result is an excellent toolchain. But also you are completely unproductive unless you master said toolchain.
The result is an excellent toolchain. But also you are completely unproductive unless you master said toolchain.
I am a big fan of rust, but I am starting to feel like this is a thing with it too. Not to the same extent at all, but I often find myself writing what feels like scaffolding boilerplate code.
That said, the code is usually there for a good reason and I'm extremely thankful for it in the instances where things are refactored and the compiler catches it, or some deviation from the norm comes up and the scaffold allows for doing things the way you need.
That said, the code is usually there for a good reason and I'm extremely thankful for it in the instances where things are refactored and the compiler catches it, or some deviation from the norm comes up and the scaffold allows for doing things the way you need.
ditto for Go. Huge fan, but it does involve a lot of boilerplate.
Not that I think that's a bad thing. I prefer it to "magic" (I never got on with Rails because of this... what just happened? why did it do that? where does it say it should do that?).
And you never know when you have a class/use case that needs the boilerplate to change. Having it the same 99% of the time so that you have the option to change it for that 1% edge case is a good thing, imho.
Not that I think that's a bad thing. I prefer it to "magic" (I never got on with Rails because of this... what just happened? why did it do that? where does it say it should do that?).
And you never know when you have a class/use case that needs the boilerplate to change. Having it the same 99% of the time so that you have the option to change it for that 1% edge case is a good thing, imho.
> (I never got on with Rails because of this... what just happened? why did it do that? where does it say it should do that?)
With Spring you get to ask all these questions...with all the boilerplate of Java as an added bonus!
With Spring you get to ask all these questions...with all the boilerplate of Java as an added bonus!
Go seems to have much less boilerplate than Java. I’m really glad that OOP was explicitly unsupported and the language designers opted for using interfaces instead.
Interfaces are as much a part of OOP as inheritance. Go is as OOP heavy as Java. They differ mostly in how/when an "class" is bound to an interface. Java declares up front, Go does this implicitly at usage sites aka static duck-typing (which is great!).
That said, I think OOP education focused a lot on inheritance during Java's rise and that was mostly a mistake in hindsight.
That said, I think OOP education focused a lot on inheritance during Java's rise and that was mostly a mistake in hindsight.
> Go seems to have much less boilerplate than Java
Maybe if you compare to Java from 10y ago. Have you ever tried to print a map in an alphabetical order of its keys?
The only thing more verbose in Java are getters/setters which hopefully will disappear once records are available.
Maybe if you compare to Java from 10y ago. Have you ever tried to print a map in an alphabetical order of its keys?
The only thing more verbose in Java are getters/setters which hopefully will disappear once records are available.
This is strange, I've been programming mostly Rust for the last six months and I'm finding it very terse actually. It is much shorter than the original Javascript code it's replacing, mostly because of the error handling.
Are you using an MVC web framework, or something like that? Asking because I'm curious about other people's experience with Rust.
Are you using an MVC web framework, or something like that? Asking because I'm curious about other people's experience with Rust.
Boilerplate may not be the best term. It's more the presence of fitting into types and structures that certain libraries/frameworks provide where you just need to be much more explicit than in some languages. It's definitely a good thing long term though.
Writing code without dependencies/frameworks that have a required structure is a joy, however.
Writing code without dependencies/frameworks that have a required structure is a joy, however.
Are there cases of such boilerplate that are not solved by macros?
That's a design feature. Java is the modern COBOL.
The idea is that you hire an army of marginally qualified developers through an agency that are making $14-18/hr in the US and have them churn out code.
To use your house example, it's like addressing clutter by having 5 children and making them clean.
The idea is that you hire an army of marginally qualified developers through an agency that are making $14-18/hr in the US and have them churn out code.
To use your house example, it's like addressing clutter by having 5 children and making them clean.
Cobol is the modern COBOL.
https://en.wikipedia.org/wiki/COBOL#COBOL_2014
https://en.wikipedia.org/wiki/COBOL#COBOL_2014
I can't imagine writing Java in a plain vim session. You would need a photographic memory.
Coding Java was what made me stop using Emacs for everything and switch to IDEs. It's a much better experience
I imagine you have an old keyboard somewhere where the "." key is worn smooth with no "." visible. Maybe the tab key on your current keyboard is now in similar condition.
As proven by Xerox PARC, EHTZ, Apple, Borland and Microsoft tooling as well.
The best programming languages for developer productivity are those that are designed with tooling in mind, instead of grammar + semantics and then we see later how everything evolves.
The best programming languages for developer productivity are those that are designed with tooling in mind, instead of grammar + semantics and then we see later how everything evolves.
my question is how do you make sense of a seemingly endless stacktrace? (really curious, it's some time since i did something in java)
Another problem i had with java: most projects are impossible to understand by looking at the source code, as they tend to do things like Spring or EJB: with delayed loading where class names are specified in some configuration files - and it is often hard to figure out how the system is initialized.
That leads to another question: Is it possible to learn about an unfamiliar java project from source code in this day and age?
i mean they used to say that perl is a write only language; but enterprise java isn't very readable either.
Another problem i had with java: most projects are impossible to understand by looking at the source code, as they tend to do things like Spring or EJB: with delayed loading where class names are specified in some configuration files - and it is often hard to figure out how the system is initialized.
That leads to another question: Is it possible to learn about an unfamiliar java project from source code in this day and age?
i mean they used to say that perl is a write only language; but enterprise java isn't very readable either.
> Is it possible to learn about an unfamiliar java project from source code in this day and age?
Yes it is, but it helps a lot if you're familiar with the framework and the original developer used sensible conventions. I guess that's the same with any language though.
Yes it is, but it helps a lot if you're familiar with the framework and the original developer used sensible conventions. I guess that's the same with any language though.
A code base is not like a house. There is no "done" state. You have to rearrange and extend code bases every few months due to changing business needs.
It's a nice analogy. And I agree with it.
For me it seems weird that the Java IDE is "smarter" than the compiler. Then why can't the compiler abstract stuff...
Like the 'auto' keyword that took how long to exist again?
For me it seems weird that the Java IDE is "smarter" than the compiler. Then why can't the compiler abstract stuff...
Like the 'auto' keyword that took how long to exist again?
The compiler is not interacting with the user. The ide isn't smart enough to apply some refactoring without the users consent.
This is just one example of a general tension between compiling to produce an asset vs compiling as a service to provide data for tooling. The environments and things you optimize for are different, so you get differences like this. Compilers like what C# and F# use unify these into the same codebase (no presentation compiler) but the tradeoffs still exist.
Java is not a cluttered house, it a mega warehouse with over billion products lying around. No amount of organizing will make it simple for you top find what you want. Hence the conveyer belt instead of better organization.
This is exactly why I've always had a strong aversion to Java. I like 'easy to remember' languages, with a strong preference towards Go and Python, and -somewhat- C though I don't use it often. In any of these languages, I can use an IDE with nice integrations, or if I want to write a quick one off, I can drop into vim and pound out a 50 liner and run it. This is an absolute nightmare to attempt in Java.
> We define the UserProvider interface
I'm strongly against creating interfaces for non-library (not shared) code, that will only ever have one concrete implementation. I see it all the time in internal service code, code that will never have a second implementation. It makes code hard to navigate, harder to understand, and tedious to manage as instead of updating one file, now you'll need to update two.
I'm strongly against creating interfaces for non-library (not shared) code, that will only ever have one concrete implementation. I see it all the time in internal service code, code that will never have a second implementation. It makes code hard to navigate, harder to understand, and tedious to manage as instead of updating one file, now you'll need to update two.
So much over-engineering comes from the fear of changing already written code. Some programmers, I believe, have the impression that changing code any reason other than new features or requirements is wasted effort. So they try and build out all possibilities they can imagine first so they won't have to change it later.
If you eventually need that interface, you can change your existing code to use it. Making that change is relatively easy compared to maintaining a bunch of code that isn't needed. But still I find it hard for some programmers to get past that mentality.
If you eventually need that interface, you can change your existing code to use it. Making that change is relatively easy compared to maintaining a bunch of code that isn't needed. But still I find it hard for some programmers to get past that mentality.
The cost of refactoring is dependent on the scale/stage of the project
+1 to this
In my own progression as an engineer who writes Java, I read Clean Architecture by Robert Martin which lead to AbstractFactory(s) and interfaces up the wazoo. The reasoning would always be "this can change!"
It definitely took me some time to understand that not everything will change, and if it does change, having some extra work for simplicity is a reasonable tradeoff sometimes. In general, knowing when to utilize OO design patterns takes time and experience.
In my own progression as an engineer who writes Java, I read Clean Architecture by Robert Martin which lead to AbstractFactory(s) and interfaces up the wazoo. The reasoning would always be "this can change!"
It definitely took me some time to understand that not everything will change, and if it does change, having some extra work for simplicity is a reasonable tradeoff sometimes. In general, knowing when to utilize OO design patterns takes time and experience.
Similar progression here.
It took a long time before I realized that a lot of the changeability that come with writing aggressively SOLID code is actually a self-fulfilling prophecy: Some tightly intertwined chain of steps that might have been implemented as five consecutive (albeit tightly coupled) statements gets blown out into 10 or 15 modules and abstractions spread across as many files. All this added complexity was invariably designed to make it easy to change the code in a way that is not actually how you ended up needing to change it, so the end result is that it increased the difficulty of changing things: Instead of editing 5 lines, you now need to edit 10 files. And, since the logic is spread across 10 files whose members are, thanks to the magic of Java's access control facilities, all public, and whose connections to other parts of the codebase are all, thanks to the magic of dependency injection, loose (read: not explicit), it's hard to even understand the scope of impact of those changes without relying extensively on an IDE.
I've since taken a big turn back toward procedural programming. Object-oriented and functional techniques have their place (and the best procedural languages will let you use both), and I've learned a lot from working in both paradigms. But most of what I learned is that no amount of abstraction can replace the KISS principle.
It took a long time before I realized that a lot of the changeability that come with writing aggressively SOLID code is actually a self-fulfilling prophecy: Some tightly intertwined chain of steps that might have been implemented as five consecutive (albeit tightly coupled) statements gets blown out into 10 or 15 modules and abstractions spread across as many files. All this added complexity was invariably designed to make it easy to change the code in a way that is not actually how you ended up needing to change it, so the end result is that it increased the difficulty of changing things: Instead of editing 5 lines, you now need to edit 10 files. And, since the logic is spread across 10 files whose members are, thanks to the magic of Java's access control facilities, all public, and whose connections to other parts of the codebase are all, thanks to the magic of dependency injection, loose (read: not explicit), it's hard to even understand the scope of impact of those changes without relying extensively on an IDE.
I've since taken a big turn back toward procedural programming. Object-oriented and functional techniques have their place (and the best procedural languages will let you use both), and I've learned a lot from working in both paradigms. But most of what I learned is that no amount of abstraction can replace the KISS principle.
> The reasoning would always be "this can change!"
As I understood Uncle Bob's argument for using interfaces though, the main benefit is not supporting alternative implementations, but rather aiding in the enforcement of the Dependency Rule[1] and of the Single Responsibility Principle[2].
Interfaces make the relation between components explicit in two ways:
- they make it clear what depends on what - they make it clear what are the responsibilities of the components on either side of the interface
So aside from the fact that it might make it easier in the future to swap implementations, it first makes it easier when writing the code to decide how to organize its components (i.e. laying out its architecture).
Of course I agree with you that interfaces can definitely be abused, and can make code more indirect and difficult to read. I just wanted to point out what I think is Uncle Bob's point. :)
[1] https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-a... [2] https://en.wikipedia.org/wiki/Single_responsibility_principl...
As I understood Uncle Bob's argument for using interfaces though, the main benefit is not supporting alternative implementations, but rather aiding in the enforcement of the Dependency Rule[1] and of the Single Responsibility Principle[2].
Interfaces make the relation between components explicit in two ways:
- they make it clear what depends on what - they make it clear what are the responsibilities of the components on either side of the interface
So aside from the fact that it might make it easier in the future to swap implementations, it first makes it easier when writing the code to decide how to organize its components (i.e. laying out its architecture).
Of course I agree with you that interfaces can definitely be abused, and can make code more indirect and difficult to read. I just wanted to point out what I think is Uncle Bob's point. :)
[1] https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-a... [2] https://en.wikipedia.org/wiki/Single_responsibility_principl...
> Single Responsibility Principle
Too many Java programmers take the SRE too far, IMO.
If you have a class that only has a single function, then it probably should be a function, not a whole class.
To give an example that I've seen, I've seen code with classes named UserGetter, UserCreator, UserDeleter, etc. All of these should have just been bundled up into a single User class.
Too many Java programmers take the SRE too far, IMO.
If you have a class that only has a single function, then it probably should be a function, not a whole class.
To give an example that I've seen, I've seen code with classes named UserGetter, UserCreator, UserDeleter, etc. All of these should have just been bundled up into a single User class.
This is taken to a pathological degree in the VIPER architecture. Every thing is decomposed and wrapped in and interface. Unfortunately this has gained some popularity in the iOS world. It makes code so much harder to follow and maintain.
Never used VIPER. MVVM with navigation flows and layout handlers works well enough.
AppKit is pretty deeply MVC. I generally prefer to just work with the underlying APIs than try to invent a new paradigm on top of them. With VC containment I feel like I have enough abstraction power already.
In my case the "Flows" are usually UINavigationControllers hidden behind a protocol. I tend to chop up my UI's really deeply into VC's but most of them get a VM because I like my VC's really, really small.
I've gone through the same learning process, and I've seen it happen with numerous devs - they go through a cargo-culting stage, where they see abstractions and patterns espoused by people they respect, only to later come to the conclusion that it makes for code that is anything but clear.
Abstractions are not evil, but it takes experience to use just enough abstraction.
Abstractions are not evil, but it takes experience to use just enough abstraction.
I catch myself doing this all the time. I'm never going to move this project off Postgres, so why the hell have I created an interface to abstract the database?
Unit testing?
Not sure about Java, but at least in C# there are clear advantages to using interfaces over concrete classes when using Moq.
Interfaces are not the only option - you can also mark methods as virtual.
But overuse of mocking is a very big problem in a lot of codebases. Often I look at a test, and all it seems to do is setup mocks, such that I'm not sure if anything is actually being tested!
My hope is that C#'s new default interface implementations feature will put an end to this mess.
But overuse of mocking is a very big problem in a lot of codebases. Often I look at a test, and all it seems to do is setup mocks, such that I'm not sure if anything is actually being tested!
My hope is that C#'s new default interface implementations feature will put an end to this mess.
As mentioned elsewhere, refactoring a concrete class into an interface is actually pretty trivial using an IDE. At least within an codebase. The only time to really be diligent about interface design is if you're designing an API.
I've been in the workforce for nearly 10 years and I still have this issue when starting a new project.
It takes a conscious effort to just move forward, and it still leaves a lingering thought in my mind about what I'm doing wrong while building exactly what I need.
It takes a conscious effort to just move forward, and it still leaves a lingering thought in my mind about what I'm doing wrong while building exactly what I need.
This is my main complaint with OOP. It's not a feature of OOP explicitly but I've only seen it in OOP codebases. And the most complicated, awful, snaggletoothed codebases I've had to unravel were all like this - heavily laden with abstractions that weren't really necessary.
OOP exists exactly to solve problems where abstraction is appropriate. So obviously it's going to be implicated in cases of over-abstraction.
It's kind of like how cars are implicated in traffic problems. Everywhere you have heavy traffic, you'll find cars.
It's kind of like how cars are implicated in traffic problems. Everywhere you have heavy traffic, you'll find cars.
So do you not believe that OOP practices can be abused?
I actually said the opposite. Any tool of sufficient power is going to more abused than a tool with less power. You can do more damage with a backhoe than a shovel.
But the fact that you can do more damage with a backhoe than a shovel doesn't mean there is anything wrong with a backhoe or anything good about shovel. Same is true of OOP.
But the fact that you can do more damage with a backhoe than a shovel doesn't mean there is anything wrong with a backhoe or anything good about shovel. Same is true of OOP.
Got it. I don't have an issue with OOP, just the ways I've seen it abused, mostly by intermediate programmers that are too clever for their own good.
Couldn't agree more, and I feel like I've spent half my career tilting at windmills trying to get other engineers to use less abstraction, instead of more.
I can't count the number of C#/Java projects I've walked into where it felt like I had to drill down through a dozen layers of abstraction just to find code that actually does something. Hundreds (thousands?) of files that contain nothing but autogenerated boilerplate crap.
Layers upon layers of boilerplate, "just in case we need to swap something out later!" No. I promise you, you won't. And if you do, I promise you things aren't as loosely coupled as you think they are. You're going to have to do a bunch of rewriting anyway.
Also, 9 times out of 10, by the time you realize some seemingly interchangeable piece of your architecture needs to be replaced, your team will be chomping at the bit to blow the whole thing up and rewrite from the ground up anyway.
In the meantime, having a simple codebase that people actually enjoy working in will make everyone's job so much easier. And fun.
I can't count the number of C#/Java projects I've walked into where it felt like I had to drill down through a dozen layers of abstraction just to find code that actually does something. Hundreds (thousands?) of files that contain nothing but autogenerated boilerplate crap.
Layers upon layers of boilerplate, "just in case we need to swap something out later!" No. I promise you, you won't. And if you do, I promise you things aren't as loosely coupled as you think they are. You're going to have to do a bunch of rewriting anyway.
Also, 9 times out of 10, by the time you realize some seemingly interchangeable piece of your architecture needs to be replaced, your team will be chomping at the bit to blow the whole thing up and rewrite from the ground up anyway.
In the meantime, having a simple codebase that people actually enjoy working in will make everyone's job so much easier. And fun.
YAGNI was coined for a reason.
Do you consider test mocks to be a second implementation that warrants the interface?
No. This is what many considered test induced damage. If the only reason you create an interface is because a test requires it, you are damaging the integrity of your design.
Interfaces are one of the more powerful abstractions available and should be well thought out points of flexibility you specifically designed into your solution. If you use interfaces for other reasons your codebase is full of false flexibility and not-actually-abstracted abstractions which makes it difficult for other people to figure out what they can actually do with it.
Interfaces are one of the more powerful abstractions available and should be well thought out points of flexibility you specifically designed into your solution. If you use interfaces for other reasons your codebase is full of false flexibility and not-actually-abstracted abstractions which makes it difficult for other people to figure out what they can actually do with it.
> No. This is what many considered test induced damage. If the only reason why you create an interface is because a test requires it, you are damaging the integrity of your design.
Writing testable code is not "damage". An interface is a small concession in exchange for the well known benefits of automated testing.
I see where you're coming from and many programmers use too many interfaces as a reflex. But I absolutely think interfaces should be used to create seams for automated tests in front of components that do things like interact with databases, file systems, networks etc.
Writing testable code is not "damage". An interface is a small concession in exchange for the well known benefits of automated testing.
I see where you're coming from and many programmers use too many interfaces as a reflex. But I absolutely think interfaces should be used to create seams for automated tests in front of components that do things like interact with databases, file systems, networks etc.
> Writing testable code is not "damage"
Correct, but you can go a long way in writing testable code without requiring mock implementations. While I don’t deny the usefulness of mock testing, it comes at a steep cost (= vastly more complex code base), and I increasingly question whether that’s worth it. In practice I find it more useful to unit test what can be tested in isolation, and to perform integration testing for the test, and to limit mock testing (and thus unit tests which would require mocking) to a few well-defined interfaces. This cuts down drastically on boilerplate, with little (if any) loss of testability. And these few remaining mock object dependencies can be modelled via simple parameters and higher-order functions, further cutting down on extraneous interface classes.
Correct, but you can go a long way in writing testable code without requiring mock implementations. While I don’t deny the usefulness of mock testing, it comes at a steep cost (= vastly more complex code base), and I increasingly question whether that’s worth it. In practice I find it more useful to unit test what can be tested in isolation, and to perform integration testing for the test, and to limit mock testing (and thus unit tests which would require mocking) to a few well-defined interfaces. This cuts down drastically on boilerplate, with little (if any) loss of testability. And these few remaining mock object dependencies can be modelled via simple parameters and higher-order functions, further cutting down on extraneous interface classes.
> Correct, but you can go a long way in writing testable code without requiring mock implementations.
For some languages (e.g. C++), if you are using classes, it can actually be fairly challenging to do unit tests without mocks. And almost every solution to this problem out there is to use interfaces/dependency injection/inversion.
For some languages (e.g. C++), if you are using classes, it can actually be fairly challenging to do unit tests without mocks. And almost every solution to this problem out there is to use interfaces/dependency injection/inversion.
> [in C++] it can actually be fairly challenging to do unit tests without mocks
Yes, but you can write integration tests instead. There’s no rule that says that unit testing must cover every aspect of the software. Unit test those units that can be used in isolation, integration test the rest. Case in point, our main software at work, written in C++, has close to 100% test coverage, and uses almost no mock tests. Instead, we have extensive integration tests where unit testing without mocking doesn’t work.
And, again: I’m not saying that there are no tradeoffs involved in that. But the benefit of having a drastically simpler code architecture outweigh the cost of having to move those tests into integration.
Incidentally this is vastly easier when the architecture cleanly separates business logic from general logic and moves as much of it as possible into side-effect free general-purpose functions.
Yes, but you can write integration tests instead. There’s no rule that says that unit testing must cover every aspect of the software. Unit test those units that can be used in isolation, integration test the rest. Case in point, our main software at work, written in C++, has close to 100% test coverage, and uses almost no mock tests. Instead, we have extensive integration tests where unit testing without mocking doesn’t work.
And, again: I’m not saying that there are no tradeoffs involved in that. But the benefit of having a drastically simpler code architecture outweigh the cost of having to move those tests into integration.
Incidentally this is vastly easier when the architecture cleanly separates business logic from general logic and moves as much of it as possible into side-effect free general-purpose functions.
> Yes, but you can write integration tests instead. There’s no rule that says that unit testing must cover every aspect of the software. Unit test those units that can be used in isolation, integration test the rest. Case in point, our main software at work, written in C++, has close to 100% test coverage, and uses almost no mock tests. Instead, we have extensive integration tests where unit testing without mocking doesn’t work.
In my last C++ job, perhaps less than 5% of our code was unit testable. We had very good test coverage using integrated tests, but those were very expensive tests. Running a full test suite took 10 hours. Our SW heavily relied on certain equipment. Fortunately, the vendor had provided a very good simulator for it - so almost all our integration tests needed that simulator. And the simulator was such that there was a fair amount of overhead in loading and setting it up and unloading it. We didn't do this for every test - that would have taken days/weeks to run - but for logically grouped tests. And unfortunately many of the integration tests we wrote were much more suited to unit tests, but because we didn't have interfaces, we were forced to test them via an integration test, which was very expensive.
Oh, and the simulator would not allow parallelism - you could have only one simulator running on a given machine at a time. So all our tests had to be run in sequence.
And of course, the simulator had bugs, so whenever we had a failure, we had to determine if the simulator was at fault. Especially for intermittent failures.
So yes, we had very thorough tests and were proud of it, but unit testing for perhaps 30% of our tests would have been better. But to unit test those we really needed interfaces. I know because I convinced management to give me some weeks to convert one of our simplest portions of code into something we could unit test. I thought it would be trivial, but I ended up needing interfaces.
There is a whole other approach which our sister team (very similar SW, but different HW) used. They did not have a reliable simulator. So they wrote a "fake" simulator. They simply reproduced the APIs and wrote code to return certain values under certain conditions - a poor man's mock. On the plus side their test suite ran very quickly. The down side was they needed to write many mocks for the same function (for when they needed to return different values). Writing tests was very expensive for them.
Why did they write fakes instead of using a mocking framework? Because every mocking framework they found in C++ required interfaces, and they didn't want to change their SW architecture.
In many cases integration tests are OK. But in our case, they were extremely expensive.
> Incidentally this is vastly easier when the architecture cleanly separates business logic from general logic and moves as much of it as possible into side-effect free general-purpose functions.
This is precisely where interfaces come from! It's not trivial to separate business logic from general logic without them. When I took the time to show how we could write unit tests for our code base, I ended up with interfaces. And no, I didn't read any books/sites telling me I should do it. I didn't even know they were called interfaces. I just took some of the simplest code in our codebase, and asked "How can I write unit tests for this?" and essentially reinvented interfaces. I separated the business logic from the logic requiring the simulator, and then wrote unit tests for the business logic, and had an interface class to interface with the simulator.
To give you an idea, the code I converted to unit tests was trivial: Given this input string with these flags set, return this converted string, etc. It should be one of the easiest things to unit test! However, the input string and flags were entities understood by the HW and so we had simulator dependencies. I wrote a class that dealt only with strings and booleans to implement the business logic, and then had the interface class translate back and forth between strings/bools to entities the simulator expected/understood.
I appreciate your comment, and trust me, I'm not trying to be difficult. But while I've heard many claims, every time I've gone into the details I end up with interface classes. I work at a large company and went around asking several teams in different C++ projects, and they all independently had come to the same conclusion: Either use interfaces or live without unit tests. I presented my work to convert portions of our code base to unit tests at internal SW conferences, and would say "This is a highly nontrivial and crappy way to do unit tests in C++. I'd really like to hear from people in the audience if they found a better way." And whoever approached me afterwards said they had gone down the same path as me and ended up with interfaces.
Testing in C++ sucks, to be frank.
As an aside, I'm not pro-mocks, BTW. I recently did a small personal Python project where I insisted doing TDD. I would not write code till I had a test. And this involved several mocks. And while all my tests involving mocks passed, in the majority of cases involving the mocked code, my program had bugs and would fail when I tested with real data.
(Maybe that's more a criticism of TDD than of mocks, though).
In my last C++ job, perhaps less than 5% of our code was unit testable. We had very good test coverage using integrated tests, but those were very expensive tests. Running a full test suite took 10 hours. Our SW heavily relied on certain equipment. Fortunately, the vendor had provided a very good simulator for it - so almost all our integration tests needed that simulator. And the simulator was such that there was a fair amount of overhead in loading and setting it up and unloading it. We didn't do this for every test - that would have taken days/weeks to run - but for logically grouped tests. And unfortunately many of the integration tests we wrote were much more suited to unit tests, but because we didn't have interfaces, we were forced to test them via an integration test, which was very expensive.
Oh, and the simulator would not allow parallelism - you could have only one simulator running on a given machine at a time. So all our tests had to be run in sequence.
And of course, the simulator had bugs, so whenever we had a failure, we had to determine if the simulator was at fault. Especially for intermittent failures.
So yes, we had very thorough tests and were proud of it, but unit testing for perhaps 30% of our tests would have been better. But to unit test those we really needed interfaces. I know because I convinced management to give me some weeks to convert one of our simplest portions of code into something we could unit test. I thought it would be trivial, but I ended up needing interfaces.
There is a whole other approach which our sister team (very similar SW, but different HW) used. They did not have a reliable simulator. So they wrote a "fake" simulator. They simply reproduced the APIs and wrote code to return certain values under certain conditions - a poor man's mock. On the plus side their test suite ran very quickly. The down side was they needed to write many mocks for the same function (for when they needed to return different values). Writing tests was very expensive for them.
Why did they write fakes instead of using a mocking framework? Because every mocking framework they found in C++ required interfaces, and they didn't want to change their SW architecture.
In many cases integration tests are OK. But in our case, they were extremely expensive.
> Incidentally this is vastly easier when the architecture cleanly separates business logic from general logic and moves as much of it as possible into side-effect free general-purpose functions.
This is precisely where interfaces come from! It's not trivial to separate business logic from general logic without them. When I took the time to show how we could write unit tests for our code base, I ended up with interfaces. And no, I didn't read any books/sites telling me I should do it. I didn't even know they were called interfaces. I just took some of the simplest code in our codebase, and asked "How can I write unit tests for this?" and essentially reinvented interfaces. I separated the business logic from the logic requiring the simulator, and then wrote unit tests for the business logic, and had an interface class to interface with the simulator.
To give you an idea, the code I converted to unit tests was trivial: Given this input string with these flags set, return this converted string, etc. It should be one of the easiest things to unit test! However, the input string and flags were entities understood by the HW and so we had simulator dependencies. I wrote a class that dealt only with strings and booleans to implement the business logic, and then had the interface class translate back and forth between strings/bools to entities the simulator expected/understood.
I appreciate your comment, and trust me, I'm not trying to be difficult. But while I've heard many claims, every time I've gone into the details I end up with interface classes. I work at a large company and went around asking several teams in different C++ projects, and they all independently had come to the same conclusion: Either use interfaces or live without unit tests. I presented my work to convert portions of our code base to unit tests at internal SW conferences, and would say "This is a highly nontrivial and crappy way to do unit tests in C++. I'd really like to hear from people in the audience if they found a better way." And whoever approached me afterwards said they had gone down the same path as me and ended up with interfaces.
Testing in C++ sucks, to be frank.
As an aside, I'm not pro-mocks, BTW. I recently did a small personal Python project where I insisted doing TDD. I would not write code till I had a test. And this involved several mocks. And while all my tests involving mocks passed, in the majority of cases involving the mocked code, my program had bugs and would fail when I tested with real data.
(Maybe that's more a criticism of TDD than of mocks, though).
I appreciate your comment and your experience. And I agree: integration tests tend to be time consuming to run. At work we actually had similar issues (and many of our integration tests need to exercise cloud APIs) before a rewrite that made the time manageable.
I just wanted to pick up one of your points:
> I've gone into the details I end up with interface classes
My reply to this is that functional programming languages tend to manage entirely without, just by doing “dependency injection” via higher-order functions (in fact, outside Java I’ve never [needed to] use a DI framework). This (often? always? I honestly don’t know!) makes mocking possible without impacting the overall architecture. And like you, I practice strict TDD for all my current own hobby projects, and I manage without interfaces. But I don’t want to make a claim of generality here: it’s entirely possible that this approach doesn’t work everywhere, or even in the majority of cases.
I just wanted to pick up one of your points:
> I've gone into the details I end up with interface classes
My reply to this is that functional programming languages tend to manage entirely without, just by doing “dependency injection” via higher-order functions (in fact, outside Java I’ve never [needed to] use a DI framework). This (often? always? I honestly don’t know!) makes mocking possible without impacting the overall architecture. And like you, I practice strict TDD for all my current own hobby projects, and I manage without interfaces. But I don’t want to make a claim of generality here: it’s entirely possible that this approach doesn’t work everywhere, or even in the majority of cases.
On the contrary, using interfaces to make testable code is precisely the damage.
You should be writing pure functions which are easily testable, and using integration code to wrap the pure and testable parts.
Interfaces are typically a hack to get around the challenge of writing well designed, pure, and testable code.
You should be writing pure functions which are easily testable, and using integration code to wrap the pure and testable parts.
Interfaces are typically a hack to get around the challenge of writing well designed, pure, and testable code.
> You should be writing pure functions which are easily testable, and using integration code to wrap the pure and testable parts.
This often ends up with interfaces...
This often ends up with interfaces...
If you write a pure function, an interface is meaningless.
The interface comes in this part of your comment:
> and using integration code to wrap the pure and testable parts.
> and using integration code to wrap the pure and testable parts.
I think you and I are talking about different things when we say "integration test".
A common pattern to get around this situation is to use mocking frameworks to fake those calls without using interfaces. It’s in the air whether this is better or worse in the general lifespan of a piece of code; but, there are alternatives.
How does that work in a static language? In most (all?) static languages, you'd need an interface or virtual method in order to on-the-fly generate a class that implements the mocked functionality.
Not entirely sure what you meant, but in Java/C# you can easily mock out a concrete class with a mock library, like Mockito. No interface required.
There are plenty of mocking frameworks for C# (Moq, FakeItEasy, etc), but for all but the most trivial classes, you generally need to use an interface or mark methods as virtual.
It's one of the reasons I tend to minimise my use of mocks as much as possible.
It's one of the reasons I tend to minimise my use of mocks as much as possible.
It's the same with Java where it's very hard/not possible to mock final methods. It's just that methods in Java are virtual by default so it's less "in the way" when you need to mock something.
What do you use instead of mocks when testing?
Real instances of the dependant class.
A "unit test purist" would likely shun this, but I take more of a realist approach; in many real-world cases, mock-heavy tests make for brittle tests that don't give confidence of correctness.
I tend to focus on integration tests, as I find these provide the most value while still executing very quickly (depending on the stack, obv).
That doesn't mean I don't use unit tests, or that I never use mocks - I just use mocks very sparingly.
A "unit test purist" would likely shun this, but I take more of a realist approach; in many real-world cases, mock-heavy tests make for brittle tests that don't give confidence of correctness.
I tend to focus on integration tests, as I find these provide the most value while still executing very quickly (depending on the stack, obv).
That doesn't mean I don't use unit tests, or that I never use mocks - I just use mocks very sparingly.
I'm so glad to see someone write this. I keep seeing all these horrible code bases that are supposed to be easily testable but all I see is a horrible design that is impossible to understand - all because Mocking is supposed to be a good idea.
I did this, and then switched back to just running the code on a test database. I found I was writing two sets of tests: one that tested the code to see if it injected the right parameters into the SQL, and another to see if the SQL worked. It just got easier to test them both together.
This can become a challenge when you want to scale a dev team. You either have people conflicting in the DB, or each dev needs to set up a local DB and deal with DB migrations. Containerized DBs help with this to some extent, but it's still a maintenance headache.
There's also a tradeoff around testing error states, but that can be a wash. Some errors are easier to create with a mock, some are easier to create with the actual driver.
There's also a tradeoff around testing error states, but that can be a wash. Some errors are easier to create with a mock, some are easier to create with the actual driver.
There are tradeoffs to this approach. When the tests break it becomes a lot harder to triangulate the origin of the problems.
I don’t buy this argument.
Once you know there’s a bug, you will definitely want to write more tests, in order to find the cause. But always writing a lot of tests, which are only usable in case you find a bug, seems like wasted effort to me.
If you need to triangulate the origin, do that when it’s necessary (i.e. when your broad test fails) and not all the time.
Once you know there’s a bug, you will definitely want to write more tests, in order to find the cause. But always writing a lot of tests, which are only usable in case you find a bug, seems like wasted effort to me.
If you need to triangulate the origin, do that when it’s necessary (i.e. when your broad test fails) and not all the time.
I prefer to just runs tests using a real database too. They might not complete as fast as mocked out ones, but they give me much more confidence of correctness, and still complete in milliseconds.
There are test frameworks that obviate the need to do this.
Not if you use something like c/c++. There are test frameworks but there is no way from preventing testing code from leeching into functional code.
But yes - Java/python, etc. you shouldn't need to do this.
But yes - Java/python, etc. you shouldn't need to do this.
What’s cheaper to own though - a whole test library / framework (assuming you don’t use any other feature), or a simple interface?
I’m generally hesitant to add test libraries if there’s an easy design choice alternative that keeps the code simpler.
I’m generally hesitant to add test libraries if there’s an easy design choice alternative that keeps the code simpler.
Adding interfaces whose only purpose is unit tests is not keeping the code simpler. Using a test library that automatically generates mocks for unit testing is what keeps the code simpler.
> Adding interfaces whose only purpose is unit tests is not keeping the code simpler. Using a test library that automatically generates mocks for unit testing is what keeps the code simpler.
I've yet to find a library for C++ that generates mocks without requiring interfaces, but I'll admit I'm not an expert on it. Can you recommend any?
As an example, Google Mock/Test will require interfaces in a C++ code base.
Of course, we may be disagreeing on what is meant by the term "interface".
I've yet to find a library for C++ that generates mocks without requiring interfaces, but I'll admit I'm not an expert on it. Can you recommend any?
As an example, Google Mock/Test will require interfaces in a C++ code base.
Of course, we may be disagreeing on what is meant by the term "interface".
>> automatically generates mocks for unit testing is what keeps the code simpler
You're possibly confusing easy with simple but adding a versioned library of some thousands of lines of code to a project is in no objective way simpler than adding a small test double which you wrote.
You're possibly confusing easy with simple but adding a versioned library of some thousands of lines of code to a project is in no objective way simpler than adding a small test double which you wrote.
Yes, it is, in the only objective way that people actually care about: the size and simplicity of the source code.
(What people don't care about is the size of libraries that are scoped for the test context only, and don't even impact artifact size at all.)
Also, a Mockito mock is simpler in usage than a hand-written and normally instantiated test double.
(What people don't care about is the size of libraries that are scoped for the test context only, and don't even impact artifact size at all.)
Also, a Mockito mock is simpler in usage than a hand-written and normally instantiated test double.
Ripping off the classic talk by Rich Hickey:
A versioned external dependency, externally versioned == opportunity for future conflict. Remember the java logging library wars? Today it's trivial to encounter conflicting spring dependencies on your classpath
A complex (but convenient) reflection based implementation, the object has non-obvious behaviour with a clever implementation
An entire api surface distinct from the components of your software system
Tens of thousands of lines of opportunity for bugs, security risks or just plain old $$ cost to hide in
Once you're able to see the world in complexity, it opens up a whole new dimension of cost & quality for your code.
All this said, in practice complexity is just a consideration. Often the complexity is worth paying. I don't want to risk the impression that i'm saying never use mockito. If my argument is anything then it's consider the impact of complexity.
Simple - composed of a single element; not compound
Complex - consisting of many different and connected parts
Easy - achieved without great effort; presenting few difficulties.
This is not as easy but is simple. It is explicit, fundamental Java. public void allocationOnlyPossibleWhenOpen() {
LocalDateTime expected = LocalDateTime.now();
TimeAllocator sut = new TimeAllocator(new Clock() {
public LocalDateTime getTime() {
return expected;
}
});
sut.setOpen(false);
assertThat(sut.allocate()).isNull();
sut.setOpen(true);
assertThat(sut.allocate()).isEqualTo(expected);
}
This is easier but more complex. It depends on a multi-thousand line library with its own API. public void allocationOnlyPossibleWhenOpen() {
LocalDateTime expected = LocalDateTime.now();
Clock mockClock = mock(Clock.class);
when(clock.getTime()).thenReturn(expected);
TimeAllocator sut = new TimeAllocator(mockClock);
sut.setOpen(false);
assertThat(sut.allocate()).isNull();
sut.setOpen(true);
assertThat(sut.allocate()).isEqualTo(expected);
}
The 2nd example has:A versioned external dependency, externally versioned == opportunity for future conflict. Remember the java logging library wars? Today it's trivial to encounter conflicting spring dependencies on your classpath
A complex (but convenient) reflection based implementation, the object has non-obvious behaviour with a clever implementation
An entire api surface distinct from the components of your software system
Tens of thousands of lines of opportunity for bugs, security risks or just plain old $$ cost to hide in
Once you're able to see the world in complexity, it opens up a whole new dimension of cost & quality for your code.
All this said, in practice complexity is just a consideration. Often the complexity is worth paying. I don't want to risk the impression that i'm saying never use mockito. If my argument is anything then it's consider the impact of complexity.
Using a test framework that bends the rules of the language is far from simple. One of the things I appreciate about Go is that any mortal can understand how a unit test works: you "just" plug in something that satisfies the interface. However, we are spoiled to have implicitly satisfied interfaces, which can be declared on the consumer side without the implementation even knowing.
Mockito is a single pom.xml (or build.gradle) entry.
edit: Not "Moquitto".
edit: Not "Moquitto".
Close! That's Mockito.
https://site.mockito.org/
https://site.mockito.org/
Oops. Haven't done Java programming full-time since last year, but I did set up a home MQTT broker recently...
It is - but my point is about complexity not ease.
The fact that dependency is versioned separately from your code base is one source of complexity that it adds. The fact that it has many lines of code is another complexity.
It is however often easier, even although i'm advocating very small self-written test doubles, they are still less easy than using mockito.
The fact that dependency is versioned separately from your code base is one source of complexity that it adds. The fact that it has many lines of code is another complexity.
It is however often easier, even although i'm advocating very small self-written test doubles, they are still less easy than using mockito.
It's a dependency that's only needed during testing, though—it's not shipped with production artifacts.
That argument doesn't address complexity. Tests are part of the system.
Sure pulling in a giant magical reflection heavy test framework has costs but I think it's well worth it.
Not on all platforms. In .NET you need an interface or virtual method in order to allow the mocking framework to dynamically generate a concrete subclass that mocks out that method's functionality.
I do that for this reason but only if I am able to inject the implementation into the classes under test. Very often I just resort to constrictor injection for internal code.
Testing with mocks is generally a bad idea; instead, one should just test using the same dependencies that are used in production (after testing those dependencies, if also part of the project).
This avoids having to introduce the extra interface, having to code the mock, sometimes having to change the mock or tests when changing the code and also lets you catch bugs in the dependencies that the tests on the dependency itself missed and that would otherwise show up in production.
One might have to do some work to partially replicate the production setup in testing and making it fast to start and run, but that's usually less work and gives a much better result.
This avoids having to introduce the extra interface, having to code the mock, sometimes having to change the mock or tests when changing the code and also lets you catch bugs in the dependencies that the tests on the dependency itself missed and that would otherwise show up in production.
One might have to do some work to partially replicate the production setup in testing and making it fast to start and run, but that's usually less work and gives a much better result.
That is more integration tests. Unit tests should target logic more then dependencies/integration.
You do know you can mock out concrete classes aswell. You do not need a interface.
I see a lot of this with C# too, with devs creating separate projects for "contracts" and "implementations" - yet almost every interface has 1 implementation, and will never have more.
The rationale I'm given is generally one of two:
The rationale I'm given is generally one of two:
1. "It's best practice"/"it's clean architecture" - which is basically dogmatic cargo-culting
2. To support mocking in unit tests
The later is also found with unit test projects rammed with mocks, making for brittle, unreadable tests that often don't really seem to actually test anything!Yep. People fundamentally misunderstand how to use mocks and stubs, and often end up writing tests that test little more than “the function was written the way it was currently written”.
This is the worst of all possible worlds, because a test suite should accomplish two goals: identify bugs and enable refactoring. Tests like these fundamentally can’t identify bugs because they don’t test any behavior. And worse, they prohibit refactoring because any meaningful code change will alter what functions are called and in what order they’re called. So any refactor breaks almost every test, and it’s impossible to quickly tell if something “real” was broken or if it’s just an artifact of poor testing.
This is the worst of all possible worlds, because a test suite should accomplish two goals: identify bugs and enable refactoring. Tests like these fundamentally can’t identify bugs because they don’t test any behavior. And worse, they prohibit refactoring because any meaningful code change will alter what functions are called and in what order they’re called. So any refactor breaks almost every test, and it’s impossible to quickly tell if something “real” was broken or if it’s just an artifact of poor testing.
I've seen things that you wouldn't believe. "Business Objects" that do absolutely nothing but forward function calls to "Data Access Objects"
UserController -> UserBo -> UserBoImpl -> UserDao -> UserDaoImpl.
Needless to say the implementations have never been replaced with something else. The middle layer is completely useless. UserBo, UserBoImpl and UserDao should be tossed into the garbage. Every single time you wanted to add a new SQL query you had to edit 5 files... Code navigation features of your IDE become useless and you are better off with string based search.
UserController -> UserBo -> UserBoImpl -> UserDao -> UserDaoImpl.
Needless to say the implementations have never been replaced with something else. The middle layer is completely useless. UserBo, UserBoImpl and UserDao should be tossed into the garbage. Every single time you wanted to add a new SQL query you had to edit 5 files... Code navigation features of your IDE become useless and you are better off with string based search.
I find myself doing this often and it’s annoying. Usually it’s because I want to use some other implementation for testing that never materialized, and then the interface sits there.
You're actually reinforcing the OP's point. Coding to an interface is ideal but we are reluctant to do so because it involves more boilerplate that is a pain to manage.
> I'm strongly against creating interfaces for non-library (not shared) code, that will only ever have one concrete implementation. I see it all the time in internal service code, code that will never have a second implementation. It makes code hard to navigate, harder to understand, and tedious to manage as instead of updating one file, now you'll need to update two.
I was just telling someone this yesterday. Not every damn thing needs to be an interface you implement if you're only implementing it once. Especially if you have to put "I" in front of it (as is standard in C#) and name the class the same thing, you should think if it's even necessary...
I was just telling someone this yesterday. Not every damn thing needs to be an interface you implement if you're only implementing it once. Especially if you have to put "I" in front of it (as is standard in C#) and name the class the same thing, you should think if it's even necessary...
> I'm strongly against creating interfaces for non-library (not shared) code, that will only ever have one concrete implementation.
Even if it does, for a mild behavior change or a test implementation, the simple fact that Java methods are virtual-by-default is a huge benefit here.
C#, on the other hand, is final-by-default. This makes code much more annoying to deal with unless it implements an interface and/or is thoughtfully written with these cases in mind.
Even if it does, for a mild behavior change or a test implementation, the simple fact that Java methods are virtual-by-default is a huge benefit here.
C#, on the other hand, is final-by-default. This makes code much more annoying to deal with unless it implements an interface and/or is thoughtfully written with these cases in mind.
If you know there'll only be one concrete implementation, then yeah don't bother.
What people sometimes forget though is that any implementations of this in your tests are also concrete.
Some things like a DB accessor, will use a mocked/lite implementation in testing for code that doesn't need access to the db (but still needs to be provided data) to test it's functionality.
What people sometimes forget though is that any implementations of this in your tests are also concrete.
Some things like a DB accessor, will use a mocked/lite implementation in testing for code that doesn't need access to the db (but still needs to be provided data) to test it's functionality.
I strongly agree with this, but if are using an inversion of control pattern for your business logic vs. I/O, then interfaces are often needed to prevent circular references. If using multiple compilation modules, this becomes evident because the code wouldn't even compile without the interface! When using this design, interfaces can sometimes be required more often than not.
That's the litmus test I use to determine if I need an interface: Will the code compile without it? If not, the interface is declared alongside the code that depends on it, and the implementation is in a second compilation module which depends on the first. Inversion of control.
That's the litmus test I use to determine if I need an interface: Will the code compile without it? If not, the interface is declared alongside the code that depends on it, and the implementation is in a second compilation module which depends on the first. Inversion of control.
I generally agree, but you can have the inferface and implementation in the same file.
I use the term “monomorphism”, as an antonym to polymorphism.
Yeah, not good.
And you meant update 3 files, not 2. You forgot the 1 file that uses the 1 implementation, which should have simply been nested the only place it was used.
Yeah, not good.
And you meant update 3 files, not 2. You forgot the 1 file that uses the 1 implementation, which should have simply been nested the only place it was used.
> I'm strongly against creating interfaces for non-library (not shared) code, that will only ever have one concrete implementation.
I agree with this, but the problem I see is that people have bad judgement when thinking about if something will only be implemented once or not.
Personally, I don't think the boiler plate / interface bloat is as cumbersome as others do. I'm currently working in a code base where things were decided to "never change", and now we need to change those things.
In a perfect world, I'd rather have less abstract code that never changes. But if it's a choice between poorly written highly coupled code or poorly written code that relies on interfaces, I'll take the interfaces and their bloats.
I agree with this, but the problem I see is that people have bad judgement when thinking about if something will only be implemented once or not.
Personally, I don't think the boiler plate / interface bloat is as cumbersome as others do. I'm currently working in a code base where things were decided to "never change", and now we need to change those things.
In a perfect world, I'd rather have less abstract code that never changes. But if it's a choice between poorly written highly coupled code or poorly written code that relies on interfaces, I'll take the interfaces and their bloats.
Ironically, I see this in golang all the time. Interfaces are defined with a single implementation in the code, and then the interface is used to generate mocks.
Interfaces in that case can be useful for unit tests since they enable mocking.
You can do that with classes -- as long the dependents don't instantiate their dependencies directly.
However, I personally like interfaces (well, traits, I'm doing scala), because it makes it much easier for readers to understand what bits are supposed to be part of the interface and which parts are supposed to be an internal implementation detail. Without interfaces it's really easy to accidentally change e.g. a return type into a more concrete one which accidentally leaks impl. details and suddenly other bits of code will come to rely on those impl. details. I work on rather large/complicated systems and I've seen this gradual spaghettification time and time again.
Disclosure: I'm usually the person who gets called in to fix the mess -- so that might bias my perceptions here.
However, I personally like interfaces (well, traits, I'm doing scala), because it makes it much easier for readers to understand what bits are supposed to be part of the interface and which parts are supposed to be an internal implementation detail. Without interfaces it's really easy to accidentally change e.g. a return type into a more concrete one which accidentally leaks impl. details and suddenly other bits of code will come to rely on those impl. details. I work on rather large/complicated systems and I've seen this gradual spaghettification time and time again.
Disclosure: I'm usually the person who gets called in to fix the mess -- so that might bias my perceptions here.
With Java you can mock concrete classes using mock libraries which can change a class to a mocked class at runtime.
The Spring "Framework" essentially demands this, though.
Not true, and where the Spring Framework <strong>allows</strong> to use interfaces only (i.e. without having to write an implementing class) it's for neat reasons, e.g. autogenerating Spring Repositories.
I think the GP is referring to the use of interfaces with only one implementing class, rather than interfaces with no implementing class. i.e. you end up creating a UserProvider and UserProviderImpl for every service class you write.
OP is probably referring to the JDK Proxy support which is used by Spring and requires classes to implement at least one interface.
OP is probably referring to the JDK Proxy support which is used by Spring and requires classes to implement at least one interface.
Kotlin is basically Java without the boilerplate. Some of the code snippets on the page, translated into Kotlin, would be:
data class User(val username: String)
data class Config(val userTable: DynamoTable, val userId: String)
typealias ItemToUserConverter = (Item) -> User
class DynamoDbUserProvider(
val configProvider: ConfigProvider,
val userTableProvider: UserTableProvider,
val dynamoReader: DynamoItemReader,
val itemToUserConverter: ItemToUserConverter) {
fun execute(id: String) = itemToUserConverter(dynamoReader.execute(userTableProvider.execute(config), id))
}
Plus it comes with great debugging/refactoring/IDE support, async/await, type inference, and good Java integration.I've heard Kotlin described as what Java would be today if it didn't have to maintain backwards compatibility, and I think that's the perfect way to think about it. But it still compiles down to the same bytecode, so there's near perfect interoperability with any other version of Java you like.
The problem is it's not though.
Kotlin isn't just more concise Java. It sets out to solve a whole set of other problems such as removing use of null pointers, enforcing immutability everywhere, etc. This has a dramatic influence on design philosophy. I'm sure if that's how you would have programmed java then its great. But if you actually just program Java normally (mutable state, null is acceptable, etc) then something like Groovy is a much more natural fit.
Kotlin isn't just more concise Java. It sets out to solve a whole set of other problems such as removing use of null pointers, enforcing immutability everywhere, etc. This has a dramatic influence on design philosophy. I'm sure if that's how you would have programmed java then its great. But if you actually just program Java normally (mutable state, null is acceptable, etc) then something like Groovy is a much more natural fit.
In fairness, you can just put ? on the end of every type and they will all be nullable like in Java. It can all be mutable state just by writing var instead of val. No big deal. The compiler will yell at you if you use a nullable pointer in a place that needs non-null but you can cast it away using !!
But I write Kotlin all the time and nullness checking is one of its best features. I wouldn't want to go back.
But I write Kotlin all the time and nullness checking is one of its best features. I wouldn't want to go back.
And, Scala is Kotlin without the boilerplate. Scala is the most expressive and powerful language on the JVM, hands down. Kotlin is just a poor imitation. Of course, Java is way worse than either, but unless you are targeting Android, do yourself a favor and use Scala. Closure is okay, I guess, but I find dynamic typing and the dogma of Clojure counterproductive.
As a polyglot dev with experience in JS, Python, Scala, Clojure, Java to name a few, Clojure is hands down in another league compared to all the rest. My experience is with Clojure is my productivity is a magnitude higher next to others. The REPL and instant feedback alone propel develoment velocity and developer satisfaction so far out there there is just no contest.
Scala has a REPL and instant feedback. Clojure is fine and expressive for sure, I just find that static typing is superior to dynamic typing. If you can write perfect code, maybe it isn't, but I personally have a level of peace of mind with Scala that I never gotten with Clojure. I have programmed with both professionally, if that matters, btw. Of course, people are different, and theoretically speaking, Clojure should have as much power as Scala.
Kotlin takes some inspiration from Scala. Not much, though, when you get down to it. The most immediately notable things are default constructors and companion objects.
In actual use, I find the two to be a night and day difference. Scala is actively, desperately straining against the confines of the JVM. It's managed to implement some semblance of every major feature from Haskell on top of the JVM's object-oriented core, but it often has to resort to clever hacks in order to do so, and its language features are a rat's nest of edge and corner cases.
For example, it takes Java's already confusing type system where most, but not all, things are objects, where some types can be reflected on, but not all of them, and where there's even a special annotation to help programmers deal with legal code that the compiler is somehow unable to type check, and it tries to render it more manageable (I guess?) by adding even more concepts to the mix: Class tags, type tags, and weak type tags. The end result is. . . well, if you really, absolutely must higher-kinded types and also the JVM, I guess that's as good as you're going to get.
Personally, I like Scala. It's my main language right now. I want an ML-style language, but am trapped on the JVM, so it suits me well enough. But if others want to observe that, no matter how much you might like mustard, it's still not a tasty thing to put on pancakes, well, I can't in good faith say that they're wrong.
Kotlin takes completely the opposite approach: It accepts that Java's decision to use an unholy blend of type reification and type erasure can't be unmade, and then focuses its effort on unifying the type system. Which is the kind of conservative and pragmatic approach that arguably represents the soul of Java. If you're looking for a revision and not an outright revolution, Kotlin is more likely to be your jam.
In actual use, I find the two to be a night and day difference. Scala is actively, desperately straining against the confines of the JVM. It's managed to implement some semblance of every major feature from Haskell on top of the JVM's object-oriented core, but it often has to resort to clever hacks in order to do so, and its language features are a rat's nest of edge and corner cases.
For example, it takes Java's already confusing type system where most, but not all, things are objects, where some types can be reflected on, but not all of them, and where there's even a special annotation to help programmers deal with legal code that the compiler is somehow unable to type check, and it tries to render it more manageable (I guess?) by adding even more concepts to the mix: Class tags, type tags, and weak type tags. The end result is. . . well, if you really, absolutely must higher-kinded types and also the JVM, I guess that's as good as you're going to get.
Personally, I like Scala. It's my main language right now. I want an ML-style language, but am trapped on the JVM, so it suits me well enough. But if others want to observe that, no matter how much you might like mustard, it's still not a tasty thing to put on pancakes, well, I can't in good faith say that they're wrong.
Kotlin takes completely the opposite approach: It accepts that Java's decision to use an unholy blend of type reification and type erasure can't be unmade, and then focuses its effort on unifying the type system. Which is the kind of conservative and pragmatic approach that arguably represents the soul of Java. If you're looking for a revision and not an outright revolution, Kotlin is more likely to be your jam.
> do yourself a favor and use Scala.
No thanks. Scala had a shot these past ten years but it failed and the world has moved on.
No thanks. Scala had a shot these past ten years but it failed and the world has moved on.
The 6,000 Scala jobs on Indeed would disagree.
> Scala is the most expressive and powerful language on the JVM, hands down.
So I'd agree with that statement...
> do yourself a favor and use Scala
...but not that one.
I've found that Scala gets too expressive, much the same way that writing production code in Common Lisp or Haskell can be slower than just writing it in Python. It offers incredibly powerful abstraction capabilities that can often dramatically reduce the amount of code you write; but at some point you end up spending more time hunting for the perfect abstraction than actually writing the code. The ideal programming language (for productivity at least, not necessarily for fun or erudition) is one that melts into the background when you program so that you can focus on the problem domain rather than the program. That's the main appeal of languages like Go, Java, and Python: they offer just enough power to write the program, but not so much that you can write the program in a dramatically better way and end up tempted to try and find that way. Kotlin embraces the "it's just a better Java" approach: it's a minor re-skinning of the concepts in Java, and to the extent that it introduces new language features (like closures, lambdas, properties, data classes, anonymous objects, type inference, and immutability), they're all concepts that skilled Java programmers would be immediately familiar with but would otherwise have to write lots of boilerplate for.
The Java interop story is also better with Kotlin, because Scala introduces abstractions for which there are no obvious Java equivalents, and hence there's an impedance mismatch when you try to envision how your Java libraries might fit into your Scala program. Kotlin's designers took care to make sure that there's an easy, intuitive mapping between Kotlin concepts and Java ones, usually one that had already been enshrined in existing Java conventions (eg. JavaBeans work nicely with properties, functional objects with lambdas), so most Java libraries just work in an obvious way. Basically they intentionally made it dumber so you don't have to think about as much, which is a win for everyday programming.
So I'd agree with that statement...
> do yourself a favor and use Scala
...but not that one.
I've found that Scala gets too expressive, much the same way that writing production code in Common Lisp or Haskell can be slower than just writing it in Python. It offers incredibly powerful abstraction capabilities that can often dramatically reduce the amount of code you write; but at some point you end up spending more time hunting for the perfect abstraction than actually writing the code. The ideal programming language (for productivity at least, not necessarily for fun or erudition) is one that melts into the background when you program so that you can focus on the problem domain rather than the program. That's the main appeal of languages like Go, Java, and Python: they offer just enough power to write the program, but not so much that you can write the program in a dramatically better way and end up tempted to try and find that way. Kotlin embraces the "it's just a better Java" approach: it's a minor re-skinning of the concepts in Java, and to the extent that it introduces new language features (like closures, lambdas, properties, data classes, anonymous objects, type inference, and immutability), they're all concepts that skilled Java programmers would be immediately familiar with but would otherwise have to write lots of boilerplate for.
The Java interop story is also better with Kotlin, because Scala introduces abstractions for which there are no obvious Java equivalents, and hence there's an impedance mismatch when you try to envision how your Java libraries might fit into your Scala program. Kotlin's designers took care to make sure that there's an easy, intuitive mapping between Kotlin concepts and Java ones, usually one that had already been enshrined in existing Java conventions (eg. JavaBeans work nicely with properties, functional objects with lambdas), so most Java libraries just work in an obvious way. Basically they intentionally made it dumber so you don't have to think about as much, which is a win for everyday programming.
I guess I'm not an everyday programmer then? I want the machine to work for me, not the other way around. I find it absurd when I find programmers actually arguing for less power and less abstraction, it's like arguing for less free speech or less freedom: contrary to your own interests.
I thought the same way when I got out of college, but changed my mind after a decade or so in the industry. What changed could be neatly summed up by this post:
https://www.kalzumeus.com/2011/10/28/dont-call-yourself-a-pr...
Basically, I used to program for the intellectual challenge and the thrill of programming itself. If that's your goal, by all means use Scala or Haskell or Lisp. But at some point that faded, and now I program because somebody has a problem that they will pay me to solve or (for various startup ideas) because I want to understand the market or some data source that has the potential to be worth a lot to a lot of people. For these, my focus is on the problem - the programming is a means to an end for solving the problem, and so anything that lets me spend less brainpower on the programming and more brainpower on the problem domain is a net win.
https://www.kalzumeus.com/2011/10/28/dont-call-yourself-a-pr...
Basically, I used to program for the intellectual challenge and the thrill of programming itself. If that's your goal, by all means use Scala or Haskell or Lisp. But at some point that faded, and now I program because somebody has a problem that they will pay me to solve or (for various startup ideas) because I want to understand the market or some data source that has the potential to be worth a lot to a lot of people. For these, my focus is on the problem - the programming is a means to an end for solving the problem, and so anything that lets me spend less brainpower on the programming and more brainpower on the problem domain is a net win.
You can program in Scala just like Java, with both hands tied behind your back if you please. If you can’t restrain yourself and not use free monads or whatever, that’s on you, not the language. I’ve worked on enterprise Scala code bases for a number of years, and no single person on the team would ever switch to Java. In fact, while many of them started as Java devs, many have sworn to never take another Java job in their life.
Compared to the currently fashionable Go, Java has quite little boilerplate. Java 8+ has a number of quite powerful APIs in the standard library, some limited type inference, reasonable interfaces that allow default methods. The compiler has a well-defined custom processing interface (@annotations) which allows for powerful boilerplate-reducing things, from Lombok to Spring to JUnit.
80% boilerplate is a large exaggeration.
80% boilerplate is a large exaggeration.
I suspect people see a lot of enterprise-y boilerplate (IAbstractFactoryBeans, for example), absurd inheritance hierarchies, and other things like getters and setters--they think these things are "part of writing Java" in a way that isn't "part of writing Go". In some sense, they're right--Java has more of a boilerplate culture even though it technically requires less boilerplate than Go. You can break from that culture (and as I understand it, Java is moving away from that boilerplate culture) to a certain extent, but you're still likely to have to use libraries that encourage it (by extending a ridiculous inheritance hierarchy, for example) and you'll probably end up working on teams that require a certain amount of boilerplate (e.g., mandates like "getters and setters for everything!").
Since Go doesn't even have inheritance or getters and setters, they may have a point (though getters and setters are pretty easy to do I guess if you want them, it's not part of the culture).
Culture is really important in programming languages and I'm not sure it is something you can ever separate from the language itself, as for example the standard library dictates in large part the structure of your own code.
I do agree though that there is no great reason Java has to be more verbose or more confusing than say Go.
Culture is really important in programming languages and I'm not sure it is something you can ever separate from the language itself, as for example the standard library dictates in large part the structure of your own code.
I do agree though that there is no great reason Java has to be more verbose or more confusing than say Go.
> Since Go doesn't even have inheritance or getters and setters
Does Go have objects with functions? If so, it has getters and setters, Go programmers just choose not to use them. I could absolutly write Python code like this:
Java is a perfectly fine language. It's Java programmers that are awful for sticking to these unnecessary paradigms that necessitate ridiculous amounts of boilerplate.
Does Go have objects with functions? If so, it has getters and setters, Go programmers just choose not to use them. I could absolutly write Python code like this:
class MyObject(object):
def set_foo(this, foo):
this.foo = foo
def get_foo(this):
return this.foo
The thing is...Java doesn't have getters and setters. There's nothing about the language that requires them. This is perfectly legal Java code: class MyObject {
public int foo;
}
Look at it! It's a public member! You can read/write to it without a getter/setter! You know, something every language has, but for some reason, it's considered verboten in Java.Java is a perfectly fine language. It's Java programmers that are awful for sticking to these unnecessary paradigms that necessitate ridiculous amounts of boilerplate.
The funny thing is that if you want to create an immutable type in Java - no problem, just create a constructor and mark fields as final[1].
In Go - you can't do it like that. You need those getters, preferably an interface (because exported struct allows anybody to create a zero-value) and a factory function.
[1] - assuming their types are also immutable
In Go - you can't do it like that. You need those getters, preferably an interface (because exported struct allows anybody to create a zero-value) and a factory function.
[1] - assuming their types are also immutable
Immutability is neat, but you can get a long way by passing by copy and/or documenting whether a value shouldn't be mutated. I would like to see immutability added to Go, but it would break my heart if it was inspired by Java (i.e., final) instead of Rust's notion of immutability.
> for some reason, it's considered verboten in Java.
It's not just "for some reason". There are plenty of reasons why getters are a superior approach to naked fields, and these have been documented to death for the past twenty years.
If your field is not final, you should hide it behind a getter/setter.
It's not just "for some reason". There are plenty of reasons why getters are a superior approach to naked fields, and these have been documented to death for the past twenty years.
If your field is not final, you should hide it behind a getter/setter.
I've read a lot of documentation, and I've never been convinced. Maybe it's because I've just never been involved in writing a large Java project, and so never seen the advantages?
Many other languages do just fine without them. What makes Java so different?
Many other languages do just fine without them. What makes Java so different?
Two things:
1. Many other languages let you redefine field assignment syntax to be a function call, which is basically what a 'property' is in Kotlin. Java doesn't.
2. The Java ecosystem distributes software in binary bytecode form and cares about binary/API compatibility.
The combination of these two mean that even if you don't want to run any code on field assignment today, if there's any chance you might want it tomorrow you have to plan for it now by using functions instead of naked fields. Changing the latter to the former means updating all the use sites.
The big win of language integrated properties is you can go from (in Kotlin):
In Java, if you want to do that, you'd have needed to write a getVeryLongText() method from the start, as otherwise you'd have nowhere to add the extra code.
1. Many other languages let you redefine field assignment syntax to be a function call, which is basically what a 'property' is in Kotlin. Java doesn't.
2. The Java ecosystem distributes software in binary bytecode form and cares about binary/API compatibility.
The combination of these two mean that even if you don't want to run any code on field assignment today, if there's any chance you might want it tomorrow you have to plan for it now by using functions instead of naked fields. Changing the latter to the former means updating all the use sites.
The big win of language integrated properties is you can go from (in Kotlin):
class Thing(val veryLongText: String)
to class Thing(text: String) {
val veryLongText by lazy { text.toLowerCase() }
}
without any of the places that use Thing.veryLongText noticing the difference. The first code just stores the string as an ordinary field. In the second, we've decided the text should actually be lower case and that lower-casing should be done on demand then memoized for efficiency.In Java, if you want to do that, you'd have needed to write a getVeryLongText() method from the start, as otherwise you'd have nowhere to add the extra code.
Other languages (e.g. Python, C#, Kotlin, even old-school Visual Basic) have actual language support for getters/setters, usually called properties.
What makes Java different is that they have properties as a concept (in the Java beans specification) but not as an actual language feature, so you have to implement them manually yourself for every field and every class. (Or let the IDE generate the code, but all that boilerplate is code that wouldn't need to exist in other languages.)
What makes Java different is that they have properties as a concept (in the Java beans specification) but not as an actual language feature, so you have to implement them manually yourself for every field and every class. (Or let the IDE generate the code, but all that boilerplate is code that wouldn't need to exist in other languages.)
Getters make sense for mutable objects.
In a lot of cases, immutable objects are superior: simpler to reason about, have less code, and cost as much as mutable objects.
In a lot of cases, immutable objects are superior: simpler to reason about, have less code, and cost as much as mutable objects.
[deleted]
I agree with your point, but the snark here is unwarranted (pretty sure the parent isn't even contesting your point) and very likely to derail the otherwise interesting conversation (IMHO).
Re-reading the other comment and my response, yeah, you're probably right.
Java programmers are a source of frustration for me. I don't write Java professionally, but I do application security which involves reading a lot of Java code. And because the developers are sticking so strictly to the typical Java paradigms, the code is very difficult to read. Testing things locally is a nightmare, since stack traces are a ridiculous stacks of .invoke, as if Java programmers are afraid of directly calling functions.
I let my frustration get the better of me and I apologize.
Java programmers are a source of frustration for me. I don't write Java professionally, but I do application security which involves reading a lot of Java code. And because the developers are sticking so strictly to the typical Java paradigms, the code is very difficult to read. Testing things locally is a nightmare, since stack traces are a ridiculous stacks of .invoke, as if Java programmers are afraid of directly calling functions.
I let my frustration get the better of me and I apologize.
In my opinion, if Go does one thing exactly right, it's the "OOP". That is, interfaces and explicit receiver arguments in functions. Interfaces are extendable, classes are not, because there are no classes per se, let alone abstract classes.
Maybe conforming to an interface without explicitly declaring it (like you declare an instance of a typeclass in Haskell) may look like a controversial decision. I still think it does more good by removing boilerplate than bad by accepting an object as conforming to an interface in a rare case when you did not mean it.
Maybe conforming to an interface without explicitly declaring it (like you declare an instance of a typeclass in Haskell) may look like a controversial decision. I still think it does more good by removing boilerplate than bad by accepting an object as conforming to an interface in a rare case when you did not mean it.
I agree with all of this, but Go also has a pretty great standard library, tooling, and deployment story that you're overlooking. Notably, Go's tools are fast (no need to run a daemon because CLIs take too long to start up), there's no imperative DSL for its build system, there's a builtin/standard test runner, there's a standard formatter, documentation is simple and comes out of the box (no special javadoc syntax, just comments above functions; no need to write a CI job to build and publish docs nor a webserver to host them), profiling and benchmarking are builtin.
That said, the thing I like about Java is that its JIT compiler and bytecode system allow for efficient metaprogramming; Go's runtime package works, but it's not fast at all. These use cases are few and far between, but it would be nice if Go had a good web framework story (e.g. rails, django, spring, etc).
> Maybe conforming to an interface without explicitly declaring it (like you declare an instance of a typeclass in Haskell) may look like a controversial decision. I still think it does more good by removing boilerplate than bad by accepting an object as conforming to an interface in a rare case when you did not mean it.
Yeah, I hear this objection pretty frequently, but I've literally never heard of a single instance of this resulting in a real production bug. If it's a rare case, it's vanishingly rare, while it's no exaggeration to say that virtually every project benefits from the advantages.
That said, the thing I like about Java is that its JIT compiler and bytecode system allow for efficient metaprogramming; Go's runtime package works, but it's not fast at all. These use cases are few and far between, but it would be nice if Go had a good web framework story (e.g. rails, django, spring, etc).
> Maybe conforming to an interface without explicitly declaring it (like you declare an instance of a typeclass in Haskell) may look like a controversial decision. I still think it does more good by removing boilerplate than bad by accepting an object as conforming to an interface in a rare case when you did not mean it.
Yeah, I hear this objection pretty frequently, but I've literally never heard of a single instance of this resulting in a real production bug. If it's a rare case, it's vanishingly rare, while it's no exaggeration to say that virtually every project benefits from the advantages.
Go has a brilliant ecosystem, even if it has slight problems still (package vendoring, etc).
The language may be lacking here and there, but the tooling around it is good, and the runtime of it is great (sub-ms garbage collection, etc).
The language may be lacking here and there, but the tooling around it is good, and the runtime of it is great (sub-ms garbage collection, etc).
> I do agree though that there is no great reason Java has to be more verbose or more confusing than say Go.
Not a Go developer, but two things off the top of my head:
1. Do not need to explicitly declare interfaces a struct conforms to.
2. Do not need to declare types of variables inferred on initialization.
3. Syntax for slice, map and struct literals.
I'm sure there's more...
Not a Go developer, but two things off the top of my head:
1. Do not need to explicitly declare interfaces a struct conforms to.
2. Do not need to declare types of variables inferred on initialization.
3. Syntax for slice, map and struct literals.
I'm sure there's more...
I'm generally of the opinion that Go is less verbose than Java as well, but I'm of the impression that a lot has changed in Java since I last used it. It now has lambdas and other niceties, for example. Go has error boilerplate and lacks generics. Personally, I think Go's boilerplate is less of a big deal in general (Java's generics, lambdas, and exception handling come with their own issues which are often more significant than Go's boilerplate). Mostly I just don't think a debate about which language has less boilerplate will be very enlightening.
Personally I think Java is more verbose because of culture and one big feature go does not have - inheritance. Indeed some regret has been expressed by the Java authors on inheritance.
The things you mention don’t bother me as much as pointless ceremony like getters, factories, and insane object hierarchies, all of which are really optional. I quite like Go and use it a lot, but it is not IMO radically different from Java in syntax, except perhaps in what it leaves out (inheritance, exceptions, explicit declarations), and its culture of radical simplicity.
The things you mention don’t bother me as much as pointless ceremony like getters, factories, and insane object hierarchies, all of which are really optional. I quite like Go and use it a lot, but it is not IMO radically different from Java in syntax, except perhaps in what it leaves out (inheritance, exceptions, explicit declarations), and its culture of radical simplicity.
> Do not need to explicitly declare interfaces a struct conforms to.
Which comes with its own (sometimes dangerous) downsides.
> 2. Do not need to declare types of variables inferred on initialization.
Java has `var` for quite some time now:
> Syntax for slice, map and struct literals
I assume you mean overloading `[]` for slices and maps. This is a nice feature (C# and Kotlin already do this). But it doesn't reduce boilerplate by much in practice.
Which comes with its own (sometimes dangerous) downsides.
> 2. Do not need to declare types of variables inferred on initialization.
Java has `var` for quite some time now:
var foo = new Foo<Bar>();
is valid.> Syntax for slice, map and struct literals
I assume you mean overloading `[]` for slices and maps. This is a nice feature (C# and Kotlin already do this). But it doesn't reduce boilerplate by much in practice.
> Java has `var` for quite some time now
Well, it was only added in the latest LTS release, which was released a little over a year ago. (Or a few months before that if you used the non-LTS Java 10.)
It's about time – C# had it since 2007.
Well, it was only added in the latest LTS release, which was released a little over a year ago. (Or a few months before that if you used the non-LTS Java 10.)
It's about time – C# had it since 2007.
> Which comes with its own (sometimes dangerous) downsides.
I don't buy this. I've heard the theoretical arguments to the contrary, but I've never (in 7 years of consistent Go use) heard of anyone being bit by this in practice--certainly not to any significant consequence. On the other hand, implicit interfaces materially benefit nearly every project in the Go ecosystem. I would make this tradeoff all day every day.
I don't buy this. I've heard the theoretical arguments to the contrary, but I've never (in 7 years of consistent Go use) heard of anyone being bit by this in practice--certainly not to any significant consequence. On the other hand, implicit interfaces materially benefit nearly every project in the Go ecosystem. I would make this tradeoff all day every day.
There was an actual bug in the golang standard library precisely due to this feature, so it's far from theoretical.
Secondly, I found that in practice, it doesn't buy you much, and on the contrary, makes working with code more difficult (even with an IDE).
It's valuable information to know what interface(s) a type implements, not to mention finding all types down the hierarchy that implement a given interface (much more tedious and resource hungry with golang).
Secondly, I found that in practice, it doesn't buy you much, and on the contrary, makes working with code more difficult (even with an IDE).
It's valuable information to know what interface(s) a type implements, not to mention finding all types down the hierarchy that implement a given interface (much more tedious and resource hungry with golang).
One incident doesn't make it "far from theoretical". I concede that it's "vanishingly small", however.
> Secondly, I found that in practice, it doesn't buy you much, and on the contrary, makes working with code more difficult (even with an IDE).
I don't know about you, but I don't like having to write boilerplate to make a third party class implement a first party interface.
> It's valuable information to know what interface(s) a type implements, not to mention finding all types down the hierarchy that implement a given interface (much more tedious and resource hungry with golang).
How is that information valuable? The only thing I can think of is "I want to change the interface and consequently I need to update the implementations". In which case, just change the interface and the compiler will tell you what broke. As for "resource hungry", who cares? I'll trade 50ms of computing time per search (note that this is a generous figure) if it means developers don't need to maintain "implements" annotations and the corresponding boilerplate discussed above.
> Secondly, I found that in practice, it doesn't buy you much, and on the contrary, makes working with code more difficult (even with an IDE).
I don't know about you, but I don't like having to write boilerplate to make a third party class implement a first party interface.
> It's valuable information to know what interface(s) a type implements, not to mention finding all types down the hierarchy that implement a given interface (much more tedious and resource hungry with golang).
How is that information valuable? The only thing I can think of is "I want to change the interface and consequently I need to update the implementations". In which case, just change the interface and the compiler will tell you what broke. As for "resource hungry", who cares? I'll trade 50ms of computing time per search (note that this is a generous figure) if it means developers don't need to maintain "implements" annotations and the corresponding boilerplate discussed above.
> How is that information valuable?
It's useful when you want to know where a certain concrete type is being passed as an interface. If you have a type Foo that has functions bar() and baz(), then it becomes very tedious to see where Foo is being used as a `Barrer` (in golang style), `Bazzer` or even `BarrerBazzer` because it now automatically implement all three interfaces.
Another use case is that it becomes awkward to define "tag" interfaces (look at Rust's `Send` trait for instance). Now you're forced to define an interface with a function `isSend` or something and hope that no one else declares another interface with the same signature and passes in types that implement that. Again, more bug-prone behavior.
> I'll trade 50ms of computing time per search (note that this is a generous figure)
On any non-trivially sized project, it takes way longer than that to look up implementations of interfaces in the IDEs I've used so far. You're looking at 30s+ sometimes.
It's useful when you want to know where a certain concrete type is being passed as an interface. If you have a type Foo that has functions bar() and baz(), then it becomes very tedious to see where Foo is being used as a `Barrer` (in golang style), `Bazzer` or even `BarrerBazzer` because it now automatically implement all three interfaces.
Another use case is that it becomes awkward to define "tag" interfaces (look at Rust's `Send` trait for instance). Now you're forced to define an interface with a function `isSend` or something and hope that no one else declares another interface with the same signature and passes in types that implement that. Again, more bug-prone behavior.
> I'll trade 50ms of computing time per search (note that this is a generous figure)
On any non-trivially sized project, it takes way longer than that to look up implementations of interfaces in the IDEs I've used so far. You're looking at 30s+ sometimes.
> It's useful when you want to know where a certain concrete type is being passed as an interface. If you have a type Foo that has functions bar() and baz(), then it becomes very tedious to see where Foo is being used as a `Barrer` (in golang style), `Bazzer` or even `BarrerBazzer` because it now automatically implement all three interfaces.
That's just a generalization of the example I gave, and the same solution applies (although there are probably automated solutions). Unless there are other special cases that you have to do so frequently that the solution previously mentioned is too tedious, then I don't see what the fuss is about.
> Another use case is that it becomes awkward to define "tag" interfaces (look at Rust's `Send` trait for instance). Now you're forced to define an interface with a function `isSend` or something and hope that no one else declares another interface with the same signature and passes in types that implement that. Again, more bug-prone behavior.
I don't know how to take this comment seriously. The odds of someone implementing isSend and passing it to the wrong interface have to be astronomically low. The frequency of such bugs must be approaching zero. It's patently unreasonable to consider this to be "bug prone", moreover, if you're really, really concerned about it, randomly generate your private method name to whatever degree of entropy you prefer.
> On any non-trivially sized project, it takes way longer than that to look up implementations of interfaces in the IDEs I've used so far. You're looking at 30s+ sometimes.
File those bugs with the IDEs. Any IDE worth its salt holds the set of types in memory. Even if the set is just a list/array (as opposed to some data structure that is indexed by the interfaces they implement), it should take no time at all to filter that to the set that implements the interface, even if there are tens of thousands of types.
That's just a generalization of the example I gave, and the same solution applies (although there are probably automated solutions). Unless there are other special cases that you have to do so frequently that the solution previously mentioned is too tedious, then I don't see what the fuss is about.
> Another use case is that it becomes awkward to define "tag" interfaces (look at Rust's `Send` trait for instance). Now you're forced to define an interface with a function `isSend` or something and hope that no one else declares another interface with the same signature and passes in types that implement that. Again, more bug-prone behavior.
I don't know how to take this comment seriously. The odds of someone implementing isSend and passing it to the wrong interface have to be astronomically low. The frequency of such bugs must be approaching zero. It's patently unreasonable to consider this to be "bug prone", moreover, if you're really, really concerned about it, randomly generate your private method name to whatever degree of entropy you prefer.
> On any non-trivially sized project, it takes way longer than that to look up implementations of interfaces in the IDEs I've used so far. You're looking at 30s+ sometimes.
File those bugs with the IDEs. Any IDE worth its salt holds the set of types in memory. Even if the set is just a list/array (as opposed to some data structure that is indexed by the interfaces they implement), it should take no time at all to filter that to the set that implements the interface, even if there are tens of thousands of types.
With Lmobok, you can use `var` for local variables, and it will infer the type for you. It also can produce default constructors of various kinds.
Can't have nice syntax for object literals, maps, slices, etc, though :( I mean, I have no hope for it to be introduced to the language any time soon, even if the desire is there among the language stewards.
Can't have nice syntax for object literals, maps, slices, etc, though :( I mean, I have no hope for it to be introduced to the language any time soon, even if the desire is there among the language stewards.
> With Lmobok, you can use `var` for local variables
`var` is part of standard Java, you don't have to use Lombok.
> Can't have nice syntax for object literals
Could you elaborate on what you mean by object literals in this context?
`var` is part of standard Java, you don't have to use Lombok.
> Can't have nice syntax for object literals
Could you elaborate on what you mean by object literals in this context?
Object literals: think JavaScript.
const foo = new Bar
a = 1,
b = 2
}
With a default constructor or a builder (both provided by Lombok, maybe with other libraries, too) this level of succinctness is quite achievable in Java, too.Some shops are still not Java 10, but Lombok helps you use `var` even there.
Yes, I personally find inheritance an anti-pattern, and with immutable objects you don't need both getters or setters.
Unfortunately, inheritance is deeply ingrained into many libraries and standard classes, though it has been replaced by interfaces in most standard APIs.
Yes, the boilerplate culture is a problem, not of the language but of the ecosystem :-\
You can stay reasonably away from it in many cases with modern libraries, though.
Unfortunately, inheritance is deeply ingrained into many libraries and standard classes, though it has been replaced by interfaces in most standard APIs.
Yes, the boilerplate culture is a problem, not of the language but of the ecosystem :-\
You can stay reasonably away from it in many cases with modern libraries, though.
I write lots of Java and Go. I much prefer writing Java where I have things like "sets" and lambdas. I also find the type system to be a lot better in Java. In Go I'm often left wondering what I'm looking at and don't know unless I dive down into the source.
> I also find the type system to be a lot better in Java. In Go I'm often left wondering what I'm looking at and don't know unless I dive down into the source.
Could you please provide an example in both Go and Java? I am not quite sure that it has to do with the type system itself but your familiarity with the language and its libraries, and/or lack of basic functionality that should be in your text editor when you write Go.
Could you please provide an example in both Go and Java? I am not quite sure that it has to do with the type system itself but your familiarity with the language and its libraries, and/or lack of basic functionality that should be in your text editor when you write Go.
Go:
value := getValue()
Java:
int value = getValue()
value := getValue()
Java:
int value = getValue()
You could do:
var value int = getValue()
Alternatively, use your text editor. For me, even Emacs displays the type when my cursor is on or around value, and if you put it on or around getValue(), you get its function prototype that includes its return type.nobody does that though. And that was the simplest example.
Once you get into more complicated types things get even harder to understand.
Once you get into more complicated types things get even harder to understand.
From Java 10 this is valid Java also:
var value = getValue()
So it's almost exactly the same in both languages. It's not a problem for most IDEs, just hover your mouse over the value or use keyboard shortcut to get the type. This was never an issue for me in Java or Go.
var value = getValue()
So it's almost exactly the same in both languages. It's not a problem for most IDEs, just hover your mouse over the value or use keyboard shortcut to get the type. This was never an issue for me in Java or Go.
If you're writing a lot of boilerplate in Go, you're either doing it wrong or chose it for something wildly out of its wheelhouse (i.e., numeric computing).
If you're writing a lot of boilerplate in Java, you're either doing it wrong, chose it for something wildly out of its wheelhouse, or you choose frameworks or libraries that have either forced or afforded high boiler-plate code.
The latter is probably the major difference that is what people are feeling. On paper Go and Java are virtually the same language, but just a few differences in the spec and a bit of focus by the developer community means that Go just generally has less annotations, external XML files for this, weird build systems, and a whole bunch of other frippery.
Although I do think that it's easy to underestimate how much cleaner it turns out to be to do interfaces the way Go does rather than Java. It seems like such a small change, but it has a big effect on how much OO boilerplate you need. You don't have to guess what interfaces someone may someday need and write them all out in advance.
And, I mean, in both languages, let's not underestimate the amount of "doing it wrong". There's a lot of us whose exposure to Java is being on the receiving end of a pile of Java code that is not written with maximal skill and precision. That's not really Java's fault. (To the extent it is, it is not uniquely so; I can tell that story about a lot of languages.)
If you're writing a lot of boilerplate in Java, you're either doing it wrong, chose it for something wildly out of its wheelhouse, or you choose frameworks or libraries that have either forced or afforded high boiler-plate code.
The latter is probably the major difference that is what people are feeling. On paper Go and Java are virtually the same language, but just a few differences in the spec and a bit of focus by the developer community means that Go just generally has less annotations, external XML files for this, weird build systems, and a whole bunch of other frippery.
Although I do think that it's easy to underestimate how much cleaner it turns out to be to do interfaces the way Go does rather than Java. It seems like such a small change, but it has a big effect on how much OO boilerplate you need. You don't have to guess what interfaces someone may someday need and write them all out in advance.
And, I mean, in both languages, let's not underestimate the amount of "doing it wrong". There's a lot of us whose exposure to Java is being on the receiving end of a pile of Java code that is not written with maximal skill and precision. That's not really Java's fault. (To the extent it is, it is not uniquely so; I can tell that story about a lot of languages.)
> If you're writing a lot of boilerplate in Go, you're either doing it wrong or chose it for something wildly out of its wheelhouse
Like shoehorning a set use case into a map? Or trying to get the list of keys or values in a map without writing out 5 lines of code?
I find golang code to be much more verbose than Java. You find code littered with one off functions that amount to nothing more than map/filter/etc. operations which are 1 liners in Java.
Like shoehorning a set use case into a map? Or trying to get the list of keys or values in a map without writing out 5 lines of code?
I find golang code to be much more verbose than Java. You find code littered with one off functions that amount to nothing more than map/filter/etc. operations which are 1 liners in Java.
I find my code occasionally has those things, and that it is a minor inconvenience, not that it is littered with it. If it is littered with it, you are probably in the "doing it wrong" category. Do not try to force Go to do "functional programming". Or you're way out of scope. (I have lost count of the number of people I've counseled to not use Go if their first interest is numeric programming, for instance.)
(I am underwhelmed by the crowd that insists map/reduce/filter is so important anyhow. I've used a substantial amount of Haskell. In Haskell, map/reduce/filter is the beginning of the functional style, not the completion of it. It goes beyond just "an alternate way to write for loops", which is most of what I see non-Haskell usage doing with it. In Go... just write the for loops. It's the same thing, just spelled differently. In Haskell, it's not the same thing.)
(I am underwhelmed by the crowd that insists map/reduce/filter is so important anyhow. I've used a substantial amount of Haskell. In Haskell, map/reduce/filter is the beginning of the functional style, not the completion of it. It goes beyond just "an alternate way to write for loops", which is most of what I see non-Haskell usage doing with it. In Go... just write the for loops. It's the same thing, just spelled differently. In Haskell, it's not the same thing.)
> If it is littered with it, you are probably in the "doing it wrong" category.
How else do you suggest transforming slices from one type to another, removing entries in slices based on certain conditions, etc. without looping? This is one of the most common operations in any basic application.
How else do you suggest transforming slices from one type to another, removing entries in slices based on certain conditions, etc. without looping? This is one of the most common operations in any basic application.
Exception handling and closing resources keep to be a major pain in the ass. Worst, the lambdas do not fit well with checked exceptions and the standard library is full of them. Another major pain are closing methods which throw checked exceptions themselves. This makes wrapping this into a decent lambda painful.
I'd have preferred much lambdas just exposing checked exceptions they experience inside. I can't imagine why the compiler shouldn't be able to do this. Maybe one would have to come up with a fancy definition of functional interfaces (although I think this could be tackled with an annotation if the language doesn't provide this for lack of another keyword a la "throwsall").
I'd have preferred much lambdas just exposing checked exceptions they experience inside. I can't imagine why the compiler shouldn't be able to do this. Maybe one would have to come up with a fancy definition of functional interfaces (although I think this could be tackled with an annotation if the language doesn't provide this for lack of another keyword a la "throwsall").
Checked exceptions should just be removed entirely from Java. It's a completely failed experiment.
If you really want something like checked exceptions then (anonymous) intersection types or row polymorphism would be much better approaches than the adhoc thing Java does... but I suspect that that wouldn't well with the variance system in Java.
(Thankfully it's only the Java compiler itself which imposes checked-ness, so other languages on the JVM thankfully don't have to deal with the insanity.)
If you really want something like checked exceptions then (anonymous) intersection types or row polymorphism would be much better approaches than the adhoc thing Java does... but I suspect that that wouldn't well with the variance system in Java.
(Thankfully it's only the Java compiler itself which imposes checked-ness, so other languages on the JVM thankfully don't have to deal with the insanity.)
There is nothing failed about checked exception. It is better then finding out about runtime exceptions as they happen.
If an exception should be expected and handled, it's not an exceptional case, it's a part of the function's signature.
The lack of sum types and pattern matching notwithstanding, such a signature can be implemented reasonably in Java.
The lack of sum types and pattern matching notwithstanding, such a signature can be implemented reasonably in Java.
Yes there is. I have enough material enough on this for several blog posts, but I won't belabor the point.
One, why do you think almost all non-standard java libraries almost exclusively use exceptions derived from RuntimeException?
Two, I encourage you to look up guava's Throwables utility class and consider why it is even a thing. Just for a single example: Methods with no throws clause cannot throw exceptions, so you must wrap in a RuntimeException... but that means that outer catch clauses which match on the checked exception WILL NOT MATCH on the RTExc-wrapped-exception. This is broken as all hell.
One, why do you think almost all non-standard java libraries almost exclusively use exceptions derived from RuntimeException?
Two, I encourage you to look up guava's Throwables utility class and consider why it is even a thing. Just for a single example: Methods with no throws clause cannot throw exceptions, so you must wrap in a RuntimeException... but that means that outer catch clauses which match on the checked exception WILL NOT MATCH on the RTExc-wrapped-exception. This is broken as all hell.
I am not against checked exceptions per se. Their excessive use of them in the standard library and especially the inability of lambdas in handling them are a problem imho. Take the stream close methods: There is absolutely no use in having those throwing a checked exception because there is nothing you can do. If closing is not possible you have problems with the underlying OS. That would rather warrant an error instead of an exception.
I like checked exceptions. It's nice getting a reminder about issues I need to handle and its trivial to just get everything to pass on exceptions if that's the disposition I choose for my code base
It's not trivial if you're forced to conform to an interface which has a narrower "throws" clause than what you need. It's an issue of contravariance vs. covariance. Implementations of an interface/superclass are typically more concrete, and so they need to be able to throw more more types of exceptions... but throws clauses work in the opposite way: You're only allowed to narrow them, not expand them in subclasses/implementations-of-interfaces.
It's simply broken.
EDIT: Just to add: What happens in practice is that either
- you just give up and declare "throws Exception" on the interface (in which case: why bother? You might as well just remove checked exceptions at that point), or
- you wrap checked exceptions in RuntimeException, but now you have a new problem: Consider a method that calls methods f and g where f declares a "throws Foo", but g doesn't declare any exceptions[0], but may throw Foo's at runtime by wrapping. Now you're totally screwed, because a "catch Foo" will not match exceptions from g. The Guava people have written a whole Throwables utility class to help with this, but using that consistently requires discipline that no team can handle, and it only handles a small subset of the problem.
This is just a small sample of the problems with checked exceptions in Java.
[0] g may be used in contexts where it's not "allowed" to declare checked exceptions in a throws clause, e.g. lambdas or just higher-order functions in general.
It's simply broken.
EDIT: Just to add: What happens in practice is that either
- you just give up and declare "throws Exception" on the interface (in which case: why bother? You might as well just remove checked exceptions at that point), or
- you wrap checked exceptions in RuntimeException, but now you have a new problem: Consider a method that calls methods f and g where f declares a "throws Foo", but g doesn't declare any exceptions[0], but may throw Foo's at runtime by wrapping. Now you're totally screwed, because a "catch Foo" will not match exceptions from g. The Guava people have written a whole Throwables utility class to help with this, but using that consistently requires discipline that no team can handle, and it only handles a small subset of the problem.
This is just a small sample of the problems with checked exceptions in Java.
[0] g may be used in contexts where it's not "allowed" to declare checked exceptions in a throws clause, e.g. lambdas or just higher-order functions in general.
Idiomatic Java has much more boilerplate than idiomatic Go.
if e3 != nil {
return _, e3
}
over and over and over..Sounds like you're not aware of the changes since Java 8 (2014). This is idiomatic Java nowadays: http://sparkjava.com
Idioms change, java can be much less boilerplate today.
Java has generics
> 80% boilerplate is a large exaggeration.
If you restrict yourself fully to the standard library it's still not that largly exaggerated, even with Java 8.
> The compiler has a well-defined custom processing interface (@annotations) which allows for powerful boilerplate-reducing things, from Lombok to Spring to JUnit.
If you add Lombok + sth. like guava or apache-commons and in general choose modern libraries/frameworks it's really OK now. The only downside of Lombok is that it really maxes out what compiler(s) allow you to do which is not always without issues and it requires an IDE plugin to be installed.
If you restrict yourself fully to the standard library it's still not that largly exaggerated, even with Java 8.
> The compiler has a well-defined custom processing interface (@annotations) which allows for powerful boilerplate-reducing things, from Lombok to Spring to JUnit.
If you add Lombok + sth. like guava or apache-commons and in general choose modern libraries/frameworks it's really OK now. The only downside of Lombok is that it really maxes out what compiler(s) allow you to do which is not always without issues and it requires an IDE plugin to be installed.
Don't rattle the cage. Go is fashionable here thanks to Google's marketing.
When I used to use Java, the thing that I found remarkable was how much stuff you had to write in other languages. You had Spring, Ant, Hibernate, Ibatis, text-based "properties" files etc. It seemed that Java programmers were willing to go to great lengths to avoid writing anything in Java. But if Java was so unsuitable for such a wide range of tasks, what made us think it was a good general purpose language?
In contrast, when I started using Python, one of the things I liked was how suitable it was for all the things that Java developers typically used other languages for. Your typically Python project back then contained very little that wasn't Python, because there weren't many things that it wasn't useful for.
Things may have changed with Java in the 9-10 years since then but I think that was certainly one of the things that led to Python becoming more popular.
In contrast, when I started using Python, one of the things I liked was how suitable it was for all the things that Java developers typically used other languages for. Your typically Python project back then contained very little that wasn't Python, because there weren't many things that it wasn't useful for.
Things may have changed with Java in the 9-10 years since then but I think that was certainly one of the things that led to Python becoming more popular.
You don't actually need to do any of that. If you're writing Ant, I assume this was at least 10 years ago. Modern Spring Boot is all annotations and using an ORM is out of fashion. Maven can be a little complicated, but it's easy to work off an archetype and it's far more sophisticated than npm. Python doesn't have this problem, but it also just doesn't have these features as an option. You can certainly build a Java project with javac but we generally don't because Maven is better.
wait.. using an ORM is out of fashion now? is everyone doing direct SQL queries again?
Yes. Or use a NoSQL DB.
Or the ORM (Hibernate by default) is abstracted behind Spring Data interfaces. It's pretty easy to autowire the EntityManager if you need it.
Spot on. Thankfully it is becoming more common to Just Write Java. Java without the frameworks and other bloated crap is actually decent. The language itself has a lot of warts, but the Java ecosystem is one of the best (Python too).
What ecosystem? The JVM doesn’t require Java. Scala is pretty much categorically better in every way than Java.
Not in maintainability. It is pretty hard to follow Scala when people use the flexibility too much. You have so many options that everyone develops their own flavor of overrides/overloads and style. This makes transferring skills between jobs or even just projects much harder as the same looking code line can mean several things.
This is how I feel about the language! Modern Java works well enough to me that the frameworks are nearly never justified.
>But if Java was so unsuitable for such a wide range of tasks, what made us think it was a good general purpose language?
Other competing languages you would use today didn't exist, and C++98 was a pain in the ass. The selling point back then was Java was C++98 with all of the difficult parts stripped out, and with good tooling and IDE support. Back then people flocked to Java like some sort of oasis. Today, even modern C++ is better in most ways.
Other competing languages you would use today didn't exist, and C++98 was a pain in the ass. The selling point back then was Java was C++98 with all of the difficult parts stripped out, and with good tooling and IDE support. Back then people flocked to Java like some sort of oasis. Today, even modern C++ is better in most ways.
This is not really true any longer. Spring, thankfully, seems to be going away. Java itself is now far more feature-rich in terms of lambdas, anonymous classes, stream processing etc.
I came back to java about 4 years ago after a long time away and have found it quite pleasant, and quite different to how you describe it.
I think I skipped the 'enterprise' years, thankfully!
I came back to java about 4 years ago after a long time away and have found it quite pleasant, and quite different to how you describe it.
I think I skipped the 'enterprise' years, thankfully!
You mean, you had to edit the build file, some SQL and some configs that weren't baked into the compiled code?
[deleted]
After developing software professionally for 30 years, I've come to believe it largely comes down to tastes and fads. I've programmed professionally in Fortran, C, C++, perl, C#, Python and Scala and various SQL flavors. I've dabbled in JS and R and have no experience with Go or Rust.
You can write verbose garbage in any language and you can write clean, well factored code in any language.
The garbage I'm most familiar with is "Consultant Java" where every class has an interface, every field has a getter and setter, every method (especially the getters and setters) has Javadoc -- oh and the test coverage is 100%, thanks for asking. But you've got 450K lines of code for an app that does CRUD on 3 tables. I've seen virtually the same thing in C#, only with the added magic of the latest MS Framework. And if "boilerplate" is such a bad word, how did Ruby on Rails ever succeed. It's an environment literally built on boilerplate. And don't get me started on perl. There are perl scripts that were healthy until they kept dividing until they were cancerous "systems" throughout the enterprise. C++ pissing contests between developers intent on showing their worth by writing the most difficult code possible.
So why do have so much garbage out there?
Because programming is a human endeavor. We have tastes. We're often unwittingly subscribe to faddish beliefs. We all suffer from a host of cognitive biases that will always prevent us from seeing things clearly.
I can say with certainty that particular tools are better suited for particular jobs. At this point though, you can't pin me down as to which for which. I can say that in the past I enjoyed using C and Java and I didn't enjoy C++ and C#. I was lukewarm over Python. I currently enjoy using Scala and I think I am most productive in it. But that is more about me than the languages. Importantly though, I think they all contribute to the software industry in ways that are not always obvious.
You can write verbose garbage in any language and you can write clean, well factored code in any language.
The garbage I'm most familiar with is "Consultant Java" where every class has an interface, every field has a getter and setter, every method (especially the getters and setters) has Javadoc -- oh and the test coverage is 100%, thanks for asking. But you've got 450K lines of code for an app that does CRUD on 3 tables. I've seen virtually the same thing in C#, only with the added magic of the latest MS Framework. And if "boilerplate" is such a bad word, how did Ruby on Rails ever succeed. It's an environment literally built on boilerplate. And don't get me started on perl. There are perl scripts that were healthy until they kept dividing until they were cancerous "systems" throughout the enterprise. C++ pissing contests between developers intent on showing their worth by writing the most difficult code possible.
So why do have so much garbage out there?
Because programming is a human endeavor. We have tastes. We're often unwittingly subscribe to faddish beliefs. We all suffer from a host of cognitive biases that will always prevent us from seeing things clearly.
I can say with certainty that particular tools are better suited for particular jobs. At this point though, you can't pin me down as to which for which. I can say that in the past I enjoyed using C and Java and I didn't enjoy C++ and C#. I was lukewarm over Python. I currently enjoy using Scala and I think I am most productive in it. But that is more about me than the languages. Importantly though, I think they all contribute to the software industry in ways that are not always obvious.
> And if "boilerplate" is such a bad word, how did Ruby on Rails ever succeed. It's an environment literally built on boilerplate
This is not remotely true. "Don't Repeat Yourself" and "Convention Over Configuration" are the two core tenants of the Rails philosophy[1], both of which are intended to reduce boilerplate.
This is why you don't have to specify table names, foreign key names, controller endpoint paths, view file names, etc...
[1]https://edgeguides.rubyonrails.org/getting_started.html#what...
This is not remotely true. "Don't Repeat Yourself" and "Convention Over Configuration" are the two core tenants of the Rails philosophy[1], both of which are intended to reduce boilerplate.
This is why you don't have to specify table names, foreign key names, controller endpoint paths, view file names, etc...
[1]https://edgeguides.rubyonrails.org/getting_started.html#what...
I can think of two cases, either :
1) the code had no external review, was written by one person. In this case, the solution to boilerplate is code review.
or 2) if even with code review there is boilerplate, it means someone somewhere has an incentive to make the code as difficult to understand as possible (for example, to justify salaries of an entire team, or to justify more maintenances, etc)
1) the code had no external review, was written by one person. In this case, the solution to boilerplate is code review.
or 2) if even with code review there is boilerplate, it means someone somewhere has an incentive to make the code as difficult to understand as possible (for example, to justify salaries of an entire team, or to justify more maintenances, etc)
In particular I know one code base of consultant written Java where part of their contract was 100% Javadoc coverage and 100% test coverage, so you would see tests of getters and setters and Javadoc for getFoo(): "Gets the value of foo"
The code worked, but 80% of it added no value.
The code worked, but 80% of it added no value.
Don't blame consultants for such requirements.
I dont think this is true any more, especially with the changes in the last few years.
My beef is with Spring. Spring seems to make simple problems very complicated. Its like you have to learn a second more complicated API on top of the original. Plus when something goes wrong you get some 100 call deep stack trace. I just wish there were more pure Java applications without the Spring magic.
My beef is with Spring. Spring seems to make simple problems very complicated. Its like you have to learn a second more complicated API on top of the original. Plus when something goes wrong you get some 100 call deep stack trace. I just wish there were more pure Java applications without the Spring magic.
I really enjoy using spring boot. My problem with Spring is that it feels like I’m mostly metaprogramming with annotations.
Also I hate that I need to literally use a program to create the boilerplate for a new spring app. And I can’t run my program without special IDE settings.
Also I hate that I need to literally use a program to create the boilerplate for a new spring app. And I can’t run my program without special IDE settings.
Weird, I've never really needed to use special IDE settings for my Spring boot projects at all; I can just gradle run my project, maybe with a different profile set in the environment.
This works for me most of the time. I use a program to generate the build.gradle, and then that file builds my project through my IDE. It’s meta all the way down.
I admittedly didn’t spend a whole lot of time with Spring Boot; I mostly did Ops with projects other teams build with the occasional Dev assistance when they painted themselves into a corner. But I’m hoping you might be able to help with a question I haven’t had much of an answer to: I agree there’s a lot of “metaprogramming with annotations”, but I didn’t ever find a great way to dig into an annotation and figure out what the hell it was actually doing, or how. Do you have any pointers for pulling back the curtain?
Edit: for example, Python “annotations” are generally just functions that return wrapped functions/attributes/classes/whatever. Java annotations seem significantly more “magical” than that.
Edit: for example, Python “annotations” are generally just functions that return wrapped functions/attributes/classes/whatever. Java annotations seem significantly more “magical” than that.
Java annotations are meta-data, they don't do anything. Actual code which uses those annotations is located elsewhere. Usually you can use "Find usages" for a given annotation to find the actual code.
That's what I figured... it seemed like a lot of things didn't really take effect until something else happened at runtime. It seemed to me (as a seasoned developer with almost no Spring Boot experience) that there was a lot of mysterious magic there that was great until it didn't work, and then it turned into a debugging nightmare (e.g. stack traces that don't, at all, mention the code where the error exists).
I, personally, despite modern trends, don't like Spring Boot. It was never a big issue for me to configure required beans manually. Spring Core reference has all the necessary examples to cover it.
I guess, Spring Boot makes sense, when you're producing a lot of little web applications with microservice architecture, so copying over that manual configurations can become burden. My applications are monolith and last for many years, so it's not an issue for me to spend some time configuring everything as I need it and it's always visible and obvious what's happening.
I guess, Spring Boot makes sense, when you're producing a lot of little web applications with microservice architecture, so copying over that manual configurations can become burden. My applications are monolith and last for many years, so it's not an issue for me to spend some time configuring everything as I need it and it's always visible and obvious what's happening.
None of those things are true.
For example for a rest service, https://spring.io/guides/gs/rest-service/ has everything you need to get started. A simple pom with a few dependencies, and a main class that invokes SpringApplication.run. You can just invoke this main class from your IDE like any other java application.
For example for a rest service, https://spring.io/guides/gs/rest-service/ has everything you need to get started. A simple pom with a few dependencies, and a main class that invokes SpringApplication.run. You can just invoke this main class from your IDE like any other java application.
If, like 99% of Spring Boot use cases, you're implementing "microservices" you should look into Javalin[1]. Javalin will forever sour you to Spring Boot the first time you try it. No annotations in sight, no code generation, no special IDE support involved, no multi-page stack traces, no POM gymnastics, no magic. With Java 11 type inference the boilerplate is reduced to near parity with Kotlin.
[1] https://javalin.io/
[1] https://javalin.io/
I can identify myself as a strictly Java developer (nowadays) and my experience with Spring can be summarized as either:
> I imported that autoconfiguration library and everything works!
> Damn it, the imported autoconfiguration library does not work (or I need to change something), now I have to spend a day around Spring docs and stackoverflow to find how to get it working. Than I have to create a new class, override some random methods and inject my ugly code which feels like a hack now...
> I imported that autoconfiguration library and everything works!
> Damn it, the imported autoconfiguration library does not work (or I need to change something), now I have to spend a day around Spring docs and stackoverflow to find how to get it working. Than I have to create a new class, override some random methods and inject my ugly code which feels like a hack now...
This hits home. A lot of my work seems to be mopping up the mess that developers created by doing the former and applying the latter. Autoconfiguration is great for quick demo-days and a literal cancer for everything else.
Frankly, after some recent development on ES6 + node + express, I'm starting to question whether staying on Spring + Java is worth it.
Frankly, after some recent development on ES6 + node + express, I'm starting to question whether staying on Spring + Java is worth it.
Just read the source. You can jump to the implementation of the autoconfiguration in your IDE, and they're usually only a dozen lines or so of bean creation.
I recall a veteran software developer commenting back in 2000 that a class written in Java was less than one third the length of the same one written in C++, so it's compactness definitely fueled it's popularity.
Since then, J2EE and EJB led to a lot of boilerplate and now everyone thinks class and member names in Java have to be at least three or four words long.
Since then, J2EE and EJB led to a lot of boilerplate and now everyone thinks class and member names in Java have to be at least three or four words long.
I'm always suspicious of people who offer opinions about Java, but then mention "J2EE", a term which has been obsolete since 2006.
Yep. And JavaEE that came after has actually made things a whole lot better. Idiomatic JavaEE can easily be <10% boilerplate.
The boilerplate isn't just in writing an individual class, though. It's also in the extra classes and interfaces that you might not create in C++.
And I wonder how much of the "compactness" you describe is simply because of Java's library. Library calls are very compact; having to write the code yourself (because your ecosystem doesn't have that library) is much more verbose.
And I wonder how much of the "compactness" you describe is simply because of Java's library. Library calls are very compact; having to write the code yourself (because your ecosystem doesn't have that library) is much more verbose.
> you have to learn a second more complicated API on top of the original
... that also doesn't actually benefit you in any tangible way.
... that also doesn't actually benefit you in any tangible way.
Have a look at http://sparkjava.com/. Very simple and pure.
Have you tried Spring Boot? It reduces lots of the "complicated" stuff to declarative annotations and will provide you with very reasonable defaults for most use cases.
I dislike Spring "at the margin". I do prefer Spring Boot because it's less Spring-like than Spring.
Annotations often bring with them implicit, hard to debug, "magic" behaviour. I prefer boilerplate over annotations.
Spring seems to make simple problems very complicated.
This. My god, this.
This. My god, this.
Interestingly, it's the culture of Java that makes it verbose.
You can write concise pure Java if you don't rely on libraries, have unit tests, or do things the Java way.
It's too bad, too, because there are some things to love about Java, really. It's simple, it's super fast thanks to the jvm, and it's well supported.
You can write concise pure Java if you don't rely on libraries, have unit tests, or do things the Java way.
It's too bad, too, because there are some things to love about Java, really. It's simple, it's super fast thanks to the jvm, and it's well supported.
As a genomicist I'm half biologist, half data scientist. I think for the majority of my problems, it's the verbosity of Java that I just cannot stand. Maybe the instruction I've had in Java is flawed, but the amount of codewords to do anything has really kept me away from it.
I don't really ever run into the same problem twice. I've never been able to write anything as concise for a one-off problem as I can using Python or Perl (or even Awk) for data wrangling.
I don't really ever run into the same problem twice. I've never been able to write anything as concise for a one-off problem as I can using Python or Perl (or even Awk) for data wrangling.
Scripts are superior in term of usability for what you do. Period.
Java is for enterprise scale applications, where dozens or hundreds of developers contribute to the code base, for a decade or more.
The boilerplate, the verbosity, strong types, the dependencies on complementary tools is what makes a large code base maintainable in the long run. If cared for.
If you need to scrap a website a couple of times, convert text from one format to another. Yea just use some script. Throw away code that nobody but you will ever need to touch or even look at. It is possible to write a python, Perl, JavaScript, or even Php app at scale. It may require a bit more due diligence though. Frameworks have been introduced to help with that.
The article is written by someone who doesn't fully understand Java. No need for interface to support injection. Interfaces are commonly written by CS graduates or uninformed long time OOP developers, but composition and DI have been around for over a decade. I stopped reading when the interface was pointed out as the only way to go.
Java is for enterprise scale applications, where dozens or hundreds of developers contribute to the code base, for a decade or more.
The boilerplate, the verbosity, strong types, the dependencies on complementary tools is what makes a large code base maintainable in the long run. If cared for.
If you need to scrap a website a couple of times, convert text from one format to another. Yea just use some script. Throw away code that nobody but you will ever need to touch or even look at. It is possible to write a python, Perl, JavaScript, or even Php app at scale. It may require a bit more due diligence though. Frameworks have been introduced to help with that.
The article is written by someone who doesn't fully understand Java. No need for interface to support injection. Interfaces are commonly written by CS graduates or uninformed long time OOP developers, but composition and DI have been around for over a decade. I stopped reading when the interface was pointed out as the only way to go.
It sounds like you want to use Clojure.
> if you don't ... have unit tests
Uhhh... you should have unit tests no matter what language (or culture) you're dealing with.
Uhhh... you should have unit tests no matter what language (or culture) you're dealing with.
Depending on your application, you can go a long way with integration tests alone.
> You can write concise pure Java if you don't rely on libraries
Hum... No, you can not. Java is missing basic tools for abstracting idioms, break your code semantically, and reusing your code. Without those, you'll just write stuff again and again, hope you have a very good IDE.
> It's simple
It's simple the same way Go is simple. The language itself is small, in ways that make your code complicated. On this competition, Brainfuck is unbeatable.
> it's super fast
C, C++, Rust and Fortran are fast. Java is not. It's a second tier, at around the same speed as C# (obviously), Go, and Haskell.
Hum... No, you can not. Java is missing basic tools for abstracting idioms, break your code semantically, and reusing your code. Without those, you'll just write stuff again and again, hope you have a very good IDE.
> It's simple
It's simple the same way Go is simple. The language itself is small, in ways that make your code complicated. On this competition, Brainfuck is unbeatable.
> it's super fast
C, C++, Rust and Fortran are fast. Java is not. It's a second tier, at around the same speed as C# (obviously), Go, and Haskell.
> Java is missing basic tools for abstracting idioms
Such as?
Such as?
Any kind of flexibility on the syntax, metaprograming, reflection is so useless that it could as well not be there, high order constructions where the compiler can infer the idiom, well, really anything.
It has relatively recently gained usable high order functions, and seems to have stopped there.
It has relatively recently gained usable high order functions, and seems to have stopped there.
Could you point out specific use cases? Are you referring to lisp-like macros? Constructs like Higher Kinded Types?
I'm just trying to understand where you're coming from.
I'm just trying to understand where you're coming from.
Well, I'm saying that Java doesn't have any of those. Other languages have some concept they run away with, like macros for Lisp, monads on Haskell, named parameter enumeration and monkey patching on Python. Even PHP relies heavily on object enumeration.
All of those techniques are kind of equivalent in power, spread through a curve trading flexibility (expresivity) with organization (possibility of analysis). Java has nothing near that curve.
All of those techniques are kind of equivalent in power, spread through a curve trading flexibility (expresivity) with organization (possibility of analysis). Java has nothing near that curve.
I'm not sure what you mean by named parameter enumeration or object enumeration to be honest.
> spread through a curve trading flexibility (expresivity) with organization (possibility of analysis). Java has nothing near that curve.
Java definitely leans towards the organization part of the curve. It doesn't need a single concept to run away with, because it knows that there is no magic bullet that will solve everything. Monkey patching is dangerous, especially when you're programming large systems that end up being used in finance or ecommerce.
On a side note, if I'm not mistaken, Brian Goetz mentioned that something similar to higher kinded types was not off the table for Java sometime down the line in a recent panel.
> spread through a curve trading flexibility (expresivity) with organization (possibility of analysis). Java has nothing near that curve.
Java definitely leans towards the organization part of the curve. It doesn't need a single concept to run away with, because it knows that there is no magic bullet that will solve everything. Monkey patching is dangerous, especially when you're programming large systems that end up being used in finance or ecommerce.
On a side note, if I'm not mistaken, Brian Goetz mentioned that something similar to higher kinded types was not off the table for Java sometime down the line in a recent panel.
Do I see a case of the Blurb? I'm not sure, but it does resemble it. (By the way, you'll have a hard time finding any site written on Python that doesn't use monkey patching, e-comerce or not).
Anyway, parameter or objects enumeration are when a library uses both the field names and their values as input, people use them to define DSLs on a function call or object construction. Parameter enumeration does afford applicative semantics, but (mutable) object enumeration affords full monadic semantic, so it's easy to construct Turing complete DSLs (and I guess, why Python people avoid it).
It is really hard to explain how those concepts are useful, mostly because they only work when everything else on the language help. If you try them on Java, they will be mostly useless. C# for example has a fully generic implementation of monads, with specialized syntax that isn't very far from Haskell's, do notation. Yet, with its bad type inference and the inflexible interface hierarchy it got from copying Java, it is nowhere near as useful.
Anyway, parameter or objects enumeration are when a library uses both the field names and their values as input, people use them to define DSLs on a function call or object construction. Parameter enumeration does afford applicative semantics, but (mutable) object enumeration affords full monadic semantic, so it's easy to construct Turing complete DSLs (and I guess, why Python people avoid it).
It is really hard to explain how those concepts are useful, mostly because they only work when everything else on the language help. If you try them on Java, they will be mostly useless. C# for example has a fully generic implementation of monads, with specialized syntax that isn't very far from Haskell's, do notation. Yet, with its bad type inference and the inflexible interface hierarchy it got from copying Java, it is nowhere near as useful.
> It's simple, it's super fast
to what baseline are you comparing it to?
to what baseline are you comparing it to?
This is potentially a bad example, but the current "fastest" web frameworks as per the techempower benchmarks are all Java.
I'm not familiar with it, do you mean this one? https://www.techempower.com/benchmarks/
Rust, C, and Go appear to be on the top.
Rust, C, and Go appear to be on the top.
Sorted by full stack frameworks only: https://www.techempower.com/benchmarks/#section=data-r18&hw=...
They're not: C++ and Rust have winners for some of the tests.
I should clarify that I sorted by fullstack frameworks with ORMs. Obviously C or C++ can be made faster.
Too late to edit now though.
Too late to edit now though.
Of course you can write more performant code with lower-level languages, though there is a reason that almost no web framework/application is written in them - the additional burden of managing memory outweighs the (actually surprisingly little) performance benefit. If you look at examples on the site, in basically every other benchmark Java and sometimes Go rank the best.
It is not Java. It is how people over engineer enterprise applications.
- Annotations are overused.
- Design patterns are overused.
- Too many layers, too many abstractions.
- Using properties where a constants would be enough.
- Using concurrency when you don't need it.
Probably a cultural problem more than a technical.
- Annotations are overused.
- Design patterns are overused.
- Too many layers, too many abstractions.
- Using properties where a constants would be enough.
- Using concurrency when you don't need it.
Probably a cultural problem more than a technical.
Precisely! EJB used to be a nightmare (a lot easier now with annotations), but I would see people use them for web apps that had 10 users tops. It was insane.
I barely remember how EJB used to be, though I recall that it involved a mess of XML and interfaces. Nowadays, EJB is a built-in dependency injection system. Put @Singleton on one class, then @EJB on a field in a Servlet (or what have you).
"...they've developed a way of writing maintainable code, but the language knows nothing about it, and is not expressive enough to describe it succintly."
Good quote. I used to struggle with this feeling when I got to roughly the ten year mark professionally and finally started to feel like I knew what I was doing. But, with the help of a little Stockholm Syndrome perhaps, I've learned to embrace the boilerplate as part of my workflow and use it to an advantage. I came up with a saying "write twice, debug once", from the old carpenter's saying "measure twice, cut once". The idea is that if you're writing sort of the same thing twice, the compiler and/or PR reviewers can at least check if those two things are consistent with each other, and debugging is more straightforward instead of a long slog. You may still write the same thing wrong twice, but that's much harder to do than writing it wrong once. I think this is a hidden value of automated testing as well. I could talk for hours about whether automated testing is "worth it" depending on the project/language/team/etc., but one thing most people don't consider is the value in the mere act of writing out some logic that must be consistent with other logic, and the inconsistencies you can find without even having to run the test. Essentially another "write twice, debug once" scenario.
I know that's all kind of vague, and I don't have time to get into specific examples, but this encapsulates the feeling I have now working with Java. The feeling that I'm sort of fact-checking myself, even though it's tedious. Sort of a "show your work" thing.
Good quote. I used to struggle with this feeling when I got to roughly the ten year mark professionally and finally started to feel like I knew what I was doing. But, with the help of a little Stockholm Syndrome perhaps, I've learned to embrace the boilerplate as part of my workflow and use it to an advantage. I came up with a saying "write twice, debug once", from the old carpenter's saying "measure twice, cut once". The idea is that if you're writing sort of the same thing twice, the compiler and/or PR reviewers can at least check if those two things are consistent with each other, and debugging is more straightforward instead of a long slog. You may still write the same thing wrong twice, but that's much harder to do than writing it wrong once. I think this is a hidden value of automated testing as well. I could talk for hours about whether automated testing is "worth it" depending on the project/language/team/etc., but one thing most people don't consider is the value in the mere act of writing out some logic that must be consistent with other logic, and the inconsistencies you can find without even having to run the test. Essentially another "write twice, debug once" scenario.
I know that's all kind of vague, and I don't have time to get into specific examples, but this encapsulates the feeling I have now working with Java. The feeling that I'm sort of fact-checking myself, even though it's tedious. Sort of a "show your work" thing.
My biggest beef with idiomatic Java code is that it is so annotation-based. For example, I recently had to do a small CLI app in Java, and the command line parser library that came up in searches was picocli. Now, I mean no offence to the creators of picocli, it's pretty typical of most java code in my experience, but why does it have to do everything via annotations? Take their example for instance: https://github.com/remkop/picocli#example
Not sure why a "just plain data" approach couldn't have been taken so that instead of:
Not sure why a "just plain data" approach couldn't have been taken so that instead of:
@Option(names = { "-v", "--verbose" },
description = "Verbose mode.")
private boolean[] verbose = new boolean[0];
You could've had something more... Option<boolean[]> verbose =
Option.<boolean[]>newBuilder()
.names("-v", "--verbose")
.description("Verbose mode.")
.default(() -> new boolean[0])
.build(); Annotations are frequently abused. They are useful in some cases but in other cases it would be easier to just call a function. It is more transparent and easy to understand, you don't have to figure out how the magic works when something fails.
Plus you can bring the rest of your abstraction toolkit with you with plain functions. E.g. you can't put annotations into loops or conditions.
You could have just kept searching?
argparse4j has existed forever and its a port of python's argparse [0].
[0] https://argparse4j.github.io/
argparse4j has existed forever and its a port of python's argparse [0].
[0] https://argparse4j.github.io/
Because then people like the ones tearing down their strawman "Java is verbose" would be closer to having a point.
Annotations work really well: they are easy to write, easy to write and typesafe enough.
Annotations work really well: they are easy to write, easy to write and typesafe enough.
I just cannot give any credence to annotations. @Transactional? @Stateless? @NotNull?
In each case (and presumably many more) you can just go and write a unit test for each of these disproving them.
In each case (and presumably many more) you can just go and write a unit test for each of these disproving them.
But they are in my opinion really difficult to understand, test or customize. In the example above, the reified Option<boolean[]> can be passed around, transformed, inspected, invoked in tests, etc. The annotation approach weaves together the aggregate class (the overarching config class), the specific field of the class (`verbose`) and how that field should be parsed from command line arguments.
Ok. I can try to help: In this particular case, when you test you just set the field to what you want.
In other cases where you actually want and need to test that the annotations works at runtime you use a test container (Arquillian was the big thing for integration testing JavaEE last time it was relevant to me, and since we are talking enterprise Java there's a fair chance that Arquillian will still work and maybe even still be the most popular solution.)
In other cases where you actually want and need to test that the annotations works at runtime you use a test container (Arquillian was the big thing for integration testing JavaEE last time it was relevant to me, and since we are talking enterprise Java there's a fair chance that Arquillian will still work and maybe even still be the most popular solution.)
The thing I hate about java the most is:
-that generics don't play well with primitives and arrays?
I understand that jep218 aims to fix the first one. But it's been quite a few years now and why was this not done the right way from the start?
> equals() and ==
I think these should've been switched.
A general principle in languages (not only in programming languages) is that things that are used often should be short.
Plus
- get() and charAt(), instead of [].
just why?
- verbose naming conventions:
Excessive redundancy does not increase safety or improve maintainability. Having one blinking red light is useful, having a thousand lights of different importance will just make you filter it out. People will simply gloss over and skip long identifiers.
we are humans and we can't operate at full attention the whole time. if something has low information content, we simply go on autopilot.
Easy things should be short, so that the hard things popout in the code.
- Obsession with software engineering and OOP Principles?
Instead of thinking about the data structures needed and the alogirthms chosen, time is spent just making a millefeuille of abstractions.
-that generics don't play well with primitives and arrays?
I understand that jep218 aims to fix the first one. But it's been quite a few years now and why was this not done the right way from the start?
> equals() and ==
I think these should've been switched.
A general principle in languages (not only in programming languages) is that things that are used often should be short.
Plus
- get() and charAt(), instead of [].
just why?
- verbose naming conventions:
Excessive redundancy does not increase safety or improve maintainability. Having one blinking red light is useful, having a thousand lights of different importance will just make you filter it out. People will simply gloss over and skip long identifiers.
we are humans and we can't operate at full attention the whole time. if something has low information content, we simply go on autopilot.
Easy things should be short, so that the hard things popout in the code.
- Obsession with software engineering and OOP Principles?
Instead of thinking about the data structures needed and the alogirthms chosen, time is spent just making a millefeuille of abstractions.
Java not having operator overloads as a design choice explains the majority of your complaints.
Generics are built around type erasure and as such generics treat the type as Object at runtime. Primitives cannot be treated this way. The reason its not a simple fix is because the language designers are not willing to break compatibility to make this happen.
The style complaints are an artifact of Java being a popular corporate language but there's no reason you need to write Java this way.
Generics are built around type erasure and as such generics treat the type as Object at runtime. Primitives cannot be treated this way. The reason its not a simple fix is because the language designers are not willing to break compatibility to make this happen.
The style complaints are an artifact of Java being a popular corporate language but there's no reason you need to write Java this way.
I'd imagine people criticise Java for the same reason they criticise PHP, or C++, or many other languages. It's very easy to attack a strawman based on outdated best practices from more than a decade ago ignoring the fact that both the language and the best practices have moved on and many people are very productive in those languages.
When I started learning C#, less than a decade ago, best practices were "Uncle Bob" style nonsense, ultra-fragmentation of the codebase, interface everything, boilerplate everywhere, pointless mocking and testing of components which should have been treated as purely internal. But you look at what changed in C# -- generics, dynamic, LINQ, async, auto properties, etc. -- in the last few versions and it's a brilliant language that can be written as verbose or as golfed as you please (I lean now towards verbosity and loops over LINQ just because while I think LINQ makes you feel clever it's also harder to reason about).
Now I don't do Java other than porting a Java codebase to C# but I'm under the impression Java has been on a similar journey with streams and whatever else. Much like PHP or C++, workhorse languages that aren't 'sexy' but are widely used and keep the internet and society running.
Different people like different things and that's ok (but assuming this new language from the advertorial doesn't support autocomplete, easy refactoring, whatever else it's going to be a hard sell to get productive teams to switch over to it!).
When I started learning C#, less than a decade ago, best practices were "Uncle Bob" style nonsense, ultra-fragmentation of the codebase, interface everything, boilerplate everywhere, pointless mocking and testing of components which should have been treated as purely internal. But you look at what changed in C# -- generics, dynamic, LINQ, async, auto properties, etc. -- in the last few versions and it's a brilliant language that can be written as verbose or as golfed as you please (I lean now towards verbosity and loops over LINQ just because while I think LINQ makes you feel clever it's also harder to reason about).
Now I don't do Java other than porting a Java codebase to C# but I'm under the impression Java has been on a similar journey with streams and whatever else. Much like PHP or C++, workhorse languages that aren't 'sexy' but are widely used and keep the internet and society running.
Different people like different things and that's ok (but assuming this new language from the advertorial doesn't support autocomplete, easy refactoring, whatever else it's going to be a hard sell to get productive teams to switch over to it!).
Lots of the boilerplate they create is totally unnecessary; other boilerplate can be avoided through use of common open source libraries, like AutoValue [1] for equals/hashcode/toString. No need for custom compilers, new ASTs, or a whole new language. Don't create interfaces for classes that only have one implementation, and don't create gratuitous indirection.
[1] https://github.com/google/auto/tree/master/value
[1] https://github.com/google/auto/tree/master/value
Convert your code to Kotlin (idea will do this for you). It won't get rid of the boilerplate automatically but it will de-clutter your code and then put you in a position to refactor to be more idiomatic Kotlin. Once you get that sorted, you can start thinking about creating a DSL for things you do a lot and get smart about eliminating boilerplate. IMHO javascript/typescript looks more boiler plate heavy these days than well written Kotlin.
I know bashing java for boilerplate and verbosity is popular but a lot of its reputation is people doing repetitive stuff without reflecting on better ways to do these things. I see the same patterns and mistakes in other languages. The best code is the code you don't need to write.
Likewise needless abstractions and over-engineering are a problem in many places. Java certainly has seen its fair share of that and its a reason I avoid the entirety of the Scala ecosystem in principle. Nothing wrong with the language but it seems to provoke over-engineering.
I know bashing java for boilerplate and verbosity is popular but a lot of its reputation is people doing repetitive stuff without reflecting on better ways to do these things. I see the same patterns and mistakes in other languages. The best code is the code you don't need to write.
Likewise needless abstractions and over-engineering are a problem in many places. Java certainly has seen its fair share of that and its a reason I avoid the entirety of the Scala ecosystem in principle. Nothing wrong with the language but it seems to provoke over-engineering.
"Using UnitilyLang we could replace the previous five code sections (3006 characters plus the hidden ConfigProvider and DynamoItemReader classes we get for free in UnitilyLang) with (248 characters)."
Let me emphasize that, there: "classes we get for free".
"UnitilyLang is designed around abstraction, immutability and composition. It's language agnostic, that is until we have to write logic."
Well, language agnostic, but apparently not domain agnostic or framework agnostic.
Then, there's
Is Java verbose? Yes. Is it possible to be less verbose, especially if you invisibly provide domain knowledge "for free"? Yep. Is it possible to be too succinct?
Let me emphasize that, there: "classes we get for free".
"UnitilyLang is designed around abstraction, immutability and composition. It's language agnostic, that is until we have to write logic."
Well, language agnostic, but apparently not domain agnostic or framework agnostic.
Then, there's
workflow DynamoDbUserStore
= 4 . (3 (2 1))
Pointfree and nameless. Anyone have any idea what it does? Does the subsequent picture help? Anyone think you could modify what it does without a lot of unpacking? I'll leave it to someone else to figure out what's going on with the "UnitilyLang complete project".Is Java verbose? Yes. Is it possible to be less verbose, especially if you invisibly provide domain knowledge "for free"? Yep. Is it possible to be too succinct?
> Pointfree and nameless. Anyone have any idea what it does? Does the subsequent picture help? Anyone think you could modify what it does without a lot of unpacking?
I don't understand why this isn't the only (or at least the highest rated) comment in this discussion. There really is nothing more to say about this article. "4. (3 (2 1))" is not a program. "We apply the second dependency to the result of the first. We then Partially apply the 3rd dependency and compose the resulting function with the 4th dependency." is not an explanation -- what is the "3rd dependency"? Dropping names to somehow implicitly number something does save keystrokes, but it doesn't make for "good code" (the claim from the article's title).
I don't understand why this isn't the only (or at least the highest rated) comment in this discussion. There really is nothing more to say about this article. "4. (3 (2 1))" is not a program. "We apply the second dependency to the result of the first. We then Partially apply the 3rd dependency and compose the resulting function with the 4th dependency." is not an explanation -- what is the "3rd dependency"? Dropping names to somehow implicitly number something does save keystrokes, but it doesn't make for "good code" (the claim from the article's title).
People in the thread have given a few different reasons why Java is verbose but in my opinion, it's not so much the language specifics or the culture but the combination of static typing together with the inheritance and class-focussed design.
A lot of the terseness of other languages is the result of dynamism (Ruby, Python) and so on which basically gives you two thirds of the infamous patterns that people complain about more or less for free, and for other static languages that are predominantly functional a lot of the boilerplate simply does not exist because the primary unit of computation is the function and that spares you a hell of a lot of complication.
It's the C++/Java class of languages that all suffer from the same verbosity and complexity and I don't think it's because the particular languages do anything wrong specifically but that the combination of paradigms simply leads to large, complicated structures.
A lot of the terseness of other languages is the result of dynamism (Ruby, Python) and so on which basically gives you two thirds of the infamous patterns that people complain about more or less for free, and for other static languages that are predominantly functional a lot of the boilerplate simply does not exist because the primary unit of computation is the function and that spares you a hell of a lot of complication.
It's the C++/Java class of languages that all suffer from the same verbosity and complexity and I don't think it's because the particular languages do anything wrong specifically but that the combination of paradigms simply leads to large, complicated structures.
I've always felt that having "private" fields by default (thereby suggesing it is the right way to code), making all code live inside classes, and thinking everything as an object, are dogmatic assumptions that make you write a lot of boilerplate as you move forward (getters/setters, constructors, etc...)
If you could write your functions out of any class, you get 3 lines an an indent level less, which I think reduces the cognitive load. you could change your private fields to be by convention instead of forced just by prefixing m_varName or whatever...
I dislike java because I feel the language somehow doesn't trust the developer to know well the system he is coding. It is like handing a blunt knife to a chef expecting he doesn't know how to handle it, making his work annoying, but if he has to work for more time, he can bill more hours right?
If you could write your functions out of any class, you get 3 lines an an indent level less, which I think reduces the cognitive load. you could change your private fields to be by convention instead of forced just by prefixing m_varName or whatever...
I dislike java because I feel the language somehow doesn't trust the developer to know well the system he is coding. It is like handing a blunt knife to a chef expecting he doesn't know how to handle it, making his work annoying, but if he has to work for more time, he can bill more hours right?
A lovely ridiculous parody of "Enterprise" style Java: https://www.github.com/EnterpriseQualityCoding/FizzBuzzEnter...
Thats not Java. These are the wet dreams of some CS graduates who fell in love with Spring and Hibernate and take rough OOP guidelines to extremes.
Nobody - NOBODY! - used to write such Java code in the 2000s, at least the early ones. This insanity slowly started around 2005 and then really took off late 2000s and then particularly 2010s.
Again, thats not Java.
Nobody - NOBODY! - used to write such Java code in the 2000s, at least the early ones. This insanity slowly started around 2005 and then really took off late 2000s and then particularly 2010s.
Again, thats not Java.
And in the late 2010s JavaEE broke through and things are now really really nice if you are working on a modern Java project where people know what thwy are doing.
90% of this post is BS. They spend most of the characters on overriding toString, equals, and hashCode to never use them.
Most code shown here should be refactored from classes to a java functional style with closure capture instead of members. Then almost all the boilerplate disappears, the result is cleaner, easier to work with and the intent of every object is straightforward.
Edit: Example rewrite of UsernameLetterCountPublisher as a function since I realized a lot of people here might not be familiar with Java:
Edit: Example rewrite of UsernameLetterCountPublisher as a function since I realized a lot of people here might not be familiar with Java:
public static Supplier UsernameLetterCountPublisher(
final Supplier<Config> configProvider,
final Function<String, Config> userIdProvider,
final Function<User, String> userProvider,
final Function<String, User> userMessageCreator,
final Consumer<String> publisher) {
return () -> {
final Config config = configProvider.apply();
final String id = userIdProvider.apply(config);
final User user = userProvider.apply(id);
final String message = userMessageCreator.apply(user);
publisher.apply(message);
// I didn't bother create a new interface for this so reused supplier.
return null;
};
}Yes - Java spells it out.
ie A lot of the 'boiler plate' is stuff that allows the compiler to check for errors, and humans understand the code but at the same time allows you to vary behavior if you want.
Java also has language simplicity and consistency [1] ( few syntax short cuts ) - which helps humans understand code - the coding equivalent of 'plain english'.
Whether you see that as a good thing or not depends on whether you are writing self contained scripts or large long lived complex systems.
[1] the later bolted on generics, not so much.
ie A lot of the 'boiler plate' is stuff that allows the compiler to check for errors, and humans understand the code but at the same time allows you to vary behavior if you want.
Java also has language simplicity and consistency [1] ( few syntax short cuts ) - which helps humans understand code - the coding equivalent of 'plain english'.
Whether you see that as a good thing or not depends on whether you are writing self contained scripts or large long lived complex systems.
[1] the later bolted on generics, not so much.
I find this disingenuous from the outset.
No, the class name is not boilerplate. No, using parentheses rather than whitespace to indicate scope is not really cutting out boilerplate.
No, defining custom hashcode, equals and toString methods is not boilerplate or even always necessary, that's something you've chosen to do.
Much of the complaint about things like constructors and getters/setters can be resolved using something like lombok.
Honestly this looks like someone really labouring the truth in order to try to prove a point.
No, the class name is not boilerplate. No, using parentheses rather than whitespace to indicate scope is not really cutting out boilerplate.
No, defining custom hashcode, equals and toString methods is not boilerplate or even always necessary, that's something you've chosen to do.
Much of the complaint about things like constructors and getters/setters can be resolved using something like lombok.
Honestly this looks like someone really labouring the truth in order to try to prove a point.
This is something I've been saying for years now; it honestly feels that sometimes the mantra of the language is "why write code when you can just make files?".
I feel like Java makes you feel like you're accomplishing a lot, but in reality if half your code is generated by an IDE, and is impossible to understand without the help of a bunch of expensive tools, I do have to wonder how good it actually is. I'm not sure I've ever seem a Java project where I didn't shortly after think "this probably would have been better with Clojure or something".
I know a lot of really smart people who absolutely love Java, so I'll admit that it's possible that I don't know what I'm talking about, but I'd like to think I'm reasonably competent at this coding thing now, and Java is routinely the worst part of my day at my current job.
To make it clear, I'm not knocking the JVM. That's a pretty cool piece of tech, and enables awesome stuff like Clojure and Eta.
I feel like Java makes you feel like you're accomplishing a lot, but in reality if half your code is generated by an IDE, and is impossible to understand without the help of a bunch of expensive tools, I do have to wonder how good it actually is. I'm not sure I've ever seem a Java project where I didn't shortly after think "this probably would have been better with Clojure or something".
I know a lot of really smart people who absolutely love Java, so I'll admit that it's possible that I don't know what I'm talking about, but I'd like to think I'm reasonably competent at this coding thing now, and Java is routinely the worst part of my day at my current job.
To make it clear, I'm not knocking the JVM. That's a pretty cool piece of tech, and enables awesome stuff like Clojure and Eta.
>> I'll admit that it's possible that I don't know what I'm talking about,
I think a lot of people here, and probably out there in the world, have missed the direction java has taken in the last 5 years or so.
I'm not saying it's definite that you don't know what you're talking about, just that perhaps the changes to more functional styles, lambdas, function passing, stream processing etc might have passed you by. It's a much more expressive language now.
Or it might not, you may be bang up to date and still hold that opinion :)
I think a lot of people here, and probably out there in the world, have missed the direction java has taken in the last 5 years or so.
I'm not saying it's definite that you don't know what you're talking about, just that perhaps the changes to more functional styles, lambdas, function passing, stream processing etc might have passed you by. It's a much more expressive language now.
Or it might not, you may be bang up to date and still hold that opinion :)
I actually do use the modern features introduced by Java 8, like Lambdas and Optionals and streams and whatnot, and while I definitely agree that they're a step in the right direction, I think most of my points still stand.
The Optionals are definitely useful, but they're a far-cry from something like Scala's algebraic data types. The streams are cool, but compared to virtually any of the list-processing libraries for virtually any other JVM language, they're fairly primitive.
Being able to pass functions around is great, but the lambda syntax is pretty messy in Java IMO (the weird dichotomy between Function and Supplier still confuses me occasionally). Not to mention that generics in Java are still really weird and confusing...the dichotomy between the primitives and boxed types is strange, at least in regards to generics.
And even with all the improvements, we still have issues like the expression problem without much of a solution; the fact that I can't add to existing types or have something akin to a multimethod means that there ends up being a ton of wrapper classes and "extending but adding one method" classes all over the place, adding to the noise of files.
If I have any choice in the matter, since my team is a JVM shop (at least on the server), I typically choose Clojure. With Clojure I get much more concise and clear code, thread safety by default, an extremely fast and interactive development process, hot code reloading, and access to literally every Java library. I will admit that occasionally I would prefer to have a good type system (which Clojure sorely lacks...typed clojure isn't great IMO), but nine times out of ten, I am pretty happy with Clojure overall.
TL;DR: I should make it clear, and I realize that I didn't in my previous post, Java (the language) has improved; I definitely won't deny that. It's just not improved fast enough to address all my complaints. The language is still incredibly wordy and noisy, even with the improvements.
The Optionals are definitely useful, but they're a far-cry from something like Scala's algebraic data types. The streams are cool, but compared to virtually any of the list-processing libraries for virtually any other JVM language, they're fairly primitive.
Being able to pass functions around is great, but the lambda syntax is pretty messy in Java IMO (the weird dichotomy between Function and Supplier still confuses me occasionally). Not to mention that generics in Java are still really weird and confusing...the dichotomy between the primitives and boxed types is strange, at least in regards to generics.
And even with all the improvements, we still have issues like the expression problem without much of a solution; the fact that I can't add to existing types or have something akin to a multimethod means that there ends up being a ton of wrapper classes and "extending but adding one method" classes all over the place, adding to the noise of files.
If I have any choice in the matter, since my team is a JVM shop (at least on the server), I typically choose Clojure. With Clojure I get much more concise and clear code, thread safety by default, an extremely fast and interactive development process, hot code reloading, and access to literally every Java library. I will admit that occasionally I would prefer to have a good type system (which Clojure sorely lacks...typed clojure isn't great IMO), but nine times out of ten, I am pretty happy with Clojure overall.
TL;DR: I should make it clear, and I realize that I didn't in my previous post, Java (the language) has improved; I definitely won't deny that. It's just not improved fast enough to address all my complaints. The language is still incredibly wordy and noisy, even with the improvements.
The examples given are full of intentionally created boilerplate. Anyone writing such production code in java has no java experience for sure.
For example, the aws sdk for dynamo db contains DynamoDBMapper, which will convert dynamoDBItem to your desired class, so the entire DynamoDbUserProvider is totally unnecessary. It would simply be "dynamoDbMapper.load(key). This also removed the need for Config and DynamoDBTable classes.
Also, the entire boilerplate of toString hashCode equals can be easily avoided by using Lombok. I am sure some people will consider Lombok a hacky tool, but I don't know of practical issues with it. It integrates well with IntelliJ and Eclipse, and I think most java build tools.
Granted, with python you don't need a class to read from db, and can read into a dictionary, but you also won't get the niceities like auto completion if you do that, and will be much more vulnerable to typos.
For example, the aws sdk for dynamo db contains DynamoDBMapper, which will convert dynamoDBItem to your desired class, so the entire DynamoDbUserProvider is totally unnecessary. It would simply be "dynamoDbMapper.load(key). This also removed the need for Config and DynamoDBTable classes.
Also, the entire boilerplate of toString hashCode equals can be easily avoided by using Lombok. I am sure some people will consider Lombok a hacky tool, but I don't know of practical issues with it. It integrates well with IntelliJ and Eclipse, and I think most java build tools.
Granted, with python you don't need a class to read from db, and can read into a dictionary, but you also won't get the niceities like auto completion if you do that, and will be much more vulnerable to typos.
This may have a kernel of truth to it, but it's difficult to treat as objective when the article is essentially an advertisement for the language the author is developing.
A lot of the boilerplate mentioned would disappear if you used a framework like lombok or autovalue. I personally prefer simple verbosity over having to learn the automagic intricacies of multiple different frameworks, but to each their own.
The "90% boilerplate" in the title is highly misleading though. If you build an application that contains no business logic at all, then by definition, you're left with nothing but boilerplate. Especially if you insist on formalities like creating interfaces with only a single implementation.
In real projects that I've worked on, the vast majority of the code is application specific logic, with heavy use of external libraries to get rid of any generic logic. The Java boilerplate makes up a small portion of the overall code base, and has certainly never been a deal breaker.
The "90% boilerplate" in the title is highly misleading though. If you build an application that contains no business logic at all, then by definition, you're left with nothing but boilerplate. Especially if you insist on formalities like creating interfaces with only a single implementation.
In real projects that I've worked on, the vast majority of the code is application specific logic, with heavy use of external libraries to get rid of any generic logic. The Java boilerplate makes up a small portion of the overall code base, and has certainly never been a deal breaker.
So tired of stupid articles like this hating on java. Java is an extremely flexible language with an insane amount of libraries, frameworks, and design patterns. Furthermore, using groovy is amazing, and can reduce your lines of code quite a bit. The author of this article just sucks at implementing java.
Well, the punchline is they wrote a tool to implement Java for you, with either a GUI tool or a DSL.
So you still have those libraries and frameworks, and its a similar strategy to using Groovy, but presumably doesn't have the same performance penalty as it generates idiomatic Java code.
So you still have those libraries and frameworks, and its a similar strategy to using Groovy, but presumably doesn't have the same performance penalty as it generates idiomatic Java code.
Someone criticises Java for being verbose, and you want to refute that with:
> using groovy is amazing, and can reduce your lines of code quite a bit
?
> using groovy is amazing, and can reduce your lines of code quite a bit
?
You just cherry picked one part of my rebuttal. Again, java is an extremely flexible language, and the verboseness is up to the user. You can implement very verbose code in plain java, or you can write extremely concise code in plain java. It’s up to the user.
I do a bit of work in Angular (2+) at the moment.
And that involves a lot of boilerplate code; in fact so much that the framework comes with a tool (ng generate) that generates the code for you. A single component is split in 4 files and needs to be explicitly wired up in another file (the module). An empty test case is more than 20 lines of code.
The point is. This is not really due to the language.
Angular is written in JavaScript which under other circumstances can be a very condensed language.
It is not really the language that mandates verboseness and boilerplate but the conventions, style, framework and so on.
Java can be low on boilerplating and a lot more condensed; though one has to look outside the big mainstream frameworks for that.
And that involves a lot of boilerplate code; in fact so much that the framework comes with a tool (ng generate) that generates the code for you. A single component is split in 4 files and needs to be explicitly wired up in another file (the module). An empty test case is more than 20 lines of code.
The point is. This is not really due to the language.
Angular is written in JavaScript which under other circumstances can be a very condensed language.
It is not really the language that mandates verboseness and boilerplate but the conventions, style, framework and so on.
Java can be low on boilerplating and a lot more condensed; though one has to look outside the big mainstream frameworks for that.
Nothing against Kotlin, but please spare us the Kotlin hype. Groovy was streamlining Java long before Kotlin even had a name.
So, this catches me in the midst of an effort to try and migrate away from Java (albeit to another JVM language) because I'm sick of the amount of effort it takes to maintain Java code.
But, all the same, I want to play devil's advocate and argue that a lot of this mess is really about Java-the-culture moreso than Java the language.
You can use `public final` fields instead of private fields with getters but no setter. You can use public fields instead fo a private field plus a getter and setter, too. It'll save a lot of boilerplate, and, if you're doing it right, be functionally equivalent. The only things stopping you are cultural factors. First and foremost, it's taboo. That's, frankly, the usual reason. Second, some libraries that rely on reflection aren't equipped to handle fields. Not because they can't, but because the getter/setter pattern is so culturally entrenched that not following it is almost unthinkable. Finally, and this is the prototypical reason that motivated this idea in the first place, because, 20-odd years ago, some very clever people decided that it should be very easy to do Truly Obnoxious and Anti-Social Things like replacing a simple field access with a database round trip or some spooky-action-at-a-distance state mutation without having to tell any of your colleagues what you'd done.
To an approximation, a big motivation for the functional backlash against OOP is the realization that maybe we shouldn't be so quick to enable ourselves to do Truly Obnoxious and Anti-Social Things. Maybe it's even desirable that we not do that. Of course, politeness mandates that we couch that observation in gentler language using terms like "referential transparency."
This maybe speaks to another aspect of Java's culture: Perhaps due to its corporate roots, Java developers tend to rely strongly on thought leaders for advice. Regular book authors, conference speakers, bloggers, etc. are the arbiters of best practices. The more books, lectures, and blog posts one delivers, the more name recognition one has, the more trusted ones opinions become. In the limit case, you spend all your time telling people how to maintain code, and no time actually maintaining code.
Strip all that mental noise away, and, while it doesn't turn Java into my favorite programming language, it at least gets easier to see how Java doesn't have to be any more of a boilerplate-ridden mess than any other language of its generation.
Just don't try actually writing clean Java code at work. That's a path that can only lead to unemployment.
But, all the same, I want to play devil's advocate and argue that a lot of this mess is really about Java-the-culture moreso than Java the language.
You can use `public final` fields instead of private fields with getters but no setter. You can use public fields instead fo a private field plus a getter and setter, too. It'll save a lot of boilerplate, and, if you're doing it right, be functionally equivalent. The only things stopping you are cultural factors. First and foremost, it's taboo. That's, frankly, the usual reason. Second, some libraries that rely on reflection aren't equipped to handle fields. Not because they can't, but because the getter/setter pattern is so culturally entrenched that not following it is almost unthinkable. Finally, and this is the prototypical reason that motivated this idea in the first place, because, 20-odd years ago, some very clever people decided that it should be very easy to do Truly Obnoxious and Anti-Social Things like replacing a simple field access with a database round trip or some spooky-action-at-a-distance state mutation without having to tell any of your colleagues what you'd done.
To an approximation, a big motivation for the functional backlash against OOP is the realization that maybe we shouldn't be so quick to enable ourselves to do Truly Obnoxious and Anti-Social Things. Maybe it's even desirable that we not do that. Of course, politeness mandates that we couch that observation in gentler language using terms like "referential transparency."
This maybe speaks to another aspect of Java's culture: Perhaps due to its corporate roots, Java developers tend to rely strongly on thought leaders for advice. Regular book authors, conference speakers, bloggers, etc. are the arbiters of best practices. The more books, lectures, and blog posts one delivers, the more name recognition one has, the more trusted ones opinions become. In the limit case, you spend all your time telling people how to maintain code, and no time actually maintaining code.
Strip all that mental noise away, and, while it doesn't turn Java into my favorite programming language, it at least gets easier to see how Java doesn't have to be any more of a boilerplate-ridden mess than any other language of its generation.
Just don't try actually writing clean Java code at work. That's a path that can only lead to unemployment.
I have a hard time filtering things out, and prefer text that errs on the side of being too concise where I can look things up if needed – e.g. Math textbooks or papers.
A lot of people are the opposite – they have an easy time filtering and skimming, and prefer all the details to be there on one page.
I think this plays a pretty big role in language choices – I find codebases in more concise languages much easier to parse & work with, but others say the same about Java/C#/etc.
A lot of people are the opposite – they have an easy time filtering and skimming, and prefer all the details to be there on one page.
I think this plays a pretty big role in language choices – I find codebases in more concise languages much easier to parse & work with, but others say the same about Java/C#/etc.
So a lot of the complaints are not entirely true anymore.
If you onboard lombok, you get data classes, which resolves the property boilerplate. If you use IntelliJ the code is generated.
I used Java and Go and I have to admit, that Java feels like the more practical language.
Generics and error-handling are really painful in Go.
The most painful part in Java is having to deal with other people's code that makes it look weird. Google has a very good set of libraries that make the Java world much more pleasant.
If you onboard lombok, you get data classes, which resolves the property boilerplate. If you use IntelliJ the code is generated.
I used Java and Go and I have to admit, that Java feels like the more practical language.
Generics and error-handling are really painful in Go.
The most painful part in Java is having to deal with other people's code that makes it look weird. Google has a very good set of libraries that make the Java world much more pleasant.
I only flew over the comments but I miss references from the past like Software through Pictures (STP, from former Aonix, riding the wave of CASE-tools) or the even older block oriented software. The latter thought of objects forming blocks that offer or consume interface and join each other through matching like LEGO.
Even big companies like Rational tried their best to generate boiler plate code from UML diagrams. Also BPMN incorporated findings from the early days.
But after nearly 30 years of programming experience I know one thing for sure: when it comes to real use cases beyond counting letters in a string only dedicated generators or very limited general ones continue to work. If it were the other way around there'd be product turning natural language directly into at least a single formal one.
And every (Java-) programmer who still repeatedly writes down lines of code doing the same boring input-processing-output did miss at least aspect orientation, Java agents and this is the most powerful of all: ANTLR. Runs right away with your BNF-grammar from Maven or a plain JDK. But be warned, this is like any other complex craftmanship, you need to start with cleaning the workshop before you can operate the heavy machinery.
Even big companies like Rational tried their best to generate boiler plate code from UML diagrams. Also BPMN incorporated findings from the early days.
But after nearly 30 years of programming experience I know one thing for sure: when it comes to real use cases beyond counting letters in a string only dedicated generators or very limited general ones continue to work. If it were the other way around there'd be product turning natural language directly into at least a single formal one.
And every (Java-) programmer who still repeatedly writes down lines of code doing the same boring input-processing-output did miss at least aspect orientation, Java agents and this is the most powerful of all: ANTLR. Runs right away with your BNF-grammar from Maven or a plain JDK. But be warned, this is like any other complex craftmanship, you need to start with cleaning the workshop before you can operate the heavy machinery.
In my opinion, this is the reason Java still _mostly_ remains the enterprise choice; When things are mostly boilerplate under the hood and following a small set of patterns and principles, it's really easy to onboard/ditch engineers.
That said, I like the motif brought up here, and I think the only thing lacking from the piece is how this stifles developers' engageability and creativity.
That said, I like the motif brought up here, and I think the only thing lacking from the piece is how this stifles developers' engageability and creativity.
"Good code is 90% boilerplate"
Have you considered that you might be doing it wrong?
Have you considered that you might be doing it wrong?
It feels like beating a dead horse. There are other options on the JVM like kotlin that are pretty much boilerplate free.
I will say this for java though : it has what is in my experience the best IDE support. Kotlin is not that bad but all these generated sugar (e.g. in a data class) make some operations like "find usages" way more complex and slower.
I will say this for java though : it has what is in my experience the best IDE support. Kotlin is not that bad but all these generated sugar (e.g. in a data class) make some operations like "find usages" way more complex and slower.
You're free to use many different languages with Java, like the new GraalVM release does.
Of course, Java needs some radical improvements, but people are doing that, and I can't wait for the day where these complaints will stop.
Verbosity is a personal choice too, and some people would prefer explicit configuration over convention, and that is a choice too.
Of course, Java needs some radical improvements, but people are doing that, and I can't wait for the day where these complaints will stop.
Verbosity is a personal choice too, and some people would prefer explicit configuration over convention, and that is a choice too.
Java hate aside, I love the idea of pictorial format for specifying data flow.
Haskell is wonderful in its partial application, '.' composing, and other such tools, that really let you quickly and easily specify how your data flows through your functions. My gripe is that it starts getting weird when the order of arguments don't line up, when you have to swap some values, when you have to take a partial result and only apply it 3 steps later.. the Haskell syntax really breaks down from its usual purity in those cases. A flow diagram picture, similar to as shown in the article, makes it immediately obvious what is happening. I wish there was more integration with current IDE's for this sort of different representation styles
Haskell is wonderful in its partial application, '.' composing, and other such tools, that really let you quickly and easily specify how your data flows through your functions. My gripe is that it starts getting weird when the order of arguments don't line up, when you have to swap some values, when you have to take a partial result and only apply it 3 steps later.. the Haskell syntax really breaks down from its usual purity in those cases. A flow diagram picture, similar to as shown in the article, makes it immediately obvious what is happening. I wish there was more integration with current IDE's for this sort of different representation styles
I wrote this article, I'd just like to clear up my intent.
Firstly the Java patterns. Obviously it's not really necessary to use them for my toy example, but I don't want to write a blog about a full scale enterprise app. I have seen multiple tech teams at different companies evolve separately towards using similar patterns in both Java and C#. Doing so solves problems in concurrency and maintainability (This is documented in the other blogs on the Unitily site https://www.unitily.com/learning.html). I've seen teams be very successful using them. I like Java. Im not criticising it, I'm trying to explain why people do.
UnitilyLang doesn't exist, I'm not writing that language, the examples in that article are the only code snippets I've ever written (and probably ever will). The point is that there is a one to one mapping between that representation and the java code using the described patterns. This highlights the extra code you have to write in Java (and similar languages) just to support the clean code patterns.
Sure if you used Kotlin or Closure (or any functional language) it becomes less of an issue, and this is exactly the point I'm trying to make. However, its rare you have the option to choose a language, so developers end up coding in Java and complaining about it.
I don't like the new title the moderators have created, (perhaps the my initial one was a bit click baity) but generating code is only mentioned once in the last paragraph. The point of this article is to highlight that once you follow a set of coding principles which solve specific problems you are going to have to write extra code to do so. Perhaps something like
Writing clean code in Java requires boilerplate, learn to love it.
would have been a better title.
The video demo at the bottom of the article generates the code from pictures not UnitilyLang. These pictures are tightly coupled to the specific coding patterns, but is (in my opinion) a more natural definition than both Java and UnitilyLang.
Firstly the Java patterns. Obviously it's not really necessary to use them for my toy example, but I don't want to write a blog about a full scale enterprise app. I have seen multiple tech teams at different companies evolve separately towards using similar patterns in both Java and C#. Doing so solves problems in concurrency and maintainability (This is documented in the other blogs on the Unitily site https://www.unitily.com/learning.html). I've seen teams be very successful using them. I like Java. Im not criticising it, I'm trying to explain why people do.
UnitilyLang doesn't exist, I'm not writing that language, the examples in that article are the only code snippets I've ever written (and probably ever will). The point is that there is a one to one mapping between that representation and the java code using the described patterns. This highlights the extra code you have to write in Java (and similar languages) just to support the clean code patterns.
Sure if you used Kotlin or Closure (or any functional language) it becomes less of an issue, and this is exactly the point I'm trying to make. However, its rare you have the option to choose a language, so developers end up coding in Java and complaining about it.
I don't like the new title the moderators have created, (perhaps the my initial one was a bit click baity) but generating code is only mentioned once in the last paragraph. The point of this article is to highlight that once you follow a set of coding principles which solve specific problems you are going to have to write extra code to do so. Perhaps something like
Writing clean code in Java requires boilerplate, learn to love it.
would have been a better title.
The video demo at the bottom of the article generates the code from pictures not UnitilyLang. These pictures are tightly coupled to the specific coding patterns, but is (in my opinion) a more natural definition than both Java and UnitilyLang.
> I have seen multiple tech teams at different companies evolve separately towards using similar patterns in both Java and C#
Could you give us some background on your professional experience? There is no "About" section on the site.
Could you give us some background on your professional experience? There is no "About" section on the site.
I've been a professional developer for 10 years at several mid size companies. I've led teams for a large proportion of that time. Not claiming to know everything, still learning. I'm just writing up my thoughts at this time, they do change (they often cycle).
The infamous "FizzBuzzEnterpriseEdition" relevant to the topic,
https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpris...
https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpris...
Java has it's problems, but I think they're mostly on the implementation side (e.g. lipservice to OOP while having a load of AbstractFooHelperFactories or leaking all your inner workings through Getters and doing "Ask, Don't Tell" rather than vice versa) but I don't get why people rail against the "boilerplate" so much.
It's just signposting, you can learn to look past it so soon it makes no difference.
I play music and don't need the treble clef on the stave to tell me what notes to play — you could call that boilerplate and get rid of it, but I don't think it would make it easier to play or read
It's just signposting, you can learn to look past it so soon it makes no difference.
I play music and don't need the treble clef on the stave to tell me what notes to play — you could call that boilerplate and get rid of it, but I don't think it would make it easier to play or read
A lot of boilerplate can be reduced by using Project Lombok. It obviates the need for a lot of this boilerplate. You can annotate a field with @Getter/Setter attribute and it will automatically generate getters and setters for you. You can also do @RequiredArgsConstructor, @Data class etc. I tried Lombok but didn't end up continuing to use it. An IDE like Intellij makes working with java painless enough to where Lombok didn't feel like this life changing solution.
- https://projectlombok.org/
- https://projectlombok.org/
> workflow DynamoDbUserStore
> = 4 . (3 (2 1))
> workflow UsernameLetterCountPublisher > = 5 (4 (3 (2 1)))
The workflow notation seems a little rough for readability even though its functional meaning fairly clear as you pretty much have to read a specific invocation of a workflow in context to confirm what's happening since the functions in a workflow could be anything and the workflow declaration is so generic.
On the other hand, when you see the named picture versions it's very clear. And I can't think of anything to address my minor criticism doesn't just make the notation worse.
> workflow UsernameLetterCountPublisher > = 5 (4 (3 (2 1)))
The workflow notation seems a little rough for readability even though its functional meaning fairly clear as you pretty much have to read a specific invocation of a workflow in context to confirm what's happening since the functions in a workflow could be anything and the workflow declaration is so generic.
On the other hand, when you see the named picture versions it's very clear. And I can't think of anything to address my minor criticism doesn't just make the notation worse.
A lot of the boilerplate can be removed by using Lombok https://projectlombok.org/
Another alternative is Groovy
Another alternative is Groovy
The only Java boilerplate that really bothers me is getters/setters. Lack of properties in Java is depressing. And even more depressing lack of any plans to add properties. There are records for Java 14 which somewhat solve the issue, but they really aimed for a bit different use-case (which is not even that important for me, personally).
Honestly the only reason I would choose Kotlin over Java today is properties. Everything else is nice to have, but absolutely not important.
Honestly the only reason I would choose Kotlin over Java today is properties. Everything else is nice to have, but absolutely not important.
> Everything else is nice to have, but absolutely not important.
I would argue the null-checking is important.
I would argue the null-checking is important.
Those who need null-checking can use @Nullable @NonNull annotations with static checkers. I've found Idea pretty good and it'll work very similar to Kotlin. Some popular libraries already annotated their interfaces.
The static checkers I found that can check those annotations are very slow.
Also, it's not well-integrated into the type system. Sure, you can get by with them, but it's certainly not ideal. It also requires someone to do work to set it up, instead of it just being the default.
Also, it's not well-integrated into the type system. Sure, you can get by with them, but it's certainly not ideal. It also requires someone to do work to set it up, instead of it just being the default.
What OOP language doesn't suffer from this? Sure Java is infamous for it but C++, JS, PHP, Ruby, Python, etc. all suffer the same issue. The percentage might be a little less, but the amount of boilerplate is staggering in most modern OOP languages. When compared to something like Clojure, it's unbelievable. You can write entire apps in Clojure in the space that most other languages just deal with boilerplate.
I've always wanted an IDE that would transform Java into a higher level language by "collapsing" the boiler plate. Just like we can collapse comments, I'd like to "collapse types" if I already know the types and just want to look at the logic. Or "collapse exception handling" if my current task involves changing the happy path and the exception stuff is just clutter.
For example, what if an IDE could read Java, but display it in a form that looks a lot like Kotlin? (Or Groovy, or your favourite high level language.) The file would still be Java underneath, so you don't need to convince your colleagues to adpot Kotlin. You can be in a happy Kotlin-esq world while working with their Java code, and they don't need to know or care.
That is actually a good idea.
One idea to get it actually usable: by default it shouldn't update any code you don't touch. (Some systems will load the file into a memory structure and then save it back ignoring the existing formatting, thereby making commit logs much harder to reas if you need to.)
One idea to get it actually usable: by default it shouldn't update any code you don't touch. (Some systems will load the file into a memory structure and then save it back ignoring the existing formatting, thereby making commit logs much harder to reas if you need to.)
Java has zillions of 'not very good' or 'not empowered' developers writing in it, and the perceived safety of frameworks is valued over the ability for devs to actually write decent code.
Especially in orgs for which the code/tech is a secondary issue, only seen as a risk, they'd rather higher 5 more low-cost people to deal with the framework cruft than just hire the right people for the job.
Especially in orgs for which the code/tech is a secondary issue, only seen as a risk, they'd rather higher 5 more low-cost people to deal with the framework cruft than just hire the right people for the job.
[deleted]
The toString method does not use a StringBuilder.
Each concatenation will create a new string. That is not good.
If you find Java verbose, just use Scala or Kotlin.
Each concatenation will create a new string. That is not good.
If you find Java verbose, just use Scala or Kotlin.
Using StringBuffer may be premature optimization. Java compiler will replace simple String concatenation with StringBuffer automatically [1] and you do not need to polute your code with StringBuffer unless you really find that to be performance bottleneck.
[1] https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.htm...
[1] https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.htm...
> a program that queries a database, gets a specific user by ID, and prints the number of letters in that user's name to the console
I'm not a Java programmer but why would you actually need all the boilerplate for this? I'm under the impression that this should be doable in any language with just two lines of code: fetch and print.
I'm not a Java programmer but why would you actually need all the boilerplate for this? I'm under the impression that this should be doable in any language with just two lines of code: fetch and print.
Yeah, they don't need all that, this whole article is basically nonsense, and puts in a lot of completely unnecessary stuff.
I personally like the explicitness of the language even if it is boiler plate (with some exceptions like checked exceptions in lambdas). It makes average code very readable with only little caveats. More than one time I struggled with my own code at a later point in time because I wanted to avoid duplication at any cost.
Has anybody managed to figure out the license and/or the cost of the software that they're promoting?
The website has a front page, an applications page with 1 example, and a read the article page with a numerated bullet point list (which turn out to be hyperlinks, but I only accidentally noticed that when mousing over the text.)
That's it.
The website has a front page, an applications page with 1 example, and a read the article page with a numerated bullet point list (which turn out to be hyperlinks, but I only accidentally noticed that when mousing over the text.)
That's it.
I loathe the fact that you need to invent a new class Just for passing some data along or piggyback on strings whenever you do a .map(). Maybe records are going to ease that pain. But why can we just have the compiler be able to construct a container to pass along some data to the next step in the stream?
We,enterprise java dev shops, don't mind because 90% of all enterprise applications are bloody CRUD apps.
Absolutely important to bring up. I don't care how creative you allow your developers to be while they leverage Ruby/Python/etc.; At the end of the day, they're accessing data from a DB and serving it to a client...
I started programming Java in 1996. It really is a static boilerplate language.
I'm wary of going too far in the direction of functional notation for the same use case though. Replacing code that is slow to read with code that is slow to expand in-head may not be the win people expect it to be.
I'm wary of going too far in the direction of functional notation for the same use case though. Replacing code that is slow to read with code that is slow to expand in-head may not be the win people expect it to be.
Looking at the youtube video of actually using Unitily, it seems way more complicated to me of having to learn how to use this UI interface. Personally I think with tools like Project Lombok, auto/value, and IDE tools, you can streamline boilerplate code fairly well.
Surprised I'm not reading many comments about Lombok. We use this with great success and predictability and it removes so much boilerplate. More importantly, it focuses on the boilerplate that is truly boring. The projects look great add a result.
I'm surprised no one has mentioned that this is an ad for a visual code-generating tool.
Just figured this article (or copy of an article originally posted by Joel on Software Engineering) fit in this discussion.
https://pastebin.com/VJUNqsU3
Kotlin solve everything about it.
It also takes a very opinionated approach in how it does it. That's fine if you want it, but if you just want dramatically less verbose code but otherwise are completely happy sticking with the general principles of Java, something like Groovy is a nicer option.
Groovy did so long before that ;)
Blows my mind how rare it is for anyone to mention groovy on hackernews. Groovy is amazing and turns java into a wild west language where anything is possible.
Clearly it's sad no big corporation or community is behind this beautiful language.
Therefore I believe kotlin has obscoleted groovy.
(except if you really want dynamic typing)
See records, coming to Java next year: https://openjdk.java.net/jeps/359
I wanted to like this, but I struggled with the "s-expression with numeric atoms" dependency injection notation.
How do I map the numbers back to their sources?
How do I map the numbers back to their sources?
I could not find any link to github/download/trials... any ideas of how can I try this language? or is this just vaporware?
Psql select count(email) from table where name = "foo"
What the hell happened to the world.
What the hell happened to the world.
i hardly know java, but have to do something with the legacy system at work. does anybody have any good, canonical resources for learning java? canonical, like the pickaxe book for ruby
Effective Java is the one I see recommended most often.
I also wanted to add something I answered on a similar question in another less public forum a few days ago, but it turns out to be in another (human) language so I'll just add a summary:
> 1. Java IDEs are way more sophisticated than anything else except Visual Studio with ReSharper. Learn one of them. I disliked Java strongly until someone used a couple of minutes here and there to show me navigation and editing. (Hint for people who come from editors: Java IDEs understand the difference between the same letters i.e. word in different context. If you rename a variable in one method it won't touch other things that contains the same characters elsewhere in the file.)
2. Learn Maven and/or Gradle. You know you've got it right when everything works as expected and there's next to nothing left.
3. Don't trust everyone: people in this thread mention consultant Java. I was exposed to it early and it only took a few months to realize that certain (well paid I assume) consultants knew less than me: adding log statements and rebooting application servers every time they debugged (the correct way is of course to set a breakpoint and single step.) I also cleaned up a lot of their code, including a 6000 lines of XML implementation of a crude authorization framework that they had made because they didn't get Facelets.
I also wanted to add something I answered on a similar question in another less public forum a few days ago, but it turns out to be in another (human) language so I'll just add a summary:
> 1. Java IDEs are way more sophisticated than anything else except Visual Studio with ReSharper. Learn one of them. I disliked Java strongly until someone used a couple of minutes here and there to show me navigation and editing. (Hint for people who come from editors: Java IDEs understand the difference between the same letters i.e. word in different context. If you rename a variable in one method it won't touch other things that contains the same characters elsewhere in the file.)
2. Learn Maven and/or Gradle. You know you've got it right when everything works as expected and there's next to nothing left.
3. Don't trust everyone: people in this thread mention consultant Java. I was exposed to it early and it only took a few months to realize that certain (well paid I assume) consultants knew less than me: adding log statements and rebooting application servers every time they debugged (the correct way is of course to set a breakpoint and single step.) I also cleaned up a lot of their code, including a 6000 lines of XML implementation of a crude authorization framework that they had made because they didn't get Facelets.
thank you!
How do I get in touch with the author of Unitily? cc @rowland_street
This twitter account will find its way through to me https://twitter.com/LLambdas
TL;DR - "Here is a lot of boilerplate code that doesn't seem necessary for what you are doing in this case..."
...
"Hey look another tool that can generate a lot of boilerplate code you then have to deal with and makes the easy stuff easier and the hard stuff almost impossible!"
...
"Hey look another tool that can generate a lot of boilerplate code you then have to deal with and makes the easy stuff easier and the hard stuff almost impossible!"
no, its because concurrency is hard and most programmers fail at it...
That's RAD
foobar_(3)
If I understand correctly, the author has written a program in their pet language, translated it directly to Java, and complains that you have to write a lot of boilerplate to do 1:1 translation from their language to Java.
I think the right consideration is that they have simply done a poor job writing this program.
I think the right consideration is that they have simply done a poor job writing this program.
It always seemed weird to me. Like if my house was really cluttered but instead of building shelves or closets into the house itself, I buy an elaborate conveyor belt and excavator system to rearrange all my things all the time so I can walk through all the mess.