Kotlin DSL: Teoria e Prática

Desenvolver testes de aplicativos não é uma experiência agradável. Esse processo leva muito tempo, requer muita concentração e é extremamente procurado. A linguagem Kotlin fornece um conjunto de ferramentas que facilita bastante a criação de sua própria linguagem orientada a problemas (DSL). Existe experiência quando o Kotlin DSL substituiu os construtores e os métodos estáticos para testar o módulo de planejamento de recursos, o que tornou a adição de novos testes e o suporte aos antigos de uma rotina um processo divertido.

No decorrer do artigo, analisaremos todas as principais ferramentas do arsenal do desenvolvedor e como elas podem ser combinadas para resolver problemas de teste. Iremos desde o design do Teste Ideal até o lançamento do teste mais aproximado, limpo e compreensível para o sistema de planejamento de recursos baseado em Kotlin.

O artigo será útil para a prática de engenheiros, aqueles que consideram o Kotlin como uma linguagem para escrever testes compactos confortavelmente e aqueles que desejam melhorar o processo de teste em seu projeto.



Este artigo é baseado em uma apresentação de Ivan Osipov ( i_osipov ) na conferência JPoint. Mais narração é conduzida em seu nome. Ivan trabalha como programador em Haulmont. O principal produto da empresa é o CUBA, uma plataforma para o desenvolvimento de empresas e várias aplicações web. Em particular, projetos de terceirização estão sendo feitos nessa plataforma, entre os quais houve recentemente um projeto no campo da educação, no qual Ivan estava envolvido na construção de um cronograma para uma instituição de ensino. Aconteceu que, nos últimos três anos, Ivan trabalha com planejadores de uma maneira ou de outra, e especificamente em Haulmont, eles vêm testando esse planejador há um ano.

Para quem deseja executar exemplos - mantenha um link para o GitHub . Sob o link, você encontrará todo o código que iremos analisar, executar e escrever hoje. Abra o código e pronto!



Hoje vamos discutir:

  • o que são linguagens orientadas a problemas;
  • linguagens orientadas a problemas incorporadas;
  • construção de um cronograma para uma instituição educacional;
  • como tudo é testado com o Kotlin.

Hoje vou falar em detalhes sobre as ferramentas que temos no idioma, mostrar algumas demos e escreveremos todo o teste do começo ao fim. Ao mesmo tempo, gostaria de ser mais objetivo, por isso vou falar sobre algumas das desvantagens que identifiquei para mim durante o desenvolvimento.

Vamos começar falando sobre o módulo de criação de cronograma. Assim, a construção do cronograma ocorre em várias etapas. Cada uma dessas etapas precisa ser testada separadamente. Você precisa entender que, apesar de as etapas serem diferentes, temos um modelo de dados comum.



Esse processo pode ser representado da seguinte forma: na entrada, existem alguns dados com um modelo comum, na saída, há um cronograma. Os dados são validados, filtrados e, em seguida, são criados grupos de treinamento. Refere-se à área de assunto da programação da instituição educacional. Com base nos grupos construídos e com base em outros dados, colocamos a lição. Hoje falaremos apenas sobre a última etapa - sobre a colocação de aulas.



Um pouco sobre como testar o agendador.

Em primeiro lugar, como você já entendeu, os diferentes estágios devem ser testados separadamente. Pode-se destacar um processo mais ou menos padrão de iniciar o teste: há inicialização de dados, há um lançamento do agendador, há uma verificação dos resultados desse próprio agendador. Há um grande número de casos de negócios diferentes que precisam ser cobertos e situações diferentes que precisam ser levadas em consideração para que, ao criar um cronograma, essas situações também persistam.

Às vezes, um modelo pode ser pesado e, para criar uma única entidade, é necessário inicializar cinco entidades adicionais, ou até mais. Assim, no total, é obtida uma grande quantidade de código, que escrevemos repetidamente para cada teste. O suporte para esses testes leva uma quantidade considerável de tempo. Se você deseja atualizar o modelo, e isso às vezes acontece, a escala das alterações afeta os testes.

Vamos escrever um teste:



Vamos escrever o teste mais simples para que você geralmente entenda a imagem.
O que vem à mente quando você pensa em testar? Talvez estes sejam alguns testes primitivos desse tipo: você cria uma classe, cria um método nela, marca-a com a anotação Teste . Como resultado, usamos os recursos do JUnit e inicializamos alguns dados, valores padrão e valores específicos de teste, fazemos o mesmo no restante do modelo e, finalmente, criamos um objeto do planejador, transferimos nossos dados para ele, começamos, recebemos resultados e os verificamos. Processo mais ou menos padrão. Mas obviamente há duplicação de código. A primeira coisa que vem à mente é a capacidade de colocar tudo em métodos estáticos. Como existem vários valores padrão, por que não ocultá-lo?



Este é um bom primeiro passo para reduzir a duplicação.



Olhando para isso, você entende que eu gostaria de manter o modelo mais compacto. Aqui temos um padrão de construtor no qual, em algum lugar sob o capô, o valor padrão é inicializado e os valores específicos do teste são inicializados ali. Está ficando melhor, no entanto, ainda estamos escrevendo o código padrão, e estamos escrevendo novamente cada vez. Imagine 200 testes - você deve escrever essas três linhas 200 vezes. Obviamente, eu gostaria de me livrar disso de alguma forma. Desenvolvendo a idéia, chegamos a um certo limite. Assim, por exemplo, podemos criar um construtor de padrões em geral para tudo.



Você pode criar um agendador do zero até o final, definir todos os valores que precisamos, iniciar o agendamento e tudo está ótimo. Se você olhar em detalhes este exemplo e analisá-lo em detalhes, acontece que muito código desnecessário está sendo gravado. Gostaria de tornar os testes mais legíveis para que você possa dar uma olhada e entender imediatamente, sem se aprofundar nos padrões e assim por diante.

Portanto, temos algum código desnecessário. A matemática simples sugere que existem 55% mais letras do que precisamos, e eu gostaria de, de alguma forma, fugir delas.



Depois de algum tempo, o suporte para nossos testes acaba sendo mais caro, porque você precisa oferecer suporte a mais código. Às vezes, se não fizermos nenhum esforço, a legibilidade deixa muito a desejar ou acaba sendo aceitável, mas gostaríamos ainda melhor. Talvez mais tarde começaremos a adicionar algum tipo de estruturas, bibliotecas, para facilitar a gravação de testes. Devido a isso, aumentamos o nível de entrada para testar nosso aplicativo. Aqui temos uma aplicação já complicada, o nível de entrada nos testes é significativo e estamos aumentando ainda mais.

Teste perfeito


É ótimo dizer o quão ruim tudo é, mas vamos pensar em como seria muito bom. Um exemplo ideal que gostaríamos de obter como resultado:



Imagine que exista alguma declaração na qual dizemos que este é um teste com um nome específico, e queremos usar um espaço para separar as palavras no nome, não o CamelCase. Estamos construindo um cronograma, temos alguns dados e os resultados do planejador são verificados. Como trabalhamos principalmente com Java e todo o código do aplicativo principal está escrito nesta linguagem, eu gostaria de ter recursos de teste compatíveis. Eu gostaria de inicializar os dados o mais óbvio possível para o leitor. Quero inicializar alguns dados comuns e parte do modelo que precisamos. Por exemplo, crie alunos, professores e descreva quando eles estiverem disponíveis. Este é o nosso exemplo perfeito.

Idioma específico do domínio




Olhando para tudo isso, parece que parece algum tipo de linguagem orientada a problemas. Você precisa entender o que é e qual é a diferença. Os idiomas podem ser divididos em dois tipos: idiomas de uso geral (o que constantemente escrevemos, resolvemos absolutamente todas as tarefas e lidamos com absolutamente tudo) e idiomas orientados a problemas. Assim, por exemplo, o SQL nos ajuda a extrair dados do banco de dados perfeitamente, e algumas outras linguagens também ajudam a resolver outros problemas específicos.



