|
|
||||||||||||||||||||
| Home > Software Quality News > Manage component dependencies for improved system quality | |
| Software Quality News: |
|
||
Where one piece of code depends on another piece of code -- whether by inheriting from it, by calling it, or just by mentioning it -- the two are coupled. By definition a system needs some coupling between its parts; otherwise it is no more than a set of disconnected parts rather than a system. But too much coupling and a system becomes hard to understand, hard to integrate, hard to extend, hard to fix, hard to test. In short, it becomes hard. If software is supposed to be soft, dependency management is one of the things that will allow it to be so. Fine structure Such problems of code quality are often addressed by partitioning code into smaller units. There are many ways to break up and organize the general big picture of the system. But inevitably, in making any separations, there is also a need to make connections. As Edsger Dijkstra observed, "Separation of tasks is a good thing; on the other hand, we have to tie the loose ends together again!" For example, from a deployment perspective, dynamic link libraries (DLL) offer distinct units of execution. Although they are discrete, they still have usage dependencies on one another. From a development perspective, one of the most common ways to partition a system is with respect to classes of objects. Again, although they are nameable and distinct, classes must still be related — by inheritance, association, or instantiation — otherwise nothing much is going to happen. The same applies to data in databases: In the relational model, data is partitioned across rows in tables. But the tables are not just buckets of arbitrary data (or at least they're not supposed to be); they are related through keys. Being normal
In other technical domains the partitioning guidelines are not always so obvious or as easily summarized, but that does not mean that such guidelines do not exist. For example, it is possible to apply the concept of normalization to code as well as to data. Duplication in code is often a candidate for re-factoring: Extracting a new class or a new method that holds the common code introduces a partitioning in the code, as well as coherent dependencies upon it. Groups of interrelated function arguments can be grouped into a single construct, reducing the length of potentially long argument lists. A common and simple example would be to replace passing around three individual integers representing a date with a single date object. Interfaces, whether for classes, procedural APIs, or distributed services, can often suffer from over featurism, becoming a bucket for unrelated operations. Such broad interfaces can unnecessarily increase the surface area of dependency for any client of the interface. Breaking up the interface according to role and common usage — so the dependency is on the interface, the whole interface, and nothing but the interface — offers a simple way of rationalizing and sorting through disparate features. This style applies just as much to header files in C as it does to packages and classes in Java. Ch-ch-changes Our program should not only reflect (by structure) our understanding of it, but it should also be clear from its structure what sort of adaptations can be catered for smoothly. Thank goodness the two requirements go hand in hand. However, just because requirements for clear structure and adaptability go hand in hand, it does not mean that they are automatically fulfilled. Such attention to structure and change is honored more in the breach than in the observance. For example, code that makes frequent use of global state, such as singleton objects, tends to resist change more than code without. Class hierarchies that are based on reuse of common code become harder to work with and evolve than class hierarchies that are based on classification and interface usage. Systems that ensure access to external dependencies via interface types rather than concrete types offer better opportunities for extension and isolation of change. Agility in architecture
This list is overlapping and by no means exhaustive, but it serves to highlight some of the practical implications of the common but relatively abstract guideline that system components should be loosely coupled and highly cohesive. Another concrete way of looking at such architectures is that they have a high degree of locality. In Implementation Patterns Kent Beck offers the following guideline: Structure the code so changes have local consequences. If a change here can cause a problem there, then the cost of change rises dramatically. Code with mostly local consequences communicates effectively. It can be understood gradually without first having to assemble an understanding of the whole. Feed back to go forward Any practical approach to dependency management needs to be continuous and responsive in nature. It cannot sensibly be based on grand planning at the start of development or stopping the whole development process for a cycle of management-visible architectural maintenance (having such work appear on the radar is often not a good sign). There is no silver bullet to dependency management, but there are a few sensibilities that will support any dependency management effort:
You must also consider recurrence and continuity. Like other forms of management, dependency management is not just a single-shot affair. -----------------------------------------
'); // --> |
|
||||||||||||||||||||||||||||||||||
| About Us | Contact Us | For Advertisers | For Business Partners | Site Index | RSS |
|
|
|
|||||||