Using the Cognitive Dimensions - Penetrability
Penetrability refers to the extent to which a developer must understand the underlying implementation details of an API and the extent to which the developer is able to understand those details. For example, an API that is basically a very thin wrapper over some lower level API probably requires that anyone using that API understands the low level implementation details before being able to use the API successfully.
Sometimes problems that users experience in an API are due to the extent to which they need to understand lower level details and the support that the API provides for gaining that understanding. Depending on the user, the necessary changes to an API in these cases may require changing the abstraction level by adding new higher level abstractions on top of the implementation details so that there is absolutely no need to understand those details. Or, at the other extreme, it may require reducing the abstraction level so that developers can more easily understand the implementation details.
In this posting, I'd like to try to explain how you might evaluate your own API to determine how much of the underlying implementation your users will need to know. Refer to the first article in this series for a description of how to do a task analysis of your API and then read on...
For each goal that the user might attempt to accomplish with the API, describe the extent to which the user needs to understand the implementation details of the API and the extent to which these details are exposed to the user.
For example, in the System.IO namespace, one user goal is to write code to append text to a file. In order to write code that uses the StreamWriter class effectively, the user needs to know:
- If the class is thread safe or not by default and the extent to which this can be configured.
- The character encoding that the class supports by default, and the extent to which this can be configured.
Users can discover from the documentation that the class is not thread safe and can find more details about the character encoding that the class supports. To create a thread safe wrapper around a StreamWriter instance the base class of StreamWriter, TextWriter is used. TextWriter exposes a Synchronize method that creates a thread safe wrapper around any class that derives from TextWriter. Users thus need to find out whether or not StreamWriter derives from TextWriter to determine whether or not they can use TextWriter.Synchronize to create a thread safe wrapper around StreamWriter.
Thus in this example, users need to know the context in which the class is defined and some of the working details of the class, particularly those details that are set by default.
There are other ways to expose API details to the user. For example, the API source code provides most of the detail that a user might need, but at the cost of having to parse and understand the code. Naming conventions used in the API can also help expose some of the details. For example, class, parameter, property, method and variable names might indicate to the user their purpose, scope, visibility etc. When inspecting these members in a debugger, such naming conventions can help users identify those members that they have direct access and control over from the internal working details of the classes they are inspecting in the debugger. It is important to figure out the details of the API that users need to know in order to be able to design the API in such a way that communicates these details clearly.
Penetrability can be defined in the following terms.
- If the API exposes only enough information to allow the user to distinguish between different methods and classes and if this is the only information that the user needs to attend to, the API provides and requires only minimal penetrability, or a snapshot view into the details of the API.
- If the API exposes enough information to allow the user to understand the context or scope of the particular part of the API that the user is working with, and if this is the only information that the user needs to attend to, the API provides and requires a context driven view of the details of the API.
- If the API exposes enough information to allow the user to understand the intricate working details of the API and if the user must attend to this information, the API provides and requires an expansive view of the details of the API.
Note that in the above definition penetrability is defined both in terms of the requirements from the perspective of the user working with the API and the extent to which the API provides support for gaining that perspective. In other words, describing penetrability from the perspective of the API describes the extent to which the developer must understand implementation details. Describing penetrability from the perspective of the developer describes the extent to which the developer demands to be able to understand the underlying implementation details. The developer might not require that amount of information in order to be able to use the API effectively, but they might want that amount of information in order to be able to feel comfortable using that API.
Having defined penetrability in the above terms, the System.IO namespace supports and requires a context driven view of the API.
Comments
- Anonymous
February 04, 2004
So the Avalon API would then require an "expansive" view, correct? The most unusable aspect of that API is the unnecessary exposure of low-level implementation details, imho.
Usually we just bag these problems all under the title "unnecessary complexity". Sometimes people break it down, and say something works at the "wrong level of abstraction." Your distinctions are very useful. However, you do not seem to have a term strictly categorizing complexity that is unnecessary.
For example, a typical design mistake is to expose several low-level details of an API implementation, simply to avoid the much harder task of figuring out an appropriate set of abstractions for the user. The usual argument is "the user needs that because of this one scenario," or more often, "performance."
But there is more than one way to skin a cat. Exposing a low-level detail may fix one problem, or solve a performance problem. But now the user is permanantly exposed to a lower level of abstraction, and the supplier must work that much harder to support upward compatability of the low-level detail. Changing the internal implementaion is no longer easy.
The better solution is often to reconsider the API at a higher level, to enable the scenario, or solve the performance problem, without exposing a low-level detail. This is much harder and developers generally prefer to skip this step, in part because it implicitly invalidates the original "architecture" (ego may be involved).
So Steve, are there terms to describe this very typical design problem? I believe it causes much unnecessary complexity in modern APIs. - Anonymous
February 09, 2004
You're right Frank, in saying that the cognitive dimensions don't describe the 'unnecessary complexity' that you talk about. Measuring the penetrability of an API on it's own doesn't really tell you much with regards to whether or not the snapshot or expansive view required by the API is reasonable.
Determining what works well is done by comparing the measurement of an API with the cognitive dimensions profile of the API user. When you look at the penetrability required to be able to use an API with that expected by a developer, you can use the cognitive dimensions framework to describe why the API is unnecessarily complex or too simple (or just right). - Anonymous
February 09, 2004
The comment has been removed - Anonymous
February 10, 2004
The comment has been removed