API Design Tips
"It's very easy to create a bad API and rather difficult to create a good one."
First think of the consumer of the API. Think about his likely situation and the problems he's facing. He doesn't want to learn a new abstraction and he doesn't like the fact that your API is going to make him change the design of his program. Design for the benefit of the consumer, not your ease of implementation.
-
Naming is everything
- Put your API in a namespace that reflects its function, not your org chart or company brand1
- Chose names that describe what each function does and what each type contains or abstracts. Avoid "
ProcessData() " disease
-
The point is to reduce complexity
APIs don't merely implement algorithms that the consumer isn't expected to understand, they also present abstractions that reduce complexity. An FTP library doesn't give the consumer a power they didn't already have, it relieves the consumer of doing the socket programming themselves. If you're wrapping a lower-level API then please don't introduce new problems. Aim for designs that reduce complexity without contributing to it in other ways
-
Eschew unnecessary side effects
- Avoid overwriting the arguments
- If you're given a DB or socket connection, assume it's open and never close it yourself
- Do not open your own connections if they're not the point of the API's purpose
-
Use Inversion of Control (IoC) to handle necessary side effects
Let the consumer give you instances of resource managers or event handlers to open auxiliary connections, write to log files, fetch lookup values, interact with the user and so-on
-
Use events for the bulk of your IoC
Don't force the consumer to create instances of a dozen different resource managers like logging classes, connection pools, name resolution classes etc. before they can call the first function. Raise events so that the consumer can handle only what they need in their circumstance
-
Use Interfaces everywhere
When events aren't sufficient for your IoC component, or your arguments require a strongly-typed value, then use Interface types--even if you expose concrete implementations as well, and even if you're certain that your concrete implementation is the only kind that will do. Assume your consumer knows what he's doing
-
Use Interfaces that cover everything you need, but not more and not less
Do you need IList, or just IEnumerable? Or do you actually need IBindingList and are trying to be too modest? If you specify too much you could force the consumer to work harder than necessary, and if you specify too little you risk being passed an insufficient implementation, or make it harder for the consumer to extend your API with a compatible wrapper
-
Don't make your own gravity
If your target platform has its own crypto primitives, graphics library, transactional memory, XML parser, or whatever--and the point of your API isn't to replace them--then don't reinvent and bind your API to your own implementations, even if you think they're better
-
return all of your results from the function call Avoid returning data by setting global variables or writing to files, tables, sockets, etc. Let the consumer decide where the data goes and use compound data structures if it's necessary to return a success code along with the results
-
The consumer isn't Sherlock Holmes
If an operation didn't work, or had an unavoidable (and un-IoC-able) side effect, then don't force the consumer to discover this by groping around referenced data structures or other environmental features. Return something other than null or void. Make sure that success is also clearly defined--either return an explicit success code or make sure that the absence of any error can be reasonably construed to mean success
-
Distinguish types of failures
Did a connection fail because of a timeout, or because the remote host refused the connection, or because the address was invalid, or the login credentials invalid, or because a bird pooped on the switch, or what? Consider exposing and returning an Enum with the types of failures your API can detect
-
Support optional parameters
Whether by a built-in language feature or via overloads, chose sensible default values or behaviors (timeout values, for example) and give the consumer the option of providing the least you need
-
true and false are lousy arguments If you spot the line Filter(widgets, true) somewhere in your code, tell me quick, what does true refer to? Does it mean be case insensitive, or to ignore errors, or to strip the results of blank lines? It's better to create Enums or other data structures that let the consumer's code be expressive and clear, so next time you see it, the line reads Filter(widgets, FilterOptions.IgnoreCase) and everything is clear
-
Use consistent calling conventions
If you ask for a filename as the first argument in one of your functions, then stick to that as a convention for any other function that needs a filename. If you arrange argument order randomly you'll force the consumer to memorize the signature for all of your functions, instead of being able to intuit them
-
Nouns should be value types, verbs should be static functions. Or: Don't Make "Manager" Classes
Avoid creating black-boxes that the consumer has to poke and prod to life
-
Fail early
Check what the consumer is passing to you and aim to fail before you begin using and modifying resources, especially if those resources don't support rollbacks
-
Don't be the weak link in the chain
If you use cryptography or handle sensitive data, then have your product audited by a security professional. Use well tested, standardized crypto primitives that have been implemented by security professionals. Be careful to avoid buffer overflow vulnerabulities. Perform strict input validation. Do not invent your own ciphers, and do not implement crypto primitives on your own (unless that's the point), because even the implementation of trusted ciphers can be easily screwed up. Your consumer doesn't want an invisible hole ripped open in their software after they did everything right on their end
-
One API at a time
Identify a single concern for your API and stick to its scope. This concern could cover a broad category (like graphics, compression, networking), and if it does be sure to break it up into focused namespaces. You probably shouldn't bundle your compression library in the same binary and namespace as your FTP library
-
One level of abstraction at a time
You may need to build more than one layer of abstraction to get up to the interface you'd like to present to the consumer. Networking now involves stacks 7 levels tall, for example. But keep each level separate, in the sense that each level could be re-used with a different "top" or a different "bottom" each coded to the right Interface
- Expose wrappers for value types, but accept strings anyway
Some consumers will appreciate a wrapper that strongly types your WidgetSerialNumber , but provide overloads that will also take a string and perform implicit conversion, too. Also favor data types already available on the platform, such as TimeSpan s for timeout values instead of integers
-
Always return the richest type
Following from above: even if the consumer passed an implicitly convertible string, always return the richest type that befits your output. If it's a date or time, return a DateTime value. If it's a number, return an Int , or a Decimal , or your own wrapper type, or whatever fits precisely. Let the consumer convert it back to a string if that's what they want
-
Implement IClonable, INotifyPropertyChanged, ISerializable and other handy interfaces for your value types
Give your types more utility for the consumer so they don't have to marshal values back and forth, just because they want a nice reactive UI or an easy way to save and restore data to disk
-
Provide flexibility and isolation for configuration
Libraries that link to the application's binary could be configured many ways: in code, as blocks in the application's own config file, or in a separate config file. The consumer will appreciate it if you offered at least the first two. When providing the option to configure the library through a shared config file, make sure to use namespaces or similar mechanism--such as .Net's ConfigurationSection class--to keep yours separate from the rest
-
Keep your knickers to yourself
Consumers don't need their Intellisense or other inspection tools cluttered up with all your helper classes and utility methods. Makepublic only what the consumer needs to use your API, and mark everything else as private or internal
-
Only throw exceptions for the exceptional
Failures in I/O operations are expected for an API that deals with I/O--an attempt to read or write can fail for a variety of reasons. You should use return values to indicate failure here and not exceptions, since throwing exceptions will add unnecessary overhead for what could be a completely normal outcome from the consumer's point of view (they may be writing a dead-link checker, and be expecting lots of FileNotFound conditions). Only throw exceptions if the fault occurs in supporting code (like when your graphics library tries to load an image from disk), or something is wrong with the consumer's code (invalid arguments, unopened connections, out-of-bounds conditions, etc.)
-
Document the exceptions you throw
Give the consumer the chance to factor exception handling into the design of their program before it's too late to change it
- Document how to do what your consumers tend to do
Release 1.0 of your documentation might not contain tutorials and How-Tos for everything your consumers use your API for, but listen to feedback and have a plan to improve the How-Tos chapter of your manual for subsequent releases
-
Flag deviations from the norm
If your API is a wrapper for a low-level service, and you change the behavior (eg: setting a timeout to zero no longer means wait indefinitely), then document this clear and in bold for those consumers who already know or reference the documentation for the underlying service
-
Consider shipping your unit tests
They will serve both as a guide to the use of your API, and a framework to reproduce and report bugs that your consumers find in the field
-
Don't ship an API you haven't used in a useful, working program
For everything that can't be covered here, you will learn whether your API is actually practical and usable by employing it in a program that actually does something useful--not just a unit test or a proof-of-concept, but something that has a UI or interfaces with a real system. That program doesn't necessarily have to be a product that you sell and support, it can be an internal utility, too. Bonus points if the programmer doesn't even work on the API team and therefore provides a fresh perspective.
If you develop the API at the same time you develop the program, you will also have the chance to see and correct mistakes that add unnecessary complexity to the consumer's program, conflicts with the naming scheme of an existing API, or exposes an interface that's unintuitive and confusing. That may be better than all of the above tips combined.
1 - It's okay to use a product name as the root of the namespace.MegaWidgets.Cryptography.DecoderRing.DRCryptoServiceProvider is acceptable, for example.
Other words of wisdom
Michi Henning's essay, referenced at the top, was the inspiration for this collection of tips. He himself was inspired to write it after experiencing the difficulties of using the socket Select() function in the .Net framework, and he passes on some excellent admonitions that bear repeating, or to whet one's appetite for more:
- General-purpose APIs should be "policy-free;" special-purpose APIs should be "policy-rich."
- APIs should be designed from the perspective of the caller
- Good APIs don't pass the buck
- A big problem with API documentation is that it is usually written after the API is implemented, and often written by the implementor
|
分享到:
相关推荐
语言:English ...存储:用于存储从ProductDesign.tips API服务器收到的最新内容信息。WebRequest:与ProductDesign.TIPS API服务器通信以收集用户的内容订阅体验。WebRequestBlocking:访问ProductDes
经验丰富的RESTful API设计技巧 API设计技巧和趋势评估的工作指南。 :books: 最初发布在我的中型博客上。 :link: :clapping_hands: 随时在这里阅读,如果喜欢,可以鼓掌/评论。 我们都是手Craft.io的徒弟,没人...
You start off by setting up the test development environment and gain tips on the advanced locater strategy and the effective use of the Selenium WebDriver API. After that, the use of design ...
Some basic concurrency knowledge is also required., What You Will Learn, Familiarize yourself with RESTful practices and apply them in Go Acquaint yourself with the best practices for API design such...
模仿网易云音乐UI,用知乎和gankio 网易新闻 豆瓣电影的API来完成一个基于Material Design Rxjava Retrofit dagger2 MVP构架的项目 为了更好的学习Material Design和主流框架,于是有了该项目。 Screenshots ...
Section III — Software Shaders and Shader Programming Tips 381 Software Vertex Shader Processing 383 Dean P. Macri x86 Shaders–ps_2_0 Shaders in Software 396 Nicolas Capens SoftD3D: A Software-only ...
Chapter 14: DevOps Service Laver Tips 14.1Console Logging 14.2CPU Profiling 14.3. Memory Leak Detection 14.4CI/CD 14.5Monitoring and Alerting PARTIII: The Presentation Laver (React/HTML) Chapter 15: ...
4. 文档:可能包含组件的使用说明和API参考。 通过理解和掌握这个自定义ToolTip组件,开发者可以更自由地设计数据展示,提高数据可读性,同时也能为用户提供更直观易懂的信息提示,从而优化整体的用户体验。在实际...
Each chapter is filled with exercises, examples, tips, and more to make sure you’re ready to bring what you’ve learned into your own work. about the technology The benefits of cloud-hosted ...
Following numerous carefully crafted examples, you'll learn about new Windows 8 features, the WinRT API, and .NET 4.5. Along the way, you'll pick up tips for deploying apps, including sale through ...
You start off by setting up the test development environment and gain tips on the advanced locater strategy and the effective use of the Selenium WebDriver API. After that, the use of design ...
Published in July 2006, the best selling book Mastering EJB is now in its fourth edition and has been updated for EJB 3.0....Best practices for EJB 3.0 application design, development and testing
- Basic and advanced concepts (such as inheritance, relationships, and so on) of Java Persistence API defined entities - Information on integrating EJB applications with the outside world via the Java...
Follow step-by-step tutorials, then apply practical tips on signing and selling your applications. Even if you’re new to iPhone development you’ll soon be cranking out great code. alk through ...
Follow step-by-step tutorials, then apply practical tips on signing and selling your applications. Even if you’re new to iPhone development you’ll soon be cranking out great code. Walk through ...
Compare REST API from Twitter, GitHub, Facebook and others in a conversational and easy-to-follow style Who This Book Is For This book is aimed at novice developers who want to gain insights into ...