How to understand the big picture in a connected application?

We develop code using loose coupling and dependency.

Many "service" style classes have a constructor and one method that implements the interface. Each individual class is very easy to understand in isolation.

However, due to the weakness of the coupling, looking at the class, you do not say anything about the classes around it or where it is suitable for the larger picture.

It's not easy to switch to employees using Eclipse because you have to go through the interfaces. If the interface is Runnable , this will not help in finding which class is really connected. Indeed, it is necessary to return to the definition of the DI container and try to figure out what is from there.

Here's a line of code from a user-entered class of service: -

  // myExpiryCutoffDateService was injected, Date cutoff = myExpiryCutoffDateService.get(); 

The grip here is as loose as it can be. The expiration date will be realized literally in any way.

Here's what it might look like in a more complex application.

  ExpiryDateService = new ExpiryDateService(); Date cutoff = getCutoffDate( databaseConnection, paymentInstrument ); 

From a closely related version, I can conclude that the cutoff date is somehow determined from the payment instrument using a database connection.

I find the first style code more complicated than the second style code.

You can argue that when reading this class I don't need to know how the cut-off date is calculated. This is true, but if I sue for a mistake or work where extension is required, this is useful information.

Anyone else having this problem? What are your solutions? Is this something you can adapt to? Are there any tools to visualize how classes relate to each other? Should I make classes larger or more connected?

(I deliberately left this question as an agnostic container, as I am interested in the answers for anyone).

+42
language-agnostic oop dependency-injection loose-coupling
Jan 13 '12 at 22:18
source share
10 answers

While I do not know how to answer this question in one paragraph, I tried to answer it in the blog instead: http://blog.ploeh.dk/2012/02/02/LooseCouplingAndTheBigPicture.aspx

To summarize, I consider the most important points:

  • Understanding a loosely coupled code base requires a different mindset . Although it’s more difficult to “shift to employees”, it should also be more or less inappropriate.
  • Free communication is an understanding of a part without an understanding of everything . You rarely need to understand all of this at the same time.
  • When zeroing the error, you should rely on stack traces , not the static code structure, to find out about collaborators.
  • The responsibility of developers is to write code to make sure it is easy to understand - it is not the responsibility of the developer to read the code.
+34
Feb 02 2018-12-12T00:
source share
— -

Some tools are aware of DI infrastructures and know how to resolve dependencies, which allows you to navigate your code in a natural way. But when this is not available, you just need to use any functions that the IDE provides as much as possible.

I use Visual Studio and the user structure, so the problem you described is my life. In Visual Studio, SHIFT + F12 is my friend. It shows all links to the character under the cursor. After a while, you get used to necessarily non-linear navigation through your code, and secondly, you think about the terms “which class implements this interface” and “where is the injection / configuration site, so I can see which class is used to satisfy this interface dependencies. "

There are also extensions available for VS that provide user interface enhancements to help with this, such as Power Tools . For example, you can hover over an interface, an information window will appear, and you can click "Implemented" to see all the classes in your solution that implement this interface. You can double-click to go to the definition of any of these classes. (I still usually use SHIFT + F12 anyway).

+12
Jan 13 '12 at 23:16
source share

I just had an internal discussion about it, and in the end I wrote this play, which, I think, is too good not to share. I copy it here (almost) unedited, but although it is part of a larger internal discussion, I think that most of it can stand alone.

The introduction of a user interface called IPurchaseReceiptService and whether it should be replaced using IObserver<T> .




Well, I can’t say that I have strong data on any of them - these are just some theories that I pursue ... However, my theory of cognitive overhead at the moment looks something like this: consider your special IPurchaseReceiptService :

 public interface IPurchaseReceiptService { void SendReceipt(string transactionId, string userGuid); } 

If we save it as a Header Interface , at the moment it has only one SendReceipt method. That's cool.

What is not so cool is that you had to come up with a name for the interface and a different name for this method. There is a little overlap between the two: the word Receipt appears twice. IME, sometimes this overlap can be even more pronounced.

In addition, the interface name is IPurchaseReceiptService , which is also not very useful. The service suffix, in fact, is the new Manager and is IMO, a designer scent.

In addition, you should not only name the interface and method, but also must specify the variable when using it:

 public EvoNotifyController( ICreditCardService creditCardService, IPurchaseReceiptService purchaseReceiptService, EvoCipher cipher ) 

At that moment, you essentially said the same thing three times. This, according to my theory, is cognitive overhead, and the smell that design could and should be simpler.

Now compare this using a well-known interface such as IObserver<T> :

 public EvoNotifyController( ICreditCardService creditCardService, IObserver<TransactionInfo> purchaseReceiptService, EvoCipher cipher ) 

This allows you to get rid of bureaucracy and reduce its essence. You still have an intention showing the names - you simply shift the design from the Type Role Type hint to the Name Argument Role Hint .




When it comes to discussing “incoherence,” I don't suspect that using IObserver<T> magically fix this problem, but I have a different theory about it.

My theory is that the reason many programmers find programming for interfaces so complex is because they are used in Visual Studio. Go to the definition function (by the way, this is another example of how a tool rots the mind ). These programmers are constantly in a state of mind, where they need to know what is "on the other side of the interface." Why is this? Maybe because abstraction is bad?

This is due to RAP , because if you confirm the opinion of programmers that there is one specific implementation for each interface, it is not surprising that they think that interfaces are only on the way.

However, if you use RAP, I hope that slowly, programmers will find out that there may be some implementation of this interface behind a particular interface, and their client code should be able to handle any implementation of this interface without changing the correctness of the system. If this theory is correct, we just added the “Liskov Substitution Principle” to the code base without scaring anyone with concepts with high eyebrows that they don’t understand :)

+8
Sep 04 '14 at 7:15
source share

However, due to the weakness of adhesion, looking at a class does not say anything about the classes around it or about where it fits into the enlarged image.

This is inaccurate. For each class, you know exactly what objects the class depends on to ensure its functionality at run time.
You know them, because you know which objects should be entered.

What you don't know is a particular concrete class that will be implemented at runtime, which will implement an interface or base class that you know of from your class.

So, if you want to see what the actual class is, you just need to look at the configuration file for that class to see the specific classes that have been introduced.

You can also use the facilities provided by your IDE.
Since you are referencing Eclipse, then Spring has a plugin for it, as well as a visual tab displaying the configured beans. Did you check it? Isn't that what you are looking for?

Also check out the same discussion in the Spring Forum

UPDATE:
Repeating your question, I do not think this is a real question.
I mean this as follows. Like all things, loose coupling not a panacea and has its own drawbacks as such.
Most of them, as a rule, are focused on advantages, but, like any solution, it has its drawbacks.

What you do in your question describes one of its main shortcomings, which is that it is not really so easy to see the big picture, because you have everything that is customizable and connected to / STRONG>.
There are other disadvantages that could have been complaints, for example. that it is slower than hard-link applications and still remains true.

In any case, repeated iteration, what you describe in your question, is not the problem you stepped on, and you can find a standard solution (or any other for this).

This is one of the disadvantages of a weakened connection, and you need to decide whether this cost is higher than what you actually get from it, for example, in any compromise solution.

This seems like a question:
Hey, I'm using this template called Singleton . It works great, but I can not create new objects! How can I get this problem? Well, you cannot; but if you need, perhaps a singleton is not for you ....

+7
Jan 13 2018-12-12T00:
source share

One thing that helped me was to put several closely related classes in one file. I know this goes against the general advice (with 1 class per file), and I generally agree with that, but it works very well in my application architecture. Below I will try to explain in which case this.

The architecture of my business layer is designed around the concept of business teams. Command classes (simple DTOs with only data and no behavior) are defined, and for each command there is a "command handler" that contains the business logic for executing this command. Each command handler implements the common ICommandHandler<TCommand> interface, where TCommand is the actual business team.

Consumers take a dependency on ICommandHandler<TCommand> and create new instances of commands and use the injection handler to execute these commands. It looks like this:

 public class Consumer { private ICommandHandler<CustomerMovedCommand> handler; public Consumer(ICommandHandler<CustomerMovedCommand> h) { this.handler = h; } public void MoveCustomer(int customerId, Address address) { var command = new CustomerMovedCommand(); command.CustomerId = customerId; command.NewAddress = address; this.handler.Handle(command); } } 

Now consumers depend only on a specific ICommandHandler<TCommand> and have no idea about the actual implementation (as it should be). However, although Consumer does not need to know anything about implementation, during development I (as a developer) are very interested in the actual business logic that runs, simply because the development runs in vertical slices; that I often work with both the user interface and the business logic of a simple function. This means that I often switch between business logic and user interface logic.

So what I did was put the command (in this example CustomerMovedCommand and implementation of ICommandHandler<CustomerMovedCommand> ) in the same file with the first command. Since the command itself is specific (because its DTO has no reason to abstract it), jumping into a class is easy (F12 in Visual Studio). By placing a handler next to a command, switching to a command also means switching to business logic.

Of course, this only works when the team and the handler live in the same assembly. When your commands need to be deployed separately (for example, when reusing them in a client / server script), this will not work.

Of course, this is only 45% of my business level. Another big world (say, 45%) is requests, and they are similarly developed using the request class and the request handler. These two classes are also placed in the same file, which -again-allows me to quickly jump to business logic.

Since commands and requests make up about 90% of my business level, in most cases I can quickly move from the presentation level to the business level and even easily move around the business level.

I have to say that these are the only two cases where I put several classes in the same file, but simplifies navigation.

If you want to know more about how I developed this, I wrote two articles about this:

+5
Jan 14 2018-12-12T00:
source share

In my opinion, loosely coupled code can help you a lot, but I agree with you regarding its readability. The real problem is that the name of the methods must also convey valuable information.

This is the principle of the Intention-Revealing Interface , as indicated in the Domain Support Project ( http://domaindrivendesign.org/node/113 ).

You can rename the get method:

 // intention revealing name Date cutoff = myExpiryCutoffDateService.calculateFromPayment(); 

I suggest you carefully read the principles of DDD, and your code can become more readable and therefore manageable.

+4
Feb 05 '12 at 8:16
source share

I found The Brain to be useful in development as a node mapping tool. If you write some scripts to parse your source in XML, then Brain accepts, you can easily browse your system.

The secret sauce is to put hints in your code comments for each item that you want to track, then nodes in The Brain can be clicked to jump to that pointer in your IDE.

+2
Jan 14 2018-12-12T00:
source share

Depending on how many developers work on projects, and whether you want to reuse some of its parts in different projects, free communication can help you a lot. If your team is large and the project should span several years, having free communication can help, as work can be assigned to different development groups more easily. I use Spring / Java with a lot of DI and Eclipse offers some graphs to display dependencies. Using F3 to open a class under the cursor helps. As indicated in previous posts, knowledge of the shortcuts for your tool will help you.

Another thing to keep in mind is creating custom classes or wrappers, as they are easier to track than regular classes that you already have (e.g. Date).

If you use multiple modules or an application layer, it can be difficult to understand what a project stream is, so you may need to create / use some kind of custom tool to see how everything is connected to each other. I created this for myself, and it helped me more easily understand the structure of the project.

+2
Feb 01 2018-12-12T00:
source share

Documentation!

Yes, you mentioned the main disadvantage of loosely coupled code. And if you probably already realized that in the end, it will pay off, however, you will always look more for “where” to make your changes, and you may have to open several files before finding the “right place”. ..

But this is when something is really important: documentation. It is strange that the answer did not explicitly mention that this is the BASIC requirement in all large amounts.

API Documentation
APIDoc with a good search function. Every file and - almost - every method has a clear description.

Documentation "Large Photo"
I think it's good to have a wiki that explains the big picture. Did Bob create a proxy system? How it works? Does it authenticate? Which component will use it? Not a complete tutorial, but just a place where you can read 5 minutes, find out which components are involved and how they are related to each other.

I agree with all of Mark Seemann's answer points, but when you first get into the project (s), even if you understand well the principles that violate the principles of decoupling, you will need to guess a lot, or some help to figure out where to implement a specific the function you want to develop.

... Again: APIDoc and a small Wiki development.

+2
Feb 06 2018-12-12T00:
source share

I am amazed that no one wrote about the testability (in terms of unit testing) of free code and the testability (in the same terms) of a tightly coupled design! This is not a problem whose design you must choose. Today, all the frameworks of Mock and Coverage are obvious, well, at least for me.

If you do not conduct unit tests of your code, or you think that you do them, but in fact you do not ... Testing in isolation can hardly be achieved with tight communication.

Do you think you need to navigate all the dependencies on your development environment? Forget it! This is the same situation as with compilation and runtime. During compilation, you may find some kind of error, you cannot be sure if it works if you do not test it, which means to execute it. Want to know what is behind the interface? Place a breakpoint and run the damn application.

Amen.

... ...

, , Eclipse , . ( , ). F4. , .

The hierarchy view in Eclipse after pressing F4

0
06 . '12 20:05
source share



All Articles