1. Organize my packages by something other than "type of thing".
This is OK for small projects but quickly breaks down as your complexity grows. If you group classes simply by their type
the chances are that most of the classes in that package will be unrelated but are dependant on code in other simlarly grouped packages. This leads to unneccessary coupling
, i.e. you will have cross-package dependencies which could be avoided, making the structure of your code more difficult to follow.
Instead, group classes by purpose
. For example, I have an animation
package that defines an animation interface, some useful base classes and helpers and a controller that actually runs animations. However the actual animation implementation classes live in the packages where they are used: I have an animator for particle systems, it lives in the particle system package, not in the animation package. Ditto audio player, rotation animator, etc.
So the implementation classes live in the areas that they belong
in and are not grouped by type. This reduces dependencies (one of your requirements) and keeps related code together, making it easier to comprehend.
2. Try to split more of the code out into separate projects.
Sounds like you've already gone some way down this path by seperating your generic library
code into a different project.
As an example: for my 3D terrain work I have the following projects:
- generic library code: higher-level collections, random utilities, etc. Could be used in just about project.
- general 3D code: shaders, VBOs, texture management, matrices, lighting, etc. Can be used in any 3D project.
- terrain engine: terrain loader and management, skybox, weather, day/night cycle, terrain following, etc. Specific higher-level stuff for terrain-based 3D projects.
- various terrain demos: i.e. the actual applications using the engine.
Each is dependant on the one(s) above it.
4. Diagramming the system at a higher level (UML?)
I find a high-level UML package structure diagram and class diagrams for the more complex packages is sufficient documentation, even if it's just a back-of-a-fag-packet diagram. Attempting to document every class and method is going to be a huge task and is generally pointless: the diagrams will become out-of-date over time as your design evolves, they eventually end up being write-only
documents and confuse more than they help. Therefore I would suggest simple and quick diagrams that you can easily amend or replace (note this is pretty much the approach in the real IT world!)
If you have some fairly complex algorithms it might also be worth outlining the approach as flowcharts or UML activity/interaction diagrams.
Also, don't overlook JavaDoc: it's structured and can be used to explain how
a class (or set of related classes) should be used from the perspective of a third-party developer (even if that's you). If you use an IDE like Eclipse than it will pop up the JavaDoc if you hover over a class/method/member which is a nice side-benefit.
Finally do you write unit-tests for your code? If not check it out. One side-benefit of unit-tests that is often overlooked is that they are essentially documentation for your code.
Any thoughts? How do you architect and/or organize your code and keep it understandable as a whole?
One other thought springs to mind: try to keep the complexity of individual classes and packages low so that it's easier to understand how they should be used.
There's a few ways to achieve this:
- Use encapsulation: i.e. avoid making every class, method and member public - if it doesn't need to be exposed then hide it. This makes the 'API' of your code neater and easier to understand.
- Reduce mutability: An immutable object is almost always easier to understand and use. If you need a mutable version, consider two implementations and only expose the immutable one.
- Avoid deep class hierarchies: Inheritance has it's place but it quickly becomes complex and brittle the deeper the hierarchy is. Check out some of the SWING UI classes for an example of how not to use inheritance, some of them have over 100 public methods!
- Consider extracting complex code into helper classes or methods: it will be easier to comprehend and maintain if the code is broken down into manageable chunks.