Team LiB   Previous Section   Next Section

1.4 Organizing Your Projects

Several different strategies are available when choosing a logical structure for your projects and solutions. So far, we have just used a single solution containing all of the projects that we are working on. (We also saw how to make sure that the physical structure of the solution on the filesystem matches the logical structure.) But there are other options, and thinking about the structure of solution(s) and projects before you start to write code will potentially save you time in the end.

Remember that a project may belong to more than one solution. This gives us some flexibility in the way that we structure our projects. We will now examine the three basic ways to organize your solution(s) and projects and discuss their pros and cons.

1.4.1 Single Solution File

The easiest way to organize your projects is to put them all in a single solution—the approach we have used so far in this chapter. The main advantage of this style is its simplicity. This structure also makes automated builds simple, since only one solution will have to be built. The disadvantage is the lack of flexibility in a large project—any developers who wish to work on the solution will always have to have all of the solution's projects downloaded from the source control database.

Problems can arise as the number of projects in the system grows. Although VS.NET has no hard limit to the number of projects that can be added to a solution, at some point a solution with a large number of projects will become unwieldy. It can take a long time to open, since VS.NET will check the status of every project in the source control database. Large solutions will cause more memory to be consumed. Big solutions may also present logistical problems if multiple developers need to make changes to the solution files. Another potential problem is that as the solution gets bigger, the build time will tend to be unnecessarily high, as VS.NET may decide to rebuild files that a developer may not even be working on right now (although you can, of course, mitigate this by creating configurations that build only subsets of the solution). The next technique provides a solution to most of these problems.

1.4.2 Multiple Solution Files with a Master

The multiple-solution-with-master strategy is similar to the single-solution approach, in that there is still a single solution file that contains all of the projects necessary to build your system. The difference is that it is not the only solution file. This master solution file will be used whenever the entire solution needs to be built (e.g., for nightly or other automated builds). However, the master solution will not normally be used by developers in their day-to-day work. Instead, they will use other solutions that contain only the projects they require to work on some particular aspect of the system.

To create one of these smaller solutions, you will start by creating a new blank solution. But rather than adding new projects, you will select Add Existing Project... from the solution's context menu in the Solution Explorer. (Or use File Add Project Existing Project... from the main menu.) You can add as many of the existing projects as you require.

This method of organizing projects and solutions is likely to be appropriate if you have a large number of projects (e.g., more than 10) and you want to make it easier for each developer to work on just one portion of the software. Using a solution that contains only the projects you need to work on has a number of advantages. It will reduce the amount of time it takes to open the solution, especially if the solution is in a revision control system. It will also reduce the amount of unwanted information displayed—the Solution Explorer, class view, and object browser will all be less cluttered and therefore easier to use.

Although working with a subset of the projects will reduce the number of files that need to be retrieved from source control in order to begin work, developers are likely to need to get updates of more of the source tree before checking their changes back in. It would be a foolhardy developer who checks in changes without first making sure that those changes won't break the nightly build. And, of course, the only way to find out for certain whether your changes will pass the nightly build and any automated unit testing is to get an update of everything and perform a test build.

Of course, if you are certain that your work won't affect certain other areas, you will probably get away without testing them yourself and just trusting to the automated processes. But do you really want to risk being the developer who broke the build?

Although this solution structure essentially builds on top of the single-solution approach, you will need a little planning to take advantage of it. You will not simply be able to pick arbitrary groups of projects and create new solutions for them—you will be restricted by the dependencies between the projects. For example, if your solution contains a UI project that uses a class library project, attempting to create a solution that contains only the UI project will not be successful—it will need a reference to the Class Library project in order to build. You should therefore try to keep the relationships between your components as simple as possible.[8]

[8] Issues with VS.NET project references notwithstanding, it is good practice to minimize cross-component dependencies in order to simplify your build and test procedures. Large-Scale C++ Software Design (Addison-Wesley) provides excellent and extensive explanations of why this is so. Despite its title, many of the issues presented in this book are of interest to developers creating large software systems in any programming language.

File References Versus Project References

Of course, you could use a file reference instead of a project reference. This would enable the UI project to exist in a solution on its own. But there are problems with doing this:

  • You must somehow get hold of a copy of the class library in order to add a file reference. (Of course, if you have a nightly build, there will always be a "most recent" version of the component somewhere on the network.)

  • If the reference's Copy Local flag is set to true, you will need to delete and recreate the reference every time you wish to pick up a new version. (Alternatively, you can dig into the build directory and delete the copy, which will cause VS.NET to make a new copy.)

  • If the reference's Copy Local is false, you will have to work out some way of making sure that the component can actually be found at runtime, since VS.NET will no longer copy it into the build directory. For COM components this is not a problem, as they are found through their registry entries, but for .NET components you will need to add a configuration file to tell the CLR where to find the components.

