Finding Unwanted Dependencies
First, I'll show you what happens if a developer makes one of the most basic mistakes in a systemcircumventing a well-defined API. Open up the file verifydesign/packagedesign/input/javasrc/biz/xsoftware/impl/client/Client.java
, and add the following code to the method called createPhoneTheWrongWay()
PhoneImpl phone = new PhoneImpl();
|Figure 4. Unwanted Dependency Change: According to the design, the client should depend only on the phoneApi. Verifydesign catches this mistake|
shows a graphical representation of what was changed in the source code; the red line illustrates the inserted dependency. Note that this represents the implemented design, not the desired "package design" defined in the design file.
After making the change, run the build again, using the same command:
ant --f bldfiles/build.xml
Instead of the success message from your previous build, you'll now see the following error:
You are violating your own design....
Class = biz.xsoftware.impl.client.Client depends on
Class = biz.xsoftware.impl.phone.PhoneImpl
The dependency to allow this is not defined in your design
Package=biz.xsoftware.impl.client is not defined to depend on
Change the code or the design
Notice the last line of the message, "Change the code or the design." That reflects the reality that the package design always evolves in every software project that exists. This statement is true whether you use Verifydesign or not. Verifydesign has been used on projects with both Agile and Waterfall processes, which have shown that no matter the process, designs always change. Verifydesign simply blatantly points out how designs evolve rather than having such design changes go unnoticed.
Before you continue, remove the breaking change to restore the file to its original state. You should once again be able to build the project successfully.
Sometimes, you want to isolate an external dependency so that only one package in your source code depends on that external entity. This is very useful if you think you might need to switch the external technology later. By limiting the external dependency to a single package, you ensure that switching entails only rewriting the one dependent package. By default, Verifydesign makes you declare dependencies on anything in javax
. Also, by default, it does not require you to declare dependencies on java.*
. You can override both these defaults.
Assume that the Phone class depends on java.rmi
and all subpackages of java.rmi
, including java.rmi.registry
. Also assume that you know you will have to change that later, and you don't want other packages (such as the client) to depend directly on javax.rmi
. For example, perhaps you might later want to use a different phone implementation that depends on Web services in addition to the phone implementation that depends on rmi
. Or suppose you know in advance that you might eventually want two different Phone implementations.
To enforce this, the first thing you need to do is override the java.*
default behaviorbut just for java.rmi
. You'd make this change to the design.xml
<package name="rmi" package="java.rmi" subpackages="include"/>
<package name="phoneImpl" package="biz.xsoftware.impl.phone">
The preceding configuration adds a subpackages
attribute, telling the design tool that you want to include all subpackages of java.rmi
in the rmi package. This means that the PhoneImpl
package can now depend on these packages:
Now, go ahead and add the following code to PhoneImpl.java
|Figure 5. Good Encapsulation: Note how the client and phoneApi no nothing about the rmi module. Only the PhoneImpl component (the implementation of the phoneApi) knows about rmi.|
public void makeCall(String number)
Registry registry =
throw new RuntimeException(e);
A graphical representation of the current "package design" would now look like Figure 5
This is encapsulation at its best. The client and API will not be allowed to know about RMIExceptions. Note that you cannot throw a RemoteException on the makeCall
method, because if you did, you'd have to add the throws RemoteException
clause to Phone.java
method in the API packageand that would violate the design by introducing a dependency from the phoneApi
to java.rmi.RemoteException. That dependency would then cause the build to fail, because the phoneApi
package is not allowed to depend on java.rmi.RemoteException. Remember, only the phone implementation
is allowed to depend on java.rmi
. The API should not depend on rmi
, because you want the client to use an API that is generic and not tied to any specific technology. I leave this for you to try yourself.