Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

The real evil here is boilerplate code.

I've seen so much boilerplate in the Java or classic .NET Framework world, it's incredible. So many layers of DTOs, Request/Response Models and so on, that could be just generated. Or most of the time even removed completely (that would cost some "architects" their job though).

This is also true for a lot of Redux or Angular/NgRx applications. So much boilerplate, that you can't find the relevant code anymore.



(I have been professionally programming Java backends for the past 16 years).

Java is not the culprit here.

I think it is something that happened on the way that has something to do with J2EE and patterns craze we had a decade ago or two ago.

It doesn't help that frameworks like Spring and their documentation go out of their way to propagate these boilerplate-heavy patters.

Copying these lazy patterns is shortest, easiest way to get to working solution for a person that doesn't want to put any extra effort. And you can't get punished for doing this. Most developers don't even know there exist any other possibilities than mandatory controller calling service calling database layer and hordes of DTOs some people call "model".


I'm working on a Dart / Flutter project where most devs are coming from Java and Android backgrounds. For me, coming mostly from JavaScript, TypeScript, and Python, the amount of pointless over-engineering is very frustrating.

We need to jam through every change through 10 layers now, because of "clean architecture". The team is very slow and can't implement even small changes quickly.

The worst part is that I feel like I'm the idiot for thinking about whether the 50 classes (dtos, models, mappers, blablabla) actually make sense and reduce coupling. I see that anytime a tiny requirement changes, I need to update the 50 classes again, so in practice, it's just doesn't bring anything positive.

When I raise my concerns, they just roll their eyes, and make me feel like "I'm just not a senior enough guy" who just accidently got in the team.


I feel your pain, I am in much the same situation just in a tech lead position.

It takes a lot of patience to undo this damage and explain that simplicity is much more important than lazily, mindlessly repeating "best" practices. I am using quotes intentionally because they aren't actually best -- "best" would suggest there are no better practices which obviously cannot be true.

The goal should always be to make the application simple and easy to work with. Patterns should be tools to achieve the goal rather than being goals themselves.

Simple is important because it allows understanding your application (which is important for developer efficiency as well as improving reliability). It also enables you to modify your application much more easily (more code usually means more work to change it) and this is important to fighting technical debts and to reduce cost of any future development.


> When I raise my concerns, they just roll their eyes, and make me feel like "I'm just not a senior enough guy" who just accidently got in the team.

Easier to do that than just admit technical dept. Some people cannot acknowledge a problem and live alongside it if it is too large to tackle immediately. It has to be explained away or compartmentalized. How simple it is blame the messenger. Sorry you had to experience that from your team.


> We need to jam through every change through 10 layers now, because of "clean architecture".

I feel like this is a rather unfair comment because it doesn't sound like a situation created by "clean architecture."

Granted, you probably should not try and force every detail into this architecture just like you should not rewrite a perfectly good library just because it does not fit into it nicely. But even then; drilling through half a dozen or more layers for every change sounds just wrong. There should not be just any kind of separation in your program. There should be a separation of concerns.

The real problem seems to me a culture in which "We do $X because of $AUTHORITY." is regarded as a sensible answer to criticism. I have worked with exceptionally confident, almost blinkered, people in charge of the big picture and never once have I heard a bullshit answer like that.


agree

i think the other thing is, theres clean architecture and then theres Clean Architecture TM where the thing is taken literally (leading to slavishly applying all the layers with lots of boilerplate, useless mocks and ridiculously coupled unit tests, over architected dependency injectors (assemblies) etc)

i was honestly surprised when i watched a series of lectures from mr uncle (bob) where he clarified a lot of things such as "use dependency injection only where it matters" and "unit tests should be replaced with integration tests after a system is finished being implemented" to "slavish following of agile "customs" is unproductive" etc etc

i think a lot of issues could be resolved if people took the time to think and listen carefully about these things and not stop at the first couple of search hits for "clean architecture"

edit:

heres the link: https://www.youtube.com/watch?v=7EmboKQH8lM&list=PLmmYSbUCWJ...


