How much low-level stuff to expose in an API? - api

When designing the public API of a generic library, how much of the low-level stuff that's used internally should be exposed? On the one hand, users should not depend too heavily on implementation details, and too many low-level functions/classes might clutter the API. Therefore, the knee-jerk response might be "none". On the other hand, some of the low-level functionality might be useful to people, and exposing more of it can prevent abstraction inversion (the re-implementing of low-level constructs on top of high-level constructs).
Furthermore, exposing more low-level details could provide performance shortcuts. For example, let's say you have a function to find the median of an array. The principle of least surprise says that you should duplicate the array so that users of your API don't have to care that its implementation involves the side effect of reordering elements. Should you, in this case, note that median() costs a memory allocation and provide another function that bypasses the allocation, but will arbitrarily reorder the user's input?
What are some general guidelines for how much of this kind of detail to expose?

As little as is possible.
The more details you expose, the more likely a change will break a consumer.

Your API shouldn't allow callers to "break" anything by mucking up the state of the internals (e.g. reordering collections, etc.). To solve that problem, your exposed interfaces should be read only when necessary.
With respect to complexity, I lean far in the direction of simple, basic methods. I try very hard not to over-engineer anything with what I think will be needed down the road.
Write to today's requirements (maybe tomorrow's), but not beyond. You can always extend in the future. It's much harder to just drop things that you can't maintain anymore.

The unix way of doing it is to provide mechanisms, not policies. Just provide the right tools to do things (say, a knife), but try not to anticipate how they are going to be used (to peel apples or to sharpen pencils).

One way I've heard it expressed: Expose the what, but not the how.
The goal is to provide a useful and rich library for clients to use, without making them dependent of the library's internals. You want to be able to change the internals, without breaking the callers (as someone else already noted).
Writing a good API involves a certain amount of artful brinkmanship.

Related

Method JavaFx TreeItem getRoot() is not visible. What is the OOP/MVC reason it is not?

I needed to get the root item of a TreeView. The obvious way to get it is to use the getRoot() on the TreeView. Which I use.
I like to experiment, and was wondering if I can get same root, buy climbing up the tree from a leaf item (a TreeItem), using recursively getParent() until the result is NULL.
It is working as well, and, in my custom TreeItem, I added a public method 'getRoot()' to play around with it. Thus finding out this method does already exist in parent TreeItem, but is not exposed.
My question : Why would it not be exposed ? Is is a bad practice regarding OOP / MVC architecture ?
The reason for the design is summed up by kleopatra's comment:
Why would it not be exposed I would pose it the other way round: why should it? It's convenience api at best, easy to implement by clients, not really needed - adding such to a framework/toolkit tends to exploding api/implementation to maintain.
JavaFX is filled with decisions like this on purpose. A lot of the reasoning is based on experience (good and bad) from AWT/Spring. Just some examples:
For specifying execution on the UI thread, there is a runLater API, but no invokeAndWait API like Swing, even though it would be easy for the framework to provide such an API and it has been requested.
Providing an invokeAndWait API means that naive (and experienced :-) developers could use it incorrectly to accidentally deadlock threads.
Lots of classes are final and not extensible.
Sometimes developers want to extend classes, but can't because they are final. This means that they can't over-ride a lot of the built-in tested functionality of the framework and accidentally break it that way. Instead they can usually use aggregation over inheritance to do what they need. The framework forces them to do so in order to protect itself and them.
Color objects are immutable.
Immutable objects in general make stuff easier to maintain.
Native look and feels aren't part of the framework.
You can still create them if you want, and there are 3rd party libraries that do that, but it doesn't need to be in the core framework.
The application programming interface is single threaded not multi-threaded.
Because the developers of the framework realized that multi-threaded UI frameworks are a failed dream.
The philosophy was to code to make the 80% use case easier and the the 20% use case (usually) possible, using additional user or 3rd party code, while making it difficult for the user code to accidentally (or intentionally) break the framework. You just stumbled upon one instance of an application of this philosophy.
There are a whole host of catch-phrases that you could use to describe the reason for this design approach. None of them are OOP or MVC specific. The underlying principles have been around far longer than software engineering, they are just approaches towards work and engineering in general. Here are some links if interested:
You ain't going to need it YAGNI
Minimal viable product MVP
Worse-is-better
Muntzing
Feature creep prevention
Keep it simple stupid KISS
Occam's razor

