Back home

Swift Concurrency Series 08|Organização de código assíncrono em projetos reais

O que é realmente difícil é saber se todo o link assíncrono pode permanecer claro por muito tempo.

Quando há apenas uma ou duas solicitações assíncronas no projeto, grande parte do código não parece tão ruim. Uma verdadeira bacia hidrográfica geralmente ocorre quando ocorrem simultaneamente as seguintes situações:

  • A mesma página tem primeiro carregamento, atualização suspensa, nova tentativa e troca de filtro
  • Cache local e solicitações remotas precisam estar envolvidas juntas
  • Ao sair da página, cancele algumas tarefas e mantenha outras
  • Vários módulos compartilham certos recursos simultâneos

Neste momento, você descobrirá que a maior dificuldade do código assíncrono já é:

Como organizar todo o link assíncrono para que não fique mais disperso e difícil de alterar conforme está escrito.

Neste artigo não quero falar de uma única API, mas sim dos princípios organizacionais que mais valorizo ​​em projetos reais.

1. O que compartilho primeiro é a responsabilidade.

Quando o código assíncrono é confuso, a primeira reação geralmente é “desmantelar a função”. Dividir funções é certamente útil, mas se as responsabilidades já estiverem misturadas, dividi-las geralmente envolve apenas dividir a bagunça em vários arquivos pequenos.

Estou mais preocupado em separar as responsabilidades primeiro. Geralmente dividido em pelo menos três camadas:

1. Camada de página

A camada da página é responsável por:

  • Acionar a intenção do usuário
  • Exibir status da página
  • Responder a mudanças interativas

Ele sabe “o que deve ser carregado agora”, mas não deve ser responsável por “como orquestrar todo o processo assíncrono”.

2. Camada de estado/ViewModel

A camada de estado é responsável por:

  • Traduzir a intenção do usuário em tarefas
  • Decidir se as tarefas devem ser paralelizadas, substituídas ou canceladas
  • Gerenciar a semântica da página, como carregamento, carregado, falhado, etc.
  • Determine quais resultados ainda são elegíveis para escrever de volta na página

É o verdadeiro ponto final dos processos assíncronos.

3. Camada de serviço

A camada de serviço é responsável por:

  • Ajustar interface
  • ler cache
  • Combine várias fontes de dados
  • Fornece recursos de domínio

Ele não deveria saber a aparência da página, nem deveria se infiltrar na semântica do estado da UI.

Muito código assíncrono fica confuso porque, uma vez misturadas essas três camadas, qualquer alteração nos requisitos afetará a interface do usuário, o processo, o status e a fonte de dados ao mesmo tempo.

2. O princípio mais importante da camada de página: saiba menos sobre detalhes assíncronos

Não gosto que a página execute muitas etapas assíncronas sozinha. Porque uma vez que a página saiba muito, ela começará a assumir estas coisas:

  • Solicitar controle de sequência
  • Erros
  • Filtrar resultados
  • Política de cancelamento
  • carregando semântica de subdivisão

Ao alterar um requisito como esse, a IU e o processo geralmente são afetados juntos.

Então prefiro que a página expresse apenas estas coisas:

  • “Preciso atualizar agora”
  • “O usuário clicou para tentar novamente”
  • “Condições de filtro alteradas”

Quanto ao que está por trás disso:

  • Leia primeiro o cache ou abra a interface primeiro?
  • Quer interromper as tarefas antigas?
  • Se o resultado expirou
  • O primeiro carregamento e a atualização devem compartilhar o mesmo link?

Coloque o máximo possível no fechamento da camada de status.

3. O status da página deve ser explícito, não confie em um monte de pedaços espalhados para soletrar a semântica.

Muitas páginas assíncronas ficarão assim na fase posterior:

-isLoading -isRefreshing -hasError -showRetry -isEmpty -items

Esses valores são razoáveis quando vistos individualmente, mas são propensos à autocontradição quando combinados:

  • Carregando com erros antigos
  • Ambos exibem o estado vazio e mantêm a lista antiga
  • Refrescante, mas o logotipo do primeiro carregamento ainda está lá

Portanto, valorizo mais o “estado semântico da página” do que “muitos estados pequenos que podem ser combinados livremente”.

Porque o que é realmente importante em uma página assíncrona é se ela consegue dizer claramente em que estágio a página se encontra.

4. Os limites das tarefas devem ser claros, caso contrário, tudo será apenas “executado primeiro”

Se a estrutura assíncrona é estável ou não, depende se ela pode responder a estas perguntas:

  • Quem é o dono desta missão? -Continua quando a página sai?
  • Quando chega uma nova tarefa, a tarefa antiga se tornará inválida imediatamente?
  • Esta é uma tarefa independente ou parte de um processo mais longo?

