A simple analysis produces this scale of units, ordered by increasing size. Every unit is composed by a set of units from the underlying level, which comes before it in the list:
- The simplest unit of software is obviously the line of code.
- The next smaller unit in object-oriented software is the method, being it public, private or with any access control policy.
- The class and its specializations: Entity, Value Object, Service, Repository...
- The package alias namespace: its purpose is to simplify references between items contained in the same unit.
- The module alias component, which often presents a Facade to simplify its access.
- The application alias BoundedContext. Different applications can work together and communicate via published protocols, anti-corruption layers, RESTful services, relational databases... There are no limits to collaboration paradigms.
Let's consider some general rules first:
- number of units: programmers have limited Ram. People normally can work on 5 to 9 units at the time (obviously more when they are very similar or dumb), so a container unit should not be composed of an high number of contained units. For example generally a class should not present 300+ methods, and a package should not contain one hundred classes.
- low coupling, high cohesion, information hiding: these concepts should be enforced at every level; one unit dependent on another is sufficient to transitively establish a dependency between the respective container units.
- TDD and refactoring: applied at the low end of the spectrum. It is simple to refactor a private method, but it's very difficult to refactor a published protocol between two applications.
- the converse situation applies at the high end of the spectrum: thinking of a good api and refining the Ubiquitous Language is very important because of the resistance to change of these kinds of units.
- choice of the system under test: testing the single classes and methods is the preferred approach in a largest part of a project's test suite, because of the reduced number of test cases necessary and the resulting design aid (applied at the low end.)
- code coverage is instead only significant at an high level. Some dumb classes may not have unit tests at all but at the same time they could be indirectly exercised by other tests. Similarly many static analysis metrics are meaningful if measured on a whole project.
- Uml diagrams must be kept in sync with code, so they really add value when they are used at the boundaries of components or without much detail in them.