The Most Important Design Guideline?

The Most Important Design Guideline?

by Scott Meyers

The activity of "design" includes many things, but certainly one of the most important aspects is interface specification. Interfaces determine which aspects of a component are accessible and to whom; they thus determine encapsulation. Interfaces specify what functionality (data, properties, methods, and so forth) is available to clients. Interfaces reflect how a system is broken down into its constituent components.

Interfaces are everywhere. They're the "I" in GUI and API, but they're much more pervasive than that. Classes and structs have interfaces; functions and methods have interfaces; templates and namespaces have interfaces; subsystems and modules have interfaces; libraries and applications have interfaces. Regardless of your role in the development of a software system, it almost certainly involves some interface design, so it's helpful to have some heuristics that indicate when you're doing it well—or poorly. Over time, I've come to the conclusion that the most important general interface design guideline is this:

Make interfaces easy to use correctly and hard to use incorrectly.

This guideline leads to a conclusion that some developers find unsettling.

Interface designers must take responsibility

Let's make the reasonable assumption that your clients—the people using your interfaces— are trying to do a good job. They're smart, they're motivated, they're conscientious. They're willing to read some documentation to help them understand the system they're using. They want things to behave correctly.

That being the case, if they make a mistake when using your interface, it's your fault. We're assuming they're doing their best—they want to succeed. If they fail, it's because you let them. So, if somebody uses your interface incorrectly, either they're working hard at it (less likely) or your interface allowed them to do something easy that was not correct (more likely). This puts the shoe on the foot not used to wearing it: it means that responsibility for interface usage errors belongs to the interface designer, not the interface user.

In a perfect world, adherence to this guideline would all but guarantee correct program behavior. In such a world, programs that wouldn't do the right thing wouldn't compile, and programs that compiled would almost certainly do the right thing. At the human-computer interface level, commands that wouldn't do the right thing would be rejected, and commands that were accepted would almost certainly do the right thing. Alas, our world isn't perfect, but the interfaces used in most software systems can be significantly improved with relatively little effort.

Consider a (C++) class for representing dates in time and how its constructor might be declared:

  class Date {   public:    explicit Date(int month, int day, int year);  };

This is a classic example of an interface that's easy to use incorrectly. Because all three parameters are the same type, callers can easily mix up the order, an error that's especially likely given that different cultures have different ordering conventions for a date's month, day, and year. Furthermore, the interface allows for nonsense data to be passed in, for example, a value of -29 for a month.

Creating separate types for days, months, and years can eliminate the ordering errors, and creating a fixed set of immutable Month objects can essentially eliminate the possibility of specifying invalid months. Here's an example in the form of a simple program:

  #include <iostream>    struct Day {                            // thin wrapper for Day    explicit Day(int day): d(day) {}         int d;  };     struct Year {                           // thin wrapper for Year    explicit Year(int year): y(year) {}      int y;   };    class Month {  public:    static const Month Jan;               // a fixed set of immutable    static const Month Feb;               // Month objects    //...    static const Month Dec;      int number() const { return m; }         private:    explicit Month(int month): m(month) {}                          int m;                                 };    const Month Month::Jan(1);               const Month Month::Feb(2);               //...  const Month Month::Dec(12);                class Date {                                        public:                                               explicit Date(Month m, Day d, Year y);       // revised (safer,    explicit Date(Year y, Month m, Day d);       // more flexible)    explicit Date(Day d, Month m, Year y)        // interface    : dNum(d.d), mNum(m.number()), yNum(y.y)     {       std::cout << "D.M.Y = "                 << dNum << '.' << mNum << '.' << yNum << '/n';    }    private:    int dNum, mNum, yNum;  };   int main() {   Date today(Day(10), Month::Jan, Year(2005)); }

This example points out two important aspects to designing interfaces that obey the guideline. First, interface designers must train themselves to try to imagine all (reasonable) ways in which their interfaces could be used incorrectly. Second, they must find ways to prevent such errors from occurring.

Perhaps the most widely applicable approach to preventing errors is to define new types for use in the interface, in this case, Day, Month, and Year. It's best if such types exhibit the usual characteristics of good type design, including proper encapsulation and well-designed interfaces, but this example demonstrates that even introducing thin wrappers such as Day and Year can prevent some kinds of errors in date specification.

A second commonly useful approach to preventing errors is to eliminate the possibility of clients creating invalid values. This approach applies when we know the universe of possible values in advance. In the date-specification example we just saw, I know that there are only 12 valid months, so I created a Month class with a private constructor, thus preventing the creation of Month values other than the 12 specific constant objects offered by the class. An alternative means to a similar end would be to use an enum, but, at least in C++, enums are less type-safe than classes, because the line between enums and ints isn't as distinct as we might wish.

In addition to introducing new types to the revised Date interface, I also added new constructors to the design. The Day, Month, and Year types make the interface harder to use incorrectly, but without the Date constructor overloads, the result is also harder to use correctly. Good interfaces support as many forms of correct use as possible while simultaneously thwarting as many incorrect forms as possible. Both efforts are necessary. One without the other won't suffice.

Forcing users of an interface to choose from a set of guaranteed-valid choices is often good design, but it's not a panacea. Consider the following figure, which shows how drop-down boxes for day, month, and year at the United Airlines' Web site still allow users to specify an invalid date (such as June 31). This is an example of an interface that appears to conform to the guideline, but doesn't, because it lulls the user into a feeling that mistakes are impossible. That is, it's easy to use incorrectly.

 Drop-down box allowing specification of an invalid date.

