Transcript of talk: "Designing Qt-style APIs"

KDE Contributor and Developer Conference

Notes taken by: Jonathan Riddell (these are not official material)

More information on the talk can be found here

Designing APIs is very complex and cannot be captured in a 45 minute talk. API is an Application Programmer Interface, not a program, and must be used by the programmers who are human. API is to the programmer what a GUI is to the end user: you can't just add things all over the place (or you end up with KControl). APIs should be both minimal and complete (which can conflict), have clear and simple semantics, be intuitive and easy to remember, guide the user to readable code in the same way as a good user interface guides you to what to do.

Minimal means minimizing the number of public members per class and the number of classes. This makes it easier to remember, debug and understand. Overloads are preferred over new items.

A complete API means the expected functionality should be there. This can conflict with keeping it minimal. If a member function is in the wrong class this can lead to extra functions in the same wrong place.

As with other design work, you should apply the principle of least surprise. Make common tasks easy; rare tasks should be possible but not the focus. Solve the specific problem so don't make it overly general when this is not needed, e.g. QMimeSourceFactory should have been a QImageLoader. Async I/O framework is the same because it was really only written to load images.

If you are not sure about something, remove it (QCom). Add it later when you understand how to do it properly.

As with anything else on a computer an API should be intuitive. This is based on experience (e.g. click on a floppy disk to save even if you have never used a floppy disk). A Qt API is intuitive if someone familiar with Qt does not have to read the documentation or if a non-Qt programmer can understand code written with it.

To make it easy to remember choose a consistent and precise naming. Use words that non-native English speakers will understand and do not use abbreviations. Use recognisable patterns and concepts.

An API should result in readable code, this is because code is read many times after it has been written. Readable code takes longer to write but saves time, which is why you should not save keystrokes by using abbreviations.

Remember your user, they will use it for different things such as subclassing. Normal classes should not require subclassing so any classes which do require this are marked as abstract. In Qt 1 and Qt 2, for example, size policy was not a Qt Property so to change the size policy you had to subclass, something that shouldn't be required.

It is a misconception that the less code you need to achieve something means, the better the API. Mind that code is written more than once but has to be understood over again.

Example:

QSlider* slider = new QSlider(12, 18, 3, 13, Vertical, 0, "Convenience level")

requires reading the documentation to understand, it is much more readable as

slider = new QSlider
slider->setRange(12,18)
slider->setPageStep(3)
slider->setValue(13);

Also consider bindings to dynamically typed language.

Consider the boolean trap when you add functionality to a function by adding a new boolean argument. This appears a lot in KDE. In the following example, the code might be read as "don't repaint":

widget->repaint(false);

Mind the inheritance structure. e.g. QToolButton has Qobject name, QWidget caption, QButton text, QToolButton textlabel. This is improved in Qt4.

APIs need quality assurance. The first revision is never right, you must test it. Make use cases by looking at code which uses this API - the API is good if the code is readable. Have somebody else use it without documentation. Document your classes but don't just write functional documentation but also a class overview which covers all the methods in the API. Always get your names right.

Names can be found by documenting it and the words are probably in the first sentence. If you cannot find a precise name it probably means the API is wrong. You can invent a name but be very careful about this (e.g. widget, buddy). An example of a bad name is QSizePolicy::setHorStretch(uint sf), which should not have been abbreviated.

Boolean properties are the most important, use "is" for singular adjectives otherwise nothing.

A case study of QProgressBar was given. For example totalSteps() and progress() are related but not named alike; it should use similar names to QSpinBox but does not. Indicator and Percentage are two words used for the same thing. This can't be automated it is human judgment, just as with GUI design.

Why no 'gets'? This was decided against because it doesn't look nice "if (widget->getVisible())" isn't as nice as "if (widget->visible())". 'Get' is used for functions which have several out pointers because it has to be obvious that this is an outputting function.

One problem if using 'is' in a name is when you have plural properties because it would have to be 'are'.

Are there guidelines for Qt (related) bindings to C libraries? All of Qt is just bindings to C libraries but the shape of the C library should not effect the shape of the API.

Could we have written guidelines to use for cleaning up KDE 4? Oh yes.

Will users of the old QProgressBar be given a refund? Talk to Eirik.