Chapter 6. Emphasis on Design

Emphasis on Design

Design matters! Well-designed software is obvious to users and to the programmers who work on it. Well-designed software is useful and easy to use, maintain, extend, and understand. And when competition is intense, design is often the difference between winner and loser.

In order to achieve sustainable software development, software must be designed to support and enhance changeability. This is because agility is possible only with the ability to easily modify and extend the software. Imagine that you are tasked with creating a set of playground equipment. If the equipment you design requires a welding torch and hammer to reconfigure, you are in a ton of trouble if your customer asks for changes to the configuration. On the other hand, if your equipment is designed to be modular and is put together with some bolts and a wrench, you can respond to almost any customer request. Software is unfortunately more complex than playground equipment because it is never left in any one arrangement, it is continually changing and evolving.

In sustainable development, you cannot afford to have a product that has a high cost of change because your inability to easily change your product will dictate a slow pace of development that will leave you in continual catch-up mode. Good design practices are one of your most important tools to help control the cost of change.

Design clearly has a critical role in software development, regardless of the methodology used. Design is also the hardest aspect of software development to get right because of factors such as schedule pressures, the people on the project, and continual change. As a result, designs fall across a spectrum that ranges between being overdesigned and underdesigned, with an unfortunate number of projects at either end.

Overdesigned projects are typically the result of overzealous application of the engineering approach to software development. This is the traditional waterfall top-down design method, which is still the most widely taught approach to software development. Top-down design advocates thorough design before coding, which of course involves understanding requirements before design. This is what most building planners use: They have a stable of mathematics, standard questions to ask the customer, and building codes that help them make the right decisions before they actually begin construction. Unfortunately, when top-down design is applied to software, the result is most often overdesign and a high cost of change. That is, if any software is produced at all—many of these projects turn into exercises that produce stacks of documents but no code.

The other extreme to top-down design is ad-hoc code-then-fix development, which is just as bad. In this case, there is little or no design, and change is just as hard because the code is a tangled mess or hard to comprehend. Underdesigned software is painful to work with. It is also unfortunately a great form of job security, because one of the symptoms of underdesigned software is the fact that the code is divided into the exclusive domains of various individuals. These heroes can crank out new features, but heaven help you if they ever leave the project.

In order to achieve sustainable development, you can’t rely on top-down or bottom-up development. What is required is a middle ground so you can capture the good points of each: You want to have the discipline and ability to think about good design while at the same time proceeding as rapidly as possible.

Extreme Programming [Beck 2004] outlines evolutionary or emergent design. This method relies on simple design (only design what you need), refactoring (disciplined code changes), and test-first development (to ensure that the behavior stays as originally intended—see Chapter 5). Evolutionary design works because of a tight feedback loop with end users of the software: The idea is to get the software into their hands as early as possible and then evolve the design as requirements change. However, evolutionary design can lead to unintended oscillation, where the same code is repeatedly changed over and over because sufficient thought was not put into the design:

One of the inherent dangers of any form of iterative development is confusing iteration with oscillation. Good iterative design involves a successive convergence on a workable, adaptable design. Poor iterative design involves flailing around randomly searching for a solution—and mistaking this oscillation for iteration. [Highsmith 2005]

For sustainable development, you need to understand how to balance the good aspects of up-front design and various design techniques with evolutionary design while avoiding the pitfalls of each. With up-front design you need to avoid overdesigning the solution by erring on the side of simple design while at the same time avoiding the documentation trap by focusing on producing software not documents. And you have to avoid design and code oscillation by thinking ahead and having a design vision and guiding principles. The design vision and guiding principles are explained as practices below.

Another way to think about up-front and evolutionary design is through the understanding of good design and good design evolution. Practitioners of up-front design concentrate on understanding what makes a good design, while practitioners of evolutionary design focus on understanding designs that result from evolution and how they evolved:

If you’d like to become a better software designer, studying the evolution of great software designs will be more valuable than studying the great designs themselves. For it is in the evolution that the real wisdom lies. The structures that result from the evolution can help you, but without knowing why they were evolved into a design, you’re more likely to misapply them or over-engineer with them on your next project. [Kerievsky 2004]

