API design tips
For every larger application, you’ll get to a point where you want to allow people to extend it, and then you have the problem of designing an API that the clients like.
Here are some things that a good API should always provide/have:
- Consistency: The biggest, single most important point besides robustness. If your API is not consistent, it will feel like patchwork, and your clients won’t like it. Just look at the Win32 API, where you immediately see that different functions use different styles. In contrast, see the .NET API, which was fully designed as a coherent API and provides a really nice programming environment. Write a design document up front, and stick to it, no matter the costs. For example, if you pass variable length arrays, stick with passing the count first, and then the array pointer, but don’t mix it.
- Robustness: Your API is the border guard between the evil client and your library. If your client can pass bad data through it, it’s entirely your fault! Make sure you validate every single parameter passed to your API, and provide means to signal errors.
- Documentation: Undocumented APIs are like non-existent APIs. Document each call, each parameter, and provide examples. Possibly the best documented API is the WinAPI/.NET, just take a look at the MSDN!
- Error reporting: Provide a consistent way to signal errors. If you are designing a C API, have each function return a success/error code. For C++/Java/.NET, provide custom exception classes. Make sure that you have some function to format the error code and get a human-readable string out of it!
- C-API tips: Pass structures instead of long parameter lists. Structures can be easily extended and only require a recompile from the client, while new parameters require the client to change their code. Use fixed-sized types: If you mean a 32-bit int, use a typedef exactly for that size. This enables your clients to do easy interop with your library, for example from C#. Make sure people with different compilers can link against your code. This means that all memory allocations that you are going to free must be done by your library, otherwise, clients with a different C-Runtime will get crashes. Don’t use fancy compiler extensions, and make sure your code compiles with at least 3+ different compilers before shipping.
-
Easy start: Make it easy for the clients to get something running. If they need to create 5 objects first, and bind three of them together, you are doing something wrong. Provide convenience functions where possible: A very good example is the Qt library. In many places, a class expects a parameter of type
QPointF
(just a pair of floats), but also provides a function which takes two floats and creates theQPointF
for you. Same for declarative structures. If your user has to describe something (for example, fill in 5 structures), don’t force the user to allocate them dynamically but allow the user to use the simplest possible solution to get started quickly: Declaring it statically.c++ Decls dec[] = { { "Module1", API_SomeOption }, { "Module2", API_Debug | API_Log } };
The user can still decide to do it dynamically later on, as long as you provide init/shutdown hooks.