Pular para o conteúdo
Development

try? transformou uma interrupção em "all caught up"

Por Victor Da Luz
iosswifterror-handlingdev-logdeep-cut-atlas

Logo depois de adicionar injeção de erro nos meus mocks, fui olhar o único lugar que a nova costura não conseguia alcançar. Acabou que ali estava escondido o pior bug do app.

O problema

O Deep Cut Atlas monta seu feed buscando o catálogo de cada artista da biblioteca, alguns artistas de cada vez:

let tasks = chunk.map { artist in
    Task { () -> [LibraryAlbum] in
        (try? await self.service.fetchCatalogReleases(for: artist)) ?? []
    }
}

Aquele try? ... ?? [] parecia inofensivo. Um artista instável não devia derrubar uma varredura de 200 artistas, certo? Resiliência por artista. Eu até tinha um comentário explicando que o chunking era uma guarda de rate limit.

Mas siga o array vazio adiante. O diff trata isso como “esse artista não tem lançamentos”. O resultado fica em cache com um TTL de 24 horas. Então, se a rede cai no meio de um refresh: todo artista “não tem lançamentos”, o diff fica vazio, o feed vazio é guardado em cache por cima do bom, e a UI alegremente reporta “You’re all caught up!” pelo dia seguinte inteiro.

Uma interrupção não parecia uma interrupção. Parecia sucesso sem dados - que é exatamente o que try? faz: torna a falha indistinguível da ausência.

O fix

Agora cada fetch devolve um Result em vez de engolir o erro, falhas são logadas por artista, e a varredura reporta se ficou completa:

private struct CatalogSweep {
    let albums: [LibraryAlbum]
    let isComplete: Bool
}

Três comportamentos saem disso. Se todos os artistas falharam, lança exceção - o caminho de erro já existente cuida disso: um carregamento a frio mostra a falha, um refresh em segundo plano continua mostrando o feed em cache. Se alguns falharam, mostra o feed parcial (fresco-mas-parcial é melhor que nada) mas não guarda em cache - o feed na tela se autocorrige no próximo refresh, enquanto um em cache seria a base por um dia inteiro. Se nenhum falhou, guarda em cache como antes.

O mock precisou de mais uma costura para testar isso: injeção de falha por artista (failingArtistNames: Set<String>), porque a injeção no nível de método da rodada anterior só conseguia falhar todos os artistas de uma vez. Quatro testes travam a matriz: falha total e parcial, em carregamento a frio e em refresh em segundo plano.

Lições

try? num pipeline de dados significa que a falha vira dado. Se esse dado alimenta um cache, você construiu um mecanismo para persistir interrupções como fatos.

Resiliência e visibilidade são decisões separadas. Manter a varredura viva além de um artista instável está certo; fingir que o artista não tinha nada não está. Result te dá as duas coisas.

E escrever testes de falha foi como encontrei isso. A costura de injeção de erro não só cobriu os branches existentes - ela me fez notar o único erro que nunca chegava a nenhum branch.

Leitura relacionada

Development

Uma seção de tracklist, e por que levou 30 minutos

Um método de protocolo, um enum de estado reaproveitado, uma convenção de roteamento que se manteve, e um orçamento de lint que forçou uma divisão que valia a pena fazer de qualquer forma.

Ler
Development

A playlist que já tinha o nome certo

Um artefato de renomeação, uma API sem campo de autor pra atualizar, e um teste em dispositivo real provando que o bug já tinha se corrigido sozinho, fechado como aceitar como está.

Ler

Você também pode achar útil

Proton

Proton Mail

E-mail criptografado de ponta a ponta, com arquitetura de acesso zero.

Como parceiro da Proton, ganho com compras qualificadas dos serviços de privacidade e segurança da Proton (Pass, Mail, VPN, Drive).

Saiba mais
Proton

Proton Pass

Gerenciador de senhas focado em privacidade, da equipe por trás do Proton Mail.

Como parceiro da Proton, ganho com compras qualificadas dos serviços de privacidade e segurança da Proton (Pass, Mail, VPN, Drive).

Saiba mais
AdGuard

AdGuard para iOS

Bloqueio de anúncios e rastreadores em todo o sistema no iOS, sem necessidade de um servidor DNS separado.

Como afiliado da AdGuard, ganho com compras qualificadas.

Saiba mais