Uma maneira de implementar linguagens orientadas a problemas é a linguagem incorporada ou interna. Essas linguagens são implementadas com base em uma linguagem de uso geral. Ou seja, várias construções de nossa linguagem de propósito geral formam uma base - é isso que usamos ao trabalhar com uma linguagem orientada a problemas. Nesse caso, é claro, surge uma oportunidade em uma linguagem orientada a problemas para usar todos os recursos e funcionalidades provenientes de uma linguagem de uso geral.



Mais uma vez, dê uma olhada no nosso exemplo perfeito e pense em qual idioma escolher. Temos três opções.



A primeira opção é Groovy. Uma linguagem maravilhosa e dinâmica que se provou na construção de linguagens orientadas a problemas. Novamente, você pode dar um exemplo de um arquivo de compilação no Gradle, que muitos de nós usamos. Há também o Scala, que tem um grande número de oportunidades para a implementação de algo próprio. E, finalmente, existe o Kotlin, que também nos ajuda a criar uma linguagem orientada a problemas, e hoje ela será discutida. Eu não gostaria de criar guerras e comparar o Kotlin com outra coisa, mas permanece na sua consciência. Hoje vou mostrar o que o Kotlin tem para o desenvolvimento de linguagens orientadas a problemas. Quando você quiser comparar isso e dizer que um idioma é melhor, retorne a este artigo e veja facilmente a diferença.



O que Kotlin nos dá para o desenvolvimento de uma linguagem orientada a problemas?

Em primeiro lugar, é uma digitação estática e tudo o que se segue. No estágio de compilação, um grande número de problemas é detectado, e isso economiza bastante, principalmente no caso em que você não deseja obter problemas relacionados à sintaxe e à gravação em testes.
Então, existe um ótimo sistema de inferência de tipos que vem do Kotlin. Isso é maravilhoso, porque não há necessidade de escrever nenhum tipo repetidamente, tudo é exibido pelo compilador com um estrondo.

Em terceiro lugar, há um excelente suporte para o ambiente de desenvolvimento, e isso não é surpreendente, porque a mesma empresa cria o principal ambiente de desenvolvimento para hoje e faz Kotlin.
Finalmente, dentro da DSL, obviamente podemos usar o Kotlin. Na minha opinião subjetiva, oferecer suporte a DSL é muito mais fácil do que oferecer suporte a classes de utilitário. Como você verá mais adiante, a legibilidade é um pouco melhor do que os construtores. O que quero dizer com "melhor": você tem um pouco menos de sintaxe do que precisa escrever - alguém que lê sua linguagem orientada a problemas o leva mais rápido. Finalmente, escrever sua bicicleta é muito mais divertido! Mas, de fato, implementar uma linguagem orientada a problemas é muito mais fácil do que aprender alguma nova estrutura.

Lembrarei mais uma vez o link para o GitHub , se você quiser escrever mais demos, poderá entrar e pegar o código no link.

Projetando o ideal no Kotlin


Vamos continuar a projetar nosso ideal, mas já em Kotlin. Veja o nosso exemplo:



E, em etapas, começaremos a reconstruí-lo.

Temos um teste que se transforma em uma função no Kotlin, que pode ser nomeada usando espaços.



Marcá-lo-emos com a anotação Teste , que está disponível para nós na JUnit. No Kotlin, você pode usar o formulário abreviado para escrever funções e, através de =, se livrar de chaves adicionais para a própria função.

Horário nos transformamos em um bloco. O mesmo acontece com muitos designs, pois ainda trabalhamos na Kotlin.



Vamos para o resto. Aparelhos cacheados aparecem novamente, não vamos nos livrar deles, mas pelo menos tentamos nos aproximar do nosso exemplo. Ao construir construções com espaços, podemos de alguma forma nos refinar e torná-los de alguma forma diferentes, mas parece-me que é melhor fazer os métodos usuais que encapsularão o processamento, mas em geral isso será óbvio para o usuário .



