226

I am junior developer among seniors and am struggling a lot with understanding their thinking, reasoning.

I am reading Domain-Driven Design (DDD) and can't understand why we need to create so many classes. If we follow that method of designing software we end up with 20-30 classes which can be replaced with at most two files and 3-4 functions. Yes, this could be messy, but it's a lot more maintainable and readable.

Anytime I want to see what some kind of EntityTransformationServiceImpl does, I need to follow lots of classes, interfaces, their function calls, constructors, their creation and so on.

Simple math:

  • 60 lines of dummy code vs 10 classes X 10 (let's say we have totally different such logics) = 600 lines of messy code vs. 100 classes + some more to wrap and manage them; do not forget to add dependency injection.
  • Reading 600 lines of messy code = one day
  • 100 classes = one week, still forget which one does what, when

Everyone is saying it's easy to maintain, but for what? Every time you add new functionality, you add five more classes with factories, entities, services, and values. I feel like this kind of code moves a lot slower than messy code.

Let's say, if you write 50K LOC messy code in one month, the DDD thing requires lots of reviews and changes (I do not mind tests in both cases). One simple addition can take week if not more.

In one year, you write lots of messy code and even can rewrite it multiple times, but with DDD style, you still do not have enough features to compete with messy code.

Please explain. Why do we need this DDD style and lots of patterns?

UPD 1: I received so many great answers, can you guys please add comment somewhere or edit your answer with the link for reading list (not sure from which to start, DDD, Design Patterns, UML, Code Complete, Refactoring, Pragmatic ,... so many good books), of course with sequence, so that I can also start understanding and become senior as some of you do.

user1318496
  • 1,817

11 Answers11

352

This is an optimization problem

A good engineer understands that an optimization problem is meaningless without a target. You can't just optimize, you have to optimize for something. For example, your compiler options include optimizing for speed and optimizing for code size; these are sometimes opposite goals.

I like to tell my wife that my desk is optimized for adds. It's just a pile, and it's very easy to add stuff. My wife would prefer it if I optimized for retrieval, i.e. organized my stuff a bit so I can find things. This makes it harder, of course, to add.

Software is the same way. You can certainly optimize for product creation-- generate a ton of monolithic code as quickly as possible, without worrying about organizing it. As you have already noticed, this can be very, very fast. The alternative is to optimize for maintenance-- make creation a touch more difficult, but make modifications easier or less risky. That is the purpose of structured code.

I would suggest that a successful software product will be only created once but modified many, many times. Experienced engineers have seen unstructured code bases take on a life of their own and become products, growing in size and complexity, until even small changes are very difficult to make without introducing huge risk. If the code were structured, risk can be contained. That is why we go to all this trouble.

Complexity comes from relations, not elements

I notice in your analysis you are looking at quantities-- amount of code, number of classes, etc. While these are sort of interesting, the real impact comes from relations between elements, which explodes combinatorially. For example, if you have 10 functions and no idea which depends on which, you have 90 possible relations (dependencies) you have to worry about-- each of the ten functions might depend on any of the nine other functions, and 9 x 10 = 90. You might have no idea which functions modify which variables or how data gets passed around, so coders have a ton of things to worry about when solving any particular problem. In contrast, if you have 30 classes but they are arranged cleverly, they can have as few as 29 relations, e.g. if they are layered or arranged in a stack.

How does this affect your team's throughput? Well, there are fewer dependencies, the problem is much more tractable; coders don't have to juggle a zillion things in their head whenever they make a change. So minimizing dependencies can be a huge boost to your ability to reason about a problem competently. That is why we divide things into classes or modules, and scope variables as tightly as possible, and use SOLID principles.

candied_orange
  • 119,268
John Wu
  • 26,955
70

Well, first of all, readability and maintability are often in the eye of the beholder.

What is readable to you may not be to your neighbour.

Maintainability often boils down to discoverability (how easily is a behaviour or concept discovered in the codebase) and discoverability is another subjective thing.

DDD

One of the ways DDD helps teams of developers is by suggesting a specific (yet still subjective) way of organising your code concepts and behaviours. This convention makes it easier to discover things, and therefore easier to maintain the application.

  • Domain concepts are encoded as Entities and Aggregates
  • Domain behaviour resides in Entities or Domain Services
  • Consistency is ensured by the Aggregate Roots
  • Persistence concerns are handled by Repositories

This arrangement is not objectively easier to maintain. It is, however, measurably easier to maintain when everyone understands they're operating in a DDD context.

Classes

Classes help with maintainability, readability, discoverability, etc... because they are a well known convention.

In Object Oriented settings, Classes are typically used to group closely related behaviour and to encapsulate state that needs to be controlled carefully.

I know that sounds very abstract, but you can think about it this way:

With Classes, you don't necessarily need to know how the code in them works. You just need to know what the class is responsible for.

Classes allow you to reason about your application in terms of interactions between well defined components.

This reduces the cognitive burden when reasoning about how your application works. Instead of having to remember what 600 lines of code accomplishes, you can think about how 30 components interact.

And, considering those 30 components probably span 3 layers of your application, you probably only every have to be reasoning about roughly 10 components at a time.

That seems pretty manageable.

Summary

Essentially, what you're seeing the senior devs do is this:

They are breaking down the application into easy to reason about classes.

They are then organising these into easy to reason about layers.

They are doing this because they know that, as the application grows, it becomes harder and harder to reason about it as a whole. Breaking it down into Layers and Classes means that they never have to reason about the entire application. They only ever need to reason about a small subset of it.

MetaFight
  • 11,619
32

Please explain me, why do we need this DDD style, lots of Patterns?

First, a note: the important part of DDD is not the patterns, but the alignment of the development effort with the business. Greg Young remarked that the chapters in the blue book are in the wrong order.

But to your specific question: there tend to be a lot more classes than you would expect, (a) because an effort is being made to distinguish the domain behavior from the plumbing and (b) because extra effort is being made to ensure that concepts in the domain model are expressed explicitly.

Bluntly, if you have two different concepts in the domain, then they should be distinct in the model even if they happen to share the same in memory representation.

In effect, you are building a domain specific language that describes your model in the language of the business, such that a domain expert should be able to look at it and spot errors.

Additionally, you see a bit more attention paid to separation of concerns; and the notion of insulating consumers of some capability from the implementation details. See D. L. Parnas. The clear boundaries empower you to change or extend the implementation without the effects rippling throughout you entire solution.

The motivation here: for an application that is part of the core competency of a business (meaning a place where it derives a competitive advantage), you will want to be able to easily and cheaply replace a domain behavior with a better variation. In effect, you have parts of the program that you want to evolve rapidly (how state evolves over time), and other parts that you want to change slowly (how state is stored); the extra layers of abstraction help avoid inadvertently coupling one to the other.

In fairness: some of it is also Object Oriented Brain Damage. The patterns originally described by Evans are based on Java projects that he participated in 15+ years ago; state and behavior are tightly coupled in that style, which leads to complications you might prefer to avoid; see Perception and Action by Stuart Halloway, or Inlining Code by John Carmack.

No matter what language you work in, programming in a functional style provides benefits. You should do it whenever it is convenient, and you should think hard about the decision when it isn't convenient. Carmack, 2012

VoiceOfUnreason
  • 34,589
  • 2
  • 44
  • 83
30

There are many good points in the other answers, but I think they miss or don't emphasize an important conceptual mistake you make:

You are comparing the effort to understand the complete program.

This is not a realistic task with most programs. Even simple programs consist of so much code that it is simply impossible to manage all of it in the head at any given time. Your only chance is to find the part of a program that is relevant to the task at hand (fixing a bug, implementing a new feature) and work with that.

If your program consists of huge functions/methods/classes this is almost impossible. You'll have to understand hundreds of lines of code just to decide if this chunk of code is relevant to your problem. With the estimates you gave it becomes easy to spend a week just to find the piece of code you need to work on.

Compare that to a code base with small function/methods/classes named and organized into packages/namespaces that makes it obvious where to find/put a given piece of logic. When done right in many cases you can jump right to the correct place to solve your problem, or at least to a place from where firing up your debugger will bring you to the right spot in a couple of hops.

I have worked in both kinds of systems. The difference can easily be two orders of magnitude in performance for comparable tasks and comparable system size.

The effect this has on other activities:

  • testing becomes much easier with smaller units
  • less merge conflicts because chances for two developers to work on the same piece of code are smaller.
  • less duplication because it is easier to reuse pieces (and to find the pieces in the first place).
Cubic
  • 301
20

Because testing code is harder than writing code

A lot of answers have given good reasoning from a developer's perspective - that maintenance can be reduced, at the cost of making the code more strenuous to write in the first place.

However, there is another aspect to consider - testing can only be fine-grained as your original code.

If you write everything in a monolith, the only effective test you can write is "given these inputs, is the output correct?". This means any bugs found, are scoped to "somewhere in that giant pile of code".

Of course, you can then have a developer sit with a debugger and find exactly where the problem started and work on a fix. This takes a lot of resources, and is a bad use of a developer's time. Imagine that ever minor bug you have, results in a developer needing to debug the entire program again.

The solution: Many smaller tests that pinpoint one specific, potential failure each.

These small tests (for example Unit Tests), have the advantage of checking a specific area of the codebase and helping to find errors within a limited scope. This not only speeds up debugging when a test fails, but also means that if all your small tests fail - you can more easily find the failure in your larger tests (i.e. if it's not in a specific tested function, it must be in the interaction between them).

As should be clear, to make smaller tests means your codebase needs to be split into smaller testable chunks. The way to do that, on a large commercial codebase, often result in code that looks like what you're working with.

Just as a side-note: This isn't to say people won't take things "too far". But there is a legitimate reason for separating codebases into smaller/less connected parts - if done sensibly.

Bilkokuya
  • 347
  • 1
  • 6
18

Please explain me, why do we need this DDD style, lots of Patterns?

Many (most...) of us really don't need them. Theoreticians and very advanced, experienced programmers write books about theories and methodologies as the result of lots of research and their deep experience - that doesn't mean that everything they write is applicable to every programmer in their everyday practice.

As a junior developer, it's good to read books such as the one you mentioned to broaden your perspectives, and make you aware of certain issues. It will also save you from getting embarrassed and flummoxed when your senior colleagues use terminology you are unfamiliar with. If you find something very difficult and doesn't seem to make sense or seem useful, don't kill yourself over it - just file it away in your head that there is such a concept or approach.

In your day to day development, unless you are an academic, your job is to find workable, maintainable solutions. If the ideas you find in a book don't help you achieve that goal, then don't worry about it right now, as long as your work is deemed satisfactory.

There may come a time when you'll discover that you can use some of what you read about but didn't quite "get" at first, or maybe not.

Vector
  • 3,241
8

As your question is covering a lot of ground, with a lot of assumptions, I'll single out the subject of your question:

Why do we need so many classes in design patterns

We do not. There is no generally accepted rule that says that there must be many classes in design patterns.

There are two key guides for deciding where to put code, and how to cut your tasks into different units of code:

  • Cohesion: any unit of code (be it a package, a file, a class or a method) should belong together. I.e., any specific method should have one task, and do that one well. Any class should be responsible for one larger topic (whatever that may be). We want high cohesion.
  • Coupling: any two units of code should depend as little on each other as possible - there should especially be no circular dependencies. We want low coupling.

Why should those two be important?

  • Cohesion: a method that does a lot of things (e.g., an old-fashioned CGI script that does GUI, logic, DB access etc. all in one long mess of code) becomes unwieldy. At the time of writing, it is tempting to just put your train of thought into a long method. This works, it is easy to present and such, and you can be done with it. Trouble arises later: after a few months, you may forget what you did. A line of code at the top might be a few screens away from a line on the bottom; it is easy to forget all details. Any change anywhere in the method may break any amount of behaviours of the complex thing. It will be quite cumbersone to refactor or re-use parts of the method. And so on.
  • Coupling: anytime you change a unit of code, you potentially break all other units that depend on it. In strict languages like Java, you might get hints during compile time (i.e., about parameters, declared exceptions and so on). But many changes are not triggering such (i.e. behavioural changes), and other, more dynamic, languages, have no such possibilities. The higher the coupling, the tougher it gets to change anything, and you might grind to a halt, where a complete re-write is necessary to achieve some goal.

These two aspects are the base "drivers" for any choice of "where to put what" in any programming language, and any paradigm (not only OO). Not everybody is explictly aware of them, and it takes time, often years, to get a really ingrained, automatic feeling on how these influence software.

Obviously, those two concepts do not tell you anything about what to actually do. Some people err on the side of too much, others on the side of too little. Some languages (looking at you here, Java) tend to favour many classes because of the extremely static and pedantic nature of the language itself (this is not a value statement, but it is what it is). This becomes especially noticeable when you compare it to dynamic and more expressive languages, for example Ruby.

Another aspect is that some people subscribe to the agile approach of only writing code that is necessary right now, and to refactor heavily later, when necessary. In this style of development, you would not create an interface when you only have one implementing class. You would simply implement the concrete class. If, later, you need a second class, you would refactor.

Some people simply do not work that way. They create interfaces (or, more generally, abstract base classes) for anything that ever could be used more generally; this leads to a class explosion quickly.

Again, there are arguments for and against, and it does not matter which I, or you, prefer. You will, in your life as software developer, encounter all extremes, from long spaghetti methods, through enlightened, just-big-enough class designs, up to incredibly blown-up class schemes that are much overengineered. As you get more experienced, you will grow more into "architectural" roles, and can start to influence this in the direction you wish to. You will find out a golden middle for yourself, and you will still find that many people will disagree with you, whatever you do.

Hence, keeping an open mind is the most important bit, here, and that would be my primary advice to you, seeing that you seem to be much in pain about the issue, judging by the rest of your question...

AnoE
  • 5,874
  • 1
  • 16
  • 17
7

Experienced coders have learned:

  • Because those smallish programs and clasess start to grow, especially successful ones. That simple patterns that worked at a simple level don't scale.
  • Because it may seem burdensome to have to add/change several artifacts for every add/change but you know what to add and it is easy to do so. 3 minutes of typing beats 3 hours of clever coding.
  • Lots of small classes is not "messy" just because you don't understand why the overhead is actually a good idea
  • Without the domain knowledge added, the 'obvious to me' code is often mysterious to my team mates... plus the future me.
  • Tribal knowledge can make projects incredibly hard to add team members to easily and hard for them to be productive quickly.
  • Naming is one of the two hard problems in computing is still true and lots of classes and methods is often an intense exercise in naming which many find to be a great benefit.
2

The answers so far, all of them good, having started with the reasonable assumption that the asker is missing something, which the asker also acknowledges. It's also possible that the asker is basically right, and it's worth discussing how this situation can come about.

Experience and practice are powerful, and if the seniors gained their experience in large, complex projects where the only way to keep things under control is with plenty of EntityTransformationServiceImpl's, then they become fast and comfortable with design patterns and close adherence to DDD. They would be much less effective using a lightweight approach, even for small programs. As the odd one out, you should adapt and it will be a great learning experience.

While adapting though, you should take it as a lesson in the balance between learning a single approach deeply, to the point you can make it work anywhere, versus staying versatile and knowing what tools are available without necessarily being an expert with any of them. There are advantages to both and both are needed in the world.

Josh Rumbut
  • 197
  • 1
  • 4
1

Since my initial post of this answer didn't accomplish what I wanted, I purchased the for-Kindle version of this book, and found exactly what I vaguely remembered, in order to directly answer the question

Why do we need so many classes in design patterns?

Short and sweet -- we don't. Exactly why that is true I'm not sure that I can put into words as well as this book, and that was the gist of my initially posted answer.

What follows are excerpts from the book that speak well directly to the question.

This excerpt from the book is from Chapter 3 "Patterns" and sub-chapter 3 "There Are Many Ways to Implement a Pattern":

In this book, you’ll find pattern implementations that look quite different from their Structure diagrams in Design Patterns. For example, here’s the Structure diagram for the Composite pattern:

Structure diagram for the Composite pattern

And here is a particular implementation of the Composite pattern:

A minimalistic implementation of the Composite pattern

As you can see, this implementation of Composite bears little resemblance to the Structure diagram for Composite. It is a minimalistic Composite implementation that resulted from coding only what was necessary.

Being minimalistic in your pattern implementations is part of the practice of evolutionary design. In many cases, a non-patterns-based implementation may need to evolve to include a pattern. In that case, you can refactor the design to a simple pattern implementation. I use this approach throughout this book.

Slightly earlier in the sub-chapter, Joshua says this:

This diagram indicates that the Creator and Product classes are abstract, while the ConcreteCreator and ConcreteProduct classes are concrete. Is this the only way to implement the Factory Method pattern? By no means! In fact, the authors of Design Patterns go to great pains to explain different ways to implement each pattern in a section called Implementation Notes. If you read the implementation notes for the Factory Method pattern, you’ll find that there are plenty of ways to implement a Factory Method.

Here is that whole section, which goes into just a little more detail:

Page screen shot from the Joshua Kerievsky book "Refactoring to Patterns" Kindle Edition, 13%, Location 1134 of 9053


[ORIGINAL POST]

I would like to point you to a book that I think answers your question with more of a heuristic sense of when it's necessary, and when it's overkill, to "use so many classes" in design patterns, as this author often implements a design pattern very frugally, with just a few object-oriented "parts".

enter image description here

This book is one of the Martin Fowler Signature Series, and is an excellent book on Design Patterns by Joshua Kerievsky, entitled "Refactoring To Patterns", but it is really about "Right-Factoring" to and/or away from patterns. It was well-worth the price, to me.

In his book, he sometimes implemented patterns without using object orientation, and instead used a few fields and a few methods, for example. And he sometimes removed a pattern altogether because it was overkill. So I loved his treatment of the subject because the "balance" of when to pattern, and when to go more lightweight, was a gift he imparted to the reader.

Here is a screen shot of the most relevant part of the table of contents. It was one of my best purchases.

Table of Contents Screenshot of Book Refactoring To Patterns by Joshua Kerievsky

And out of this table of contents, I especially emphasize the following:

  • And I emphasize the following:
  • Over-Engineering
  • The Patterns Panacea
  • Under-Engineering
  • Human-Readable Code
  • Keeping it Clean
  • Patterns Happy
  • There Are Many Ways to Implement a Pattern
  • Refactoring to, towards, and away from Patterns
  • Do Patterns Make Code More Complex?
  • Code Smells

So, I think your question can be paraphrased as an internal sense of questioning "Do we really have to use so many classes to implement patterns?" and the answer is "sometimes", and of course, the seemingly ever-present "it-depends". But it takes more of a book to answer you, so I am recommending that you get this one if you can. (I don't actually have it with me at the moment, otherwise I would give you a good example from it -- sorry.) HTH.

-4

Making more class and function as per use will be a great approach to resolve problems in a specific manner that help in future to resolve any issues.

Multiple classes help to identify as their basic work and any class can be called at any time.

However if you many class and functions from their getable name they are in easy to call and manage. This is called a clean code.