Monday 11 May 2015

Achieving Quality Attributes - Modifiability


So far I have mostly written about quality attributes, documenting them using scenarios  and architect's goal in achieving them. An architect needs tools to tackle this. 

How does an architect do it?


The answer is there are different levels of action you can take to achieve desired qualities in your system. I will start from the most granular level, where you use the so called tactics.

A tactic is a fundamental design decision that influences the control of a quality attribute response (, do yo remember the QA scenarios we talked about earlier?).

Each tactic can be refined to other tactics, for example a tactic to achieve availability is redundancy, it can be refined to redundancy of data, and redundancy of computation.
However to implement this tactic we may need synchronisation(,to keep redundant copies in synch with original). This means a collection of tactics maybe used in some cases, which we call them patterns

In the remainder of this post I will examine tactics for achieving modifiability, as I find it to be easily understood by developers. And some more quality tactics in the next posts.

And please notice I wont cover all quality attribute tactics. Just enough to give you a sound understanding for full details I refer you to this book

Modifiability Tactics

The goal of the following tactics is to control the time and cost to implement, test, and deploy changes.

There are three main categories for these tactics which are explained in details in each section:

LOCALIZE MODIFICATIONS 

Generally speaking the less modules a change request affects, results in less cost. The goal of tactics in this set is to assign responsibilities to modules during design such that anticipated changes will be limited in scope :
  • Maintain semantic coherenceSemantic coherence refers to the relationships among responsibilities in a module. The goal is to ensure that all of these responsibilities work together without excessive reliance on other modules. Achievement of this goal comes from choosing responsibilities that have semantic coherence. Coupling and cohesion metrics are an attempt to measure semantic coherence, but they are missing the context of a change. Instead, semantic coherence should be measured against a set of anticipated changes. 
  • Anticipate expected changesConsidering the set of envisioned changes provides a way to evaluate a particular assignment of responsibilities. The basic question is "For each change, does the proposed decomposition limit the set of modules that need to be modified to accomplish it?" An associated question is "Do fundamentally different changes affect the same modules?" How is this different from semantic coherence? Assigning responsibilities based on semantic coherence assumes that expected changes will be semantically coherent. The tactic of anticipating expected changes does not concern itself with the coherence of a module's responsibilities but rather with minimising the effects of the changes. In reality this tactic is difficult to use by itself since it is not possible to anticipate all changes. For that reason, it is usually used in conjunction with semantic coherence.
  • Generalise the moduleMaking a module more general allows it to compute a broader range of functions based on input. The input can be thought of as defining a language for the module, which can be as simple as making constants input parameters or as complicated as implementing the module as an interpreter and making the input parameters be a program in the interpreter's language. The more general a module, the more likely that requested changes can be made by adjusting the input language rather than by modifying the module.
  • Limit possible options: This one is regarding product lines, which exceeds this blog's scope.

PREVENT RIPPLE EFFECTS

A ripple effect from a modification is the necessity of making changes to modules not directly affected by it. For instance, if module A is changed to accomplish a particular modification, then module B is changed only because of the change to module A. B has to be modified because it depends, in some sense, on A. 

There are different dependency types: syntactic, semantic, location, existence of, behaviour of,.. .
  • Hide information:Information hiding is the decomposition of the responsibilities for an entity (a system or some decomposition of a system) into smaller pieces and choosing which information to make private and which to make public. The public responsibilities are available through specified interfaces.
  • Maintain existing interfaces:If B depends on the name and signature of an interface of A, maintaining this interface and its syntax allows B to remain unchanged. Of course, this tactic will not necessarily work if B has a semantic dependency on A, since changes to the meaning of data and services are difficult to mask. Also, it is difficult to mask dependencies on quality of data or quality of service, resource usage, or resource ownership. Interface stability can also be achieved by separating the interface from the implementation.
  • Restrict communication paths: Restrict the modules with which a given module shares data. That is, reduce the number of modules that consume data produced by the given module and the number of modules that produce data consumed by it. This will reduce the ripple effect since data production/consumption introduces dependencies that cause ripples.
  • Use an intermediary: If B has any type of dependency on A other than semantic, it is possible to insert an intermediary between B and A that manages activities associated with the dependency. 
DEFER BINDING TIME

Tactics discussed so far minimise the number of modules that require changing to implement modifications. How about time to deploy and allowing non-developers to make changes?

Deferring binding time supports both of those scenarios at the cost of requiring additional infrastructure to support the late binding. We discuss tactics that affect deployment time.
Many tactics are intended to have impact at load-time or runtime, such as the following.
  • Runtime registration supports plug-and-play operation at the cost of additional overhead to manage the registration. Publish/subscribe registration, for example, can be implemented at either runtime or load time.
  • Configuration files are intended to set parameters at startup.
  • Polymorphism allows late binding of method calls.
  • Component replacement allows load time binding.
  • Adherence to defined protocols allows runtime binding of independent processes.

And finally the tactics distilled:


1 comment: