How to split a large Java project into smaller components

We are trying to separate a large code base from logical modules. I would like some recommendations regarding tools, as well as what you do with such things.

The application consists of a WAR server and several rich clients distributed in the JAR. The trouble is that all this is in one large, hairy code base, one source tree s> 2k war files. Each JAR has a dedicated class using the main method, but the entanglement of dependencies quickly disappears. Not everything is so bad, good practices have been consistently followed, and there are components with specific tasks. It just needs some improvement to help our team scale as it grows.

Each module will be in a maven project built by the parent POM. This process has already begun by moving each JAR / WAR into its own project, but obviously this will only scratch the surface: several classes in each JAR application and a mammoth “old” project with everything else. In addition, there are already unit tests and integration tests.

In any case, I’m interested in tools, methods and general tips to break up a too large and confusing code base into something more manageable. Free / open source is preferred.

+7
java refactoring
source share
6 answers

See Structure 101 . This is awesome for visualizing dependencies and displaying dependencies for moving to a cleaner structure.

+8
source share

We recently performed a similar task, that is, a project consisting of> 1k source files with two main classes that needed to be divided. We ended up with four separate projects, one for the base utility classes, one for the client database material, one for the server (the project is the rmi-server-client application) and one for the gui client. Our project had to be separated because other applications used the client only on the command line, and if you accidentally used any of the gui classes, you had headless exceptions that occurred only when running on a silent deployment server.

Some things to remember from our experience:

  • Use the entire sprint to separate projects (do not let other tasks interfere with the separation; you will need all the time sprinting)
  • Use version control
  • Write down unit tests before you move any function to another place
  • Use a continuous integration system (it doesn’t matter if you’ve grown at home or out of the box)
  • Minimize the number of files in the current set of changes (you will save a lot of work when you need to undo some changes)
  • Use the dependency analysis tool all the way to moving classes (we did a great job with DependencyFinder )
  • Spend time restructuring packages into packages that are reasonable for each package.
  • Do not be afraid to change interfaces, but all dependent projects in the workspace to get all compilation errors
+6
source share

Two tips: the first thing you need is test suites. The second tip is to take small steps.

If you already have a strong test program, you are in a good position. Otherwise, I would have done a good high-level test (aka: system tests).

The main advantage of high-level tests is that a relatively small number of tests can provide you with great coverage. They won’t help you point out the error, but you really won’t need it: if you work in small steps and you are sure to run the tests after each change, you can quickly detect (accidentally entered) Errors: the root of the error in a small part of the code has changed since last run of tests.

+2
source share

I would start with the various tasks you need to complete.

Recently, I faced a similar problem, given the 15-year-old code base created by a group of developers who did not have communication with each other (one worked on the project, on the left, then another hiring, etc., without cross-talk). The result is a common mishmash of a wide variety of styles and qualities.

To make it work, we had to isolate the necessary functionality other than decorative fluff so that it all worked. For example, there were many different string classes, and one person spent what must have spent a lot of time converting a 2k string between COleDateTime and const char* and vice versa; it was fluff, code for solving the problem associated with the main goal (getting things to and from the database).

What we needed to do was identify the big goal that this code accomplished, and then write the basic logic for that. When there was a task that we needed to accomplish, which we knew before, we found it and wrapped it in library calls so that it could exist on its own. For example, one piece of code activates a USB device driver to create an image; this code is not affected by this current project, but is called if necessary through library calls. Another piece of code works with a security key, and another requests remote servers for data. This is all the necessary code that can be encapsulated. The drawing code, however, was built over 15 years, and such a building is insane that rewriting in OpenGL for a month was a better use of time than trying to figure out what someone else had done, and then how to add to it.

I am a little useful here because our project was MFC C ++ for .NET C #, but the basic principles apply:

  • find the main goal
  • identify all the small goals that make the main goal possible.
  • Isolate already encapsulated code parts, if any, for use as library calls
  • figure out the logic to put it all together.

I hope this helps ...

+1
source share

To continue, answer, I suggest reading Michael Perce "Effectively works with outdated code" (pdf) . He also recommends that every step be reinforced by trials. There is also a book version .

+1
source share

Maven lets you customize small projects like larger children. If you want to extract part of your project as a separate library for other projects, then maven will also do this.

Having said that, you definitely need to document your tasks, which will be carried out by each smaller project, and then (as has been said here several times) a test, test, test. You need tests that work through the entire project, and then pass tests that work with individual parts of the project, which will end as subsidiary projects.

When you start pulling out functionality, you need additional tests to make sure your functionality is consistent and that you can mock your child projects.

+1
source share

All Articles