Note also that restricting users to choosing from a set of guaranteed-valid choices doesn't necessarily guarantee that the resulting data will be correct. The most constrained GUI drop-down boxes (or class constructors) in the world can't keep me from specifying August 27 when what I really meant was July 27.

In fact, forcing users to specify information via this kind of interface might actually increase the chances of specifying invalid data. Many GUI forms (in both applications and at Web sites) use drop-down boxes for specifying a state, for example, and my experience has been that I inadvertently specify the wrong state much more frequently than I mistype my state's two-letter abbreviation. If my experience is at all typical (and anecdotal evidence suggests that it is), that indicates that a drop-down box for this information is inferior to a simple text input box when considering which interface is easier to use correctly and harder to use incorrectly. It's important not to lose sight of this goal lest we confuse means and ends. The goal is an interface that's easy to use correctly and hard to use incorrectly. An approach that's often helpful in achieving this is to restrict the available input values, but sometimes that approach can be counterproductive.

Another example of an easy-to-misuse interface is one where a function returns a resource that the caller is responsible for releasing. Even languages with garbage collection exhibit this problem, because memory isn't the only resource. Consider this example:

  class Resource {  public:    Resource();    void release();          static Resource getResource();  // caller must call release                                    // on the returned object  };

Here, the interface presented by the getResource method is a resource leak waiting to happen. All it takes is a client who forgets to call release when they are supposed to. The C++ approach to this problem would be to put the resource-releasing code (possibly as part of a reference-counting scheme) in Resource's destructor. Callers of getResource could then forget about resource management, because it would be automatic.

Unfortunately, languages such as Java and the .NET languages don't offer destructors or their equivalent, and the idioms that address resource issues such as this (finally or using blocks, for example) put the onus on clients to remember to use the idioms. But interfaces that rely on clients remembering to do something are easy to use incorrectly.

In situations like this, good interface designers fall back on simple encapsulation: if something is tricky or error-prone and there's no way to get around it, they hide the tricky or error-prone code as much as possible, so as few people as possible have to deal with it. For example, getResource might be declared private or protected so that the easy-to-use-incorrectly interface is accessible to relatively few clients. In addition, Resource might be outfitted with debugging capabilities so that situations in which objects that are leaked or that have unusually long lifetimes (suggesting an overly late call to release) are easy to identify.

Adhering to the guideline that interfaces should be easy to use correctly and hard to use incorrectly leads to systems that are both more usable and more likely to be used correctly. That's why it's the most important general design guideline in my arsenal. To employ it, designers need to train themselves to anticipate what clients might reasonably like to do, and then facilitate that activity. They also must anticipate what clients might incorrectly do, and prevent that activity. Above all, it requires remembering that when an interface is used incorrectly, the fault is that of the interface designer, not the interface user.

(以上文章转载自http://www.aristeia.com/Papers/IEEE_Software_JulAug_2004_revised.htm，有时间我会将其翻译成中文。)

• 本文已收录于以下专栏：

What is the most important thing in your life?

What is the most important thing in your life?I thought everyone would have his own idea about it. I...
• baymoon
• 2006年12月12日 21:00
• 4387

App Store审核4.2.2被拒问题，及其解决方案！

App Store审核4.2.2被拒问题，及其解决方案； 4. 2 Design: Minimum FunctionalityGuideline 4.2.2 - Design - Minimum ...
• ShellM
• 2017年06月09日 19:48
• 6318

苹果审核被拒问题总结

• Tutarala
• 2017年08月23日 10:31
• 1410

Appstore审核被拒-[4. DESIGN: PREAMBLE]

Appstore审核被拒原文如下：原因是设置里有一个版本信息可以响应点击事件进入一个版本详情页，苹果要求版本更新必须使用iOS版本更新内置更新机制。 4. DESIGN: PREAMBLE D...
• icebule531
• 2017年01月03日 15:11
• 1682

Issue 111( Most important discoveries or creation are accidental; it is usually while seeking the answer to one question that

Most important discoveries or creation are accidental; it is usually while seeking the answer to o...
• wangming2046
• 2008年02月29日 11:08
• 1530

求 N!末尾0的个数

http://acm.hust.edu.cn/vjudge/contest/view.action?cid=43142#problem/C Description The mo...
• u013573047
• 2014年03月29日 14:57
• 899

杭电OJ——1124 Factorial（水题）

Factorial Problem Description The most important part of a GSM network is so called Base Tra...
• lishuhuakai
• 2012年12月15日 17:49
• 2096

关于Guideline 4.3 - Design - spam 以及 3.2(f) of the Apple Developer Program License Agreement 上架被拒

• liujiaw2012
• 2017年07月18日 22:23
• 3342

【纯干货】我所知道的苹果审核4.3问题，和它的10种解决办法！

【纯干货】我所知道的4.3，和它的10种解决办法！ itunesconnect.apple关于4.3的被拒回复： Guideline 4.3 - Design Your app duplic...
• ShellM
• 2017年05月16日 15:46
• 13888

托福写作5-what is the most important characteristic that a person can have to be successful in life

In your opinion, what is the most important characteristic (for example, honesty,intelligence, a sen...
• fmc201314
• 2017年02月16日 12:49
• 371

举报原因： 您举报文章：The Most Important Design Guideline? 色情 政治 抄袭 广告 招聘 骂人 其他 (最多只允许输入30个字)