Nosso aluno se transforma em um bloco no qual trabalhamos com propriedades, com métodos, e continuaremos analisando isso com você.



Finalmente, os professores. Aqui temos alguns blocos aninhados.



No código abaixo, passamos para as verificações. Precisamos de verificações de compatibilidade com linguagens Java - e sim, o Kotlin é compatível com Java.



Arsenal de desenvolvimento DSL em Kotlin




Vamos passar para a lista de ferramentas que temos. Aqui eu trouxe um tablet, talvez ele liste tudo o que é necessário para desenvolver linguagens orientadas a problemas no Kotlin. Você pode voltar para ela de vez em quando e refrescar sua memória.

A tabela mostra algumas comparações da sintaxe orientada a problemas e da sintaxe usual disponível no idioma.

Lambdas em Kotlin


val lambda: () -> Unit = { }

Vamos começar com os tijolos mais básicos que temos em Kotlin - estes são lambdas.
Hoje, por tipo lambda, vou me referir apenas a um tipo funcional. Lambdas são indicadas da seguinte forma: ( ) -> .

Inicializamos o lambda com a ajuda de chaves, dentro delas podemos escrever algum código que será chamado. Ou seja, um lambda, de fato, apenas oculta esse código em si. A execução de um lambda parece uma chamada de função, apenas parênteses.



Se queremos passar algum tipo de parâmetro, primeiro devemos descrevê-lo no tipo
Em segundo lugar, temos acesso ao identificador padrão, que podemos usar, no entanto, se isso não nos convém, podemos definir nosso próprio nome de parâmetro e usá-los.



Ao mesmo tempo, podemos pular o uso desse parâmetro e usar o sublinhado para não produzir identificadores. Nesse caso, para ignorar o identificador, seria possível escrever nada, mas no caso geral de vários parâmetros, existe o "_" mencionado.



Se queremos passar mais de um parâmetro, precisamos definir explicitamente seus identificadores.



Finalmente, o que acontecerá se tentarmos passar o lambda para alguma função e executá-lo lá. Ele parece na aproximação inicial da seguinte forma: temos uma função para a qual passamos lambda entre colchetes e, se no Kotlin lambda é escrito como o último parâmetro, podemos colocá-lo fora desses colchetes.



Se não houver mais nada entre parênteses, podemos removê-los. Aqueles familiarizados com o Groovy devem estar familiarizados com isso.



Onde isso se aplica? Absolutamente em todo lugar. Ou seja, os aparelhos muito encaracolados de que já falamos, nós os usamos, essas são as mesmas lambdas.



Agora, vamos olhar para uma das variedades de lambdas, eu as chamo de lambdas com contexto. Você encontrará outros nomes, por exemplo, lambda com receptor, e eles diferem dos lambdas comuns ao declarar um tipo da seguinte maneira: à esquerda, adicionamos alguma classe de contexto, pode ser qualquer classe.



Para que é isso? Isso é necessário para que, dentro do lambda, tenhamos acesso à palavra-chave this - esta é a palavra-chave em si, ela nos diz o nosso contexto, ou seja, para algum objeto que vinculamos ao nosso lambda. Assim, por exemplo, podemos criar um lambda que produzirá alguma string, naturalmente, usaremos a classe string para declarar um contexto e a chamada de um lambda parecerá com isso:







Se você deseja passar um contexto como parâmetro, também pode fazê-lo. No entanto, não podemos transmitir completamente o contexto, ou seja, um lambda com um contexto requer atenção! - contexto sim. O que acontece se começarmos a passar um lambda com um contexto para algum método? Aqui, examinamos novamente nosso método exec:



Renomeie-o para o método do aluno - nada mudou:



Então, passamos gradualmente para a nossa construção, a construção do aluno, que, sob o aparelho, oculta toda a inicialização.



Vamos descobrir. Temos algum tipo de função de aluno que usa uma lambda no contexto do aluno.



Obviamente, precisamos de contexto.