The reality, however, is that design is hard because you need to understand good design AND good design evolution. You can’t understand design evolution if you can’t recognize good design, and design is pointless without evolution because evolution is inevitable. And good design without evolution is pointless, otherwise the tendency would be to design for the sake of design. Hence, design has a yin (good design) and a yang (good design evolution) and this I think explains why it is so hard to get right. Good design doesn’t just happen, it requires hard work and thought plus experience and knowledge. However, I also believe that good design doesn’t just emerge from a single individual, no matter how brilliant. Good design requires collaboration, because collaboration provides support as the team collectively works toward their goal while balancing the yin and yang of design.

Design in sustainable software development means creating an environment where decisions are continually made within a framework that emphasizes change and designing for change, while ensuring consistency in the decisions. Design work is recognized as being crucial and something that is done every day by every member of the team in as collaborative a fashion as possible. The challenge is to ensure that the time spent on design tasks is appropriate for what is being designed and that the effort spent documenting the design is appropriate for the conditions of the project and is minimized to the greatest extent possible.

Other elements of design in sustainable software development are:

  • Agile development emphasizes having working software not comprehensive documentation. In many ways, the agile movement is a reaction to the almost compulsive behavior that requires the production of binder upon binder of design documentation. This type of documentation is rarely kept up to date with the code, and if it is, then the amount of effort required to keep it up to date frequently exceeds or equals the effort required to modify the actual code.

  • Binders of documentation don’t solve customer’s problems, working products do. Hence, agile development stresses the working product and lightweight ways of documenting the design work that the team naturally does every day.

  • The requirement for documentation increases with the size and complexity of the project or where there is distributed development. If you can’t get all the developers in one room on a regular basis, then more documentation is required—with the goal, as always, of keeping the documentation as simple as possible.

  • The primary value of design work is the process of doing the design itself, not the documentation of the design. Documentation increases comprehension and the ability to communicate about the design, but reading design documentation is nowhere near as effective as actually participating in the design process itself.

  • Collaboration and face-to-face communication are a key part of design in agile development. The challenge is finding the right media to enhance communication and collaboration. For collaborative design sessions, a discussion around one or more whiteboards in pseudo-UML is often the best approach with the documentation of the discussion being a digital photo that can be posted on a team web page for reference [Ambler and Jeffries 2002].

  • Teams need to discuss and understand their design criteria. What are the attributes of the design that matter the most? These guiding principles are a critical part of design and help ensure consistent design decisions over time.

  • Agile development advocates simple design. According to many, this means only designing and coding what is needed immediately. I think this is impractical, because if you know you will need something, you should go ahead and design it and build some or all of it. The key, of course, is knowing what you absolutely need and what you might need. This is why design must be collaborative; you shouldn’t make these decisions by yourself. Use the wisdom of your team.

  • Teams must design for change. The constantly evolving complex ecosystem of technologies, markets, and competition that software systems inhabit means that the design must promote change, particularly in areas of the product where change is most likely. An example might be the use of a third-party component such as a web browser or instant messaging system. Users may already have their own solutions and will resist your product if you install a solution they already have, so a logical design decision would be to spend the extra time to design an abstract interface that will allow the integration of a component of choice. This independence will also provide you the flexibility to change preferred components and deal with new and (up to a point) old versions of the component.

In typical software projects there are many kinds of design. The most prevalent are the design of the software architecture (classes, hierarchies, methods, etc.), the user interface, web site, marketing and sales material, user documentation, test infrastructure, and database structure. I am intrigued by the idea that while each of these areas is different, there are common elements that can be applied. Hence, although this chapter focuses on software design, the design practices are applicable to other areas of design as well.

Practice 1: Design Vision

From a project management standpoint, iterative development is really hard if you don’t have a vision. Hence, just as you need a vision for the project as a whole, you should have a vision for your design as well, even if all you are doing is adding to an already existing code base. A design vision, or conceptual design, is an overall picture of the pieces of your software: what they are, what each does, and how they interoperate to solve the task at hand. Without a design vision, oscillation will likely result because the team will not have a clear idea of how the software is and should be structured.

The design vision can be a simple hand-drawn annotated sketch on a whiteboard or a UML diagram. My personal bias is toward a hand-drawn UML diagram on a whiteboard that is kept up to date. However, it doesn’t matter what form the design vision is captured in, as long as it exists.

