- Flexible but convenient Flexibility is something everyone understands, unfortunately convenience quite often forgotten. Some use cases are frequent, others are not. One key to success is to have dedicated API for most common use cases along side the general more flexible API.
- You need a no-argument constructor to create empty object, that will be filled later
- You need methods to get/set first and last name
- You need methods to get/set a list of middle names (because some people have more than one)
- A constructor, that takes first and last names as an argument, because that's what most people have
- A method to set/get middle name, because very little people have more than one
- Flexible but not bloated This one is part of first one, but it's so frequent, that it deserves to be a separate point.
- Something that very high impact and/or hasn't changed for a long time is unlikely to change without early warning
- i.e. IPv6. We're moving in this direction so long, because too many software were "hardcoded" for IPv4. Were they wrong? No, they saved a lot by doing so. Right now you can chose to either support these two or to make your system flexible to support a growing list of different formats. Is the later worth the additional time? It's up to you to decide.
- Convenience classes and method are only convenient as long as it's clear what they do and what's the difference between any two of them; here are couple of bad examples:
- AbstractSingletonProxyFactoryBean
- Why Java needs String.valueOf() and Integer.toString()?
- String is simple and very flexible data type
- A good example is PostgreSQL function PQconnectdb
- Names should be meaningful, guessable, structured, consistent and short Naming is one the most important things in API. While meaningful is the most emphasized one, it's far from being the only one. Although most of the time programmer reads code, he also writes it, so being able to guess a name improves his productivity quite a lot, especially accompanied with code-completion.
- Performance oriented, but convenient Some APIs are not meant to be called often, others should think about performance. However, convenience should be maintained. Windows API is an example of API, that sacrificed convenience for performance. What it lacks is functions to fill various structures with some default values. It's really annoying to set every struct member. At the same time it is bloated in amount of functions, there are often several functions instead of one. I.e. FindFirstFile() and FindNextFile() could easily be just one functions and who really needs ZeroMemory() when you can use much more powerful memset().
- Convenient defaults The less user has to specify explicitly, the better. Most of the time...
- Carefully chose data formats In short - don't just blindly use XML.
- Configurable, but not over-configurable This one applies to frameworks. Many of them allow user to change the behavior via some setting in configuration. This is good, but there are limits. First of all, it should be clear which configuration is valid and which is not. The more settings there are, the harder it is to list all allowed combination, not to mention that they should work! "Everything is pluggable, extensible and configurable" is never the answer as it will result in huge and buggy mess. No setting is better then a non-working one.
- Synchronous vs. Asynchronous Asynchronous API is good for operation that might take long to complete. Ideally all long taking operations should be asynchronous.
- The are non-English speaking people out there If you have UI, it should be localizable. If you throw exceptions or otherwise report errors, error messages should be somehow localizable too, either provide error codes along messages, or localize messages themselves.
- Backward/forward compatibility Ideally every new version should be backward compatible with all previous ones, but in practice it's almost impossible. The guidelines here are:
- Design API from beginning to be extended in future. Be especially carefully with boolean arguments (enum is a good replacement). In C using opaque structures is a good way to provide extensibility in the future, avoid reserved members/arguments, because you can end-up with something like this
- If future features are known or very apparent, prepare API for them in advance (forward compatibility)
- Avoid breaking backward compatibility of API, but don't add workarounds to API itself, because later you'll have to be backward compatible with those too
- Prefer big breaks to often ones: no one likes API breaks, but often breaks seems to be hated more; when you break API, use the chance to fix all known shortcomings
- Be clear about your API stability, so that users know, when to expect breakages; major releases are good candidates for breaks, subreleases should be backward compatible
- It is also good to have alpha/beta testing, where new APIs are introduced for users to try, but are not yet stable and might change in final release, user feedback is best way to determine shortcomings
- Be realistic, if your thing will survive long enough, you will, eventually, have to break it's API
- Convention over configuration is dangerous The point is to save developers time. In practice, not in theory! Just because developer writes less code/configuration does not mean he actually spends less time on it.
For example, consider a very simple Person class:
The point here is not to limit API to the basic all-cases set, but add additional APIs to make shortcuts for common use-cases.
Most APIs are designed in a "what-if" way, however you have to stop in time, because there are no limits to "what-if". There's no clear recipe here, since it all depends on exact domain. But there are few guidelines:
Structuring API is very important, when API is large. A good example of how not to structure your API is Window API, while GTK+ is an example of good structuring. In short: your API should have something that separates it from the rest of the world (namespace, package, ... or simple prefix). Large API should be divided into submodules etc.
Another things that make names guessable is consistency. Naming conventions should be consistent across the API, preferably consistent with other APIs in the same field, domain, language etc.
Finally, names should not be longer than needed. While longer usually means clearer, there's always certain point beyond which length no longer adds clarity. I.e. MAX_INT is perfectly fine name and making it MAXIMUM_INTEGER_VALUE adds no additional value.
Default values should be intuitive and meaningful. Otherwise it's better to require to explicitly specify the value.
If you're making Web Service, think which format for data is most appropriate. It might be XML, JSON or anything else. Remember, that someone will have to use it and it's for their sake.
It's also important for configuration files. Forcing people to write XML by hand is... well don't use XML when something simpler works just fine.
For complicated cases white-box can be used: instead of super-configurable black box component have a component composed of smaller components - when limits are reached, user can compose his own component reusing parts of original one.
At the same time, every asynchronous API is only good if it provides synchronous alternatives. Strange? Asynchronous is good for UI as it makes it responsive rather than hanging it. But when you're already on on non-UI thread, you want to perform sequential actions synchronously, rather then messing up with asynchronous continuations. Since you can't predict where which API will be used, it's better to provide both to the user and let him decide, rather than pushing one or another down his throat.
Finally, if you have no idea about localization, don't implement it without consulting someone who does. A good start is GNU gettext manual, especially the section about plural form, to get some grasp what you're dealing with.
When things work auto-magically, they sometimes don't work in the same magic way and finding out why can be very time consuming.
Besides, changing convention is very painful, as it's less apparent (everything compiles as it did, but it doesn't work as it did, happy debugging).
Komentarų nėra:
Rašyti komentarą