Aqui, criamos um objeto e executamos esse lambda nele.



Como resultado, também podemos inicializar alguns valores padrão antes de iniciar o lambda, portanto, encapsulamos tudo o que precisamos para a função.



Por esse motivo, dentro da lambda, temos acesso a essa palavra-chave - é por isso que, provavelmente, existem lambdas com contexto.



Naturalmente, podemos nos livrar dessa palavra-chave e temos a oportunidade de escrever essas construções.



Novamente, se não temos apenas métodos proprietários, mas também alguns métodos, também podemos chamá-los, parece bastante natural.



Aplicação


Todos esses lambdas no código são lambdas de contexto. Há um grande número de contextos, eles se cruzam de uma maneira ou de outra e nos permitem construir nossa linguagem orientada a problemas.



Resumindo as lambdas - temos lambdas comuns, existem com o contexto e essas e outras podem ser usadas.



Operadores


O Kotlin possui um conjunto limitado de operadores que você pode substituir usando convenções e a palavra-chave operator.

Vejamos o professor e sua acessibilidade. Suponha que digamos que o professor trabalha às segundas-feiras das 8h à 1 hora. Também queremos dizer que, além desta hora, funciona a partir das 13h00 por 1 hora. Eu gostaria de expressar isso usando o operador + . Como isso pode ser feito?



Há algum método de disponibilidade que aceita uma lambda com um contexto AvailabilityTable . Isso significa que há alguma classe chamada assim, e o método de segunda-feira é declarado nessa classe. Este método retorna DayPointer desde você precisa anexar nosso operador a alguma coisa.



Vamos descobrir o que é DayPointer. Isso indica a tabela de disponibilidade de algum professor e o dia está na agenda dele. Também temos uma função de tempo que, de alguma forma, transformará algumas linhas em índices inteiros: no Kotlin, temos uma classe IntRange para isso.

À esquerda está o DayPointer , à direita está a hora e gostaríamos de combiná-los com o operador + . Para fazer isso, você pode criar nosso operador na classe DayPointer . Será necessário um intervalo de valores do tipo Int e retornará DayPointer para que possamos encadear nossa DSL repetidamente.
Agora, vamos dar uma olhada no design principal com o qual tudo começa, com o qual nossa DSL começa. Sua implementação é um pouco diferente, e agora vamos descobrir.
Kotlin tem um conceito singleton embutido na linguagem. Para fazer isso, em vez da palavra-chave da classe, a palavra-chave do object é usada. Se criarmos um método dentro de um singleton, poderemos acessá-lo de tal maneira que não seja necessário criar uma instância dessa classe novamente. Simplesmente nos referimos a ele como um método estático em uma classe.



Se você observar o resultado da descompilação (ou seja, no ambiente de desenvolvimento, clique em Ferramentas -> Kotlin -> Mostrar Bytecode do Kotlin -> Descompilar), poderá ver a seguinte implementação de singleton:



Esta é apenas uma aula comum, e nada de sobrenatural acontece aqui.
Outra ferramenta interessante é a declaração de invoke . Imagine que temos alguma classe A, temos sua instância e gostaríamos de executá-la, ou seja, chamar parênteses em um objeto dessa classe, e podemos fazer isso graças ao operador de chamada.



De fato, parênteses nos permitem chamar o método invoke e possui um modificador de operador. Se passarmos um lambda com contexto para esse operador, obteremos essa construção.



Criar instâncias a cada vez é outra atividade, para que possamos combinar conhecimentos anteriores e atuais.

Vamos fazer um singleton, chamá-lo de agendamento, dentro dele declararemos o operador de chamada, dentro criaremos um contexto e ele aceitará uma lambda com o contexto que criamos aqui. Acontece um único ponto de entrada em nossa DSL e, como resultado, obtemos o mesmo cronograma de construção com chaves.



Bem, falamos sobre programação, vamos dar uma olhada em nossos cheques.
Temos professores, construímos algum tipo de cronograma e queremos verificar se no cronograma desse professor em um determinado dia de uma determinada lição há algum objeto com o qual trabalharemos.



