Information hiding is a software design principle, where certain aspects of a program or module (the “secrets”) are inaccessible to clients. The primary goal is to prevent extensive modification to clients whenever the implementation details of a module or program are changed. This is done by hiding aspects of an implementation that might change behind a stable interface that protects clients from the implementation details. Users of that interface (whether it is a module, class, or function) will perform operations purely through its interface. This way, if the implementation changes, the clients do not have to change.
Information hiding serves as a criterion that can be used to decompose a system into modules. The principle is also useful for reducing coupling within a system.
Applying Information Hiding
The key challenge when applying information hiding is determining what information should be hidden and what should be exposed. Parnas suggests that the heuristic we should use is hiding those details that are “likely to change”. This way our changes have only a local effect, since we have hidden the details to be changed behind a firewall of some kind (e.g., an abstract interface).
Using this heuristic, information hiding can be thought of as a three-step process:
- Identify all of the pieces of a design that are likely to change or other design details that you might want to hide
- We call these our “secrets”, and we want to hide these details from external entities.
- Isolate each secret into its own module, class, or function
- This is done so that changes to the secret are isolated and don’t affect the rest of the program
- Parnas: “All data structures that reveal the presence or number of certain components should be included in separate information hiding modules with abstract interfaces.”
- Design intermediate interfaces that are insensitive to changes in the underlying secrets.
- That is, make sure that your secrets aren’t leaked or revealed by your interfaces’
As Parnas reminds us:
The interface must be general but the contents should not. Specialization is necessary for economy and flexibility.
For advice on practically implementing information hiding in C, please see this entry.
What to Hide?
Here are some ideas that are likely to change:
- Hardware dependencies
- Physical storage layout
- Data formats
- Data conversions
- Data access/traversal
- Algorithms
- Implementation dependencies
- Object creation (e.g., factories)
- Transport layer mechanisms
- Non-standard language features
- Library routines
- Complex data structures
- Complex logic
- Global variables (hide behind access routines if they are actually needed)
- Data constraints, such as array sizes and loop limits
- Business logic
Benefits
Information hiding helps us improve the ability to change our system while minimizing the impact on other parts of the system.
Since the implementation details are hidden behind a common interface, changes to the implementation do not require changes to the rest of the program (as long as the implementation sufficiently satisfies the specified interface). Changes are isolated to a single location in the ideal case.
For example, a common sensor API may be provided, and any sensor driver that satisfies the interface requirements can be used within the system. This enables a team to swap between components (and even processors or board designs) as necessary.
Areas of Concern
- Careful attention must be given to interface design, because interfaces may unintentionally leak “secrets” or implementation details to the rest of the program. A suitable method for designing and reviewing abstract interfaces be found in this paper by Parnas et al..
- Inheritance may leak details about how a class works to its children such that decisions that a developer thought were hidden are not actually hidden
Critiques
Revisiting Information Hiding – Reflections on Classical and Nonclassical Modularity provides some critiques on the classical idea of information hiding, mainly:
-
Information hiding as described corresponds to classical logic, but we know that humans aren’t very good at (don’t default to) reasoning via classical logic; instead, we tend to rely on inductive reasoning
-
Some stakeholders of a system will eventually care about aspects of the system that are supposed to be “hidden”
When information is hidden behind an abstraction barrier, there are potential stakeholders (or concerns), who are interested in that hidden information.
If a stakeholder wants to reason about “nonfunctional” aspects of a system, such as time or space complexity or power consumption, he probably needs to reason about implementation details hidden behind abstraction barriers
For example, different implementations have different time or space behavior of the operations, different rounding errors, different optimizations that the compiler will apply, or different power consumption. To some stakeholders, such concerns may well be important; while some require higher performance, others require higher precision.
-
For large systems, basic assumptions of information hiding (e.g., monotonicity and composability) may not seem to hold:
Composing two programs which are each separately correct with respect to, say, lock-based concurrency or transactions, are in general no longer correct when composed. More importantly, the non-composability can in general not be deduced from the interfaces of these components
-
Information hiding and separation of concerns can be contradictory:
For instance, in the canonical AOP example of updating a display when a figure element changes, a figure element module hides less information behind its interface when the display updating logic is separated from the figure element module. In that sense, and contrary to the common notion that information hiding and separation of concerns go hand in hand, information hiding and separation of concerns can actually be contradictory.
There are many concerns that, when separated, need to expose implementation detail in such a way that information hiding is impaired. Developers have to decide what information to hide and what to separate. This is a fundamental problem of classical modularity
-
Information hiding is limited by “the tyranny of the dominant decomposition”
What can be hidden behind an interface depends on the chosen decomposition, but there is no “best” decomposition; rather, from each point of view (such as the points of views of the different stakeholders) a different decomposition (and hence information hiding policy) would be most appropriate. What one stakeholder would hide as an implementation detail be- hind an interface is of primary importance to another stakeholder, who would hence choose a different decomposition that exposes that information.
-
Information hiding may still hinder modifiability
Even if a software system is successfully modularized, and the information needs of all stakeholders and concerns are reflected in the interfaces of components, information hiding might still hinder software evolution. This might be surprising at first, because information hiding is supposed to facilitate software evolution by hiding design decisions behind interfaces, so that they can be changed at will. The problem is that the original developers have to anticipate change and to modularize the software accordingly.
Unfortunately, it is not clear how to decide up-front which design decisions need to be hidden and which need to be exposed.
One could argue that successful modularization just needs better planning to better assess what is likely to change, but we believe that this is an implausible assumption because large-scale software systems are assembled from many independently developed and independently evolving parts; hence, a big global “plan” is infeasible and unanticipated changes are unavoidable in long-living projects.
If the design decision is hidden behind the interface, software evolution might bring a new stakeholder (or concern) into the system which needs to access that hidden information. So, to support the information need of this stakeholder (or concern), the design decision should not have been hidden in the first place.
General Response
In our view, these critiques are valid for some definitions or applications of information hiding – particularly, those which seem to be more far reaching and absolute than our own uses.
Our primary focus is using information hiding as a way to design for change. We do not view information hiding as strictly meaning we do not need to examine implementation details of a module. Instead, we think that implementation details should be hidden from other software modules as much as possible. In fact, in our own model, we still dive into secrets often: our focus is on abstracting common behaviors that other modules require for interaction, while still allowing custom APIs for non-common behaviors. After all, we still need to initialize and configure many modules and properties for our system’s use cases, often in a non-generic and not-easily-abstractable way. For example, every IMU component has a different set of configuration options and setup procedures; however, our algorithms that operate on IMU samples can do so in a generic way that can be made common across devices. We do not have to be so absolute in the application of information hiding.
Certainly it is true every implementation that satisfies an interface is not composable into a suitable system that meets our requirements – we must implement for our system specifically. We aren’t striving to implement general, idealized abstract components that are suitable in all cases.
Engineering is a balancing act: we cannot purely focus on any one desirable quality. Sometimes, we may decide to trade off information hiding for improving separation of concerns. But it also might be true that we still hide things for the rest of the system behind a single interface, allowing the deeper implementation to be tightly coupled.
Finally, we must remember that we are human. We will not create perfect artifacts, no matter how hard we try. Changes will come, some of our assumptions will be invalidated, and we will have to rework the system. This is inevitable, and it is not a reason to apply the techniques that we have at hand to improve our chances of success.
Related Concepts
- Information hiding is often achieved by hiding secrets behind an abstract interface .
- Information hiding is often used interchangeably with encapsulation, but the two concepts have slightly different meanings
- Information hiding is often used in conjunction with separation of concerns, where the implementation details for a given concern are hidden behind an interface
- Information hiding is a principle that can be used to achieve loose coupling
References
-
The original source for information hiding is David Parnas’s Paper Designing Software for Ease of Extension and Contraction. Also recommended is his paperOn the Criteria to Be Used in Decomposing Systems into Modules
-
The Secret History of Information Hiding by David Parnas
Nonetheless, the paper did explain that it was information distribution that made systems “dirty” by establishing almost invisible connections between supposedly independent modules
After some thought, it became clear to me that information distribution, and how to avoid it, had to be a big part of that course. I decided to do this by means of a project with limited information distribution and demonstrate the benefits of a “clean” design
This program is still used as an example of the principle. Only once has anyone noticed that it contains a flaw caused by a failure to hide an important assumption. Every module in the system was written with the knowledge that the data comprised strings of strings. This led to a very inefficient sorting algorithm because comparing two strings, an operation that would be repeated many times, is relatively slow. Considerable speed-up could be obtained if the words were sorted once and replaced by a set of integers with the property that if two words are alphabetically ordered, the integers representing them will have the same order. Sorting strings of integers can be done much more quickly than sorting strings of strings. The module interfaces described in [9] do not allow this simple improvement to be confined to one module.
My mistake illustrates how easy it is to distribute information unnecessarily. This is a very common error when people attempt to use the information-hiding principle. While the basic idea is to hide information that is likely to change, one can often profit by hiding other information as well because it allows the re-use of algorithms or the use of more efficient algorithms
Several software design “experts” have suggested that one should reflect exciting business structures and file structures in the structure of the software. In my experience, this speeds up the software development process (by making decisions quickly) but leads to software that is a burden on its owners should they try to update their data structures or change their organisation. Reflecting changeable facts in software structure is a violation of the information-hiding principle.
In determining requirements it is very important to know about the environment but it is rarely the right “move” to reflect that environment in the program structure.
-
Missing in Action: Information Hiding by Steve McConnell
In the 20th Anniversary edition of The Mythical Man-Month, Fred Brooks concludes that his criticism of information hiding was one of the few ways in which the first edition of his book was wrong. “Parnas was right, and I was wrong about information hiding,” he proclaims (Brooks 1995). Barry Boehm reported in 1987 that information hiding was a powerful technique for eliminating rework, and he pointed out that it was particularly effective during software evolution (“Improving Software Productivity,” IEEE Computer, September 1987). As incremental, evolutionary development styles become more popular, the value of information hiding can only increase.
To use information hiding, begin your design by listing the design secrets that you want to hide. As the example suggested, the most common kind of secret is a design decision that you think might change. Separate each design secret by assigning it to its own class or subroutine or other design unit. Then isolate–encapsulate–each design secret so that if it does change, the change doesn’t affect the rest of the program.
Aside from providing support for structured and object-oriented design, information hiding has unique heuristic power, a unique ability to inspire effective design solutions.
Object design provides the heuristic power of modeling the world in objects, but object thinking wouldn’t help you avoid declaring the ID as an int instead of an IDTYPE in the example. The object designer would ask, “Should an ID be treated as an object?” Depending on his project’s coding standards, a “Yes” answer might mean that he has to create interface and implementation source-code files for the ID class; write a constructor, destructor, copy operator, and assignment operator; document it all; have it all reviewed; and place it under configuration control. Unless the designer is exceptionally motivated, he will decide, “No, it isn’t worth creating a whole class just for an ID. I’ll just use _int_s.”
Note what just happened. A useful design alternative, that of simply hiding the ID’s data type, was not even considered. If, instead, the designer had asked, “What about the ID should be hidden?” he might well have decided to hide its type behind a simple type declaration that substitutes IDTYPE for int. The difference between object design and information hiding in this example is more subtle than a clash of explicit rules and regulations. Object design would approve of this design decision as much as information hiding would. Rather, the difference is one of heuristics–thinking about information hiding inspires and promotes design decisions that thinking about objects does not.
-
Revisiting Information Hiding – Reflections on Classical and Nonclassical Modularity
sInformation hiding is to distinguish the concrete implementation of a software component and its more abstract interface, so that details of the implementation are hidden behind the interface. This supports modular reasoning and independent evolution of the “hidden parts” of a component. If developers have carefully chosen to hide those parts ‘most likely to change’, most changes have only local effects: The interfaces act as a kind of firewall that prevents the propagation of change.
A key question in information hiding is which information to hide and which information to expose. Parnas suggested the heuristic to hide what is ‘likely to change’.
the programming research community, in which information hiding is nowadays such an undisputed dogma of modularity that Fred Brooks even felt that he had to apologize to Parnas for questioning it.
Both information hiding and abstraction imply some notion of substitutability: A module’s implementation can be replaced by a different implementation adhering to the same interface, and since the implementation was hidden to other components in the system in the first place, these other components should not be disturbed by the change.
The distinction between an interface and implementations of that interface, which is the at the core of information hiding and abstraction, is related to logic. The interface corresponds to a set of axioms, and the implementation of the interface corresponds to a model of the axioms. Substitutability is reflected by the fact that the same theorems hold for all models of the axioms (by soundness of the logic), hence we cannot distinguish two different models within the theory. The heuristic of hiding what is most likely to change is reflected by the design of axiom systems (say, the axioms of a group in abstract algebra) in such a way that there are many interesting models of the axioms.
As in the case of information hiding and abstraction, compositionality implies a strong notion of substitutability: If a subprogram is substituted by a different subprogram with the same meaning, the meaning of the whole program will still be the same. In other words, we can successfully reason more abstractly on an expression by thinking of its meaning rather than of the expression itself. When reasoning about the program, we can identify expressions having the same meaning. This process is typically called equational reasoning. Since the actual expression is hidden behind its meaning, compositionality can also be seen as a specific form of information hiding by considering the meaning of a program to be its interface.
« Back to Glossary Index
