Esta é a segunda parte final do tutorial em que escrevemos o cliente TodoMVC usando a estrutura 
ds js reativa minimalista.
Resumo da 
primeira parte : recebemos uma lista de tarefas do servidor no formato JSON, construímos uma lista HTML, adicionamos a capacidade de editar o nome e o sinal de conclusão de cada caso e implementamos uma notificação do servidor sobre essas edições.
Resta realizar: exclusão de casos arbitrários, adição de novos casos, instalação / redefinição em massa e filtragem de casos com base na conclusão e na função de excluir todos os casos concluídos. É isso que faremos. A versão final do cliente, a qual abordaremos neste artigo, pode ser vista 
aqui .
A opção que escolhemos da última vez pode ser atualizada 
aqui .
Aqui está o código dele:
'#todoapp'.d("" ,'#header'.d("" ,'H1'.d("") ,'INPUT#new-todo placeholder="What needs to be done?" autofocus'.d("") ) ,'#main'.d("" ,'#toggle-all type=checkbox'.d("") ,'UL#todo-list'.d("*@ todos:query" ,'LI'.d("$completed=.completed $editing= $patch=; a!" ,'INPUT.toggle type=checkbox' .d("#.checked=.completed") .ui("$patch=($completed=#.checked)") ,'LABEL.view' .d("? $editing:!; ! .title") .e("dblclick","$editing=`yes") ,'INPUT.edit' .d("? $editing; !! .title@value") .ui("$patch=(.title=#.value)") .e("blur","$editing=") ,'BUTTON.destroy'.d("") ) .a("!? $completed $editing") .u("? $patch; (@method`PATCH .url:dehttp headers $patch@):query $patch=") ) ) ,'#footer'.d("" ,'#todo-count'.d("") ,'UL#filters'.d("" ,'LI'.d("") ) ,'#clear-completed'.d("") ) ) .DICT({ todos : "//todo-backend-express.herokuapp.com/", headers: {"Content-type":"application/json"} }) .FUNC({ convert:{ dehttp: url=>url.replace(/^https?\:/,'') } }) .RENDER() 
Agora, existem apenas cinquenta linhas aqui, mas até o final do artigo, haverá duas vezes mais - até 100. Haverá muitas solicitações HTTP para o servidor; portanto, abra as ferramentas do desenvolvedor (no Chrome, como você se lembra, Ctrl + Shift + I) - haverá Primeiro, a guia Rede é interessante e, em segundo lugar, o console. Além disso, não se esqueça de visualizar o código de cada versão da nossa página - no Chrome, é Ctrl + U.
Aqui eu tenho que fazer uma pequena digressão. Se você não leu a 
primeira parte do tutorial, ainda recomendo começar com ele. Se você o leu, mas não entendeu nada, é melhor lê-lo novamente. Como mostram os comentários nos meus dois artigos anteriores, a sintaxe e o princípio da dap nem sempre são imediatamente entendidos por um leitor despreparado. Outro artigo não é recomendado para leitura para pessoas que se sentem desconfortáveis com a aparência de sintaxe não semelhante a si.

Esta segunda parte do tutorial será um pouco mais complicada e interessante que a primeira. 
[TODO: peça ao token para encontrar uma imagem de cérebro explodindo de um aluno na internet] .
Com sua permissão, continuarei a numerar os capítulos com a parte 1. Lá contamos até 7. Então,
8. Fazendo uma lista de tarefas da variável de estado
Para remover um caso da lista, existe um botão 
BUTTON.destroy . A remoção consiste em enviar uma solicitação DELETE para o servidor e remover de vista o elemento 
UL#todo-list > LI correspondente 
UL#todo-list > LI com todo o conteúdo. Com o envio de uma solicitação DELETE, tudo fica claro:
  ,'BUTTON.destroy'.ui("(@method`DELETE .url:dehttp):query") 