Gostaria de usar colchetes e acessar nossa programação de uma maneira que pareça visualmente o acesso a matrizes.



Isso pode ser feito usando o operador: get / set:



Aqui não estamos fazendo nada de novo, basta seguir as convenções. No caso do operador set, precisamos passar adicionalmente os valores para o nosso método:



Assim, os colchetes para leitura se transformam em get e os colchetes através dos quais atribuímos se transformam em set.

Demo: objeto, operadores


Você pode ler mais texto ou assistir ao vídeo aqui . O vídeo tem um horário de início claro, mas nenhum horário de término é especificado - em princípio, uma vez iniciado, você pode assisti-lo antes do final do artigo.

Por conveniência, descreverei brevemente a essência do vídeo diretamente no texto.

Vamos escrever um teste. Temos algum objeto de agendamento e, se formos implementados por meio de ctrl + b, veremos tudo o que eu falei antes.



Dentro do objeto de agendamento, queremos inicializar os dados, realizar algumas verificações e, dentro dos dados, gostaríamos de dizer que:

  • nossa escola está aberta das 8 da manhã;
  • existe um certo conjunto de itens para os quais criaremos um cronograma;
  • alguns professores descreveram algum tipo de acessibilidade;
  • ter um aluno;
  • em princípio, para um estudante, precisamos apenas dizer que ele está estudando um assunto específico.



E aqui uma das desvantagens do Kotlin e das linguagens orientadas a problemas se manifesta em princípio: é bastante difícil abordar alguns objetos que criamos anteriormente. Nesta demonstração, indicarei tudo como índices, ou seja, rus é o índice 0, matemática é o índice 2. E o professor naturalmente também lidera alguma coisa. Ele não apenas vai trabalhar, mas está envolvido em alguma coisa. Para os leitores deste artigo, gostaria de oferecer mais uma opção de endereçamento, você pode criar tags exclusivas e armazenar entidades nelas no Map e, quando precisar acessar uma delas, sempre poderá encontrá-las por tag. Continue a desmontar o DSL.

Aqui, o que deve ser observado: em primeiro lugar, temos o operador +, para cuja implementação também podemos ir e ver que realmente temos a classe DayPointer, que nos ajuda a vincular tudo isso com a ajuda do operador.

E, graças ao fato de termos acesso ao contexto, o ambiente de desenvolvimento nos diz que, em nosso contexto, por meio dessa palavra-chave, temos acesso a alguma coleção e a usaremos.



Ou seja, temos uma coleção de eventos. O evento encapsula um conjunto de propriedades, por exemplo: que há um aluno, um professor, em que dia eles se encontram e em que lição.



Continuamos a escrever o teste ainda mais.



Aqui, novamente, usamos o operador get; não é tão fácil chegar à sua implementação, mas podemos fazê-lo.



De fato, apenas seguimos o contrato, para ter acesso a esse design.
Vamos voltar à apresentação e continuar a conversa sobre Kotlin. Queríamos que as verificações fossem implementadas no Kotlin e passamos por esses eventos:



Um evento é essencialmente um conjunto encapsulado de 4 propriedades. Gostaria de decompor esse evento em um conjunto de propriedades, como uma tupla. Em russo, essa construção é chamada de multi-declaração (eu encontrei apenas essa tradução) ou declaração de desestruturação e funciona da seguinte forma:



Se um de vocês não estiver familiarizado com esse recurso, ele funcionará da seguinte maneira: você pode pegar o evento e, no local em que ele é usado, usando parênteses, decompõe-o em um conjunto de propriedades.



Isso funciona porque temos um método componentN, ou seja, é um método gerado pelo compilador graças ao modificador de dados que escrevemos antes da classe.



Junto com isso, um grande número de outros métodos voa para nós. Estamos interessados ​​no método componentN, que é gerado com base nas propriedades listadas na lista de parâmetros do construtor primário.



