Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Professional C++ [eng].pdf
Скачиваний:
284
Добавлен:
16.08.2013
Размер:
11.09 Mб
Скачать

Designing for Reuse

Consider cars again. Most new cars these days provide remote keyless entry systems, with which you can unlock your car by pressing a button on a key fob. However, these cars always provide a standard key that you can use to physically unlock the car. Although these two methods are redundant, most customers appreciate having both options.

Sometimes there are similar situations in program interface design. For example, suppose that one of your methods takes a string. You might want to provide two interfaces: one that takes a C++ string object and one that takes a C-style character pointer. Although it’s possible to convert between the two, different programmers prefer different types of strings, so it’s helpful to cater to both approaches.

Note that this strategy should be considered an exception to the “uncluttered” rule in interface design. There are a few situations where the exception is appropriate, but you should most often follow the “uncluttered” rule.

Provide Customizability

In order to increase the flexibility of your interfaces, provide customizability. People generally appreciate customizability the most when it’s absent. For example, one of the authors recently purchased a new

car with an antitheft device. This alarm automatically deactivates when the doors are unlocked using the remote keyless entry. Unfortunately, if the doors are not opened within 30 seconds after they are unlocked, the alarm reactivates. This feature becomes quite annoying when trying to use the trunk of the car. It’s inconvenient to open a car door just to access something in the trunk. However, if the alarm is not deactivated, slamming the trunk lid triggers it. The most annoying aspect of this problem is that there is no way to permanently deactivate the antitheft device! The car designers must have assumed that everyone would want the same functionality in their antitheft devices, and didn’t provide a mechanism to customize it.

Customizability can be as simple as allowing a client to turn on or off error logging. The basic premise of customizability is that it allows you to provide the same basic functionality to every client, but gives clients the ability to tweak it slightly.

You can allow greater customizability through function pointers and template parameters. For example, you could allow clients to set their own error-handling routines. This technique is an application of the decorator pattern described in Chapter 26.

The STL takes this customizability strategy to the extreme and actually allows clients to specify their own memory allocators for containers. If you want to use this feature, you must write a memory allocator object that follows the STL guidelines and adheres to the required interfaces. Each container in the STL takes an allocator as one of its template parameters. Chapter 23 provides the details.

Reconciling Generality and Ease of Use

The two goals of ease of use and generality sometimes appear to conflict. Often, introducing generality increases the complexity of the interfaces. For example, suppose that you need a graph structure in a map program to store cities. In the interest of generality, you might use templates to write a generic map structure for any type, not just cities. That way, if you need to write a network simulator in your next program, you could employ the same graph structure to store routers in the network. Unfortunately, by using templates, you made the interface a little clumsier and harder to use, especially if the potential client is not familiar with templates.

117

Chapter 5

However, generality and ease of use are not mutually exclusive. Although in some cases increased generality may decrease ease of use, it is possible to design interfaces that are both general purpose and straightforward to use. Here are two guidelines you can follow.

Supply Multiple Interfaces

In order to reduce complexity in your interfaces while still providing enough functionality, you can provide two separate interfaces. For example, you could write a generic networking library with two separate facets: one presents the networking interfaces useful for games, and one presents the networking interfaces useful for the Hypertext Transport Protocol (HTTP) Web browsing protocol.

The STL takes this approach with its string class. As noted in Chapter 4, the string class is actually a char instantiation of the basic_stream template. You can think of the string class as an interface that hides the full complexity of the basic_stream template.

Optimize the Common Functionality

When you provide a general-purpose interface, some functionality will be used more often than other functionality. You should make the commonly used functionality easy to use, while still providing the option for the more advanced functionality. Returning to the map program, you might want to provide an option for clients of the map to specify names of cities in different languages. English is so predominant that you could make that the default but provide an extra option to change languages. That way most clients will not need to worry about setting the language, but those who want to will be able to do so.

This strategy is similar to the performance principle discussed in Chapter 4 of optimizing the parts of the code that are executed most often. Focus on optimizing those aspects of your design that provide the most benefit for the most people.

Summar y

By reading this chapter, you learned why you should design reusable code and how you should do it. You read about the philosophy of reuse, summarized as “write once, use often,” and learned that reusable code should be both general purpose and easy to use. You also discovered that designing reusable code requires you to use abstraction, to structure your code appropriately, and to design good interfaces.

This chapter presented three specific tips for structuring your code: avoid combining unrelated or logically separate concepts, use templates for generic data structures and algorithms, and provide appropriate checks and safeguards.

The chapter also presented six strategies for designing interfaces: develop intuitive interfaces, don’t omit required functionality, present uncluttered interfaces, provide documentation and comments, provide multiple ways to perform the same functionality, and provide customizability. It concluded with two tips for reconciling the often-conflicting demands of generality and ease of use: supply multiple interfaces and optimize common functionality.

This chapter concludes the discussion of design themes that began in Chapter 2. Chapter 6 finishes the design section of this book with a discussion of software-engineering methodologies. Chapters 7 through 11 delve into the implementation phase of the software engineering process with details of C++ coding.

118

Maximizing Software-

Engineering Methods

When you first learned how to program, you were probably on your own schedule. You were free to do everything at the last minute if you wanted to, and you could radically change your design during implementation. When coding in the professional world, however, programmers rarely have such flexibility. Even the most liberal engineering managers admit that some amount of process is necessary. Knowing the software-engineering process is as important these days as knowing how to code.

This chapter surveys various approaches to software engineering. It does not go into great depth on any one approach — there are plenty of excellent books on software-engineering processes. The idea is to cover the different types of processes in broad strokes so you can compare and contrast them. We try not to advocate or discourage any particular methodology. Rather, we hope that by learning about the tradeoffs of several different approaches, you’ll be able to construct a process that works for you and the rest of your team.

Whether you’re a contractor working alone on projects or your team consists of hundreds of engineers on several continents, understanding the different approaches to software development will help your job on a daily basis.

The Need for Process

The history of software development is filled with tales of failed projects. From over-budget and poorly marketed consumer applications to grandiose mega-hyped operating systems, it seems that no area of software development is free from this trend.

Even when software successfully reaches users, bugs have become so commonplace that end users are forced to endure constant updates and patches. Sometimes the software does not accomplish