General design guidelines
- Be minimal: A minimal API is one that has as few public members per class and as few classes as possible. This makes it easier to understand, remember, debug, and change the API.
- API should do one thing and do it well: Functionality should be easy to explain
- API should be as small as possible but no smaller: API should satisfy its requirements so when in doubt leave it out
- Be complete: A complete API means the expected functionality should be there. This can conflict with keeping it minimal. Also, if a member function is in the wrong class, many potential users of the function won't find it.
- Have clear and simple semantics: As with other design work, you should apply the principle of least surprise. Make common tasks easy. Rare tasks should be possible but not the focus. Solve the specific problem; don't make the solution overly general when this is not needed.
- Be intuitive: As with anything else on a computer, an API should be intuitive. Different experience and background leads to different perceptions on what is intuitive and what isn't. An API is intuitive if a semi-experienced user gets away without reading the documentation, and if a programmer who doesn't know the API can understand code written using it.
- Use consistent parameter ordering across methods
- Be easy to memorize: To make the API easy to remember, choose a consistent and precise naming convention. Use recognizable patterns and concepts, and avoid abbreviations.
- Lead to readable code: Code is written once, but read (and debugged and changed) many times. Readable code may sometimes take longer to write, but saves time throughout the product's life cycle.
- Supply your tests and mocks to your users
- The Convenience Trap: It is a common misconception that the less code you need to achieve something, the better the API. Keep in mind that code is written more than once but has to be understood over and over again. So don't sacrifice readability over code size.
- The Boolean Parameter Trap: Boolean parameters often lead to unreadable code. In particular, it's almost invariably a mistake to add a bool parameter to an existing function. The thinking is apparently that the bool parameter saves one function, thus helping reducing the bloat. In truth, it adds bloat; can you quickly tell without having to look at documentation what each of the next three lines does?
widget->repaint();A somewhat better API might have been
- General naming rules: Do not abbreviate. Even obvious abbreviations such as "prev" for "previous" don't pay off in the long run, because the user must remember which words are abbreviated.
- Naming classes: Identify groups of classes instead of finding the perfect name for each individual class.
- Naming functions and parameters: The number one rule of function naming is that it should be clear from the name whether the function has side-effects or not. Parameter names are an important source of information to the programmer, even though they don't show up in the code that uses the API. Since modern IDEs show them while the programmer is writing code, it's worthwhile to give decent names to parameters in the header files and to use the same names in the documentation.
- Naming boolean getters, setters, and properties: Finding good names for the getter and setter of a bool property is always a special pain. Should the getter be called checked() or isChecked()? scrollBarsEnabled() or areScrollBarEnabled()?
- Adjectives are prefixed with is-. And be consistent: Adjectives applying to a plural noun have no prefix (like are).
- Verbs have no prefix and don't use the third person (-s):
- Nouns generally have no prefix. (Generally because sometimes not having an is prefix can be misleading)
- Write to your API early and often: Code lives on and is understood as examples, unit tests
- Implementation should not impact API: Implementation details confuse users and inhibit freedom to change implementation. Shield your users from them
- Minimize visibility of everything
- Documentation matters: An API is supposed to be read (several times) and understood by others and this kind of people normally don't read all words you write (I know I don't) so be succinct, precise and cover every single class/function.
- Consider performance consequences of API design decisions: Bad decisions can limit performance (Making type mutable; Providing constructor instead of static factory; Using implementation type instead of interface)
- Fail fast: Report errors as soon as possible after they occur
- Avoid long parameter lists
- Avoid return values that Demand Exceptional Processing: return zero-length array or empty collection, not null
- Provide programmatic access to all data available in string form, Otherwise, clients will parse strings
- Overload with care: Avoid ambiguous overloadings. Just because you can doesn't mean you should and it's often better to use a different name
- Use appropriate parameter and return types:
- Favor interface types over classes for input: Provides flexibility, performance
- Use most specific possible input parameter type: Moves error from runtime to compile time
- Don't use string if a better type exists: Strings are cumbersome, error-prone, and slow
- Don't use floating point for monetary values: Binary floating point causes inexact results.
- Use double (64 bits) rather than float (32 bits): Precision loss is real, performance loss negligible
- Supply Interfaces (Good reasons: Callbacks, Multiple inheritance, Dynamic proxies), but keep in mind that interfaces:
- Can be implemented by anybody.
- Cannot have constructors or static methods.
- Cannot evolve.
- Cannot be serialized.
- Be careful with packages: The Java language has fairly limited ways of controlling the visibility of classes and methods. In particular, if a class or method is visible outside its package, then it is visible to all code in all packages. This means that if you define your API in several packages, you have to be careful to avoid being forced to make things public just so that code in other packages in the API can access them. The simplest solution to avoid this is to put your whole API in one package. For an API with fewer than about 30 public classes this is usually the best approach. If your API is too big for a single package to be appropriate, then you should plan to have private implementation packages.
- Avoid Static Methods
- Avoid making classes and methods sealed or final or non-virtual
- Immutable classes are good: If a class can be immutable, then it should be.
- The only visible fields should be static and final.
- Avoid eccentricity. There are many well-established conventions for Java code, with regard to identifier case, getters and setters, standard exception classes, and so on.
- Don't implement Cloneable: It is usually less useful than you might think to create a copy of an object. If you do need this functionality, rather than having a clone() method it's generally a better idea to define a "copy constructor" or static factory method.
- Exceptions should usually be unchecked: Use a checked exception "if the exceptional condition cannot be prevented by proper use of the API and the programmer using the API can take some useful action once confronted with the exception." In practice this usually means that a checked exception reflects a problem in interaction with the outside world, such as the network, filesystem, or windowing system. If the exception signals that parameters are incorrect or than an object is in the wrong state for the operation you're trying to do, then an unchecked exception (subclass of RuntimeException) is appropriate.
- Design for inheritance or don't allow it: Every method should be final by default (perhaps by virtue of being in a final class). Only if you can clearly document what happens if you override the method should it be possible to do so. And you should only do that if you have coded useful examples that do override the method.
- Static Polymorphism: Similar classes should have a similar API. This can be done using inheritance where it makes sense -- that is, when run-time polymorphism is used. But polymorphism also happens at design time. Static polymorphism also makes it easier to memorize APIs and programming patterns. As a consequence, a similar API for a set of related classes is sometimes better than perfect individual APIs for each class.
- Naming Enum Types and Values: When declaring enums, we must keep in mind that in C++ (unlike in Java or C#), the enum values are used without the type. So one guideline for naming enum types is to repeat at least one element of the enum type name in each of the enum values.
- Pointers or References: Most C++ books recommend references whenever possible, according to the general perception that references are "safer and nicer" than pointers. However, sometimes it's best to use pointers because they make the user code more readable by making it clear that there's a high probability that its parameters will be modified by the function call. By accepting references one could infer from a function call that looks by copy is actually by reference. You can't have this doubt with pointers.