How flexible should you make your classes?

I have always wondered if I am thinking ahead too much or too little before I code something. This is especially true for me if I am not sure what possible future requirement changes I will be required to account for are. I don't know how flexible or abstracted I should make my classes. I'll give a quick example.
You want to write a program that plays blackjack against a computer and you're the type of person that likes to experiment. You begin to write the code for the deck, but then you realize blackjack could have 1, 2, 4, or any number of decks. You account for that, but then you realize that maybe the deck will be altered and not have any cards of value ten. You then decide that the deck should be completely versatile to allow any number of suits or ranks. You then decide that the rules for the deck should be able to be altered from the standard number of suits multiplied by the unique ranks to equal the total amount of cards in the deck... You can see where I am going here.
My question is this, are there any guidelines for how flexible a class should be?
Favor minimalism and encapsulation, avoiding functionalities you don't need.
It's of course good to design based on needs, but cluttering designs with things you do not use -- or could possibly use in the future -- should be minimized. It's fine to consider and implement what you are sure you will need.
When you understand and specify a 'future problem' (specifically, at that point in the future), you will often solve it different from today's solution.
Check out the great paper "On the Criteria to be Used in Decomposing Systems into Modules" by David Parnas, from back in 1972.
Generally speaking, you should try to identify areas of responsibility that can be pushed behind a very simple interface that hides useful functionality and complexity. You should strive to separate the what from the how in areas you feel are most likely to change (i.e. predicting variation).
Flexibility is indeed are a requirement for an application / system to be maintainable. Usually I find that a design following SOLID design principle, TDD and stateless business logic is easier to maintain.
Among all of SOLID principle, I find that [S]RP is the rule that makes the application maintainable. Following [S]RP, your system will be broken down to smaller pieces, with replaceable classes. Say that it can be broken to Deck, DeckRule, HitAction, etc.
Interface or inheritance will help, since you can easily swap your Deck with NoTenDeck or SpadeOnlyDeck. And you can swap the DeckRule to HardToWinDeckRule or ImpossibleWinDeckRule. With decorator or other design patterns such as composite will also help to make your system flexible. Don't forget Test Unit, it will help you to refactor the code.
And also sometimes you will need something like Breaking Change in which you need to tear down your current architecture and interfaces to be replaced with another design. Sometimes it is needed, but mostly not.
You can find several discussion at stackoverflow answer for DI vs Singleton and little about state or stateless.
I try to follow the agile principle of YAGNI ! - You Ain't Gonna Need It.
It isn't worth the effort of coming up with all these possible future requirements. There are an infinite number of possible future requirements. You can't account for all of them. Just do what you need to do to fulfill the requirements you already have.
If in the future you get new requirements, THEN change the system. (you do have good tests to make sure you don't break anything during refactoring right?)
Overall thoughts on flexibility
From your description of the problem I don't think your classes should be flexible as in "keep throwing every new aspect of the game rules into the same class". A class with too many responsibilities is fragile, hard to maintain and thus hard to change - ironically, treating a class as if it were flexible will eventually make it rigid ! Don't put all your eggs in one basket. Separate concern means separate class. Your overall design should be flexible, not so much your classes.
On the blackjack problem
Card games, especially complex and/or evolving ones, are generally most peculiar animals and thus probably not a good standard example to start experimenting with when trying to improve your design skills.
If you want real modularity, you'll probably need a pluggable rules engine that allows plugins to hook at different stages of a game, giving you access to relevant resources to alter anything from scores to the sequence of events in a turn to even other rules.
My take on this is
You already know your game will evolve in the future and you're going to need such an engine. To answer the "thinking ahead" part of your question, this means you'll start with a simple standard turn structure, a minimal rules engine and incrementally add to it as you implement each feature in your backlog. The thinking ahead you shouldn't do is trying to forecast every little detail in the engine upfront. In other words you'll use YAGNI as in "I ain't gonna need this rule/type of hook into the game" rather than "I ain't gonna need a rules engine" since you know you've got to have one anyway.
Or,
It's going to be, at least at first, a one-shot fixed-rules game. You'll need less raw flexibility in the game system here. Concentrate on use cases and try to make acceptance tests pass with the simplest possible technical solution. Take one little step at a time. Implement a working solution for just 1 deck with simple rules at first, then expand to more complex areas. This hopefully will lead you to a no-nonsense, well-designed system which may or may not involve some kind of rules engine.
You might also want to have a look at https://gamedev.stackexchange.com/ for game-specific design guidelines.
When writing code I tend to look for things that may be obvious additions later on. "Obvious is probably a word to define though. :-) It means things that you are sure will be in a future release. Other than that, I try not to worry about it.

What is meant by "getting the right level of abstraction"?

I've read about how a hard job in programming is writing code "at the right level of abstraction". Although abstraction is generally hiding details behind a layer, is writing something at the right level of abstraction basically getting the methodss to implement decision correct? For example, if I am going to write a class to represent a car, I will provide properties for the wheels etc, but if I am asked to write a class for a car with no wheels, I will not provide the wheels and thus no method to "drive" the car. Is this what is meant by getting the abstraction right?
Thanks
Not Quite,
Providing the right level of abstraction is knowing how much of the information from the lower levels to pass up through your level.
Suppose you were writing a high level HTTP library. Perhaps you would provide a Get() method, a Head() method, a Post() method etcetera, but you wouldn't need to provide access to the underlying Sockets because you are abstracting that detail away from the user.
And below that Socket that you are using, there are layers of abstraction that you don't need to deal with. (You only access an abstraction one layer below you, beyond that it is the job of that layer to deal with the layer below it, and so forth).
For instance, you don't care about the sliding window flow control protocol because TCP is abstracting away those details.
--
If you are coding at too high of an abstraction layer for the purposes you are trying to achieve, you will run into multiple implementation details. When you are fighting with the library for control, it is an indication that perhaps you are working at too high a level.
Conversely, if you are coding at too low a level of abstraction you will get lost in the implementation details. Going back to my HTTP example, if you just want to run a Get request against the server and you are implementing a TCP handshake in your code, then perhaps you either want to try to use a library or abstract out your TCP code into a library and interface with it through that.
--
In one class that I took on the subject, the teacher had an interesting method of explaining abstractions. He had us think of them simply as a 'point of view' or 'perspective' on an object or a scenario.
The details that are important from one perspective aren't important at all from another perspective.
He put a book on a table and assigned roles to students such as "Reader", "Book Seller", "Author", "Librarian", or "Book Shipper" and asked us what details about the book we thought were important to us in that role. Based on the roll assigned to a person, their answers varied widely.
This represents an abstraction. Only needing those details that are important to you, and letting all other details be handled elsewhere (or simply fall to the wayside).
I don't think that's it.
To me, abstraction is synonymous with generalization. The more abstract something is, the more the author is trying to think about a problem in such a way that it's easier to extend and apply to new problems.
In general, greater abstraction requires more effort to develop and to understand. If you're a developer, and you're given a highly abstract framework to work with, you'll be forced to think in terms of the framework rather than using concepts that your common sense might suggest.
So, as in your example, a Car would be a very low level of abstraction. A RollingVehicle might be a higher one, and Transport might be the most abstract of all.
The right choice depends on how broadly you wish to apply your classes and how easily understood you'd like them to be.
I think one dangerous aspect of abstraction is its ability to erase or hide the reality or the design it represents. You should always maintain a reasonable distance between what you represent and the representation. By "reasonable" I mean easily understandable by an external developer how hasn't been coding on this specific project.
Joel Spolsky stated it quite right talking about the dangers of "architecture astronauts":
When great thinkers think about problems, they start to see patterns. They look at the problem of people sending each other word-processor files, and then they look at the problem of people sending each other spreadsheets, and they realize that there's a general pattern: sending files. That's one level of abstraction already. Then they go up one more level: people send files, but web browsers also "send" requests for web pages. And when you think about it, calling a method on an object is like sending a message to an object! It's the same thing again! Those are all sending operations, so our clever thinker invents a new, higher, broader abstraction called messaging, but now it's getting really vague and nobody really knows what they're talking about any more. Blah.

How do you write good highly useful general purpose libraries?

I asked this question about Microsoft .NET Libraries and the complexity of its source code. From what I'm reading, writing general purpose libraries and writing applications can be two different things. When writing libraries, you have to think about the client who could literally be everyone (supposing I release the library for use in the general public).
What kind of practices or theories or techniques are useful when learning to write libraries? Where do you learn to write code like the one in the .NET library? This looks like a "black art" which I don't know too much about.
That's a pretty subjective question, but here's on objective answer. The Framework Design Guidelines book (be sure to get the 2nd edition) is a very good book about how to write effective class libraries. The content is very good and the often dissenting annotations are thought-provoking. Every shop should have a copy of this book available.
You definitely need to watch Josh Bloch in his presentation How to Design a Good API & Why it Matters (1h 9m long). He is a Java guru but library design and object orientation are universal.
One piece of advice often ignored by library authors is to internalize costs. If something is hard to do, the library should do it. Too often I've seen the authors of a library push something hard onto the consumers of the API rather than solving it themselves. Instead, look for the hardest things and make sure the library does them or at least makes them very easy.
I will be paraphrasing from Effective C++ by Scott Meyers, which I have found to be the best advice I got:
Adhere to the principle of least astonishment: strive to provide classes whose operators and functions have a natural syntax and an intuitive semantics. Preserve consistency with the behavior of the built-in types: when in doubt, do as the ints do.
Recognize that anything somebody can do, they will do. They'll throw exceptions, they'll assign objects to themselves, they'll use objects before giving them values, they'll give objects values and never use them, they'll give them huge values, they'll give them tiny values, they'll give them null values. In general, if it will compile, somebody will do it. As a result, make your classes easy to use correctly and hard to use incorrectly. Accept that clients will make mistakes, and design your classes so you can prevent, detect, or correct such errors.
Strive for portable code. It's not much harder to write portable programs than to write unportable ones, and only rarely will the difference in performance be significant enough to justify unportable constructs.
Even programs designed for custom hardware often end up being ported, because stock hardware generally achieves an equivalent level of performance within a few years. Writing portable code allows you to switch platforms easily, to enlarge your client base, and to brag about supporting open systems. It also makes it easier to recover if you bet wrong in the operating system sweepstakes.
Design your code so that when changes are necessary, the impact is localized. Encapsulate as much as you can; make implementation details private.
Edit: I just noticed I very nearly duplicated what cherouvim had posted; sorry about that! But turns out we're linking to different speeches by Bloch, even if the subject is exactly the same. (cherouvim linked to a December 2005 talk, I to January 2007 one.) Well, I'll leave this answer here — you're probably best off by watching both and seeing how his message and way of presenting it has evolved :)
FWIW, I'd like to point to this Google Tech Talk by Joshua Bloch, who is a greatly respected guy in the Java world, and someone who has given speeches and written extensively on API design. (Oh, and designed some exceptionally good general purpose libraries, like the Java Collections Framework!)
Joshua Bloch, Google Tech Talks, January 24, 2007:
"How To Design A Good API and Why it
Matters" (the video is about 1 hour long)
You can also read many of the same ideas in his article Bumper-Sticker API Design (but I still recommend watching the presentation!)
(Seeing you come from the .NET side, I hope you don't let his Java background get in the way too much :-) This really is not Java-specific for the most part.)
Edit: Here's another 1½ minute bit of wisdom by Josh Bloch on why writing libraries is hard, and why it's still worth putting effort in it (economies of scale) — in a response to a question wondering, basically, "how hard can it be". (Part of a presentation about the Google Collections library, which is also totally worth watching, but more Java-centric.)
Krzysztof Cwalina's blog is a good starting place. His book, Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries, is probably the definitive work for .NET library design best practices.
http://blogs.msdn.com/kcwalina/
The number one rule is to treat API design just like UI design: gather information about how your users really use your UI/API, what they find helpful and what gets in their way. Use that information to improve the design. Start with users who can put up with API churn and gradually stabilize the API as it matures.
I wrote a few notes about what I've learned about API design here: http://www.natpryce.com/articles/000732.html
I'd start looking more into design patterns. You'll probably not going to find much use for some of them, but as you get deeper into your library design the patterns will become more applicable. I'd also pick up a copy of NDepend - a great code measuring utility which may help you decouple things better. You can use .NET libraries as an example, but, personally, i don't find them to be great design examples mostly due to their complexities. Also, start looking at some open source projects to see how they're layered and structured.
A couple of separate points:
The .NET Framework isn't a class library. It's a Framework. It's a set of types meant to not only provide functionality, but to be extended by your own code. For instance, it does provide you with the Stream abstract class, and with concrete implementations like the NetworkStream class, but it also provides you the WebRequest class and the means to extend it, so that WebRequest.Create("myschema://host/more") can produce an instance of your own class deriving from WebRequest, which can have its own GetResponse method returning its own class derived from WebResponse, such that calling GetResponseStream will return your own class derived from Stream!
And your callers will not need to know this is going on behind the scenes!
A separate point is that for most developers, creating a reusable library is not, and should not be the goal. The goal should be to write the code necessary to meet requirements. In the process, reusable code may be found. In that case, it should be refactored out into a separate library, where it can be reused in the future.
I go further than that (when permitted). I will usually wait until I find two pieces of code that actually do the same thing, or which overlap. Presumably both pieces of code have passed all their unit tests. I will then factor out the common code into a separate class library and run all the unit tests again. Assuming that they still pass, I've begun the creation of some reusable code that works (since the unit tests still pass).
This is in contrast to a lesson I learned in school, when the result of an entire project was a beautiful reusable library - with no code to reuse it.
(Of course, I'm sure it would have worked if any code had used it...)

How do you define a good or bad API? [closed]

As it currently stands, this question is not a good fit for our Q&A format. We expect answers to be supported by facts, references, or expertise, but this question will likely solicit debate, arguments, polling, or extended discussion. If you feel that this question can be improved and possibly reopened, visit the help center for guidance.
Closed 10 years ago.
Background:
I am taking a class at my university called "Software Constraints". In the first lectures we were learning how to build good APIs.
A good example we got of a really bad API function is the socket public static void Select(IList checkRead, IList checkWrite, IList checkError, int microseconds); in C#. The function receives 3 lists of sockets, and destroys them making the user have to clone all the sockets before feeding them into the Select(). It also has a timeout (in microseconds) which is an int, that sets the maximum time the server can wait for a socket. The limits of this is +/-35 minutes (because it is an int).
Questions:
How do you define an API as
'bad'?
How do you define an
API as 'good'?
Points to consider:
Function names that are hard to remember.
Function parameters that are hard to understand.
Bad documentation.
Everything being so interconnected that if you need to change 1 line of code you will actually need to change hundreds of lines in other places.
Functions that destroy their arguments.
Bad scalability due to "hidden" complexity.
It's required from the user/dev to build wrappers around the API so that it can be used.
In API design I've always found this keynote very helpful:
How to Design a Good API and Why it Matters - by Joshua Bloch
Here's an excerpt, I'd recommend reading the whole thing / watching the video.
II. General Principles
API Should Do One Thing and Do it Well
API Should Be As Small As Possible But No Smaller
Implementation Should Not Impact API
Minimize Accessibility of Everything
Names Matter–API is a Little Language
Documentation Matters
Document Religiously
Consider Performance Consequences of API Design Decisions
Effects of API Design Decisions on Performance are Real and Permanent
API Must Coexist Peacefully with Platform
III. Class Design
Minimize Mutability
Subclass Only Where it Makes Sense
Design and Document for Inheritance or Else Prohibit it
IV. Method Design
Don't Make the Client Do Anything the Module Could Do
Don't Violate the Principle of Least Astonishment
Fail Fast - Report Errors as Soon as Possible After They Occur
Provide Programmatic Access to All Data Available in String Form
Overload With Care
Use Appropriate Parameter and Return Types
Use Consistent Parameter Ordering Across Methods
Avoid Long Parameter Lists
Avoid Return Values that Demand Exceptional Processing
You don't have to read the documentation to use it correctly.
The sign of an awesome API.
Many coding standards and longer documents and even books (Framework Design Guidelines) have been written on this topic, but much of this only helps at a fairly low level.
There is also a matter of taste. APIs may obey every rule in whatever rulebook, and still suck, due to slavish adherence to various in-vogue ideologies. A recent culprit is pattern-orientation, wherein Singleton Patterns (little more than initialized global variables) and Factory Patterns (a way of parameterizing construction, but often implemented when not needed) are overused. Lately, it's more likely that Inversion of Control (IoC) and associated explosion in the number of tiny interface types that adds redundant conceptual complexity to designs.
The best tutors for taste are imitation (reading lots of code and APIs, finding out what works and doesn't work), experience (making mistakes and learning from it) and thinking (don't just do what's fashionable for its own sake, think before you act).
Useful - it addresses a need that is not already met (or improves on existing ones)
Easy to explain - the basic understanding of what it does should be simple to grasp
Follows some object model of some problem domain or real-world. It uses constructs that make sense
Correct use of synchronous and asynchronous calls. (don't block for things that take time)
Good default behavior - where possible allow extensibility and tweaking, but provide defaults for all that is necessary for simple cases
Sample uses and working sample applications. This is probably most important of all.
Excellent documentation
Eat your own dog food (if applicable)
Keep it small or segment it so that it is not one huge polluted space. Keep functionality sets distinct and isolated with few if any dependencies.
There are more, but that is a good start
A good API has a semantic model close to the thing it describes.
For example, an API for creating and manipulating Excel spreadsheets would have classes like Workbook, Sheet, and Cell, with methods like Cell.SetValue(text) and Workbook.listSheets().
A good API allows the client to do pretty much everything they need to do, but doesn't require them to do a lot of mindless busy-work. Examples of "mindless busy work" would be initializing data structure fields, calling several routines in a sequence that never varies with no real custom code in between, etc.
The surest sign of a bad API is if your clients all want to wrap it with their own helper code. At the absolute least, your API should have provided that helper code. Most likely, it should have been designed to provide the higher level of abstraction the clients are rolling themselves on their own every time.
A bad API is one that is not used by its intended audience.
A good API is one that is used by its intended audience for the purpose for which it was designed.
A great API is one that is used by both its intended audience, for the its intended purpose, and an unintended audience for reasons unanticipated by its designers.
If Amazon publishes its API as both SOAP and REST, and the REST version wins out, that doesn't mean the underlying SOAP API was bad.
I'd imagine that the same will be true for you. You can read all you want about design and try your best, but the acid test will be use. Spend some time building in ways to get feedback on what works and what doesn't and be prepared to refactor as needed to make it better.
A good API is one that makes simple things simple (minimum boilerplate and learning curve to do the most common things) and complicated things possible (maximum flexibility, as few assumptions as possible). A mediocre API is one that does one of these well (either insanely simple but only if you're trying to do really basic stuff, or insanely powerful, but with a really steep learning curve, etc). A horrible API is one that does neither of these well.
There are several other good answers on this already, so I thought I'd just throw in some links I didn't see mentioned.
Articles
"A Little Manual Of API Design" by Jasmin Blanchette of Trolltech
"Defining QT-Style C++ APIs" also Trolltech
Books:
"Effective Java" by Joshua Bloch
"The Practice Of Programming" by Kernighan and Pike
I think a good API should allow custom IO and memory management hooks if it's applicable.
A typical example is you have your custom compressed archive format for data on disk and a third party library with a poor api wants to access data on disk and expects a path to a file where it can load its data.
This link has some good points:
http://gamearchitect.net/2008/09/19/good-middleware/
If the API produces an error message, ensure that the message and diagnostics help a developer work out what the problem is.
My expectation is that the caller of an API passes in input that is correct. A developer is the consumer of any error messages produced by the API (not the end user), and messages aimed at the developer help the developer debug their calling program.
An API is bad when it is badly documented.
An API is good when it is well documented and follows a coding standard.
Now these are two, very simple and also very hard points to follow, this brings one into the area of software architecture. You need a good architect that structures the system and helps the framework follow its own guidlines.
Commenting code, writing an well explained manual for the API is Mandatory.
An API can be good if it has a good documentation which explains how to use it. But if the code is clean, good and follows a standard inside itself, it doesnt matter if it doenst have a decent documentation.
I've written a little about coding structure here
I think what is paramount is readability, by which I mean the quality that makes the greatest number of programmers to make sense of what the code is doing in the shortest possible amount of time. But judging which piece of software is readable and which is not has that indescribable human quality: fuzziness. The points you mention do partly succeed in crystallizing it. However, in its entirety it has to remain a case-by-case affair and it would be really hard to come up with universal rules.