Kotlin DSL – definindo parâmetros obrigatórios

Tempo de leitura: 3 minutes

Desde que o Kotlin foi introduzido, várias bibliotecas optaram pela implementação DSL de sua API.
Kotlin DSL é uma ótima ferramenta, torna sua API mais legível e fácil de usar, e se você ainda não começou a brincar com ela, eu recomendo fortemente que você experimente.

Mas este artigo não pretende explicar como implementar uma API baseada em Kotlin DSL, existem alguns artigos excelentes sobre isso por aí.
Nem vou falar sobre o quão incrível o Kotlin DSL é (e é).
Em vez disso, vou me concentrar nos problemas que enfrentei quando tentei aplicá-lo aos meus próprios casos de uso.

Primeiro

Um bom exemplo de DSL pode ser encontrado em uma biblioteca Android chamada Anko, que permite o layout de componentes de IU programaticamente (pode parecer familiar para desenvolvedores de Flutter/Jetpack):

verticalLayout {
   editText {
    hint = "Nome"
    textSize = 24f
  }  
   button("Diga olá") {
    onClick { toast(“Olá!”) }
  }
}

Parece muito fácil de entender, certo?

Então, onde está o truque?

Vamos olhar mais de perto:

button("Diga olá") {
    onClick { toast("Olá!") }
}

Como você pode ver, o texto do botão é fornecido como um parâmetro, pois é obrigatório para a criação de um botão, ao contrário do onClickListener que é opcional e, portanto, definido dentro do bloco lambda.

Para um único parâmetro, isso ainda parece bastante legível, mas e se houvesse vários parâmetros obrigatórios?

Depois de experimentar o DSL, o que descobri logo é que tinha o mesmo problema que os antigos Builders – receber parâmetros obrigatórios.

Qual é a solução então?

A solução óbvia é verificar se todos os parâmetros obrigatórios estão definidos como parte da construção do objeto final, mas isso é uma solução de tempo de execução e um sinal de API ruim em meus livros.

Outra solução que eu usaria em construtores era definir parâmetros obrigatórios no construtor.
O mesmo truque funciona com o DSL, mas descobri que ele contradiz um dos propósitos do DSL – ser legível.

A última opção que tentei foi usar contratos Kotlin, que rapidamente descartei por dois motivos:
1. Não achei que fosse maduro o suficiente.
2. Eles se aplicam apenas a funções de nível superior, não exatamente o que eu estava procurando.

Meu objetivo era claro – uma verificação em tempo de compilação dos parâmetros obrigatórios.
Eu queria uma solução simples para esse problema comum.

Apresentando DSLint

Eventualmente, decidi ser meu melhor amigo e implementar uma solução para esse problema usando uma biblioteca linter Android personalizada (desculpe, desenvolvedores não-Android).

Então é assim que funciona, digamos que eu fosse implementar um construtor Person usando DSL e queria ter certeza de que o nome da pessoa foi definido.

Tudo o que preciso fazer é anotar a classe e a propriedade name e deixar meu linter personalizado fazer a mágica:

@DSLint
class Person {

    @set:DSLMandatory
    var name: String
}

Limpo e simples.

Eu o tornei open source, então você pode usá-lo em seu projeto ou clonar o repo e criar sua solução personalizada com base nele.

Você pode dar uma olhada aqui:

ironSource/dslinit (Link)

Resumindo

Como eu disse anteriormente, DSL é uma ótima ferramenta e encorajo você a experimentá-la, mas também esteja ciente de seus limites.

Espero que você tenha gostado de ler e estou ansioso para ouvir sobre os desafios que enfrentou ao experimentar o DSL e como você os abordou.