Interfaces de programmation (API)
Ce qui suit vise à regrouper diverses réflexions et considérations quant au
développement d'une interface de programmation (Application Programming
Interface, API), incluant le choix des noms,
des signatures de fonctions, les modalités d'appel, la documentation, la gestion
des erreurs, etc.
Notez au passage que toutes ces considérations sont traitées individuellement
sur d'autres pages de ce site; ce que j'ai essayé de grouper ici tient à l'art
(il serait présomptueux de parler d'Art ou de science ici) du design d'outils
logiciels qui serviront à des multitudes, et qui constitueront un point d'entrée
pour une gamme de services ou de fonctionnalités.
Je dois avouer que le sujet m'intéresse beaucoup, mais que le temps me
manque, ce qui explique que vous trouverez ici surtout des réflexions de tiers
(un jour, j'ajouterai les miennes). De même, l'organisation par section de cette
page est embryonnaire, mais je ferai mieux (éventuellement) quand j'en aurai le
temps.
Généralités
Considérations d'ordre général sur le design d'une API.
- Petite introduction à ce que sont les API :
https://zapier.com/learn/apis/
- Conjecture de John D. Cook en 2012 sur la relation
entre la facilité à comprendre les paramètres à
passer à une méthode et la probabilité que cette méthode
soit utilisée en pratique :
http://www.johndcook.com/blog/2012/06/18/methods-that-get-used/
- Que vous raconte votre architecture? Un texte de
Robert C. Martin en
2011 :
http://blog.8thlight.com/uncle-bob/2011/09/30/Screaming-Architecture.html
- À propos de la demi-vie d'une API, par Gabriel
Weinberg en 2011 :
http://www.gabrielweinberg.com/blog/2011/11/api-half-lives.html
- Ce serait la fin des gros Frameworks
MVC tels que Rails, selon ce texte de 2012 :
http://caines.ca/blog/programming/the-sun-is-setting-on-rails-style-mvc-frameworks/
- En 2012, Charlie Kindel relate une expérience
très intéressante de support technique et d'abus d'une
API, de laquelle on peut tirer des apprentissages :
http://ceklog.kindel.com/2012/04/18/dont-build-apis/
- En 2013, Charles Bloom se confie : écrire une API
pour usage public est une tâche vraiment ardue :
http://cbloomrants.blogspot.ca/2013/03/03-06-13-sympathy-for-library-writer.html
- En 2009, Olivier Coudert propose son
API Design 101 :
http://www.ocoudert.com/blog/2009/10/08/api-design-101/
- Présentation d'Alexander
Stepanov, architecte de STL,
sur le design de bibliothèques efficaces (texte de
2003, mais qui reste d'actualité) : http://www.stepanovpapers.com/Designing%20libraries.pdf
- Présentation de Joshua
Bloch, architecte logiciel réputé s'il en est un, sur le
design d'une bonne API et les raisons pour lesquelles
c'est quelque chose d'important : http://www.examville.com/examville/How%20to%20Design%20a%20Good%20API%20and%20Why%20Does%20It%20Matter-ID5567
- En 2010, Alan Downie exprime l'opinion qu'il faut
absolument publier une API avec nos applications :
http://blog.angrymonkeys.com.au/why-you-absolutely-must-write-an-api-before-y
- Une opinion de Ferenc Mihaly, offerte en 2011,
sur ce qui fait qu'il soit si important de s'assurer qu'une
API soit conviviale pour les développeurs :
http://theamiableapi.com/2011/08/23/why-developer-friendliness-is-central-to-api-design/
- En 2012,
Zed A. Shaw nous rappelle que dans une
API comme de manière générale, l'abstraction et l'indirection sont deux
choses différentes l'une de l'autre :
http://zedshaw.com/essays/indirection_is_not_abstraction.html
- Selon Mathieu Fenniak en 2013, le temps est venu
de cesser de produire des API fragiles pour le
Web :
http://mathieu.fenniak.net/stop-designing-fragile-web-apis/
- Ce que les gens développant une API pourraient
apprendre de l'industrie des paiements, par Alejandro Revilla en
2013 : http://jpos.org/blog/2013/11/w55y/
- A-t-on vraiment besoin d'une API sur chaque site
Web? Mieux vaut réfléchir avant d'agir, selon Ruben Verborgh en
2013 :
http://ruben.verborgh.org/blog/2013/11/29/the-lie-of-the-api/
- Selon Keith Axline en 2014, le monde entier est
programmable, et il faudrait des API pour tout!
http://www.wired.com/2014/04/the-universe-is-programmable/
- En quoi l'API est-elle ce qui importe le plus
dans une offre logicielle? Opinion de Sean Cassidy en 2014 :
http://blog.seancassidy.me/your-interface-is-what-matters.html
- En quoi une API destinée à être utilisée
concurremment diffère-t-elle d'une API qui ne l'est
pas? Texte de Robert C. Martin en 2014 :
http://www.randomprogramming.com/2014/08/stdvector-vs-tbbconcurrent_vector-api-design/
- Définir une API pour qu'elle dure, selon Jason
Harmon en 2014 :
http://apiux.com/2014/09/05/api-design-sustainability/
- La documentation d'une API
a elle aussi besoin d'amour, comme le rappelle Ian Watson en
2015 :
http://apimetrics.io/2015/07/22/api-documentation-needs-love-too/
- En 2015,
Robert C. Martin nous implore de faire
disparaître la magie de nos Frameworks :
http://blog.8thlight.com/uncle-bob/2015/08/06/let-the-magic-die.html
- Texte de 2016 par Jared Parsons qui, dans une
perspective
C#,
se demande si les membres privés d'une classe font partie de la surface d'une
API :
http://blog.paranoidcoding.com/2016/02/15/are-private-members-api-surface.html
- Design d'une API destinée au langage
C, texte
de 2016 :
https://anteru.net/2016/05/01/3249/
- Comme le rappelle Marshall Clow en 2016, tout
le monde peut se tromper, et il faut avoir l'humilité de corriger ses erreurs
de design :
https://cplusplusmusings.wordpress.com/2016/02/01/sometimes-you-get-things-wrong/
- Réflexion intéressante de Timothy Lottes en 2016
sur la relation entre l'API Win32
et la liberté d'expression programmatique :
http://timothylottes.blogspot.ca/2016/07/on-killing-win32.html
- S'acclimater à un API en lisant du code, un texte de 2011 :
http://code-recommenders.blogspot.ca/2011/11/do-you-still-read-source-code-to-learn.html
- Texte de 2016 par Tom Smith, qui collige les
perspectives de diverses entreprises à propos des politiques de gestion et de
design d'API :
https://dzone.com/articles/use-of-api-design-and-management-policies
Technique
À propos des aspects plus techniques du design d'API et de Framework :
- La question du positionnement des membres dans les objets et des paramètres
dans les signatures de fonctions comme de méthodes, un texte de
Jon
Skeet en 2012 : http://msmvps.com/blogs/jon_skeet/archive/2012/02/29/subtleties-in-api-design-member-placement.aspx
- Quelques recommandations en lien avec le design de bibliothèques
en langage C,
par Rusty Russell en 2010 : http://rusty.ozlabs.org/?p=140
- En 2010, Alex Williams expose dix erreurs typiques
commises par des fournisseurs d'API, avec un accent
fort sur les API de développement
Web : http://www.readwriteweb.com/cloud/2010/08/the-new-api-movement-may.php
- Ce texte d'Anthony Ferrara en 2012 suggère
qu'avoir une « fixation » sur les Frameworks
serait une forme d'anti-pattern :
http://blog.ircmaxell.com/2012/07/framework-fixation-anti-pattern.html
- Développer une bibliothèque pour l'espace usager sous
Linux, quelques
règles de bienséance :
https://git.kernel.org/?p=linux/kernel/git/kay/libabc.git;a=blob_plain;f=README
- En 2012, Ed Finkler indique qu'à son avis, moins de code signifie moins de
problèmes, et qu'il vaut mieux à ses yeux construire de petites unités de code
qu'utiliser des Frameworks souvent trop lourds :
http://webadvent.org/2012/more-code-more-problems-by-ed-finkler
- Développer une API de type
REST :
../Web/REST.html#rest_api
- Mieux vaut réfléchir au design d'une API
aussi en termes de temps de compilation, d'impact sur la consommation
de mémoire et ainsi de suite, pour éviter des critiques telles
que celle formulér par Tomas Andrle en 2012 :
http://www.catnapgames.com/blog/2012/03/02/boost-bloat.html
(à la défense des gens de Boost, les métriques de compilation
en Debug ont une valeur limitée)
- Texte de 2013 où John Wilander critique le choix
de l'API des Servlets en Java de représenter un
en-tête http
par une String, et le fait bien (mais
prétend ensuite que String devrait être une classe
abstraite... et le défend beaucoup moins bien) :
http://appsandsecurity.blogspot.ca/2013/05/should-string-be-abstract-class.html
- Contrôle des versions d'une API, un texte de Troy
Hunt en 2014 :
http://server.dzone.com/articles/your-api-versioning-wrong
- L'optimisation de l'API de Netflix, décrite par
Jeevak Kasarkod en 2013 :
http://www.infoq.com/news/2013/02/netflix-api-optimization
- Texte de John Regehr en 2015, à propos d'une
technique qu'il nomme l'API Fuzzing :
http://blog.regehr.org/archives/1269
L'influence des langages impliqués
Réflexions de Raymond Chen :
Qualité
Qu'est-ce qui fait qu'une API soit bonne ou
excellente?
Dans une excellente présentation de 2014 (https://www.youtube.com/watch?v=zgOF4NrQllo
pour la présentation,
http://meetingcpp.com/tl_files/2013/talks/Keynote-cxx11-library-design-ericniebler.pdf
pour les diapositives électroniques),
Eric Niebler offre quelques recommandations quant au design d'une
API en
C++
depuis C++ 11.
En résumé :
- Pour le design d'une fonction :
- est-elle facile à appeler correctement?
- est-elle difficile àappeler incorrectement?
- peut-on l'appeler efficacement? (Ici, par efficacité, Niebler parle de
réduire au minimum les copies, l'aliasing et l'allocation dynamique de
ressources)
- est-elle composable avec d'autres fonctions?
- est-elle utilisable dans des constructions de plus haut niveau?
Il met de l'avant un tableau comparatif entre les pratiques recommandées avec
C++ 03
et C++ 11 :
Catégorie |
Avec
C++ 03 |
Avec C++ 11 |
Petit intrant |
Passage par valeur |
Passage par valeur |
Sink |
s/o |
Passage par valeur |
Gros intrant |
Passage ref-vers-const |
Passage ref-vers-const |
Petit extrant |
Retourner par copie |
Retourner par copie |
Gros extrant |
Passage par référence non-const |
Retourner par copie (pertmettre le
mouvement et le Copy
Elision) |
Intrant et extrant |
Passage par référence non-const |
Passage par référence non-const
ou repenser l'interface avec des objets
représentant des algorithmes |
Comme il le rappelle, dans le cas des intrants, certains sont voués à être
utilisés en lecture seule (Read-Only), alors que d'autres sont ce
qu'on nomme des Sinks, et sont consommés par la fonction appelée :
- pour les paramètres en lecture seule, mieux vaut continuer d'utiliser des
paramètres ref-vers-const (sauf s'ils sont très petits, dans quels cas le passage par copie convient)
- pour les Sinks, on serait tenté d'utiliser des
références sur des
rvalue,
mais cela pose problème. En effet :
- dans une interface avec un paramètre, il faudrait couvrir deux cas :
template <class T>
void f(const T&); // lecture seule
template <class T>
void f(T&&); // Sink
- ... mais dans une interface avec deux paramètre, il faudrait couvrir
quatre cas :
template <class T, class U>
void f(const T&, const U&); // lecture seule, lecture seule
template <class T, class U>
void f(const T&, U&&); // lecture seule, Sink
template <class T, class U>
void f(T&&, const U&); // sink, lecture seule
template <class T, class U>
void f(T&&, U&&); // Sink, Sink
- ... et la situation dégénèrerait par la suite. Pour cette raison, Niebler
recommande de consommer les paramètres Sink
par valeur
- Les paramètres passés comme
références sur des
rvalue sont pour lui pertinents dans trois cas, soit :
Pour ce qui est des objets représentant des algorithmes, Niebler met de
l'avant que dans bien des cas, la raison pour laquelle nous avons recours à des
paramètres d'entrée/ sortie est que notre algorithme est Stateful, et
que cette caractéristique mériterait d'être explicitée par un objet encapsulant
ce concept.
Il propose de réécrire getline(), qui « compose
mal » avec d'autres fonctions (elle retourne une référence sur le flux duquel
la lecture a été faite, pas la ligne lue!) par un
getlines() (pluriel) qui encapsulerait les opérations plus primitives de
lecture de lignes et réutiliserait l'espace alloué pour la chaîne dans laquelle
la première lecture est faite pour que les lectures subséquentes soient plus
rapides.
Pour le design des types :
- Si votre type implémente le
mouvement, visez des opérations de
mouvement
qui soient
noexcept, sinon les conteneurs tels que std::vector ne pourront en déplacer les
instances sans risque et devront les copier. Vous perdrez au change
- Un objet sur lequel un mouvement a été appliqué, doit se trouver dans un
état valide, à tout le moins pour être détruit. L'état d'un objet
auquel un mouvement a été
appliqué devrait faire partie des invariants de son
type
- Pour un type supportant le
mouvement,
il est souhaitable d'avoir un état par défaut qui soit très peu coûteux à
atteindre
- Privilégiez les types réguliers (ceux qui se comportent comme des
int, en gros)
- Être un type régulier est différent en
C++ 03
et en
C++ 11 :
- avec
C++ 03,
il faut pour l'essentiel s'assurer que le type implémente la
Sainte-Trinité et
les opérateurs relationnels, qu'on enrichit souvent d'une implémentation
spécialisée de swap() pour fins de rapidité d'exécutiona
- avec
C++ 11, il faut essentiellement
implémenter la règle de cinq (ou de
zéro) et les opérateurs relationnels. Étant donné la présence des opérations de
mouvement, le std::swap() devient à toutes fins pratiques idéal et il n'est
plus pertinent de le spécialiser
Eric Niebler propose le trait suivant pour tester si un type T
est
régulier :
template <typename T>
struct is_regular
: std::integral_constant<
bool, std::is_default_constructible<T>::value &&
std::is_copy_constructible<T>::value &&
std::is_move_constructible<T>::value &&
std::is_copy_assignable<T>::value &&
std::is_move_assignable<T>::value &&
>
{
};
Ce trait omet les opérateurs
==, != et < qui
sont typiquement associés aux types réguliers, alors il est sans doute
perfectible.
Pour ce qui est des modules :
- Le langage
C++
n'a pas, même avec
C++ 14,
de modules à proprement dit. Cependant, il est possible d'atteindre une forme
de contrôle de versions raisonnable avec des
espaces nommés inline.
Pour cette raison :
- Les foncteurs sont mieux pris en charge que les fonctions pour tout ce qui
a trait aux règles du
Argument-Dependent Lookup.. Ainsi,
les
foncteurs globaux
constexpr
sont préférables aux fonctions globales, du
moins en général
Saines pratiques
Quelques guides de saines pratiques et quelques groupes de conseils pour vous
guider dans le design de vos API :
- Celles de Qt : http://wiki.qt-project.org/API_Design_Principles
- Celles du Framework de la plateforme
.NET : http://web.archive.org/web/20081219222454/http://msdn.microsoft.com/en-us/library/czefa0ke.aspx
- Texte de Michael L. Perry, publié dans le
Code Project) en 2012, sur la
conception d'une API pensée pour réduire les
risques de mauvaise utilisation :
http://www.codeproject.com/Articles/473446/Provable-APIs
- De l'avis d'Armin Ronacher en 2013, en
Python,
mieux vaudrait écrire plus de classes :
http://lucumr.pocoo.org/2013/2/13/moar-classes/
- Les périls des bibliothèques communes, par Cody Powell en
2013 :
http://www.codypowell.com/taods/2013/06/tragedy-of-the-common-library.html
- Développer une API, les fichiers d'en-tête,
par Sean Barrett en 2013 :
http://nothings.org/stb/stb_howto.txt
- Développer une API décente (pour le Web), selon
Phil Sturgeon en 2013 :
http://philsturgeon.co.uk/blog/2013/07/building-a-decent-api
- Réflexion d'Armin Conacher, en 2012, sur
le design d'une API efficace pour exploiter
http : http://lucumr.pocoo.org/2012/4/14/im-doing-http-wrong/
- En 2014, Daniel Wertheim suggère d'accroître
l'utilisabilité d'une API en
C#
par l'exposition d'opérateurs de conversion implicite :
http://danielwertheim.se/2014/05/02/c-using-implicit-operator-overloads-to-keep-your-api-discoverable-and-open/
- Développer une API de qualité avec
C++ 11,
une présentation de 2014 :
https://github.com/boostcon/cppnow_presentations_2014/raw/master/files/cxx11-library-design.pdf.
En résumé, ses conseils clés sont :
- recevoir les paramètres en lecture seule par référence-vers-const (outre
ceux de petite taille, qu'il est raisonnable de recevoir par copie)
- recevoir les paramètres Sink, ceux qui peuvent consommer les
entités nommées ou non, par valeur
- encapsuler les états d'un algorithme dans un objet qui implémente cet
algorithme
- cherchez à faire de vos types des types réguliers (ceci rejoint plus
formellement la maxime de
Scott Meyers :
« Do as the ints do »)
- faites en sorte que vos constructeurs de mouvement soient
noexcept dans la mesure du
possible
- suite à un mouvement,
l'état de l'objet dont on vient de déplacer les états fait partie des
invariants de son type. Si vous ne parvenez pas à
définir cet état, alors le type ne devrait sans doute pas implémenter le
mouvement
- à titre corollaire, un type implémentant le
mouvement devrait avoir un
état par défaut à la fois valide et rapide à construire
- utilisez systématiquement des
espaces nommés
pour faciliter la gestion des versions
- faites en sorte que la version courante de vos outils soit
systématiquement dans un
espace nommé inline
- placez la définition de vos types dans un
espace nommé non-inline, pour bloquer le
mécanisme
ADL, puis exportez-les avec des déclarations
using
- préférez des foncteurs
constexpr
globaux à des fonctions
- Réflexions d'Andrzej Krzemieński sur la qualité d'une interface pour une classe donnée, et
qui met de l'avant que ce que l'interface refuse est parfois aussi important
que ce qu'elle accepte :
- En 2013, Marshall Clow admet avoir commis une
erreur dans la définition d'une interface pour une fonction qu'il avait publié
dans Boost.Algorithm, et explique son processus de correction d'une telle
erreur :
http://cplusplusmusings.wordpress.com/2013/02/27/fixing-an-interface-bug/
- Petit manuel de design pour les API, proposé
par Jasmin Blanchette en 2008 :
http://www4.in.tum.de/~blanchet/api-design.pdf
- Conseils pour offrir une bonne API, promulgués
par Pieter Hintjens en 2015 :
http://hintjens.com/blog:94
Pratiques malsaines
Certains ont exprimé leur point de vue par la négative, et nous offrent leur
conception de ce qu'il vaut mieux ne pas faire lors du développement d'une
API.
- Comment ne pas écrire une API, texte
de 2013 :
http://ghost.teario.com/how-not-to-write-an-api/
- Réflexion de James Hague, en 2012, sur la
complexité que nous nous imposons inutilement par des mauvaises décisions
de design : http://prog21.dadgum.com/139.html
- Parfois, un langage semble « stupide » alors que c'est surtout l'interface
(l'API) de certains de ses services qui est
discutable. À cet effet : texte de Ned Batchelder en 2013 :
http://nedbatchelder.com/blog/201301/stupid_languages.html
- Critique d'une API mal foutue, soit celle des
ports séries de la plateforme
.NET, par Ben Voigt en 2014 :
http://www.sparxeng.com/blog/software/must-use-net-system-io-ports-serialport
- En 2014, Casey Muratori décrit ce qu'il
qualifie de « pire API jamais conçue », soit celle
permettant de faire du Event Tracing sous
Microsoft Windows :
http://mollyrocket.com/casey/stream_0029.html
- À propos des paramètres booléens :
- en 2011, Ariya Hidayat décrit ce qu'elle
présente comme le piège du booléen, qui prévaut
trop souvent dans les API :
http://ariya.ofilabs.com/2011/08/hall-of-api-shame-boolean-trap.html
- le problème des paramètres booléens, trop pauvres sur le plan sémantique,
et des voies de contournement, par No'am Rosenthal en 2014 :
http://fluxible.blogspot.ca/2014/09/a-trivial-way-to-work-around-boolean.html
- en 2012, Ke Pi ajoute sa voix aux nombreuses
autres qui recommandent d'éviter les paramètres booléens dans une
API :
http://www.pixelstech.net/article/1335076725-Do-NOT-use-boolean-variable-as-function-parameters
- texte de 2017 par Andrzej Krzemieński, qui
discute du problème des fonctions dont les paramètres (typiquement booléens)
voient leur sémantique encodée dans leur nom, ce qui n'est pas très porteur,
et propose une technique (les Tagged Booleans) pour contourner le
problème :
https://akrzemi1.wordpress.com/2017/02/16/toggles-in-functions/
- la vision que promulgue Bartlomiej Filipek à propos des paramètres booléens dans les
fonctions, texte de 2017 :
http://www.bfilipek.com/2017/03/on-toggle-parameters.html
- Quelques conséquences du retrait d'une API,
soit le cas du remplacement de l'API de Skype en
2013, un texte de Mark Gibbs :
http://www.networkworld.com/article/2225703/software/microsoft-to-can-skype-api--third-party-products-will-not-work.html
- Quelques conseils sur ce qu'il ne faut pas faire :
- Comment en arriver à une API de m**** selon
Alex Dovzhanyn en 2016 :
https://www.runtimerror.com/how-to-make-a-shitty-api/
Critiques d'API existantes
Les saines pratiques de conception d'une bibliothèque, d'une
API ou d'un Framework sont un créneau important des saines
pratiques de programmation en général. Plusieurs ont publié
leur vision ou leurs expériences à se sujet.
À propos de l'API de Facebook :
À propos de l'API de Dropbox :
À propos du design d'OpenCV :
À propos de l'API de Twitter :
API de mauvaise qualité :