Generating a Java program with 90% less code
unitily.com
unitily.com
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.
Python is not static typed, it won’t refactor as easily.
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.
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?
(Yes, I know you can do Java in VS Code but I wouldn't want to manage a large Java codebase with it.)
Why not?
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.
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.
The same can't be said for languages like JS.
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.
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.
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.
Are you relying on a correct and comprehensive project/workspace setup within the IDE?
Of course. Not much point in using an IDE otherwise.
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.
You generally get compilation/build errors highlighting the bits that were missed (for whatever reason)
That's not something I "rely" on, it's the first thing I make happen whenever I use an IDE.
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.
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!
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.
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'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).
Only on monolith code bases without any signs of modularity.
In other words, don't make it look complicated before it actually is.
That was kind of the gist of my comment.
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...
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.
Then different uses of the same identifier still resolve to the same implementation, and make the IDE jump to the same location, etc.
(I acknowledge that everything can be something else. And in theory you can redefine macros to have uses of the same identifier resolve to different implementations. Etc, etc. I'm sure you know about the IOCCC. But here we talk about good programming practice. It's a good practice not to suggest something complicated while the reality is simple. There's no argument to win here.)
> 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.
Which is why I do not use C++, or at least avoid most of its features. For me, it's all about clarity and reducing number of moving parts. It's not a contest in making complicated things that pretend to be simple. And it's not a contest in making simple things that look complicated. And it's not a contest in reducing the number of characters in individual identifiers at all costs.
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 ?
What is needed in the face of changing requirements is actually this: lean structure with few dependencies that can be adapted.
That wasn't hard.
Also you've made your logging library project-specific by making a log_core_event function. Thanks but I'll keep using spdlog in my projects, which do not need newcomers to learn about your log_core_event, what is a core module and what is not, just to be able to write the three functions they are called for in that project !
That all has nothing to do with virtual methods.
> thread initialization if you have multiple threads doing logging on startup, right ? :-) )
There is no technical difference between "global" and "local" variables. It's merely a syntactical difference.
If you run into concurrency problems with global variables, you'll run into problems just as well with objects. Unless you make multiple objects. In which case, you could also have done thread-local storage, no?
In the end, it's much clearer to me to use global variables (TLS or not) since this way I can actually see the data relations. If there is just one instance of a thing, why would I pass it around as objects? It's not going to help understandability.
> Also you've made your logging library project-specific by making a log_core_event function.
That doesn't make sense. Can't I just add another function without breaking everything? Note that I probably wouldn't even add this function to "the logging library" but to the core modules...
> what is a core module and what is not
If you prefer having everything switched behind your back, resulting in unreadable and unintelligible source code... you do you.
More technical notes...
If you don't like the presence of two or more functions for logging, then why not have an explicit context argument? You call log_event(MODULE_FOO, foo, xyz) instead, and the log_event function implements all the plumbing logic in a central place, easily understood (Most logging approaches do that, I think. They have "facility" and "severity" arguments). This approach seems much more preferable to me compared to an implementation that you can't see locally, and that can't be influenced locally. What would you do if we go for the OOP approach with a virtual method, and now call into a different module that would need a different logger?
If having a dynamically switched implementation was really what we want to do, there is no problem with doing that. My post was just about using method syntax for static things, which I think harms readability.
well, that cuts short to the discussion. There are plenty of places & coding standards where those are outright forbidden.
> If you prefer having everything switched behind your back, resulting in unreadable and unintelligible source code... you do you.
we have very different notions of readable. For me the less stuff I have to read and the more readable it is - I just want my code to be the pure domain problem and could not give a rat's ass about how logging or networking or whatever is implemented if those aren't my domain.
> Can't I just add another function without breaking everything?
that does not break the code but that breaks Joe intern's mental model and expectations of anyone who will think that log_* functions are from the same library
> then why not have an explicit context argument?
and here we are, reinventing OOP by hand in C. There is literally no difference between log_event(context, ...) and context.log_event(...) - but you get better tooling :
* completion -for the first I'd most certainly end up typing the full name if there are a lot of l-started functions while for the second I'd type c<shortcut>.l<shortcut><enter> and only see the few relevant functions in the autocompletion list) etc... in the second.
* namespacing - if I want to see all the logging functions available I can just click on "context" and my IDE will happily show me all the available methods in a panel and nothing else. With C functions, well, I have to scroll in a list of 150000 functions. And also wonder (& have to explain) why log_event() is in <util/logger.h> and log_event_core is in <core/core_logger.h>.
* split implementations - if at some point you want to port your software to, say, an arduino without network capabilities (talking literally forom past experience here), the monolithic log_event function now becomes an #ifdef HAS_NETWORK / #ifdef HAS_JOURNALD / ... mumbo-jumbo. While with multiple logger subtypes it's just a matter of changing your build system to not compile the unavailable implementations, without even needing to change the code (or change a list of types in a header if you use static polymorphism instead).
> What would you do if we go for the OOP approach with a virtual method, and now call into a different module that would need a different logger?
dependency injection ? modules can request either for a default logger which will be sourced from the gui configuration, or whatever specific logger they actually need due to functional requirements.
Besides, I still don't see the readability problem with virtuals. Again in my IDE if I want to see "what happens" I press F2 on context.log_event and it displays me the list of all the possible implementations if it isn't able to determine that one is used statically at that point. And I much prefer to read three five-lines email_logger::log, journald_logger::log, websocket_logger::log functions than a pile of if/else's.
But when I had a glance at the project you linked on github, that was basically the first thing I found there...
A suggestion that I have there would be to use the linker instead (make different source files based on implemntation / existance of an implementation). This way you can eliminate most of the #ifdefs.
Regarding dependency injection, I don't see a practical difference to just using global variables / global functions, apart from the fact that by convention DI gives you back objects with virtual implementations, which I don't like.
I won't go into the rest of the things you said because we pretty much discussed it all.
* jar-hell (and dll-hell) aside
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.
For a clearer description of what I mean, see my other comment below where I write two lines of code in both ways.
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.
> 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.
pub fn add(a: i64, b: i64) i64 {
return a - b;
}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 = ReflHow 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.
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 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.
(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.)
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.
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.
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 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.
Current job I've seen single files that are over 30k lines.
“Code should be written for humans to read and only incidentally for machines to run” (paraphrasing a famous quote by Harold abelson)
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.
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)
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.
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.
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.
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.
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.
I must also admit I woild rather ten lines of code and be clear and certain, so being verbose doesn't bother me much.
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.
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'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.
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.
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 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.
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 can rewrite any bad written code in less lines, same language same framework as initial code.
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.
How does it compare to tmux/screen?
* 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 think the conundrum of java is not quite the same thing as strictness.
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.
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.
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.
That said, I think OOP education focused a lot on inheritance during Java's rise and that was mostly a mistake in hindsight.
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.
With Spring you get to ask all these questions...with all the boilerplate of Java as an added bonus!
Are you using an MVC web framework, or something like that? Asking because I'm curious about other people's experience with Rust.
Writing code without dependencies/frameworks that have a required structure is a joy, however.
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.
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.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 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.
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.
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?
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 use Sublime with a handful of plugins, linters, etc... enabled. But if someone wanted to hack on my projects with Notepad they totally could.
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 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.
Small programs can be written in java. It's a general purpose programming language. You could write grep in java.
But I thought we were talking about Java:
"For example Java programs are comparatively slow to boot up."
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 real reason to move away from Java is to move towards different software architectures (microservices!)
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).
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.
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.
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 result is an excellent toolchain. But also you are completely unproductive unless you master said toolchain.
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.
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.
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.
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.
But yes - Java/python, etc. you shouldn't need to do this.
I’m generally hesitant to add test libraries if there’s an easy design choice alternative that keeps the code simpler.
edit: Not "Moquitto".
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.
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.
(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.
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.
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".
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.
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.
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.
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.
It's one of the reasons I tend to minimise my use of mocks as much as possible.
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.
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.
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.
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.
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 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.
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.
This often ends up with interfaces...
> and using integration code to wrap the pure and testable parts.
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.
OP is probably referring to the JDK Proxy support which is used by Spring and requires classes to implement at least one interface.
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.
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.
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.
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...
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.
Abstractions are not evil, but it takes experience to use just enough abstraction.
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.
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's kind of like how cars are implicated in traffic problems. Everywhere you have heavy traffic, you'll find cars.
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.
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.
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.
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.
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.
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 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 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.
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!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.
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.
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.
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.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.
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.
No thanks. Scala had a shot these past ten years but it failed and the world has moved on.
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.
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.
But I write Kotlin all the time and nullness checking is one of its best features. I wouldn't want to go back.
80% boilerplate is a large exaggeration.
if e3 != nil {
return _, e3
}
over and over and over.. e3?
^ Go could very easily add this syntax, but haven't for some reason.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.
value := getValue()
Java:
int value = getValue()
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.Once you get into more complicated types things get even harder to understand.
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.
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").
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.)
The lack of sum types and pattern matching notwithstanding, such a signature can be implemented reasonably in Java.
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.
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.
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.
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...
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.
`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?
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.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.
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.
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).
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.
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.
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.
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.
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.
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.
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.
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.
Many other languages do just fine without them. What makes Java so different?
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.)
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.
In a lot of cases, immutable objects are superior: simpler to reason about, have less code, and cost as much as mutable objects.
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
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.
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.
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).
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.
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'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.)
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 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.)
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.
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.
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!
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.
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.
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...
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)
The code worked, but 80% of it added no value.
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.
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.
Edit: for example, Python “annotations” are generally just functions that return wrapped functions/attributes/classes/whatever. Java annotations seem significantly more “magical” than that.
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.
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.
... that also doesn't actually benefit you in any tangible way.
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.
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.
> 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...
Frankly, after some recent development on ES6 + node + express, I'm starting to question whether staying on Spring + Java is worth it.
This. My god, this.
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.
to what baseline are you comparing it to?
Too late to edit now though.
Rust, C, and Go appear to be on the top.
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.
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.
Uhhh... you should have unit tests no matter what language (or culture) you're dealing with.
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.
Such as?
It has relatively recently gained usable high order functions, and seems to have stopped there.
I'm just trying to understand where you're coming from.
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.
> 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.
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.
- 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.
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.
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 work really well: they are easy to write, easy to write and typesafe enough.
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 each case (and presumably many more) you can just go and write a unit test for each of these disproving them.
argparse4j has existed forever and its a port of python's argparse [0].
-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.
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.
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!).
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.
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?
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).
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.
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?
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.
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;
};
}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.
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.
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 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 :)
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.
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.
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.
> using groovy is amazing, and can reduce your lines of code quite a bit
?
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.
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.
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.
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.
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.
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.
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.
Have you considered that you might be doing it wrong?
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.
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.
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
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.
Could you give us some background on your professional experience? There is no "About" section on the site.
https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpris...
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
> 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.
Another alternative is Groovy
Honestly the only reason I would choose Kotlin over Java today is properties. Everything else is nice to have, but absolutely not important.
I would argue the null-checking is important.
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.
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.)
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.
Each concatenation will create a new string. That is not good.
If you find Java verbose, just use Scala or Kotlin.
[1] https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.htm...
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.
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'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.
How do I map the numbers back to their sources?
What the hell happened to the world.
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.
...
"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!"
I think the right consideration is that they have simply done a poor job writing this program.