So you are usually better off with a project reference.

This style of solution structure introduces a new challenge. Now that there are multiple solutions, it will probably not be possible to make your filesystem structure match all of the solutions. For example, if we create solutions for working on a Windows Forms UI project and a Web Forms UI project, both of these solutions might need to contain the same Class Library project. Since a directory cannot be contained by multiple parent directories,[9] there is no single filesystem structure that matches both solutions. The simplest way of dealing with this is to choose just one solution and make the filesystem match that. The obvious solution to choose for this is the master solution.

[9] Strictly speaking, NTFS 5 reparse points do allow a directory to have multiple parents. However, even if all of your developers' machines have appropriate filesystems, your source control system almost certainly won't be able to deal with such a directory structure.

There will be some extra subdirectories in the master solution for this approach. Visual Studio .NET insists on giving each solution its own directory. (And although you can move .sln files after VS.NET creates them, it will insist on putting each in its own directory in your version control system, regardless of how you may have restructured the files on your local filesystem.) So there will be a directory for each secondary solution you create, containing just the solution files. The project files will be inside the project directories as before.

Projects inside of the master solution can then be contained by multiple different secondary solutions. This enables each developer to download and work with only those projects that are related to the part of the system she is currently working on. The only problems with this technique are the constraints imposed by use of project references and the fact that the master solution can become a bottleneck—anytime a new project is added, the master solution will need to be updated. (In software shops where people are in the habit of keeping files checked out for a long time, this can be a problem.) The final way of structuring your projects can get around both of these issues, although not without some inconvenience.

1.4.3 Multiple Solution Files with No Project References

If you want developers to have the maximum possible flexibility as to which projects they can download and work on, you could create one solution per project and have no master solution at all. The cost of this flexibility is that you have to deal with dependencies manually, because VS.NET has no way of representing cross-solution dependencies.

It is likely that some of your projects will depend upon other projects, but if they all live in their own solutions, you will have no way of representing this formally. You will have to use .NET file references instead of project references. This is inconvenient because you need to delete and recreate the references (or delete the copied component from the build directory) every time the component you are using changes. It also makes automated builds harder, since the build script will have to build multiple solutions, and it will also be responsible for getting the build order correct.

You may think that you could mitigate this by creating a master solution on top of this multisolution structure and adding the relevant project references to it. However, this will not work, because references are stored in projects, not in the solution, so if you add a project reference to a project, it doesn't matter what solution you happened to be using when you added the reference—you will have changed the project for everyone. Anyone who wanted to build the project would now be obliged to have a copy of the project on which it depends, defeating the whole purpose of this strategy.

However, although adding project references will not work, you could create a master solution and add explicit dependencies instead. (Remember that although implicit dependencies are inferred from project references, explicit dependencies are stored in the solution. So using explicit dependencies would not negate the benefit of being able to download any individual project in isolation.) This would make automated build scripts easier to create, but you would still be responsible for working out for yourself what the appropriate dependencies are. You also still need to recopy or recreate file references every time anything changes.

1.4.4 Choosing an Organizational Method

The simplest structure is the single-solution approach. Using this will mean that your solution's physical layout can easily match its logical structure, and you can always use project references to make sure that every project will be rebuilt and copied automatically when it needs to be. Choosing this structure as a starting point is almost always the right decision.

If the number of projects makes dealing with the solution too unwieldy, then you should consider migrating to the multiple-solution-with-master-solution structure. Your existing single solution will become your master solution, and you can add new solutions to partition your projects as required. When creating the new solutions, you will find that if you include a project that has a reference to another project, VS.NET will complain. (If you expand the project's References node in the Solution Explorer, you will see that the reference is still there but now has an exclamation mark in a yellow triangle over it.) You will need to add all referenced projects to the new solution in order to be able to build it.

If at all possible, you should always have a master solution—an organization with multiple solutions but no master should be chosen only as a last resort, as shown in Table 1-7. The advantages of more flexible partitioning rarely outweigh the disadvantages of not being able to use project references and the increased difficulty of automating builds.

Table 1-7. Solution organizational choices

Master solution

Advantages

Disadvantages

Single solution file

Simplest.

Can use project references for other assemblies in the solution.

Makes automated builds simple.

Might have to open more projects than you need.

Rebuilds may take a long time unless you add solution configurations to build selected subsets.

Multiple solution files with master solution

Can still use master solution for automated builds.

May be faster to work with a smaller set of projects than the whole.

Can use project references.

More work to add new projects as you have to add them to multiple solutions.

Cannot divide up master solution into arbitrary project groups—grouping is constrained by project references.

Multiple solution files with no project references

Adding new projects is easier (no sharing of projects between solutions).

Can split your projects however you like.

Can't use project references across solutions, so dependencies must be managed by hand.

Harder to automate the build of the entire system.

    Team LiB   Previous Section   Next Section