I totally feel your pain. Go and work somewhere else.

What you can sometimes do, is to remove all those layers to be able to implement or fix something. Then tell the team that you didn’t have time „to do it properly“ and you focused on functionality and efficiency over design. Management loves that.

And after it’s done, some of those abstraction nazis can refactor in all those abstractions again. So they don’t distract you from the next meaningful task.

But make sure, that you understand what benefit this decoupling brings. Because sometimes it’s useful, just not often enough.


>Java is not the culprit here.

It definitely is the culprit. They didn't even want to add `var` to the language until recently, and let's not even go to the anonymous class vs lambdas retardation.

These are just the things that they eventually buckled on, but Java is extremely boilerplatey - the bad patterns and XML crap got invented to deal with that problem.

DDD and onion are another issue, mostly coming out of the TDD movement and "make everything unit-testable". If I liked one thing about working with Rails is that they just gave you E2E tests from the start. But if Java/.NET were more flexible (dynamic or FP do far better at enabling simpler unit testing from my experience) mocking would be simpler so the unit testing part would be simpler too.


The TDD movement came from smalltalk programmers (not that I think it has anything to do with smalltalk, just the programmers came from there). In my experience it was in Ruby and javascript code where I have seen the most inane micro(nano?)-unit tests. Some of this was partly because the unit tests were testing what a static typechecker could verify automatically (at the cost of a little verbosity).

I don't see how you figured there is a relationship between DDD and "make everything unit testable". DDD is about high level architecture. It's at the opposite end of the spectrum.


Also, smalltalkers didn't mean the same thing by "unit". What would be called unit tests in one context may mostly be considered integration tests in another.

I agree, the crazy mock- and stub-heavy unmaintainable micro unit testing thing seems to have been an innovation that came out of the Ruby scene.


Maybe I shouldn't call it TDD camp, but the people that were prothletising TDD were also selling onion/DDD in C# world, so I just bunch those together, it's more about onion, layered architectures, testing at different layers and mocking them, etc.

While I consider TDD the wrong approach in 90% of scenarios, in dynamic languages it works out much better because the object model is so flexible you can mock just about anything trivially. In C# and Java it's just boilerplate on top of boilerplate.


>They didn't even want to add `var` to the language until recently, and let's not even go to the anonymous class vs lambdas retardation.

This realization is always endlessly infuriating to me as someone who was taught way to much Java in Uni, and had to intensionally push myself into other languages to realize why simple things like higher-order functions were subjugated under the tyranny of classes in java.

But far worse than that is the absolutely abysmal and destructive philosophy around types in java. Just mash em together with namespaces and classes, and then nerf type inference to the point that it couldn’t infer what is literally the most trivial reflexion-based equality: “Object o = New Object()”.


It's not the fault of the language or the runtime. But some common Java/.NET frameworks nudge you into this direction.

Also OOP is very commonly abused in those languages, to make easy stuff more complicated.


Just because objects are involved doesn't mean it is OOP code... OOP is completely misunderstood especially in Java community to the point where it is pretty difficult to see actually object oriented code.

A "service" with a bunch of stateless functions (I am intentionally not calling them methods) is really just a library of routines and the class is used mostly for namespace purposes (to group related functions together) and maybe deliver access to some dependencies. But those dependencies could be thought about almost the same way as global variables in a C program, because usually there exists only single instance of the service.

Neither are DTOs being passed between these services an OOP meachanism -- they are almost C-like structs to make it easier to pass data between functions and to have single reference to them. The only exception maybe is things like equals(), hashcode() etc, but this is very shallow use of OOP patterns.

So it is really difficult to say this is abuse of OOP, when there is very little of actual OOP in it.


