API Design - Matt Gemmell
mattgemmell.com
mattgemmell.com
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.
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 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.
This simple statement just changed the way that I think about API's.
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.
I wound up using Delphi.
Intuitive
Forgiving
Frictionless
Most of this article applies equally well to humans...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.
(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).
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. - (NSInteger)nextPageNumber:(NSInteger)currentPageNumber; // zero-based pageNumber
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?
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.
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?
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.
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.
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.
Huh?