Skip to content

Physical Code Organization

Oleg Subbotin edited this page Mar 19, 2018 · 11 revisions

Physical Code Organization

The code in BDE is structured in three different levels of physical aggregation:

  • Components (e.g., bsls_platformutil)
  • Packages (e.g., bsls )
  • Package Groups (e.g., bsl )

The naming conventions make finding code a bit easier in large systems - a source file named bsls_platformutil.h will always be in package group bsl, in its bsls package. In our directory layout, the path becomes groups/bsl/bsls/bsls_platformutil.h.

These descriptions are taken mainly from Large-Scale C++: Process and Architecture by John Lakos, which will be published (once completed) by Addison-Wesley. Additional information about physical design and large-scale C++ development can be found in Large-Scale C++ Software Design by John Lakos, published in 1996 by Addison-Wesley.

Components

A component is the lowest level of physical aggregation in the BDE code base. Each component consists of a header (.h) file and its (possibly near-empty) implementation (.cpp) file. Associated with each component is a standalone test driver (.t.cpp) file.

We contend that the component, and not the class, is the appropriate unit of both logical and physical design. Without hindering conventional class-level design, components provide the "real estate" on which to encapsulate closely related logical entities while admitting consideration of essential physical http://github.com/bloomberg/bde/issues that routinely impact design decisions.

Component Design Rules

  1. Cyclic physical dependencies among components are not permitted.

  2. Every logical construct that we write outside of a file that defines main resides in a component, which consists of exactly one .h file and a corresponding .cpp file having the same root name, and which together satisfies the four fundamental properties of components:

    1. A component's .cpp file includes its corresponding .h file as the first substantive line of code.

    2. All logical constructs having external physical linkage defined in a component's .cpp file are declared in that component's .h file.

    3. All constructs having external physical linkage declared in the .h file of a component (if defined at all) are defined within that component.

    4. A component's functionality is accessed via a #include of its header file, and never by a forward (extern) declaration.

  3. The .h file of each component contains unique and predictable include guards: INCLUDED_*PACKAGE*_*COMPONENT* (e.g., INCLUDED_BDET_DATETIME).

  4. Any direct use of one component by another requires the corresponding #include directive to be present directly in the client component (in either the .h or .cpp file, but not both) unless indirect inclusion is substantially implied by logical content (i.e., public inheritance).

  5. The private details of a logical module must not span components -- i.e., no "long-distance" (inter-component) friendship.

  6. Every component resides within a package.

  7. The name of each component (all lower case) begins with the name of the package to which it belongs (all lower case) followed by an underscore ('_'); both component files (.h/.cpp) have the same root name as the component (i.e., they differ only in suffix).

  8. The name of every logical construct (other than free operators, and operator-like "aspect" functions, such as swap) declared at package-namespace scope must have, as a prefix, the base name of the component that implements it; macro names (all upper case), which are not scoped by the package namespace, must incorporate as a prefix the entire (uppercased) name of the component.

  • This rule may not apply when the external ("client-facing") components headers are already specified otherwise -- e.g., standardized, or established legacy libraries. The bsl package group contains examples of this exception in its bsl+bslhdrs and bsl+stdhdrs packages -- a '+' in a package name often indicates such externally driven naming exceptions.

Component Design Guidelines

  1. The .h file of each component should contain only those #include directives (and forward class declarations) necessary to ensure that the header file can compile in isolation.

  2. Aim for one (public) class per component unless there is an engineering justification to do otherwise. There are 4 conditions where we will put multiple (public) classes in one component:

    1. Friendship: The classes need friendship to collaborate. One example of necessary (local) friendship is that iterators are often co-located with their containers.

    2. Cyclical Dependency: The set of classes form a dependency cycle. Since dependency cycles are not allowed between components, classes that, if separated, would result in such cyclic dependencies must be co-located in a single component.

    3. Single Solution: The set of classes forms a single solution. One possible example of a single solution is a suite of functor templates, where separate classes may be needed for different argument counts, but the component implements the generalized concept.

    4. "Flea on an Elephant": The additional classes are tiny in comparison to the component's main class, and have no additional dependencies. An example would be a scoped guard class for a much larger subsystem (e.g., a lock, a logger) which simply manages ownership via RAII.

Packages

A large program can span many developers, several layers of management, and even multiple geographic sites. The physical structure of our system will reflect not only the logical structure of the application, but also the organizational structure of the development team that implements it. Large systems require physical organization beyond what can be accomplished by a levelizable hierarchy of individual components alone. In order to encompass more complex functionality, we need to exploit a unit of physical design at a higher level of abstraction. A package is the first architecturally significant aggregate larger than a component.

A package is an architecturally significant (i.e., globally visible) unit of physical design that serves to aggregate components, subject to a strict dependency criteria. A package is also a means for making related components nominally cohesive. In these ways, packages enable designers to capture and reflect, in source code, important architectural information not easily expressed in terms of components alone.

Package Design Rules

  1. The name of each package is unique throughout the enterprise.

  2. Every component resides within a package.

  3. Anticipated allowed (direct) dependencies of each package are stated explicitly.

  4. Cyclic physical dependencies among packages (as defined by their allowed dependencies) are not permitted.

  5. Every logical construct declared within a package namespace in one of our components is also be nested within a single, overarching enterprise-wide namespace.

  6. Only classes, structs, inline member functions, and member function templates are defined in package namespace scope in a component's .h file; all non-member inline-function and function-template definitions in a .h file reside outside the package namespace (but inside the enterprise-wide namespace).

Package Groups

Our ability to aggregate components effectively is essential to our development process. Packages are an important architectural entity, but alone are not sufficient for truly large development efforts. If a package were our only means of physical aggregation then every component would reside in the same namespace. By introducing a third level of aggregation -- the package group -- we facilitate the bundling of much larger quantities of physically similar logical content.

A package group is an architecturally significant collection of packages, organized as a physically cohesive unit, and forming a Unit of Release (UOR) - all code within a package group is released atomically as a single library with all of its associated headers.

A package group G depends directly on another unit of release (UOR) U if any package in G #includes any header from U. Note that, by rule 1 below, G must have declared a dependency on U. (Note that in this release we have only one package group, bsl, and it has no other package groups on which to depend. This rule will come into effect later on, as more parts of the system get released - all other package groups will depend (at least) on bsl.)

Package Group Design Rules

  1. Anticipated allowed (direct) dependencies of each package group are stated explicitly.
  2. The allowed dependencies among UORs must be acyclic.
    • It follows from this rule that cyclic physical dependencies among package groups are not permitted.
  3. The name of every C++-only package group is exactly three characters.
    • The "C++-only" qualifier is there because we add a z_ prefix if we create a C-language interface wrapper for a C++ package group. If such a wrapper existed for a package group named bae, it would be named z_bae.
  4. The (lowercase) name of each package within a package group begins with the name of its parent group, followed by one, two, or three alphanumeric characters that uniquely distinguish the package within its group.
    • This gives us a large enough space of names to avoid collisions, and keeps package and component names blissfully short, so they will be used in-place rather than referred to via a using namespace construct.
    • This rule allows us to avoid an extra separator between the group and package names, thus helping to reduce the overall length of explicitly qualified names.

Questions, Comments, and Feedback

If you have questions, comments, suggestions for improvement or any other inquiries regarding BDE or this wiki, feel free to open an issue in the issue tracker.


Creative Commons License  BDE Wiki by Bloomberg Finance L.P. is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.