Se não tivéssemos um modificador de dados, seria necessário escrever manualmente um operador que fará a mesma coisa.





Portanto, temos alguns métodos componentN, e eles se decompõem em uma chamada:



Em essência, é o açúcar sintático sobre o chamado de vários métodos.

Já falamos sobre alguma tabela de disponibilidade e, de fato, eu o enganei. Isso acontece Não existe avaiabilityTable , não existe na natureza, mas existe uma matriz de valores booleanos.



Nenhuma classe adicional é necessária: você pode pegar a matriz de valores booleanos e renomeá-la para obter mais obviedade. Isso pode ser feito usando as chamadas tipealias ou alias de tipo . Infelizmente, não recebemos bônus adicionais por isso, é apenas uma renomeação. Se você pegar a disponibilidade e renomeá-la novamente para a matriz de valores booleanos, nada será alterado. O código funcionou e funcionará.

Vamos dar uma olhada no professor, essa é exatamente essa acessibilidade e falar sobre ele:



Temos um professor e o método de disponibilidade é chamado (você ainda não perdeu o fio do raciocínio? :-). De onde ele veio? Ou seja, um professor é algum tipo de entidade que tem uma turma, e esse é um código comercial. E não pode haver método adicional.



Este método aparece devido a funções de extensão. Tomamos e fixamos em nossa classe alguma outra função que podemos executar em objetos dessa classe.
Se passarmos algum lambda para essa função e, em seguida, executá-lo em uma propriedade existente, tudo estará bem - o método de disponibilidade em sua implementação inicializa a propriedade de disponibilidade. Você pode se livrar disso. Já sabemos sobre o operador de chamada, que pode ser anexado a um tipo e, ao mesmo tempo, ser uma função de extensão. Se você passar um lambda para esse operador, então, na palavra-chave this, podemos executar esse lambda. Como resultado, quando trabalhamos com um professor, a acessibilidade é uma propriedade do professor, e não um método adicional, e nenhum rassynchron acontece aqui.



Como bônus, funções de extensão podem ser criadas para tipos anuláveis. Isso é bom, porque se houver uma variável com um tipo anulável contendo um valor nulo, nossa função já estará pronta para isso e não cairá do NullPointer. Dentro desta função, isso pode ser nulo e isso precisa ser tratado.



Resumindo as funções de extensão: você precisa entender que há acesso apenas à API pública da classe, e a própria classe não é modificada de forma alguma. Uma função de extensão é determinada pelo tipo da variável e não pelo tipo real. Além disso, um membro da classe com a mesma assinatura será priorizado. Você pode criar uma função de extensão para uma classe, mas escrevê-la em uma classe completamente diferente e, dentro dessa função de extensão, haverá acesso a dois contextos simultaneamente. Acontece a interseção de contextos. E, finalmente, esta é uma ótima oportunidade para levar e prender os operadores em geral a qualquer lugar onde desejamos.



A próxima ferramenta são funções infix. Outro martelo perigoso nas mãos do desenvolvedor. Por que perigoso? O que você vê é código. Esse código pode ser escrito em Kotlin, e não faça isso! Por favor, não faça isso. No entanto, a abordagem é boa. Graças a isso, é possível livrar-se de pontos, colchetes - de toda a sintaxe barulhenta da qual estamos tentando chegar o mais longe possível e tornar nosso código um pouco mais limpo.



Como isso funciona? Vamos dar um exemplo mais simples - uma variável inteira. Vamos criar uma função de extensão para ela, vamos chamá-la shouldBeEqual, ela fará algo, mas isso não é interessante. Se adicionarmos o modificador infix à esquerda, isso é suficiente. Você pode se livrar de pontos e colchetes, mas há algumas nuances.



Com base nisso, apenas a construção de dados e asserções é implementada, unida.


Vamos descobrir. Temos um SchedulingContext - o contexto geral da inicialização do agendamento. Existe uma função de dados que retorna o resultado desse planejamento. Ao mesmo tempo, criamos uma função de extensão e, ao mesmo tempo, as asserções da função infix, que iniciarão um lambda que verifica nossos valores.