Practice 2: Guiding Principles

Guiding principles are a short list of statements that specify overall design goals and requirements. Their intent is not to specify the design but to help team members make daily design decisions that are consistent with the design intent. Some people also refer to them as pillars because they never change, or are not changed in major ways. They are literally the ideas that everything is built upon.

Guiding principles can be thought of as the vision for the design. However, where the vision describes what the project is about, the guiding principles describe how the project should be built. As with the vision, the guiding principles need to be understood and agreed to by everyone on the team, and they should be created before the start of the project. Ideally, they should be placed in a visible place where everyone can see them and even pinned up at everyone’s desk.

Two important and distinct guiding principles are recommended (others are possible):

  • Engineering guiding principles. These describe how the project is implemented in technical terms that the project team’s developers can understand.

  • User experience guiding principles. These describe attributes that the user cares about. They are described in a user-centric way that everyone on the team can understand.

The easiest way to explain guiding principles is by example. Let’s use one hypothetical example and one real example.

A Hypothetical Example of Guiding Principles

Suppose an IT project team is faced with the task of having to replace an existing backend system for sales tracking. The existing system has an obscure user interface because it has evolved over many years and users complain that they cannot get the information they need without calling for assistance. The system also needs replacing because it is becoming increasingly hard to maintain. And most important, the system needs to be replaced because it can’t handle multibyte characters in the customer name field, yet the company has a growing customer base in China and Japan. The new system must be able to store and display Asian characters.

The vision of this project might be something like:

Enable the growth of the company’s sales in Asia by providing a sales tracking system that can record and display customer information regardless of geography.

The engineering guiding principles might be:

  • Internationalized by default; allow use of any language

  • Clean 3-tier architecture

  • Replaceable user interface tier

  • No hacks or ugly workarounds

Of these guiding principles, you should particularly note words and phrases like “clean 3-tier” and “replaceable.” These statements will help ensure that the team is not just groping in the dark toward an end system, but has clear design goals. Even though the team does not have the detailed design at the beginning of the project, these goals will guide team members through every design decision made while the project is developed and help ensure that the end architecture has the desired characteristics. The last guiding principle is a statement that will make the developers think: They should avoid cutting corners and take the time to do things right.

The user experience guiding principles might be:

  • Simple: Users of the existing system must be able to use the new system in less than 5 minutes

  • Users can generate their own reports with a few minutes of up-front training and no ongoing support

  • No glitches when the existing system is replaced

These statements are tangible and measurable and can be used by the product team to make daily decisions. Each of these principles stresses the importance of a clean transition to the new system in a different way. Their presence will help the team with their daily design work by focusing on usability issues that will ease the transition to the new system.

The vision provides the team with a clear statement of what they are building and why. The guiding principles tell the team how the product should be built, with the user experience guiding principles describing what the user’s experience should be like, and the engineering guiding principles describing the “internal” characteristics of the implementation. In this case, the guiding principles describe the important characteristics of the new system that are required to address the deficiencies of the existing system (which is hard to use, learn, extend, and maintain).

A Real-World Example of Guiding Principles