The pattern you are describing here (Service classes with static'ish methods together with data classes) is a very functional approach (modules and records). I would consider this much better than "real" OOP.

The OOP "abuse" I was referring to is mostly caused by inheritance. Five or more levels of inheritance is not so uncommon in some enterprise business logic. And once you have to work with that, you arrived in hell. Especially if it is split into different projects, that you can't navigate or debug as one easily.


In java 16, they've added record objects to help with this pattern.


An IDE or other tools will generate correct boilerplate code. Seems a gripe from someone that prefers hidden magical code that setups code behind their back.

The evil is that someone trained an AI on random text , not even with some AST, so you have garbage in so no surprise you get garbage out.

A true AI would understand that "the dev wants trough find all lines of text in a file that have this property", the AI just does "this code string is similar to this other code string using this `black box metric`"


The problem with boilerplate code is, that it is mostly generated once (by the ide) and then slightly modified. More like a template. In the end you get a lot of meaningless code (the generated code), with some meaningful parts inside. But you can’t see anymore what was generated and what was added manually without deep analysis of the commit log.

It is much better to generate code on the fly during build, so it doesn’t even go to source control and people can’t modify it.


Code generated by magic is worse in my experience, with non-magic code you put a breakpoint where the project starts and you can run step trough it line by line, function by function and it makes sense. What I hate are magic frameworks that are terrible at reporting the issues, say in angular1 you have some bindings and soemtimes they don't trigger , you can't debug the magic strings of the templates in the debugger to see what is happening (it could be a typo but because is all Google dev magic it won't warn you about it).

I hope we are talking about same things, like you dislike creating classes and function explicitly and prefer adding a comment or some templating system that generates a ton of obscure code behind the scenes.


Generated code doesn't have to be magical. The generated code should off course be reviewed by a person from time to time. It must be debuggable and readable/understandable too. Otherwise it doesn't make any sense.

A good example for generated code are typed clients for an OpenAPI interface. Instead of writing a REST client on your own based on a spec, you generate it. And if something isn't right in the first place, don't edit the generated code, fix/configure the generator instead!

Or database models. Either generate the database from the models or the models from the database.


>Generated code doesn't have to be magical.

Sure, other good example is for example in Qt, the Designer tool will create some XML that shows the widget you placed properties, then a tool will generate code that is easy to read(not obfuscated or clever).

Can you give some examples on what kind of bad code generation/boilerplate you mean when you think at Java/C# ?


Not without trade-off though, I've been on both spectrum. Template generated code allows you to modify things if you know how, while generation on the flies will need a bunch of options, hooks, and worse string-based evaluation call to make it modifiable.

So it's better for code with little to no modification, while boilerplate / template are better for things that will be modified.


> Template generated code allows you to modify things if you know how

That's exactly the problem. and because templates allow you to modify the code, the template creators do no work on generalizing it so it covers all the use cases. So, on practice, templates usually require that you modify it.

And now you have a huge codebase, mostly with the default text, but with some changes at random, and one of those changes is breaking it. Good luck finding it.


Or you can use Lisp, call your boilerplate generator a macro and get rid of any boilerplate and never again have any problems with modifying any duplicate pieces of code.

Sigh... every time I see people generating any code (say through external tool or through IDE) I am always thinking how this could be a simple macro in any Lisp language.


Templates are a fancy way of copy&paste programming.

So if you need a lot of template code, then your design or your framework is not well suited for the task.

In software development the goal is usually to move commonly used functions or patterns into a library or a framework, instead of copy&pasting them with slight modifications.


Between explicit boilerplate vs. hidden magic, give me the boilerplate every time. At least then it is obvious (maybe painfully obvious) what code is executing. Hidden magic is the worst since code should be optimized for reading and diagnosing, not for write-time which only happens once.


This is because "Copy'n'paste" programming get's more and more common.

I see more and more juniors pasting code or shellcommands from StackOverflow with careless ease, without even pretending anymore that they're interested in how it actually works.


Should look at Vue with the composition-api layer, there's close to zero boilerplate.

A store in Vue 3 can basically be:

  export default { state: readonly(state), ...setterFunctions }
It doesn't get more easy to read and streamlined than that.


It is true that lines of code are correlated with bugs. In fact, that's the best predictor of the number of bugs - there was some study somewhere that concluded that.

I still doubt that that's a result of DTOs.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: