views:

544

answers:

5

I've been running some metrics on my Java project and apparently there are a lot of dependency cycles between packages. I didn't really know how to organize stuff into packages, so I just did what made sense to me, which is apparently wrong.

My project is a neural network framework. Neural networks have Neurons, which are connected to each other with Connections. They need to depend on each other. However, there are also different types of Neurons, so I thought it'd be a good idea to put them all in there own 'neurons' package. Obviously a Connection isn't a Neuron so it shouldn't be in the package, but since they refer to each other, I now have a circular dependency.

This is just an example, but I have more situations like this. How do you handle these kinds of situations?

Also, I read that classes in a package higher up in the package hierarchy are not supposed to refer to classes in packages that are deeper. This would mean that a NeuralNetwork class in package 'nn' can not refer to the Neuron in package 'nn.neurons'. Do you guys follow this principle? And what if I would move NeuralNetwork to 'nn.networks' or something? In that case, it would refer to a sibling package instead of a child. Is that better practice?

+1  A: 

I do not think cyclic dependencies like the ones you describe have to be bad. As long as the concepts that are interdependent are at the same level of abstraction and relate to the same parts of the architecture, it may not be necessary to hide these from each other. Neurons and Connections fit this bill in my understanding.

A common to reduce such couplings is to extract interfaces, and possibly even put these in a separate module. Simply organizing by packages inside a single project does not allow you to hide implementation details sufficiently. A common pattern that allows you to really hide implementations is as follows:

Client Code ----> Interfaces <--- Implementation

In this pattern, you hide the "Implementation" module from the client code, which means the code in the "Client code" module doesn't even see the implementation code.

The nesting of packages serves several purposes: Some projects may have a domain model which is organized in packages. In this case the packages reflect some grouping of the domain, and references may go up/down packages. When it comes to things like implementation of services, your suggested pattern is quite common and a good thing to follow. The deeper in the package hierarchy you get the more specific the class is believed to be.

krosenvold
+2  A: 

What kind of code-size are we talking about? If you only have 10-20 classes, you probably don't need to (and shouldn't) over-organize your code into packages just for the sake of it.

As your project grows, the first distinction you want to make is to separate user-interface code from the underlying datamodel and the logic. Having a cleanly separated layers is crucial in order to be able to do proper unit testing.

If you're having trouble in getting rid of the circular dependencies, it is probably the case the the classes are actually interdependent, and should reside in the same package.

Getting the abstraction layers right is probably one of the most important aspects when designing the overall code-structure.

JesperE
A: 

How do you handle these kinds of situations?

Circular dependencies aren't inherently bad. In fact, this can sometimes be a case of the "cure being worse than the disease": extracting an interface increases the level of complexity of your code and adds another layer of indirection. That's probably not worth it for very simple relationships.

John Feminella
+2  A: 

First of all, you are rightfully concerned because circular dependencies between packages are bad. Problems that come out of it grow in importance with the size of the project, but no reason to tackle this situation on time.

You should organize your classes by placing classes that you reuse together in the same package. So, if you have for example AbstractNeuron and AbstractConnection, you’d place them in the same package. If you now have implementations HumanNeuron and HumanConnection, you’d place these in the same package (called for example *.network.human). Or, you might have only one type of connection, for example BaseConnection and many different Neurons. The principle stays the same. You place BaseConnection together with BaseNeuron. HumanNeuron in its own package together with HumanSignal etc. VirtualNeuron together with VirtualSignal etc. You say: “Obviously a Connection isn't a Neuron so it shouldn't be in the package..”. This is not that obvious, nor correct to be exact.

You say you placed all your neurons in the same package. Neither this is correct, unless you reuse all your implementations together. Again, take a look at scheme I described above. Either your project is so small you place all in the single package, or you start organizing packages as described. For more details take a look at The Common Reuse Principle:

THE CLASSES IN A PACKAGE ARE REUSED TOGETHER. IF YOU REUSE ONE OF THE CLASSES IN A PACKAGE, YOU REUSE THEM ALL.

Dan
+2  A: 

The antcontrib VerifyDesign task will help you do what you want:

For example, if there are three packages in one source tree

* biz.xsoftware.presentation
* biz.xsoftware.business
* biz.xsoftware.dataaccess

and naturally presentation should only depend on business package, and business should depend on dataaccess. If you define your design this way and it is violated the build will fail when the verifydesign ant task is called. For example, if I created a class in biz.xsoftware.presentation and that class depended on a class in biz.xsoftware.dataaccess, the build would fail. This ensures the design actually follows what is documented(to some degree at least). This is especially nice with automated builds

So once you have decided how things should be organized you can enforce the requirements at compile time. You also get fine-granied control so you can allow certain cases to break these "rules". So you can allow some cycles.

Depending on how you want to do things, you might find that "utils" package makes sense.

For the particular case that you cite... I might do something like this:

  • package nn contains Nueron and Connection
  • package nn.neurons contains the subclasses of Nueron

Neuron and Connection are both high-level concepts used in the NeuralNetowrk, so putting them all together makes sense. The Neuron and Connection classes can refer to each other while the Connection class has no need to know about the Neuron subclasses.

TofuBeer