Se essas perguntas não puderem ser respondidas com clareza, o código a seguir certamente entrará em um estado:

  • Em todos os lugares parece ser autoexplicativo
  • Mas ninguém consegue fazer uma releitura completa de como esse link funciona.

Se um trecho de código assíncrono for difícil de repetir em linguagem natural, geralmente será difícil mantê-lo estável posteriormente.

5. Tentarei o meu melhor para incorporar a “validade do resultado” ao processo

Muitas páginas estão bagunçadas. Superficialmente, parece que o resultado falhou, mas na verdade está mais próximo:

  • Resultados antigos retornados com sucesso
  • Mas já não corresponde ao contexto atual da página

Este tipo de problema é especialmente provável de ocorrer quando:

  • Pesquisa
  • Interruptor de filtro
  • Paginação
  • Acesse rapidamente a página de saída

Se a eficácia dos resultados não estiver prevista no design, a página, mais cedo ou mais tarde, desenvolverá estes estranhos fenômenos:

  • Reversão de conteúdo
  • o carregamento termina inexplicavelmente
  • Os prompts de erro substituem o status de sucesso atual

Então eu me importo com:

  • Quem julgará se o resultado expirou?
  • Se cada ponto de chamada deve ser julgado por você mesmo
  • Ainda feche a interface uniformemente na camada de status

Minhas preferências são muito claras: **Tente ser consistente no fechamento e não deixe que cada ramo de visualização julgue por si só “se o resultado desta vez ainda conta”. **

6. Não coloque a semântica da página na camada de serviço

Muito código está confuso. Superficialmente, parece que a camada de serviço não fará solicitações. Na verdade, está mais próximo da camada de serviço e aos poucos começa a incorporar conceitos de página.

Por exemplo, nomes ou lógicas como esta começam a aparecer na camada de serviço:

  • “Pedido especial para primeiro carregamento da página inicial”
  • “A página de detalhes está vazia e cheia de lógica”
  • “Redação errada para esta página”

Uma vez misturados, as estruturas subsequentes tornar-se-ão cada vez mais difíceis de reutilizar. Como a camada de serviço começa a saber a aparência da página e a camada de página começa a saber quais detalhes de implementação a camada de serviço possui, os limites ficam rapidamente confusos.

A camada de serviço é mais adequada para focar em:

-Quais dados você obteve?

  • Como combinar fontes de dados
  • Qual é a estratégia de cache?

Em vez de “como esta página deve se comportar?”

7. Um princípio me preocupa: o processo assíncrono deve ser claramente recitado

Este é um critério comumente usado quando eu mesmo faço revisões de código.

Se você pegar um trecho de código assíncrono, já será difícil repeti-lo em linguagem natural:

  • Qual é a entrada?
  • Qual é o processo principal?
  • Quais tarefas serão canceladas?
  • Quais resultados serão descartados
  • Quais estados são alterados por qual camada?

Mesmo que esse código possa ser executado hoje, provavelmente se tornará cada vez mais difícil de evoluir no futuro.

O verdadeiro medo do código assíncrono é que ele não seja claro. Como você não consegue saber com clareza, quaisquer iterações subsequentes são apenas uma questão de sorte.

8. Uma ideia organizacional mais próxima da implementação

Se eu quisesse resumir em uma frase mais prática, organizaria assim:

  • Camada de página: expressando intenção
  • Camada de estado: tarefas de fechamento e semântica de estado
  • Camada de serviço: fornecendo capacidades
  • Camada de recursos compartilhados: Use Ator ou outros meios de isolamento para gerenciar o estado compartilhado quando necessário

Em seguida, esclareça essas relações na camada de estado:

  • Quais tarefas são mutuamente exclusivas
  • Quais tarefas são paralelas
  • Quais resultados podem ser descartados
  • Quais estados devem ser atualizados pelo ator principal

Uma situação comum é que isso “parece uma camada extra”, mas em projetos verdadeiramente complexos, essas camadas evitam que a lógica assíncrona se espalhe diretamente para cada evento da página.

9. Conclusão: O núcleo da organização assíncrona em projetos reais é o limite

O mais importante em um projeto real nunca é se será Task, async let ou Actor. O que realmente determina se o código pode evoluir a longo prazo é se esses limites são claros:

  • Limite entre a camada de página e a camada de processo
  • Limite entre a camada de estado e a camada de serviço
  • Limite entre estado compartilhado e estado normal
  • O limite entre os resultados válidos atuais e os resultados expirados

Então, se eu tivesse que resumir este artigo em uma frase, eu diria:

A chave para a organização do código assíncrono em projetos reais não é “como escrever uma API assíncrona”, mas “se os quatro limites de tarefas, status, resultados e responsabilidades estão claramente traçados”.

Somente quando esses limites são claros o código assíncrono pode realmente mudar de “pode ser executado” para “pode ser mantido por um longo tempo”.