Há um sujeito, objeto e ação, e você precisa conectá-los de alguma forma. Nesse caso, o resultado da execução de dados com chaves é o assunto. A lambda que passamos para o método assertions é um objeto, e o próprio método assertions é uma ação. Tudo isso parece ficar junto.



Falando no infix da função, é importante entender que este é um passo para se livrar da sintaxe barulhenta. No entanto, precisamos ter um assunto e um objeto dessa ação e precisamos usar o modificador de infixo. Pode haver exatamente um parâmetro - ou seja, zero parâmetros não podem ser, dois não podem ser, três - bem, você entende. Você pode passar, por exemplo, lambdas para essa função e, dessa maneira, construções que você nunca viu antes são obtidas.

Vamos para a próxima demonstração. É melhor assistir ao vídeo e não ler o texto.



Agora tudo parece pronto: o infixo da função que você viu, a extensão da função que você viu, a declaração de desestruturação está pronta.

Vamos voltar à nossa apresentação, e aqui passaremos para um ponto bastante importante ao criar linguagens orientadas a problemas - o que você deve pensar é no controle de contexto.



Há situações em que podemos usar o DSL e reutilizá-lo dentro dele, mas não queremos fazer isso. Nosso usuário (possivelmente um usuário inexperiente) grava dados dentro de dados, e isso não faz sentido. De alguma forma, gostaríamos de proibi-lo de fazer isso.

Antes da versão 1.1 do Kotlin, tínhamos que fazer o seguinte: em resposta ao fato de termos um método de dados no SchedulingContext , tivemos que criar outro método de dados no DataContext , no qual aceitamos uma lambda (embora sem implementação), devemos marcar esse método anotação @Deprecated e diga ao compilador para não compilar isso. Você vê que esse método é iniciado - não compile. Usando essa abordagem, até recebemos alguma mensagem significativa quando escrevemos código sem sentido.



Após a versão Kotlin 1.1, uma maravilhosa anotação @DslMarker . Esta anotação é necessária para sinalizar anotações derivadas. Com eles, por sua vez, marcaremos as linguagens orientadas a problemas. Para cada linguagem orientada a problemas, você pode criar uma anotação que marque @DslMarker e pendure-a em cada contexto necessário. Não é mais necessário escrever métodos adicionais que devem ser proibidos de compilar - tudo funciona. Não compilado.

No entanto, existe um caso especial quando trabalhamos com nosso modelo de negócios. Geralmente é escrito em Java. , , . , ? Student . – -, Kotlin .





- , : . , , .



.

  1. , . StudentContext. , . – , , , .
  2. – , , . . StudentContext , IStudent . , Student, IStudent StudentContext. DslMarker , .
  3. : deprecated . , . , . extension-, . .



, , , .



. . , , , . , . @DslMarker, . , @DslMarker, @Deprecated, , .

, :






Primeiro, reutilize as peças DSL. Hoje você já viu que o endereçamento de entidades criadas usando DSL pode ser problemático. Existem maneiras de contornar isso, mas é aconselhável pensar com antecedência, a fim de ter um plano para isso.

, - , , , , - , . ? for — . DSL, , , DSL. this it. , Kotlin 1.2.20 , . , it.

. DSL, --, , . , . , , , - , , . . , - , ..

, . , - – DSL. , Kotlin-, . , DSL , , Kotlin- . -? Gradle-, , , , - . - , , – , DSL.



DSL' , . , . , DSL , , . - – . -, - . , - .
, Kotlin. , , , , , , . (, - , ), , DSL , , , . .

«», . , Kotlin . , , . , — , .
, DSL. , - . DSL, , 10 , , - . DSL – , , .

, . , Telegram: @ivan_osipov , Twitter: @_osipov_ , : i_osipov . .

Minuto de publicidade. JPoint — , 19-20 - Joker 2018 — Java-. . , .

Source: https://habr.com/ru/post/pt416725/


All Articles