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 |
Figure 4 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
Package=biz.xsoftware.impl.phone
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.
External Dependencies
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 file:
<package name="rmi" package="java.rmi" subpackages="include"/>
<package name="phoneImpl" package="biz.xsoftware.impl.phone">
<depends>phoneApi</depends>
<depends>rmi</depends>
</package>
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:
- java.rmi
- java.rmi.activation
- java.rmi.dgc
- java.rmi.registry
- java.rmi.server
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)
{
try
{
Registry registry =
LocateRegistry.createRegistry(5000);
}
catch(RemoteException e)
{
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's
makeCall 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.