Mas com a remoção de um elemento da tela, são possíveis opções. Pode-se simplesmente introduzir outra variável de estado, digamos 
$deleted , e ocultar o elemento com CSS, incluindo a classe CSS 
deleted  ,'LI'.d("$completed=.completed $editing= $patch= $deleted=; a!"  
E funcionaria. Mas seria trapaça. Além disso, mais adiante, teremos filtros e contadores de casos ativos e concluídos (o que está em 
#footer ). Portanto, é melhor remover imediatamente o objeto da lista de tarefas com honestidade, "fisicamente". Ou seja, precisamos da capacidade de modificar a própria matriz, que recebemos inicialmente do servidor - o que significa que essa matriz também deve se tornar uma variável de estado. Vamos chamá-la de 
$todos .
O escopo da variável 
$todos é selecionar o ancestral comum de todos os elementos que acessarão essa variável. E será acessado por 
INPUT#new-todo partir de 
#header e contadores de 
#footer e, na verdade, 
UL#todo-list . O ancestral comum de todos eles é o elemento raiz do modelo, 
#todoapp . Portanto, em sua regra d, definiremos a variável 
$todos . No mesmo local, enviamos imediatamente os dados do servidor para ele. E para criar a lista 
UL#todo-list , agora também faremos dela:
 '#todoapp'.d("$todos=todos:query"  
É importante. Se durante o teste repentinamente a lista de tarefas a carregar não for possível - é bem possível que alguém tenha excluído todos eles (este é um servidor público e tudo pode acontecer lá).
Nesse caso, vá para um 
exemplo completo e crie alguns casos para experimentar.
Nós olhamos . Aqui 
$todos declarado na regra d do elemento 
#todoapp e é 
inicializado imediatamente 
com os dados necessários. Tudo parece funcionar, mas um recurso desagradável apareceu. Se o servidor responder à solicitação por um longo tempo (o Chrome permite simular essa situação: na guia Rede das ferramentas do desenvolvedor, você pode escolher diferentes modos de simular redes lentas), nossa nova versão do aplicativo até que a solicitação seja concluída parece um pouco triste - não há nada além de CSS artefatos. Essa imagem definitivamente não adicionará entusiasmo ao usuário. Embora a versão anterior não sofresse com isso - até os dados serem recebidos na página, apenas a lista estava ausente, mas outros elementos apareceram imediatamente, sem aguardar os dados.
Aqui está a coisa. Como você se lembra, o conversor 
:query é assíncrono. Essa assincronia é expressa no fato de que, até que a solicitação seja concluída, apenas a execução da regra atual é bloqueada, ou seja, a geração de um elemento que realmente precisa dos dados solicitados (o que é lógico). A geração de outros elementos não está bloqueada. Portanto, quando 
UL#todo-list acessou o servidor, apenas ele foi bloqueado, mas não o 
#header e o 
#footer , que foram desenhados imediatamente. Agora, todo o 
#todoapp está aguardando a conclusão da solicitação.
9. Carregamento atrasado de dados
Para corrigir a situação e evitar o bloqueio de elementos não envolvidos, adiaremos o carregamento inicial de dados até que tudo já esteja desenhado. Para fazer isso, não carregaremos imediatamente os dados na variável 
$todos , mas, primeiro, simplesmente os inicializamos com "nothing"
 '#todoapp'.d("$todos="  
Portanto, ela não bloqueará nada e todo o modelo funcionará - embora por enquanto com uma "lista de tarefas" vazia. Mas agora, com uma tela inicial chata, você pode 
modificar com segurança 
$todos fazendo o upload de uma lista de tarefas. Para fazer isso, adicione este descendente a 
#todoapp  ,'loader' .u("$todos=todos:query")  
Este elemento possui uma regra-u que se parece exatamente com a regra de bloqueio que rejeitamos, mas há uma diferença fundamental.
Deixe-me lembrá-lo de que a regra-d (de 
baixo para 
baixo ) é a regra de geração de elemento que é executada quando o modelo é criado de cima para 
baixo , de pai para descendente; e u-rules (de 
cima para 
baixo ) são regras de reação que são executadas em resposta a um evento que aparece de baixo para 
cima , de filho para pai.
Portanto, se algo (incluindo "nada") for atribuído 
a uma variável 
na regra d , isso significa sua 
declaração e inicialização no escopo deste elemento e seus descendentes (escopos aninhados são implementados no dap, como em JS ) A atribuição 
em up-rules significa uma 
modificação de uma variável declarada anteriormente no escopo. A declaração e a inicialização de variáveis na regra d permitem que o pai transmita aos descendentes pela hierarquia as informações necessárias para a construção, e a modificação permite que o pai transmita atualizações dessas informações para o topo e, assim, inicie a reestruturação correspondente de todos os elementos que dependem dela.
O elemento 
loader , descendente de 
#todoapp , em sua regra-u 
modifica a variável 
$todos , carregando dados do servidor para ele, o que causa a regeneração automática de todos os elementos de consumo dessa variável (e somente eles, o que é importante!). Os consumidores de uma variável são elementos cujas regras d contêm essa variável como um rvalor, ou seja, Quem 
lê essa variável (levando em consideração o escopo) ao construir.
Agora temos um consumidor da variável 
$todos - a própria 
UL#todo-list , que, consequentemente, será reconstruída após o carregamento dos dados.
  ,'UL#todo-list'.d("*@ $todos"  
Portanto, 
agora temos uma lista de tarefas a fazer é uma variável de estado em 
#todoapp , sem bloquear a renderização inicial do modelo.
10. Excluindo e adicionando tarefas
Agora podemos modificar 
$todos todas as formas. Vamos começar removendo os itens. Já temos um botão cruzado 
BUTTON.destroy , que até agora simplesmente envia as solicitações de remoção do servidor
  ,'BUTTON.destroy'.ui("(@method`DELETE .url:dehttp):query") 
É necessário garantir que o objeto correspondente também seja excluído da variável 
$todos - e como isso será uma modificação, 
UL#todo-list , como consumidor dessa variável, será reconstruída automaticamente, mas sem o elemento excluído.
Por si só, o dap não fornece nenhum meio especial para manipular dados. As manipulações podem ser perfeitamente escritas em funções em JS, e as regras dap simplesmente fornecem dados para elas e capturam o resultado. Nós escrevemos uma função JS para remover um objeto de uma matriz sem saber seu número. Por exemplo, isto:
 const remove = (arr,tgt)=> arr.filter( obj => obj!=tgt ); 
Você provavelmente pode escrever algo mais eficaz, mas não é sobre isso agora. É improvável que nosso aplicativo precise trabalhar com listas de tarefas de milhões de itens. O importante é que a função retorne um novo objeto de matriz e não apenas remova o elemento do que é.
Para tornar essa função acessível a partir das regras dap, você precisa adicioná-la à seção 
.FUNC , mas antes disso, decida como queremos chamá-la. A opção mais simples nesse caso é, talvez, chamá-lo do conversor, que aceita o objeto 
{ todos, tgt } e retorna uma matriz filtrada
 .FUNC({ convert:{ dehttp: url => url.replace(/^https?\:/,''),  
mas não há nada que o impeça de definir essa função dentro do 
.FUNC (eu já disse que 
.FUNC é na verdade um método JS comum e seu argumento é um objeto JS comum?)
 .FUNC({ convert:{ dehttp: url => url.replace(/^https?\:/,''), remove: x => x.todos.filter( todo => todo!=x.tgt ) } }) 
Agora podemos acessar este conversor a partir das regras dap
  ,'BUTTON.destroy' .ui("$todos=($todos $@tgt):remove (@method`DELETE .url:dehttp):query") 
Aqui, primeiro formamos um objeto que, na notação JS, corresponde a 
{ todos, tgt:$ } , passa-o para o conversor 
:remove descrito em 
.FUNC e retorna o resultado filtrado para 
$todos , modificando-o. Aqui 
$ é o 
contexto de dados do elemento, esse objeto de negócios da matriz 
$todos no qual o modelo é construído. Após o símbolo 
@ , o alias do argumento é indicado. Se 
@ ausente, o nome do argumento será usado. Isso é semelhante à recente inovação ES6 - 
abreviação de propriedades .
Da mesma forma, adicionamos um novo caso à lista usando o elemento 
INPUT#new-todo e a solicitação POST
  ,'INPUT#new-todo placeholder="What needs to be done?" autofocus' .ui("$=(#.value@title) (@method`POST todos@url headers $):query $todos=($todos $@tgt):insert #.value=") ... .FUNC({ convert:{ dehttp: url => url.replace(/^https?\:/,''), remove: x => x.todos.filter( todo => todo!=x.tgt ),  
A regra de reação do elemento 
INPUT#new-todo a um evento de interface do usuário padrão (para os elementos 
INPUT , o evento de 
change é considerado o dap padrão) inclui: ler a entrada do usuário a partir da propriedade 
value desse elemento, formando um contexto 
$ local com esse valor como um campo 
.title , enviando 
$ context para o servidor usando o método POST, modificando a matriz 
$todos adicionando o contexto 
$ como um novo elemento e finalmente limpando a propriedade 
value do elemento 
INPUT .
Aqui, um jovem leitor pode perguntar: por que usar 
concat() ao adicionar um elemento a uma matriz, se isso pode ser feito usando 
push() regular 
push() ? Um leitor experiente entenderá imediatamente qual é o problema e escreverá sua resposta nos comentários.
Nós olhamos o que 
aconteceu: os casos são adicionados e excluídos normalmente, as solicitações correspondentes são enviadas ao servidor corretamente (você mantém a guia Rede aberta o tempo todo, certo?). Mas e se quisermos mudar o nome ou o status de um caso recém-adicionado? O problema é que, para notificar o servidor sobre essas alterações, precisamos de 
.url , que atribui o servidor a esse negócio. Quando criamos o negócio, não conhecíamos seu 
.url , respectivamente, não podemos formar a solicitação PATCH correta para alteração.
De fato, todas as informações necessárias sobre o caso estão contidas na resposta do servidor à solicitação POST, e seria mais correto criar um novo objeto de negócios não apenas a partir da entrada do usuário, mas da resposta do servidor, e adicionar esse objeto a 
$todos - com todas as informações fornecidas. informações do servidor, incluindo o campo 
.url  ,'INPUT#new-todo placeholder="What needs to be done?" autofocus' .ui("$todos=($todos (@method`POST todos@url headers (#.value@title)):query@tgt ):insert #.value=") 
Olhamos - tudo bem, agora tudo está sendo resolvido corretamente. As notificações ao servidor sobre a edição de casos recém-criados dão certo.
Pode-se parar com isso, mas ... Mas se você olhar atentamente, ainda poderá notar um pequeno atraso entre inserir o nome do novo caso e o momento em que ele aparecer na lista. Esse atraso é claramente visível se você ativar uma rede lenta simulada. Como você deve ter adivinhado, a questão é uma solicitação ao servidor: primeiro, solicitamos dados para um novo caso do servidor e somente após recebê-los, modificamos 
$todos . O próximo passo, tentaremos corrigir essa situação, mas primeiro voltarei sua atenção para outro ponto interessante. Se voltarmos um pouco para a 
versão anterior , observamos: embora a solicitação também esteja lá, o novo caso é adicionado à lista instantaneamente, sem esperar que a solicitação termine.
   
Esse é outro recurso da elaboração de conversores assíncronos no dap: se o resultado do conversor assíncrono não for usado (ou seja, não estiver atribuído a nada), você não poderá esperar pela conclusão - e a execução da regra não será bloqueada. Isso geralmente é útil: você pode ter notado que, ao excluir casos da lista, eles desaparecem da tela instantaneamente, sem aguardar o resultado da solicitação DELETE. Isso é especialmente perceptível se você excluir rapidamente vários casos seguidos e rastrear solicitações no painel Rede.
Mas, como usamos o resultado da solicitação POST - atribua-a ao contexto 
$ - precisamos esperar que ela seja concluída. Portanto, você precisa encontrar outra maneira de modificar 
$todos antes de executar a solicitação POST. Solução: ainda, primeiro crie um novo objeto de negócios e adicione-o imediatamente a 
$todos , deixe a lista ser 
.url e somente depois, após a renderização, se o negócio não tiver 
.url (ou seja, o negócio acabou de ser criado), execute uma solicitação POST e impor seu resultado no contexto de dados deste caso.
Então, primeiro basta adicionar um espaço em branco contendo apenas 
.title à 
.title  ,'INPUT#new-todo placeholder="What needs to be done?" autofocus' .ui("$todos=($todos (#.value@title)@tgt):insert #.value=") 
UL#todo-list > LI A regra de geração de elemento 
UL#todo-list > LI já contém 
a! iniciando uma regra após o primeiro elemento ser desenhado. Lá, podemos adicionar o lançamento de uma solicitação POST na ausência de 
.url . Para injetar campos adicionais no contexto, dap possui o operador 
&  .a("!? $completed $editing; ? .url:!; & (@method`POST todos@url headers $):query") 
Nós olhamos . Outra coisa! Mesmo com uma rede lenta, a lista de tarefas é atualizada instantaneamente e a notificação do servidor e o carregamento de dados ausentes ocorrem em segundo plano, após o desenho da lista atualizada.
11. Daw todos!
No elemento 
#header , há um botão para instalação em massa / redefinição do sinal de conclusão para todos os casos na lista. Para a atribuição em massa de valores aos campos dos elementos da matriz, basta escrever outro conversor 
:assign , e aplicá-lo a 
$todos , clicando em 
INPUT#toggle-all  ,'INPUT#toggle-all type=checkbox' .ui("$todos=($todos (#.checked@completed)@src):assign") ... assign: x => x.todos && x.todos.map(todo => Object.assign(todo,x.src)) 
Nesse caso, estamos interessados apenas no campo 
.completed , mas é fácil ver que, com esse conversor, você pode alterar massivamente os valores de qualquer campo dos elementos da matriz.
Ok, na matriz 
$todos as 
$todos trocadas, agora precisamos notificar o servidor sobre as alterações feitas. No exemplo original, isso é feito enviando solicitações PATCH para cada caso - não uma estratégia muito eficaz, mas não depende mais de nós. Ok, para cada caso, enviamos uma solicitação PATCH
  .ui("*@ $todos=($todos (#.checked@completed)@src):assign; (@method`PATCH .url:dehttp headers (.completed)):query") 
Observamos : Um clique em um daw comum alinha todos os daws individuais e o servidor é notificado por solicitações de PATCH apropriadas. Norma
12. Filtrando casos com base na conclusão
Além da lista de tarefas pendentes, o aplicativo também deve poder filtrar casos pelo sinal de conclusão e mostrar contadores de tarefas concluídas e inacabadas. Obviamente, para filtrar, seremos triviais usar o mesmo método 
filter() fornecido pelo próprio JS.
Mas primeiro você precisa garantir que o campo 
.completed de cada caso seja sempre verdadeiro e, quando você clicar, o daw individual do caso será atualizado junto com a variável 
$completed . Anteriormente, isso não era importante para nós, mas agora será.
  ,'INPUT.toggle type=checkbox' .d("#.checked=.completed") .ui("$patch=(.completed=$completed=#.checked) $recount=()")  
O ponto importante aqui é que o contexto de dados de cada caso é o próprio objeto de caso, que fica na matriz 
$todos . Não é uma cópia única ou construção relacionada, mas o próprio objeto. E todas as chamadas para os campos 
.title , 
.completed , 
.completed url - leitura e gravação - se aplicam diretamente a esse objeto. Portanto, para filtrar a matriz 
$todos para funcionar corretamente, precisamos garantir que a conclusão do caso seja refletida não apenas na madrugada na tela, mas também no campo 
.completed do objeto de 
.completed .
Para mostrar apenas os casos com o sinal necessário de completude 
.completed na lista, filtraremos simplesmente 
$todos acordo com o filtro selecionado. O filtro selecionado é, você adivinhou, outra variável de estado de nosso aplicativo, e nós o chamaremos: 
$filter . Para filtrar 
$todos acordo com o 
$filter selecionado 
$filter vamos seguir a miniatura e apenas adicionar outro conversor, do formulário 
{list, filter} => a lista filtrada , e pegaremos os nomes e as funções de filtragem da "matriz associativa" (ou seja, JS comum objeto) 
todoFilters const todoFilters={ "All": null, "Active": todo => !todo.completed, "Completed": todo => !!todo.completed }; '#todoapp'.d("$todos= $filter="  
Nós verificamos . Os filtros funcionam corretamente. Há uma nuance em que os nomes dos filtros são exibidos juntos, porque aqui nos afastamos um pouco da estrutura DOM do original e saímos do CSS. Mas voltaremos a isso um pouco mais tarde.
13. Contadores de casos concluídos e ativos.
Para mostrar os contadores de casos concluídos e ativos, basta filtrar 
$todos com os filtros apropriados e mostrar os comprimentos das matrizes resultantes
  ,'#footer'.d("$active=($todos @filter`Active):filter $completed=($todos @filter`Completed):filter" ,'#todo-count'.d("! (active $active.length)format")  
Nesse 
formulário, os contadores mostram os valores corretos no momento da inicialização, mas não respondem a alterações subseqüentes na conclusão dos assuntos (ao clicar nas daws). O fato é que cliques em gralhas, alterando o estado de cada caso individual, não alteram o estado de 
$todos - uma modificação de um elemento da matriz não é uma modificação da própria matriz. Portanto, precisamos de um sinal adicional sobre a necessidade de registrar novamente os casos. Esse sinal pode ser uma variável de estado adicional, que é modificada toda vez que é necessária a recontagem. Chame de 
$recount .
Declararemos um ancestral comum na regra d, atualizaremos quando clicarmos no daws e #footertornaremos o elemento um consumidor - para isso, basta mencionar essa variável em sua regra d '#todoapp'.d("$todos= $filter= $recount="  
Agora tudo funciona como deveria, os contadores são atualizados corretamente.14. Exclusão de todos os casos concluídos.
A exclusão em lote de casos no TodoMVC é implementada como não kosher como modificação em lote - por várias solicitações. Bem, vamos dar um suspiro, encolher os ombros e executar por solicitação DELETE para cada caso concluído - e já temos todos eles $completed. Assim, $todosapós a remoção dos casos concluídos, o que já deveria estar em$active  ,'#clear-completed' .d("! (completed $completed.length)format") .ui("$todos=$active; *@ $completed; (@method`DELETE .url:dehttp):query") 
Olhamos : criamos alguns assuntos desnecessários, marcamos com daws e excluímos. A guia Rede mostrará o horror dessa abordagem para operações em lote.15. Status na barra de endereço
Voltar para a seleção de filtros. No exemplo original, o filtro selecionado é refletido na barra de endereço após #. Ao alterar o fragmento # na barra de endereços manualmente ou durante a navegação, o filtro selecionado também muda. Isso permite que você vá para a página do aplicativo por URL com o filtro de tarefas já selecionado.Você pode escrever com um location.hashoperador urlhash, por exemplo, na regra de um elemento #todoapp(ou qualquer de seus descendentes), que será executado a cada atualização$filter .a("urlhash $filter") 
E você pode inicializar a variável com um $filtervalor da barra de endereço e depois atualizar pelo evento hashchange usando um pseudo-conversor :urlhashque retorna o estado atual location.hash(sem #) .d("$todos= $filter=:urlhash $recount=" .e("hashchange","$filter=:urlhash") 
O evento hashchange é gerado pelo navegador quando um fragmento # na barra de endereço é alterado. No entanto, por alguma razão única window, e document.bodypode ouvir este evento. Para rastrear esse evento a partir de um elemento #todoapp, você precisará adicionar um operador à regra d listenque assina o elemento para retransmitir eventos do objetowindow '#todoapp' .a("urlhash $filter") .e("hashchange","$filter=:urlhash") .d("$todos= $filter=:urlhash $recount=; listen @hashchange" 
Observamos : alternar filtros, rastrear alterações na barra de endereço, seguir os links com #Active , #All , #Completed . Tudo funciona. Mas voltando ao original. Parece que a escolha do filtro é implementada - clicando nos links. Embora não seja muito prático, faremos o mesmo por uma questão de integridade.  ,'UL#filters'.d("* filter" ,'LI'.d("" ,'A'.d("!! (`# .filter)concat@href .filter@") ) ) 
E para destacar o filtro selecionado, adicione um operador de estilização condicional !?que adicionará uma classe CSS ao elemento selectedse o valor no campo de .filtercontexto for igual ao valor da variável$filter  ,'A'.d("!! (`# .filter)concat@href .filter@; !? (.filter $filter)eq@selected") 
Em deste modo a funcionalidade dos nossos Dap-aplicações já estão completamente (tanto quanto eu posso dizer) corresponde ao que faz o original .16. Alguns retoques finais
Não gosto muito disso no original, a forma do cursor não muda sobre os elementos ativos, por isso, adicionaremos headesse estilo ao nosso documento HTML  [ui=click]{cursor:pointer} 
Então, pelo menos, veremos onde você pode clicar.Ah sim! Resta escrever a palavra "todos" em letras maiúsculas. Mas aqui talvez eu me permita finalmente mostrar um pouco de imaginação e criatividade e, em vez de apenas "todos", escreverei "dap todos"  ,'H1'.d("","dap todos") 
Uau. Agora, nosso aplicativo pode ser considerado completo e o tutorial realizado (se você ler honestamente essas linhas).Em conclusão
Talvez, durante a leitura, você tenha tido a impressão de que o programa dap é escrito por tentativa e erro - tudo isso é "vamos ver o que aconteceu", "parece funcionar, mas há uma nuance", etc. Este não é realmente o caso.
Todas essas nuances são bastante óbvias e previsíveis ao escrever código. Mas pensei que seria útil mostrar pelo exemplo dessas nuances por que essa ou aquela decisão está presente nas regras e por que é feita dessa maneira e não de outra maneira.Faça, como eles dizem, perguntas.