Adobe’s Eve (http://opensource.adobe.com) provides an interesting real-world example of guiding principles. Eve consists of a language that is used to specify user interfaces and a cross-platform layout engine. Although the authors do not use the term guiding principles, they do refer to the goals and requirements of the project:

Goals:

  1. Make it easier to specify and modify the layout of a human interface.

  2. Have a single definition of the interface for all platforms.

  3. Have a single definition of the interface for all languages.

Requirements:

  1. Must allow piecemeal incorporation into an application, freeing the entire human interface from being converted in one pass.

  2. Generate layouts as good or better than those generated by hand.

These goals and requirements are guiding principles. They were developed before any code was written and therefore serve as the design vision. They are simple statements that serve as daily reminders for the development team as they make design decisions and tradeoffs. For example, the first requirement ensures that the team creates an architecture that allows existing applications to be converted to using Eve piecemeal. This must have been a challenge to the developers because it implicitly dictates that Eve be efficient and as lightweight as possible so that an application being converted does not dramatically increase in size or decrease in speed.

Practice 3: Simple Design

Simple design means you should only design and code what you know you need while always striving for simplicity. In the pure definition of simple design (such as in Extreme Programming) the goal would be to only design what you need immediately. The problem with this purist approach is that it can lead to oscillation, where a body of code is continually refactored to meet the current requirements because there was no design or planning ahead. If you know that something is going to be required, then you should design it and build it even if this means the user may only see a limited immediate benefit. But you need to keep the design as simple as possible. Add the interfaces later that you don’t immediately need, for example. Likewise, if a problem is understood well enough to know that its solution can be found in a design pattern, then the pattern should be used because patterns work and are well understood. But patterns aren’t perfect (see the Design Pattern practice below), and again you need to always think about simplicity first.

Tip: Making architecture work visible to users

In agile development, it is sometimes hard to get architecture work recognized. The cards are user-centric features, as they should be, and users can too easily dismiss architecture work if it doesn’t have a recognized benefit.

One effective way I’ve seen to deal with this problem is to attach architecture cards to user feature cards. The architecture cards not only document what architecture and design work needs to be put in place in order to enable the user feature, but they also document the benefits (from a user’s perspective) of the work.

In the hotkey case above, a benefit of having a well-designed hotkey manager would be that it makes the addition of subsequent commands trivial. If the users can’t see or understand a benefit to architecture work, then perhaps the work isn’t necessary after all . . .

Some readers may be uncomfortable with the gray area surrounding being certain that supporting architecture and future extensibility are required. I think it’s important to accept that you’re going to get it wrong sometimes and over- or underdesign in some instances. That’s software development. Fix the problem as soon as you can, learn from the experience, and move on. Teamwork, knowledge, experience, and collaboration are vital to make the right decision. The more people make design decisions with others and use each other as sounding boards for ideas, and the more people learn from each other, and the greater the collective experience and knowledge in the team, the greater the chance that the correct decisions are going to be reached.

Refactoring

Refactoring is the process of changing a software system in such a way that it does not alter the external behavior of the code yet improves its internal structure. It is a disciplined way to clean up code that minimizes the chances of introducing bugs. In essence when you refactor you are improving the design of the code after it has been written. [Fowler 1999]

Refactoring involves changing software in a disciplined way so that the structure of the code is changed but the code’s behavior remains unchanged. Refactoring is distinguished from merely rewriting code (which developers do all the time) by the discipline required. Refactoring is performed in a disciplined, step-by-step manner with tests in place to catch problems as you proceed. Rewriting is performed in a more ad-hoc delete/replace manner. Refactoring will help keep your code clean; rewriting may not.

The quality of communication is higher with refactoring than with rewriting. This is because with refactoring the person who made the changes can describe the changes through the type of refactoring that was performed. For example, a developer could say “In this change I did an ‘extract method’ refactoring, removing largely duplicated code from methods X, Y, and Z.” The type of refactoring (extract method) immediately conveys to any other developer an immediate and rough feeling of the changes that were made and the complexity of the work required without having to look at the code. Without the refactoring discipline, then, the only recourse for other developers to understand the complexity of the change is to actually look at the code before and after the changes were made.

The level of confidence is higher with refactoring than with rewriting. This is mostly because of the discipline required; with refactoring you are discouraged from trying to make multiple changes at the same time and rather to make changes in small, safe steps that can each be verified through tests. However, when rewriting code, it is too easy to approach a problem with wild abandon, which often results in a worse state or no improvement.

Practice 5: Design Patterns

Design patterns [Gamma et al 1995] are an invaluable resource for software developers. They are essentially a catalogue of solutions to frequently encountered software development problems. Unfortunately, many developers have never heard of design patterns, and others feel that simple design means they should not use patterns because using a design pattern seems like up-front design. But if you are trying to solve a known problem, why reinvent the wheel? The most common design patterns were not just invented, they were observed by some clever and experienced software developers as solutions that worked time and again in different software projects and over time. Patterns are known to work, they are tested, and they have a known behavior.

The value of design patterns as a common vocabulary should not be underestimated. For example, I was in a design discussion one day with my team. One of my teammates started describing a solution in a message-passing system where a message was passed to a number of classes in turn until one of the classes was able to deal with the message. This design provided the greatest amount of flexibility because it allowed the message-handling classes to change independent of the message sources. There was a lot of confusion within the team about how this would work in detail, until I realized that what was being proposed was the Chain of Responsibility pattern. As soon as I pointed this out, the tone of the conversation completely changed. The confusion disappeared because everyone knew what this pattern did. The discussion then switched to how the pattern could be extended to meet the needs of prioritized messages and other detailed, not philosophical, problems. Hence, our common vocabulary of a design pattern as a higher level concept allowed us to have a productive and focused conversation on solving the real problems.

Design patterns can be abused just like any other practice. But just because they have problems doesn’t mean they should be ignored. They are too valuable to be ignored, as long as you understand what can go wrong. The most commonly cited problems with design patterns are:

  • They are often overzealously used. Many examples can be found on the Internet, in software development papers, or in books of projects where it seems like every line of code cleverly employs some design pattern. The software that results from such projects is often unnecessarily hard to understand and modify due to the complex relationships between classes. These projects have sacrificed the rule of simple design for being clever.

  • Design patterns are often used to solve problems where a much simpler solution is possible. If you use design patterns a lot, you’ll find that they’re extremely reliable. They solve problems. However, once you get to the point where you know patterns will work the majority of the time, it’s often too easy to think about how a design pattern can be used for all problems and forget that sometimes a more ad-hoc solution that involves a small bit of code is even better because it’s simpler! The mistake in this case is thinking of design patterns before simplicity.

  • There are too many different ways to code a pattern. Since design patterns only provide an outline for implementation, it is still possible to have implementations of patterns that are too complex.

  • The use of design patterns can lead to an unhealthy emphasis on up-front design. I have been on projects where people spent too much time trying to analyze a problem so they could try to identify what design patterns would apply. If you aren’t sure, don’t use a pattern. Simplicity, as always, should be the primary goal. Chances are, if there is a pattern, there it will emerge. You can always refactor to it later, as in [Kerievsky 2004].

  • If you don’t know design patterns, the code can appear to be overly complex. This really can’t be helped except through education. However, it is a very real problem when new developers join a project where patterns are used.

In summary, design patterns are valuable and they shouldn’t be ignored. They do have problems, but as pointed out in the Simple Design practice, teamwork, experience, knowledge, and collaboration are the best way to minimize the risks in software design while maximizing the potential returns. It also helps to recognize that making mistakes is human and part of software development. Fix your mistakes as quickly as possible, learn from them, and move on.

Practice 6: Frequent Rapid Design Meetings

The rapid design meeting is an important mechanism for a team to ensure it is having frequent design discussions. The output of these meetings is a shared understanding of the design issues and a lightweight artifact such as a picture of a whiteboard diagram. This is a departure from more formal design meeting where the output is a document. The desired output of a rapid design meeting is shared understanding, learning, and collaboration. This is a recognition that the source code and automated tests should be where the design is ultimately documented, supported by the most recent design illustrations, and the emphasis should be on the finished product not the design documentation.

There are two types of design meetings that teams can use: the design discussion and the design review. A design discussion is when a number of team members get together to discuss design decisions of any kind. A design review is an opportunity for a team to analyze its current architecture and discuss short and long-term changes that need to be made.

Design discussions should happen more frequently than design reviews. Most design discussions are going to be purely ad-hoc, when an issue comes up, a number of people get together to discuss it. If ad-hoc discussions aren’t taking place on your project, then you have a serious collaboration problem. You may find it useful to introduce a regularly scheduled (e.g., every Friday from 3–5) design discussion so the team can do a deeper review of one particular area of the design, either what has been completed or what needs to be added. These discussions are rarely a waste of time, because they give the entire team a chance to learn and collaborate.

Design review meetings might be held after every Nth iteration, or they might be added to the agenda in a retrospective. As with a retrospective, the goal of the design review is not to assign blame but to learn and provide a positive future direction for the design. Typically, the design review would consist of answering some basic questions about the product’s design as it currently stands, such as:

  • What areas of the current design are working well?

  • What areas of the current design are not working well and should be refactored or rewritten?

  • Are there areas of the code that are too tightly coupled?

  • Are there areas of the code that are going to be changed a great deal with upcoming features, but are difficult to understand or are fragile?

The result of answering these questions should be a list of problem areas. The team should prioritize these, record the main benefits to the work, incorporate the important work into upcoming iterations, and ensure the rest are recorded (e.g., in the bug-tracking system) to be completed in an upcoming release. As part of incorporating some of this work in upcoming iterations, a recommended approach is to write up a feature card for the work. Even though the work is not a user feature per se, users need to understand why they are not getting a feature and ideally should understand the work (at a high level) so appropriate tradeoffs can be made. People are always skeptical when they hear this suggestion, but in my experience, users care about the stability of the applications they use and are willing to make these tradeoffs when the benefits can be explained to them.

Note that as part of answering these questions, there are excellent opportunities to learn about good design and bad design. This is team professional development, and it helps everyone on the team improve his or her craft and become better software professionals. It is also an excellent opportunity for more experienced developers to pass on the knowledge they have acquired through their careers.

Tip: Design sessions and reviews are valuable for more than just software

Many possible areas of design are vital to a typical software project (e.g., database, web site, user interface, customer support, etc.). Think about all the areas of design for your project and ensure you spend adequate time on them, too.

Practice 7: Commitment to Rearchitecture

Refactoring is a powerful discipline, but sometimes it is necessary to completely rearchitect and replace some portion of a product. In many cases, this is the preferred approach since the cost of the replacement will over time be less than trying to bend the current architecture in the required direction. Therefore, teams (and their management) need to be committed to understanding when rearchitecture is required and be committed to making it happen. Using the chemical plant analogy from Chapter 1, this work should be looked upon as preventive maintenance [md] e.g., taking your pumps offline for work that helps them last longer.

Some of the common reasons for considering rearchitecture work are:

  • A team has just released the first version of a product. Shipping a version one of a product is the most difficult task any team can undertake, because team members are most often learning about the product’s ecosystem as they are developing. They are bound to make at least one, and most likely quite a few, design decisions that will ultimately limit long-term sustainability.

  • A product was written for a single platform (operating system, database, networking protocol, etc.) and it needs to be ported to a new platform. These ports are an excellent opportunity to introduce greater abstraction to hide the underlying platform-dependent implementations. When done well, this type of abstraction should simplify the application and make it easier to modify in the future.

  • There have been changes to the underlying technology such as system apis or third-party libraries that the product depends on. Examples might be new input or display devices, or perhaps new hardware or programming models.

  • A single-threaded application needs to be multi-threaded. If the goal is to maximize the usage of multiple processors, single-threaded software most often needs to be rewritten to break up its tasks into separable units of computation. Without this rework, usually the only work that can be done is to optimize loops or small sections of code. This type of optimization will not maximize use of additional processors.

  • It is natural that the team will understand the problem domain and ecosystem better over time. Often, the current knowledge can be used to further simplify the product.

  • The roadmap for the product has changed and in upcoming releases the architecture is going to have to support originally unanticipated workflows. This case is tricky, since you need to practice simple design and not build unnecessary architecture, but sometimes there are some simple changes that will make these future changes much easier.

  • A section of the code is particularly defect prone, is difficult to modify, and/or excessively coupled to other sections of the architecture.

When considering whether rearchitecture is required, it is vital to remember the underlying tenets of sustainable development. Your product is going to last for a long time—hopefully it will last far longer than you can imagine. Consider the consequences of not making the necessary decision when the cost is low (i.e., early in time) versus being forced to do even more work in the future. It is vital that the decision to rearchitect or not should be made consciously by the team. The most dangerous scenario is when the need to rearchitect is ignored by the team or not discussed due to time pressures, external factors such as overwhelming customer requests or unrealistic expectations, or a laissez-faire attitude. Sustainability is often at stake, and the team must have the discipline, and guts, to confront these issues head-on.

Practice 8: Design for Reuse

The topic of reusable software has somehow become the antithesis of agility, especially among many advocates of emergent design. And yet, the best way to enhance responsiveness to new opportunities is to have something to start from. If you have to start from scratch or are continually reinventing key algorithms, you are wasting effort, effort that should be spent concentrating on the new aspects of your project that will help you differentiate from what has already been done elsewhere.

Reusable software has fewer defects and cleaner interfaces than non-reusable software. Once a piece of software is used but not duplicated in more than one place, it is going to be tested twice, and likely in different ways. This makes the software more robust. When coupled with automated tests of the interfaces for each use, the resulting software has a much greater chance of being extremely low in defects than if it were used only once.

Reusable software doesn’t emerge. Although reuse can be arrived at through refactoring, there are often some key architectural decisions that need to be made as early as possible and carried through the project to make subsequent work much easier and less prone to needing to be redone. For example, when extensibility is important, it’s wise to design the plug-in architecture early and then use it heavily, ideally so that all new features are developed as plug-ins. Without this up-front design and architecture work, the risk is that the system might be extensible, but not in a sustainable way.

Reuse is vital to sustainability because it minimizes wasted effort and allows new initiatives to be launched quickly. Unfortunately, it is only enabled by sound design and coding practices because you need to understand how to balance reuse against wasted effort.

I suspect the reaction to reuse stems from projects where software is overdesigned to make it reusable (which conflicts with simple design), resulting in a complex, defect-laden mess. This is where design intelligence comes in:

  • If you aren’t sure a piece of software is going to be reused, at least do everything you can to minimize coupling and dependencies with other software modules, ensure there are automated tests for the interfaces, and make it separately buildable (this is just commonsense). This at least makes it easier for the next developer to get started and make the software truly reusable.

  • If you know the software definitely won’t be reused, don’t use that as an excuse to ignore good design practices! (See the previous point.)

  • If you know that there is a high (>80%) likelihood of reuse, do it! But keep the design and interfaces simple so that extension is possible when the code really is reused. Don’t overdesign and waste effort on aspects of the implementation you don’t immediately need and aren’t certain of.

One of the largest problems and most important reasons to have reusable software is to eliminate duplicated code. It’s tempting to just copy and paste a section of code, and although there may be a savings in effort in the short term, over the long term there will be a large amount of wasted effort. Duplicated code not only makes the size of the program larger, it also adds to complexity and is a common source of error, where a problem is fixed in one copy of the code but not another.

Reusable software also eases replaceability.If some portion of the architecture needs to be replaced, it is always easier to take out one section and change its interfaces as required than it is to try and replace the entire system. If there are no interface changes required, even better, because then at least you can reuse the automated tests on the interface to ensure your new implementation has the same behavior.

The notion of completely componentized software has been around since the introduction of Object-Oriented Programming. The notion of software ICs (i.e., software chips that can be wired together, much like chips on a printed circuit board) [Cox 1986] comes from the success of product line manufacturing. The automotive industry, for example, has reduced costs and defects by using as many common components (body panels, windshield wipers, motors, seats, instruments, etc.) as possible within a line of related vehicles.

Unfortunately, the ideal of fully componentized reuse as in manufacturing still largely eludes the software industry. I think this is because of the constant change in the software ecosystem and the level of overall complexity. However, despite the complexity, it is still possible to attain a high degree of reuse, and this aids sustainability by allowing teams to be more responsive to opportunities and to avoid duplicating effort. One of my hopes for open source software is that over time we can dramatically cut down on duplicated effort within the industry and allow a greater amount of common infrastructure to be developed and enhanced over time.

Summary

Design is vital to sustainable development because in order for software to last over the long term, it must be well designed. The problem, however, is that design is hard. There is a dilemma that every project faces and that is knowing when to design up-front and how much effort to invest in up-front design versus letting the design emerge. Good design practices should stress finding a balance between up-front and emergent design and emphasizing a collaborative design process that is not built around heavy requirements for documentation of the design.

I believe it is important to start with a vision and guiding principles for the design. These are the goalposts that team members must consider every day as they make tradeoffs. From there, the design practices should stress design iteration, constant change, design visibility, and collaboration. Mistakes are inevitably made, where software is over- or underdesigned. Software teams should rely on a focus on simplicity and a desire to avoid duplicated effort plus teamwork, knowledge, experience, and collaboration to reduce the number of mistakes and their severity. It is vital that design mistakes are fixed as quickly as possible, the appropriate lessons are learned, and then the team moves on. It is also critical that the team has the guts and instincts to make difficult decisions, such as to rearchitect before the cost of change becomes too high.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.142.2