5 ms·
I've seen people take Bob Martin's concepts and do some truly awful things with it. Mind-bogglingly awful contortions of concepts into classes in arrangements t
by arcbyte 3y ago
I've seen people take Bob Martin's concepts and do some truly awful things with it. Mind-bogglingly awful contortions of concepts into classes in arrangements that have to be sourced from demonic inspiration.
At the same time, I've seen the best code of my entire life formed from his concepts. Code that will last decades, far outlasting the UIs that feed it data or the databases that will store it.
I think the difference is all on whether the developers who wrote it understood that the "concepts" are not meant to be put into code on a 1 for 1 basis. For example, making a AddToDoUseCasePresenterInteractor class is literally taking the concept and making it 1 to 1 in the code. On the other hand, writing domain appropriate code, minimizing accidental complexity, and recognizing the clean code concepts as emerging from groups of classes and methods and packages in a code base leads to really clean, testable, maintainable, and FAST TO WRITE code.
I think the single biggest improvement for java programmers is to group all the classes related to a use case together in the same package - which means STOP MAKING "controller", "service", "model", etc packages where every different unrelated except by use is just dumped. If you're working on a part of the code base you should just have to change classes in one single folder. A new feature should just be a new folder. That change alone speeds up teams by huge factors.
- ajmurmann 3y agoYour last paragraph is really interesting to me. It obviously makes total sense. Yet, three environment I've used most in my career is Rails which also splits these things into folders by type rather than feature. It's never bothered me. Now I wonder if that because Ruby isn't Java and folders aren't packages or because I'm so very used to it.
- _ea1k 3y agoI've worked in a few codebases that tried to group things by feature. In my experience, it never really worked that well. Usually there would either be poor isolation between them or they'd be so well isolated that I'd wonder why they were even in the same project. In the latter, they'd often be difficult to maintain because of a web of dependencies pulled in by the little isolated subfeatures. I prefer the separation by type, tbh. It also has the upside that it naturally encourages the developer to follow the same patterns within that particular package.
- lcnPylGDnU4H9OF 3y agoI'm not sure what I prefer because I'm most familiar with Rails. I guess I'm used to type-separation. An individual Rails app could be structured using "engines"[0], which could easily allow for this kind of separation. Each engine will have its own app/ directory which contains models/, controllers/, services/, etc. The point is that the feature is its own "project" which would likely be loaded in the host application as a gem. I don't think this is actually a strong convention either way in Rails, so it would still be compatible with convention-over-configuration to build an app this way. (Kinda just thinking out loud. FWIW, I have worked on an application with a similar sort of "engine-primary" structure but not what I currently work on.) [0] https://guides.rubyonrails.org/engines.html https://guides.rubyonrails.org/engines.html
- arcbyte 3y agoThere's a certain amount of disciplined duplication that you have to adopt as well. I think this hangs a lot of people up and failure to do it right leads to the complications you're discussing. Adding a todo note, marking it done, editing it, and deleting it are all different use cases and each have thier own package and classes. However, most people want to have some kind of single ToDoNote class which is the "To Do Note". Doing that means they have to decide which package to put it in and then pull it in everywhere else. And then "common" logic starts piling up and features depend on crap from other features and accidental complexity starts creeping in. Now you've got a single ToDoNote class that has different sets of properties null or pipulated depending on where it was instantiated and what use case its being fed to, all requiring the programmer to keep this in their head instead of getting the compiler to help. The reality is that the set of data you need to create a todo note is different from the data you need to edit it which is different from that which you need to delete it. They share common elements, but never at the same time. The solution is to create "NewToDo", "EditedToDo", and "DeleteToDo" models that for each feature. Sure, some of the models will share a "title" property (new and edit), and some will share an "id" property (edit and delete) but never all at the same time. This offloads this complexity from the programmer to the compiler and speeds up development. You already know youre operating at the lowest of the low leves of excellence if you rely on monkey see monkey do code standards. Having low complexity code with minimal programmer brain space needed to operate in is how you level up to higher levels.
- gregmac 3y ago> If you're working on a part of the code base you should just have to change classes in one single folder. A new feature should just be a new folder. That change alone speeds up teams by huge factors. This is a neat idea. Have you done this in practice, and how does it work over a long time frame? One of the big advantages of separate packages is purely for references: The model package has no reference to service or database code, so it's not possible to include SQL or other hidden service calls in it -- at least not without adding a new dependency which makes it blatantly obvious you're doing something wrong. On the other hand, if your features are self-contained, and you have good unit test coverage of all the logic, then I guess it doesn't really matter as much what the structure is. The fact it's unit tested forces it to be loosely coupled, and testability is one of the main reasons to organize code into layers in the first place.
- putnambr 3y agoBeen doing this for a few years. If I have an `Item` class, it's going into its own package. Along with `ItemService` (business logic), `ItemResource` (endpoint), `ItemDao` (persistence interface), etc. If `Widget` has a dependency on `Item`, then `WidgetService can either import `ItemClient` or roll its own. Makes it super easy to split out microservices when the monolith gets big. Just keep from injecting one Service class into another, rely on the Resource or Client instead.
- damethos 3y ago> WidgetService can either import `ItemClient` or roll its own Can you clarify what is ItemClient in your context?
- tracker1 3y agoBeen doing similar for years... I refer to it as feature oriented structure/organization. To me, that includes tests. I hate that so many code bases are effectively mirrored trees that are hard to break apart. You can still have effective layers, even classes if you like them. But there's little reason they can't live next to each other on disk in the same project even.
- 3y ago
- 0xdeadbeefbabe 3y ago> sourced from demonic inspiration. Actually writing demonic technical debt on purpose in one sitting would be quite an accomplishment too.