Devemos sempre saber o que uma API está fazendo apenas olhando o código?

20

Recentemente, desenvolvi minha própria API e, com esse interesse investido em design de API, fiquei muito interessado em saber como posso melhorar meu design de API.

Um aspecto que surgiu algumas vezes é (não pelos usuários da minha API, mas na minha observação de discussão sobre o tópico): deve-se saber apenas olhando o código que chama a API do que está fazendo .

Por exemplo, veja esta discussão no GitHub para o repositório de discurso , é algo como:

foo.update_pinned(true, true);

Apenas olhando o código (sem saber os nomes dos parâmetros, a documentação etc.), não se pode adivinhar o que vai fazer - o que significa o segundo argumento? A melhoria sugerida é ter algo como:

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

E isso esclarece as coisas (o segundo argumento foi sobre se deve ou não ser globalmente, suponho), e concordo que, neste caso, o posterior certamente seria uma melhoria.

No entanto, acredito que possa haver casos em que métodos para definir um estado diferente, mas logicamente relacionado, seriam melhor expostos como uma chamada de método, em vez de separados, mesmo que você não saiba o que está fazendo apenas observando o código . (Portanto, você teria que procurar nos nomes dos parâmetros e na documentação para descobrir - o que pessoalmente eu sempre faria, independentemente de não estar familiarizado com uma API).

Por exemplo, eu exponho um método SetVisibility(bool, string, bool)em um FalconPeer e reconheço apenas olhando a linha:

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

Você não teria ideia do que está fazendo. Ele está configurando três valores diferentes que controlam a "visibilidade" do falconPeerno sentido lógico: aceitar solicitações de associação, apenas com senha e responder a solicitações de descoberta. Dividir isso em três chamadas de método pode levar um usuário da API a definir um aspecto da "visibilidade", esquecendo-se de definir os outros que eu os forço a pensar, expondo apenas o método único para definir todos os aspectos da "visibilidade" . Além disso, quando o usuário deseja alterar um aspecto, quase sempre desejará alterar outro aspecto e agora pode fazê-lo em uma chamada.

markmnl
fonte
13
Esta é precisamente a razão pela qual alguns idiomas nomearam parâmetros (às vezes até aplicam parâmetros nomeados). Por exemplo, você poderia agrupar um monte de configurações em um simples updatemétodo: foo.update(pinned=true, globally=true). Ou: foo.update_pinned(true, globally=true). Portanto, a resposta para sua pergunta também deve levar em consideração os recursos do idioma, porque uma boa API para o idioma X pode não ser boa para o idioma Y e vice-versa.
precisa saber é o seguinte
Acordados - Pare de usar booleans deixou-nos :)
Ven
2
É conhecido como "armadilha booleana"
user11153 03/06
@Bakuriu Até C tem enumerações, mesmo que sejam números inteiros disfarçados. Eu não acho que exista uma linguagem do mundo real onde os booleanos sejam um bom design de API.
Doval
1
@Doval Não entendo o que você está tentando dizer. Eu usei booleanos nessa situação simplesmente porque o OP os usou, mas meu argumento não tem nenhuma relação com o valor passado. Por exemplo: setSize(10, 20)não é tão legível quanto setSize(width=10, height=20)ou random(distribution='gaussian', mean=0.5, deviation=1). Em idiomas com parâmetros nomeados impostos, os booleanos podem transmitir exatamente a mesma quantidade de informações que o uso de enumerações / constantes nomeadas, para que possam ser boas nas APIs.
Bakuriu

Respostas:

27

Seu desejo de não dividi-lo em três chamadas de método é completamente compreensível, mas você tem outras opções além dos parâmetros booleanos.

Você pode usar enumerações:

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

Ou até mesmo uma enumeração de sinalizadores (se o seu idioma suportar):

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

Ou você pode usar um objeto de parâmetro :

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

O padrão Parameter Object fornece algumas outras vantagens que você pode achar úteis. Isso facilita a transmissão e a serialização de um conjunto de parâmetros, e você pode facilmente fornecer valores "padrão" de parâmetros não especificados.

Ou você pode apenas usar parâmetros booleanos. A Microsoft parece fazer isso o tempo todo com a API do .NET Framework, então você pode simplesmente dar de ombros e dizer "se é bom o suficiente para eles, é bom o suficiente para mim".

BenM
fonte
O padrão Parameter Object ainda tem um problema que o OP declarou: " Dividir isso em três chamadas de método pode levar um usuário da API a definir um aspecto da" visibilidade "esquecendo-se de definir outros ".
Lode
É verdade, mas acho que melhora a situação (se não for perfeita). Se o usuário for forçado a instanciar e transmitir um objeto VisibilityOptions, ele poderá (com alguma sorte) lembrá-lo de que existem outras propriedades no objeto VisibilityOptions que ele pode querer definir. Com a abordagem das três chamadas de método, tudo o que eles precisam para lembrá-las são os comentários sobre os métodos que estão chamando.
BenM
6

Obviamente, sempre há exceções à regra, mas como você explicou bem por si mesmo, há certas vantagens em ter uma API legível. Os argumentos booleanos são particularmente incômodos, pois 1) eles não revelam uma intenção e 2) implicam que você chama uma função, na qual você realmente deve ter dois, porque coisas diferentes vão acontecer dependendo do sinalizador booleano.

A principal questão é: quanto esforço você deseja investir para tornar sua API mais legível? Quanto mais voltado para o exterior, mais esforço pode ser facilmente justificado. Se é uma API que é usada apenas por outra unidade, não é grande coisa. Se você está falando sobre uma API REST na qual planeja deixar o mundo inteiro perder, também pode investir mais algum esforço para torná-la mais compreensível.

Como no seu exemplo, existe uma solução simples: aparentemente, no seu caso, a visibilidade não é apenas uma coisa verdadeira ou falsa. Em vez disso, você tem um conjunto de coisas que considera "visibilidade". Uma solução pode ser a introdução de algo como uma Visibilityclasse, que cubra todos esses tipos diferentes de visibilidade. Se você aplicar ainda o padrão Builder para criá-los, poderá acabar com um código como o seguinte:

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