¿Debería uno saber siempre qué está haciendo una API simplemente mirando el código?

20

Recientemente he estado desarrollando mi propia API y con ese interés invertido en el diseño de la API, me ha interesado mucho cómo puedo mejorar mi diseño de la API.

Un aspecto que ha surgido un par de veces es (no por los usuarios de mi API, sino en mi discusión de observación sobre el tema): uno debe saber simplemente mirando el código que llama a la API lo que está haciendo .

Por ejemplo, vea esta discusión en GitHub para el repositorio del discurso , es algo así como:

foo.update_pinned(true, true);

Simplemente mirando el código (sin conocer los nombres de los parámetros, la documentación, etc.) no se puede adivinar qué va a hacer: ¿qué significa el segundo argumento? La mejora sugerida es tener algo como:

foo.pin()
foo.unpin()
foo.pin_globally()

Y eso aclara las cosas (supongo que la segunda argumentación fue si era necesario ubicar a todo el mundo), y estoy de acuerdo en que, en este caso, la posterior sin duda sería una mejora.

Sin embargo, creo que puede haber casos en los que los métodos para establecer un estado diferente pero relacionado lógicamente estarían mejor expuestos como una llamada a un método en lugar de hacerlo por separado, a pesar de que no sabría lo que está haciendo con solo mirar el código . (Por lo tanto, tendría que recurrir a mirar los nombres de los parámetros y la documentación para averiguarlo, lo que personalmente siempre haría sin importar si no estoy familiarizado con una API).

Por ejemplo, expongoSetVisibility(bool, string, bool) un método en un FalconPeer y reconozco solo mirando la línea:

falconPeer.SetVisibility(true, "aerw3", true);

No tendrías idea de lo que está haciendo. Establece 3 valores diferentes que controlan la "visibilidad" de la falconPeeren el sentido lógico: aceptar solicitudes de unión, solo con contraseña y responder a solicitudes de descubrimiento. Dividir esto en 3 llamadas a métodos podría llevar a un usuario de la API a establecer un aspecto de "visibilidad", olvidando establecer otros aspectos en los que les obligue a pensar exponiendo solo el método para configurar todos los aspectos de "visibilidad" . Además, cuando el usuario quiere cambiar un aspecto, casi siempre querrá cambiar otro aspecto y ahora puede hacerlo en una llamada.

markmnl
fuente
13
Esta es precisamente la razón por la cual algunas lenguas han nombrado parámetros (a veces incluso forzadas parámetros con nombre). Por ejemplo, usted podría agrupar una gran cantidad de ajustes en un sencillo updatemétodo: foo.update(pinned=true, globally=true). O: foo.update_pinned(true, globally=true). Por lo tanto, la respuesta a su pregunta también debe tener en cuenta las características del lenguaje, ya que una buena API para el lenguaje X podría no ser buena para el lenguaje Y y viceversa.
Bakuriu
De acuerdo - dejemos de usar booleans :)
Ven
2
Se conoce como "trampa booleana"
usuario11153
@Bakuriu Incluso C tiene enumeraciones, incluso si son números enteros disfrazados. No creo que haya ningún lenguaje del mundo real donde los booleanos sean un buen diseño de API.
Doval
1
@Doval No entiendo lo que intentas decir. Usé booleanos en esa situación simplemente porque el OP los usó, pero mi punto no tiene ninguna relación con el valor pasado. Por ejemplo: setSize(10, 20)no es tan legible como setSize(width=10, height=20)o random(distribution='gaussian', mean=0.5, deviation=1). En los idiomas con parámetros con nombre forzados, los booleanos pueden transmitir exactamente la misma cantidad de información que el uso de enumeraciones / constantes con nombre, por lo que pueden ser buenos en las API.
Bakuriu

Respuestas:

27

Su deseo de no dividirlo en tres llamadas a métodos es completamente comprensible, pero tiene otras opciones además de los parámetros booleanos.

Podrías usar enumeraciones:

falconPeer.SetVisibility(JoinRequestOptions.Accept, "aerw3", DiscoveryRequestOptions.Reply);

O incluso una enumeración de banderas (si su idioma lo admite):

falconPeer.SetVisibility(VisibilityOptions.AcceptJoinRequests | VisibilityOptions.ReplyToDiscoveryRequests, "aerw3");

O podría usar un objeto de parámetro :

var options = new VisibilityOptions();
options.AcceptJoinRequests = true;
options.ReplyToDiscoveryRequest = true;
options.Password="aerw3";
falconPeer.SetVisibility(options);

El patrón de objeto de parámetro le ofrece algunas otras ventajas que pueden resultarle útiles. Hace que sea fácil pasar y serializar un conjunto de parámetros, y puede dar fácilmente valores "predeterminados" de parámetros no especificados.

O simplemente podría usar parámetros booleanos. Microsoft parece hacerlo todo el tiempo con la API .NET Framework, por lo que podría encogerse de hombros y decir "si es lo suficientemente bueno para ellos, es lo suficientemente bueno para mí".

BenM
fuente
El patrón de objeto de parámetro todavía tiene un problema que el OP declaró: " Dividir esto en 3 llamadas de método podría llevar a un usuario de la API a establecer un aspecto de" visibilidad "olvidando establecer otros ".
Lode
Es cierto, pero creo que mejora la situación (si no es que perfecta). Si el usuario se ve obligado a crear instancias y pasar un objeto VisibilityOptions, puede (con un poco de suerte) recordarle que hay otras propiedades en el objeto VisibilityOptions que podrían querer establecer. Con el enfoque de tres llamadas de método, todo lo que tienen que recordarles es comentarios sobre los métodos que están llamando.
BenM
6

Obviamente, siempre hay excepciones a la regla, pero como usted mismo explicó bien, hay ciertas ventajas en tener una API legible. Los argumentos booleanos son particularmente molestos, ya que 1) no revelan una intención y 2) implican que se llama una función, donde en realidad debería tener dos, porque diferentes cosas van a suceder dependiendo de la bandera booleana.

La pregunta principal es más bien: ¿cuánto esfuerzo desea invertir para hacer que su API sea más legible? Cuanto más exterior es, más esfuerzo puede justificarse fácilmente. Si se trata de una API que solo utiliza otra unidad, no es tan importante. Si está hablando de una API REST donde planea dejar que todo el mundo pierda, entonces también puede invertir un poco más de esfuerzo para hacerlo más comprensible.

En cuanto a su ejemplo, hay una solución simple: aparentemente, en su caso, la visibilidad no es solo una cosa verdadera o falsa. En cambio, tiene todo un conjunto de cosas que considera "visibilidad". Una solución puede ser introducir algo como una Visibilityclase, que cubre todos estos diferentes tipos de visibilidad. Si aplica aún más el patrón Builder para crearlos, puede terminar con un código como el siguiente:

Visibility visibility = Visibility.builder()
  .acceptJoinRequests()
  .withPassword("aerw3")
  .replyToDiscoveryRequests()
  .build();
falconPeer.setVisibility(visibility);
Franco
fuente