|
Table of ContentsThe progression of chapters in this book roughly follows the standard evolution of an API, from initial design choices, through implementation decisions, versioning, documentation, and testing. The book also includes details on more advanced topics, such as creating scripting and plugin interfaces for your C++ APIs, as well as covering details on how to create static and shared libraries for your API implementation. Foreword Preface Acknowledgments Introduction
The Introduction chapter defines what an Application Programming Interface (API) is with various examples and describes how an API is represented in C++. This chapter details the difference between API development and standard application development as well as enumerating the pros and cons of APIs in software development. It then illustrates the various layers of APIs that a modern application is built upon, with specific reference to a large open source client/server program. The relationship of the terms API and SDK is explained, as is the relationship between APIs, file formats, and network protocols.
What Are APIs? Contracts and Contractors APIs in C++ What’s Different about API Design? Why Should You Use APIs? More Robust Code Code Reuse Parallel Development When Should You Avoid APIs? API Examples Layers of APIs A Real-Life Example File Formats and Network Protocols About This Book Qualities
The Qualities chapter enumerates the set of language-neutral qualities that define a good API. This includes providing a good abstraction for the problem being solved and hiding implementation details through a combination of physical and logical hiding techniques. The quality of minimal completeness means that an API should be as small as possible but no smaller. An API should also be easy to use, which includes facets such as being discoverable, difficult to misuse, consistent, orthogonal, platform independent, and making appropriate use of smart pointers such as shared pointers, weak pointers, and scoped pointers. Robust API designs tend to be loosely coupled. Therefore, the term coupling is defined and several techniques are provided to reduce coupling in your APIs. Finally, it is noted that a good API should offer a stable interface that is well documented and regression tested.
Model the Problem Domain Provide a Good Abstraction Model the Key Objects Hide Implementation Details Physical Hiding: Declaration versus Definition Logical Hiding: Encapsulation Hide Member Variables Hide Implementation Methods Hide Implementation Classes Minimally Complete Don’t Over Promise Add Virtual Functions Judiciously Convenience APIs Easy to Use Discoverable Difficult to Misuse Consistent Orthogonal Robust Resource Allocation Platform Independent Loosely coupled Coupling by Name Only Reducing Class Coupling Intentional Redundancy Manager Classes Callbacks, Observers, and Notifications Stable, Documented, and Tested Patterns
Design patterns are general solutions to common software design problems. This chapter provides details for various design patterns and C++ idioms that are particularly relevant to API development. This includes the pimpl idiom, which provides a way to hide all internal details from your public header files. The Singleton pattern provides access to global state. Its implementation in C++ is rather tricky so Singleton best practices are discussed, including initialization and thread safety issues. The Factory Method pattern makes up for several limitations of C++ constructors and can be used to hide implementation details for derived classes. Then various patterns are discussed for wrapping a new API on top of an incompatible or legacy interface. These include the Proxy, Adapter, and Façade patterns. Finally, the Observer pattern is covered as a way to reduce coupling between otherwise unrelated classes.
[Download PDF] Pimpl Idiom Using Pimpl Copy Semantics Pimpl and Scoped Pointers Advantages of Pimpl Disadvantages of Pimpl Opaque Pointers in C Singleton Implementing Singletons in C++ Making Singletons Thread Safe Singleton versus Dependency Injection Singleton versus Monostate Singleton versus Session State Factory Methods Abstract Base Classes Simple Factory Example Extensible Factory Example API Wrapping Patterns The Proxy Pattern The Adapter Pattern The Façade Pattern Observer Pattern Model–View–Controller Implementing the Observer Pattern Push versus Pull Observers Design
The Design chapter begins with a cautionary tale of how software systems can accrue technical debt and eventually decay without good initial designs that are maintained as the software evolves. Subsequently, functional requirement gathering is discussed, including advice on how best to collect and maintain these requirements. The complementary methods of use case modeling and user story creation are also described. Then the topic of how to approach the design of an API is covered in detail, breaking this process up into high-level architecture design and detailed class design. This proceeds all the way down to the level of function and parameter design, as well as error handling techniques such as error codes and exceptions.
A Case for Good Design Accruing Technical Debt Paying Back the Debt Design for the Long Term Gathering Functional Requirements What are Functional Requirements? Example Functional Requirements Maintaining the Requirements Creating Use Cases Developing Use Cases Use Case Templates Writing Good Use Cases Requirements and Agile Development Elements of API Design Architecture Design Developing an Architecture Architecture Constraints Identifying the Major Abstractions Inventing the Key Objects Architectural Patterns Communicating the Architecture Class Design Object-Oriented Concepts Class Design Options Using Inheritance Liskov Substitution Principle The Open/Closed Principle The Law of Demeter Class Naming Function Design Function Design Options Function Naming Function Parameters Error Handling Styles
There are several different API styles that can be used to implement any given design. This chapter covers four different programming styles available to C++ developers and describes the pros and cons of each method. These include the use of a flat C API, without any C++ features such as classes, namespaces, or references; object-oriented C++ APIs, which can make use of inheritance, encapsulation, and polymorphism; template-based APIs, which allow functions and data structures to be written in terms of generic types; and data-driven APIs, which map well to web service APIs and support data-driven testing.
Flat C APIs ANSI C Features Benefits of an ANSI C API Writing an API in ANSI C Calling C Functions from C++ Case Study: FMOD C API Object-Oriented C++ APIs Benefits of Object-Oriented API Case Study: FMOD C++ API Template-Based APIs An Example Template-Based API Templates versus Macros Advantages of Template-Based APIs Disadvantages of Template-Based APIs Data-Driven APIs Data-Driven Web Services Advantages of Data-Driven APIs Disadvantages of Data-Driven APIs Supporting Variant Argument Lists Case Study: FMOD Data-Driven API C++ Usage
This chapter deals with C++ language specific features that can affect API design. This includes using namespaces, either via a consistent prefix for all API symbols or the C++ namespace keyword. The topic of how to declare constructors and assignment operators correctly is then covered, including the rule of “The Big Three.” Const correctness issues are then covered, including method, parameter, and return value const correctness. Template API issues are covered, such as implicit versus explicit instantiation and best practices for structuring template header files. The section on operator overloading describes how to define operators in the most robust fashion and how to decide whether an operator should be a member or free function. The use of default arguments is presented as one way to make backward compatible API changes, though sometimes overloaded functions provide a better solution. The topic of symbol exporting is also explained, in particular the use of __declspec attributes for exposing symbols in a dynamic library on Windows. Finally, the use of good coding conventions is advocated and an example framework for such a document is provided.
Namespaces Constructors and Assignment Controlling Compiler-Generated Functions Defining Constructors and Assignment The Explicit Keyword Const Correctness Method Const Correctness Parameter Const Correctness Return Value Const Correctness Templates Template Terminology Implicit Instantiation API Design Explicit Instantiation API Design Operator Overloading Overloadable Operators Free Operators versus Member Operators Adding Operators to a Class Operator Syntax Conversion Operators Function Parameters Pointer versus Reference Parameters Default Arguments Avoid #define for Constants Avoid Using Friends Exporting Symbols Coding Conventions Performance
This chapter covers C++ API features that can impact the performance of your code. In general, you should not warp your API to achieve high performance: always measure performance and target any optimizations at actual bottlenecks. The performance increases of passing arguments as const references and of reducing your #include dependencies are analyzed. It’s shown how naive constant declarations can add weight to your classes, and it’s explained why using initialization lists is a good idea. The chapter also deals with memory optimizations so that your API maps well to modern CPU caching strategies, including variable clustering, bit fields, unions, and size-based types. The use of inlining can improve performance at the cost of exposing internal details, while the technique of copy on write provides a way to define large objects that are efficient to pass around by value. Finally, a survey is presented of performance profiling tools, including time, memory, and multithreading profilers.
Pass Input Arguments by Const Reference Minimize #include Dependencies Avoid "Winnebago" Headers Forward Declarations Explicit Include Guards Declaring Constants The New constexpr Keyword Initialization Lists Memory Optimization Don’t Inline Until You Need To Copy on Write Iterating over Elements Iterators Random Access Array References Performance Analysis Time-Based Analysis Memory-Based Analysis Multithreading Analysis Versioning
The Versioning chapter deals with the important topic of change management and control. This begins by discussing the relevance of version numbers and introducing an example Version API, to let clients query the current version and feature set for an API. The life cycle of a typical API is then described, including pre-release, release, maintenance, completion, and deprecation. The various terms for backward and forward compatibility are defined, including functional compatibility, source compatibility, and binary compatibility. The important issue of how you can make changes to an API while still maintaining backward compatibility is discussed, including how to make API changes such as adding, changing, deprecating, and removing functionality. Finally, the importance and implementation of API reviews are presented.
Version Numbers Version Number Significance Esoteric Numbering Schemes Creating a Version API Software Branching Strategies Branching Strategies Branching Policies APIs and Parallel Branches File Formats and Parallel Products Life Cycle of an API Levels of Compatibility Backward Compatibility Functional Compatibility Source Compatibility Binary Compatibility Forward Compatibility How to Maintain Backward Compatibility Adding Functionality Changing Functionality Deprecating Functionality Removing Functionality API Reviews The Purpose of API Reviews Pre-Release API Reviews Pre-Commit API Reviews Documentation
Well-written documentation is a critical element of an API. It describes what an API does and how it behaves, such as defining units for parameters, error conditions, or thrown exceptions. For behavior-only API changes, documentation is the only way to communicate a change to the API. Documentation is also necessary to communicate an interface’s contract when using contract programming. This chapter provides a checklist for what you should document and also details the various types of documentation that you can produce, including automated API documentation, overview documentation, code samples, tutorials, release notes, and license information. A large part of the chapter is dedicated to the effective use of Doxygen as a way to create API documentation automatically.
Reasons to Write Documentation Defining Behavior Documenting the Interface’s Contract Communicating Behavioral Changes What to Document Types of Documentation Automated API Documentation Overview Documentation Examples and Tutorials Release Notes License Information Documentation Usability Using Doxygen The Configuration File Comment Style and Commands API Comments File Comments Class Comments Method Comments Enum Comments Sample Header with Documentation Testing
Automated testing is one of the most important activities you can invest in to ensure that API changes don’t break your clients’ programs. This chapter starts off by presenting the case for writing tests and then categorizes the various types of testing you can perform on an API, including white box testing, black box testing, and gray box testing, as well as unit testing, integration testing, and performance testing. It then covers how to write good tests by detailing the qualities of a good test, what to test, how to focus your testing effort, and how to work effectively with a QA team. The next topic looks at how you can write code that is more amenable to testing. This covers techniques such as test-driven development (TDD), stub objects and mock objects, how to test private code, proper use of assertions, contract programming, and internationalization issues. In the final section, various automated testing tools are presented, such as a range of test harnesses, code coverage tools, bug tracking systems, and continuous build systems.
Reasons to Write Tests Types of API Testing Unit Testing Integration Testing Performance Testing Writing Good Tests Qualities of a Good Test What to Test Focusing the Testing Effort Working with QA Writing Testable Code Test-Driven Development Stub and Mock Objects Testing Private Code Using Assertions Contract Programming Record and Playback Functionality Supporting Internationalization Automated Testing Tools Test Harnesses Code Coverage Bug Tracking Continuous Build System Scripting
The Scripting chapter covers the advanced topic of how to expose your C++ API through a scripting language, such as Python, Ruby, Lua, Tcl, or Perl. This begins by explaining why you may want to do this and the issues that you will face in doing so, such as cross-language compatibility issues. The range of script binding technologies is presented, including Boost Python, Simplified Wrapper and Interface Generator (SWIG), Python-SIP, COM Automation, and CORBA. The remainder of the chapter provides practical instruction on how to add Python bindings using Boost Python and Ruby bindings using SWIG. In each case, specifics are covered such as how to expose a class hierarchy, define constructors, support inheritance, use language specific features such as properties in Python, and how to achieve cross-language polymorphism.
Adding Script Bindings Extending versus Embedding Advantages of Scripting Language Compatibility Issues Crossing the Language Barrier Script Wrapping Technologies Boost Python SWIG Python-SIP COM Automation CORBA Adding Python Bindings with Boost Python Building Boost Python Wrapping a C++ API with Boost Python Constructors Extending the Python API Inheritance in C++ Cross-Language Polymorphism Supporting Iterators Putting It All Together Adding Ruby Bindings with SWIG Wrapping a C++ API with SWIG Tuning the Ruby API Constructors Extending the Ruby API Inheritance in C++ Cross-Language Polymorphism Putting It All Together Extensibility
Extensibility is an important aspect of API design as it lets users expand the base functionality without requiring you to change the API. The bulk of this chapter deals with how to create a Plugin API, i.e., a way for users to extend the functionality of your API in defined ways by creating dynamic libraries that are discovered and loaded at run time. This deals with how to create stable C++ interfaces and covers the entire architecture, including the plugin API itself, the plugin manager, and example plugins. The chapter also covers other ways to extend an API, such as through inheritance, with a specific example of the Visitor design pattern. It also describes extensibility via templates, where the concept of policy-based templates and the curiously recurring template pattern (CRTP) are presented.
Extending via Plugins Plugin Model Overview Plugin System Design Issues Implementing Plugins in C++ The Plugin API An Example Plugin The Plugin Manager Plugin Versioning Extending via Inheritance Adding Functionality Modifying Functionality Inheritance and the STL Inheritance and Enums The Visitor Pattern Prohibiting Subclassing Extending via Templates Policy-Based Templates The Curiously Recurring Template Pattern Appendix A: Libraries
APIs are implemented and distributed as static or dynamic libraries, such as Dynamic Link Libraries (DLLs) on Windows. This chapter describes the difference between static and dynamic libraries, the pros and cons of each, as well as covering the use of dynamic libraries to create plugins. The bulk of the chapter focuses on how to create libraries on Windows, Linux, and Mac OS X. This addresses issues such as exporting symbols, DLL entry points; how to use Microsoft Visual Studio, XCode, and the GNU C++ compiler; using frameworks on the Mac; useful utilities to analyze library files such as libtool, nm, ldd, otool, tasklist.exe, and dlister.exe; and how to write code to load plugins at run time.
Static versus Dynamic Libraries Static Libraries Dynamic Libraries Dynamic Libraries as Plugins Libraries on Windows Importing and Exporting Functions The DLL Entry Point Creating Libraries on Window Useful Windows Utilities Loading Plugins on Windows Libraries on Linux Creating Static Libraries on Unix Creating Dynamic Libraries on Unix Shared Library Entry Points Useful Linux Utilities Loading Plugins on Unix Finding Dynamic Libraries at Run Time Libraries on Mac OS X Creating Static Libraries on Mac OS X Creating Dynamic Libraries on Mac OS X Frameworks on Mac OS X Finding Dynamic Libraries at Run Time Bibliography Index |