This is a great document. Lots of detail, good recommendations. I recently gave
a short lecture on API design and came up with the following "touchpoints" of
API design. These are sort of general guidelines and a lot less specific than
the article was.
orthologonal - Properties and methods should not overlap in functionality.
If two methods do sort of the same thing but differently that
is a design issue. They should either do entirely different
things or there should only be one method. Providing two is
confusing and can potentially lead to bugs.
consistent - Naming of methods, properties and other entities should be
consistent. Example if you have a method "getColor" you should
not have a method "get_width" or "width" or any other variation.
You should stick with one format. In the same way argument orders
should be consistent. For example, if you have a method
"translateCoords(x, y)" you should not have a method
"setTopRight(y, x)". This principle is extensible into other
domains as well (such as exceptions).
composable - Methods and functions should be reasonably composable. Instead of
creating giant methods which do many things create small methods
which do one thing really well. Then provide a clear way to
compose methods together in order to accomplish a task. In this
way methods do not have to be written which explicitly perform
complex tasks.
learnable - All design is for naught if the API is not well documented. Much
has been written on the importance of this step but it should be
stressed again. Good documentation can make up for any number of
short comings in the design. So document your api, provide
examples of how to accomplish tasks.
discoverable - Hand in hand with documentation comes the ability to be able to
naturally discover other parts of an API. This can be provided
in a number of ways based on language and platform. In Python
providing great dynamic help via the "help()" command and
docstrings is a good start. For web APIs consider providing an
"explain" API which returns documentation.
A valuable advice. I'd add purposefulness, for the lack of better word. API is usually intended for some specific use cases, and it's important to communicate what these are—in documentation and in API design itself. (Although this point could be seen as an extension to your “provide examples of how to accomplish tasks” suggestion, it probably deserves its own mention.)
I'd also say that discoverability does not matter as much as documentation, IMO only very extensive APIs with lots of endpoints would actually benefit from it. I.e., in the case of web service APIs, don't waste resources on implementing complex nested endpoints with discoverability and HATEOAS when just a bit of clear documentation would do.
I believe that the word orthologonal should read orthogonal.
I don't mean to downplay your points; I agree wholeheartedly with your post, but I actually did a double take and thought I might have been spelling the word incorrectly for some time.
I work so much with APIs I began to gather a list of top APIs by experience and usability. Stripe is right up there at the top of the list, everything about it is just plain beautiful.
I literally paused reading when I read this simply put exact statement. I try to focus on minimal, simple apis and preach it to developers and clients for quick integration and maintenance. Now I have this powerful statement to save hours of explaining mainly to non technical people. I have always thought this but never so crisp and to the point. We need t-shirts. Doing a quick search of Google, it appears a few others have recently stated this as much but it is an idea that should take hold: http://www.kryogenix.org/code/apis-like/
I don't think the topic is new per se, as really a lot of software engineering history has been about dealing with the human aspects of development. Even fundamental concepts like object-orientation are grounded in psychology and even before software, there were precursors for this kind of thinking in the way machines and working environments were designed. Josh Bloch's Effective Java is a more recent example.
The difference now is that UX has become a popular, well-understood, discipline. So we have a lot more concepts to take back into API design.
Some of us have been capturing relevant links on the topic on a Developer Experience page (https://plus.google.com/116834904360889286443/posts) and under the #devexp hashtag. We don't have t-shirts, but we do have a logo in need of much love! I'll go add a link to that presentation too.
Seems like decent advice but perhaps pretty library specific.
The only thing I'd argue with is: "Use semantic objects for parameters".
I haven't had any trouble passing a null pointer and casting it. Your API is going to be expanding regularly and the more message-passing objects you create, the more message passing objects' definitions you'll have to expand as the API expands.
The best approach I've seen for expanding APIs in iOS is the one Apple tends to use: pass a single NSDictionary of keyed parameter values. The API parameter list never has to change, it's guaranteed to be backwards compatible and you can still enforce use of semantic objects as the allowed values for the new keys you are adding.
(It just struck me that this mirrors the approach Apple used often in Mac Toolbox: passing parameter structs with a version field, instead of modifying the API parameter list itself).
This is a comprehensive list of many considerations. Top of the list should be to have as small of an API as possible. API is forever. If you expose too much up-front then it increases the surface area of getting things wrong, which developers will be stuck with for a long time or need to deal with eventual deprecations which is fun for no one. Also, more API leads to higher complexity in learning. It is better to see how your API is being used and slowly reveal more functionality as you become confident it is what developers need.
I also believe API should go through a feedback/review processes with peers. Other developers will have used different patterns, have a different base of experience with the platform, and can generally help find common mistakes. So, here's some of my feedback/nitpicks on MGTileMenuController.h :) --
@property (nonatomic, weak, readonly) id<MGTileMenuDelegate> delegate; // must be specified via initializer method.
- (id)initWithDelegate:(id<MGTileMenuDelegate>)theDelegate;
It is very rare for delegates to be required for initialization, and I don't see why the @property for the delegate needs to be readonly? Perhaps an owner would be a better model?
The isVisible property looks like this:
@property (nonatomic, readonly) BOOL isVisible;
But typically for BOOLs the property is the normal form with a specific "is" getter:
@property(nonatomic,readonly,getter=isVisible) BOOL visible;
// N.B. All of the following properties should be set BEFORE displaying the menu.
This makes me wonder what happens if I want to change them later? It seems that they should always be able to be changed, and in general comments shouldn't be required to understand library behavior as they'll just be missed by developers anyways.
Why would the nextPageNumber not be +1 from currentPageNumber? Also, why do I need to supply a currentPageNumber when it appears currentPage is a @property of the class? Is there a difference between "currentPage" and "currentPageNumber" as their naming indicates there might be one? Also, it might be better to name the method similar to the NSIndexSet convention "indexGreaterThanIndex." Should an NSIndexSet actually be used instead?
- (UIBezierPath *)_bezelPath;
This method looks like it is private/internal because of a leading underscore, so it shouldn't be exposed in the header. Also, Apple has reserved naming methods with a leading underscore (especially important when you are subclassing!). https://developer.apple.com/library/mac/#documentation/Cocoa...
In general, if the developer doesn't need the utilities, I highly question adding them to the header since you'll be responsible for them for all time.
Finally, why not add the delegate protocol to the header file so everything is consolidated for the developer using the class?
"API is forever. If you expose too much up-front then it increases the surface area of getting things wrong, which developers will be stuck with for a long time or need to deal with eventual deprecations which is fun for no one. Also, more API leads to higher complexity in learning. It is better to see how your API is being used and slowly reveal more functionality as you become confident it is what developers need."
This is so, so true. If you find yourself thinking about exposing something because someone might have a use case for it or to provide greater control over some very specific corner case, write it down, file it somewhere, and revisit that when you see how people are actually using your API. Don't mistake all the ways your API could be used for all the ways people want to use it.
This is a fantastic article. I'm glad to see that I have picked up some of these rules intuitively, from reading others' code, and studying the design of Apple's APIs. I do have a couple questions though, that hopefully someone can answer.
With the introduction of blocks in iOS 4, some APIs have shifted to using blocks for messaging. What is the general opinion on this?
Also, for components that benefit from an internal state machine - which is best practice to notify objects of a change in state? Delegate messaging, notifications, or just KVO?
I enjoy using blocks in provided API, as well as writing some of my own private API with them. The code locality is very nice for many things like enumeration and animation.
As for notifying objects of state changes, all three methods are used. I use NSNotificationCenter if there will be multiple objects who may need to know about it, KVO as glue for the model and the view, and delegate messaging if there is only one object to notify.
This is great advice for anybody who does iOS development. If you are creating a component that is only going to be used internally in your application, it's tempting to just slap it together without really thinking about it. In the long run this really will save you time and headaches.
This is one of my favorite points of the writeup, because I've been bitten by it repeatedly over the past year.
Not providing a getter for that data means that if the client wants to use it elsewhere, it needs a second home. So now I'm keeping track of not just a HNCommentsView, but also an object holding the HNText and HNUser it's displaying.
Any time an API doesn't expose that data, there's an implicit assumption of, "The person using this class won't (or shouldn't) need to get at this data this way, so I can leave it out." Which makes it extra irritating to me when it's not there and I need it.
Like many things it's generally useful but not a law that applies to every class.
Some counterexamples that come to mind...
- [NSImage initWithPasteboard:] (must NSImage retain the original pasteboard and/or its contents for all eternity, in case you want it back, even if it was in some odd format like NSPICTImageRep that probably doesn't match the internal representation?)
- [NSDictionary initWithContentsOfFile:] (must NSDictionary remember the file path for eternity, and/or its contents, in case you want it back?)
Yes, if an initializer is basically accepting things that become properties anyway, it makes sense to expose those properties. But initializer arguments are not necessarily sensible or useful to keep around in all situations.
orthologonal - Properties and methods should not overlap in functionality. If two methods do sort of the same thing but differently that is a design issue. They should either do entirely different things or there should only be one method. Providing two is confusing and can potentially lead to bugs.
consistent - Naming of methods, properties and other entities should be consistent. Example if you have a method "getColor" you should not have a method "get_width" or "width" or any other variation. You should stick with one format. In the same way argument orders should be consistent. For example, if you have a method "translateCoords(x, y)" you should not have a method "setTopRight(y, x)". This principle is extensible into other domains as well (such as exceptions).
composable - Methods and functions should be reasonably composable. Instead of creating giant methods which do many things create small methods which do one thing really well. Then provide a clear way to compose methods together in order to accomplish a task. In this way methods do not have to be written which explicitly perform complex tasks.
learnable - All design is for naught if the API is not well documented. Much has been written on the importance of this step but it should be stressed again. Good documentation can make up for any number of short comings in the design. So document your api, provide examples of how to accomplish tasks.
discoverable - Hand in hand with documentation comes the ability to be able to naturally discover other parts of an API. This can be provided in a number of ways based on language and platform. In Python providing great dynamic help via the "help()" command and docstrings is a good start. For web APIs consider providing an "explain" API which returns documentation.