TL;DR: Tudo que o guard
faz poderia ser feito com if
. Mas os guards foram criados para melhorar a leitura do código, indicando um “posto de verificação” para os comandos seguintes: “Preciso que esta condição seja verdadeira, senão não é possível executar nada daqui pra frente.”
O guard é uma dessas coisas em Swift que a gente aprende como funciona, mas no começo fica sem usar porque no fundo não entende qual é a verdadeira utilidade. Afinal, já que o guard é uma forma de teste condicional, qual é o problema em usar o velho e conhecido “if”, com o qual estamos acostumados e que nunca nos deixou na mão?
A “pirâmide da perdição”
Bom, o objetivo do guard é melhorar a legibilidade do código, para evitar aquelas situações em que tem uma sequência de “if”s um dentro do outro e, por causa da indentação acumulada, o código fica muito deslocado para a direita.
Vamos começar com um exemplo. Digamos que eu tenha criado a seguinte classe para representar um filme (você pode baixar o código-fonte dos exemplos neste link):
public class Movie { // [aqui tem a declaração da enum Genre] public var originalTitle: String public var isAdult: Bool? public var year: Int? public var runtimeMinutes: Int? public var genres: Set? public var portugueseTitle: String? public var imdbRating: Double? public var isFavourite: Bool // [aqui tem o init] }
Agora, estou querendo escolher um filme para ver. Queria uma comédia mais antiga — digamos, anterior a 1990 — e que seja bem conceituada no IMDB (quero nota mínima de 8,5). Infelizmente, como todos esses campos são opcionais, eu preciso ter o cuidado de verificar primeiro se cada campo contém nil
antes de verificar se ele corresponde aos meus critérios de busca.
Assim, uma possível solução está no código a seguir:
for movie in movieCatalog { if movie.year != nil { let year = movie.year! if year < 1990 { if movie.genres != nil { let genres = movie.genres! if genres.contains(.comedy) { if movie.imdbRating != nil { let imdbRating = movie.imdbRating! if imdbRating >= 8.5 { print("Uma alternativa é: \(movie.originalTitle).") } } } } } } }
Garanto que não existe nada de errado na lógica desse trecho de código. O problema é a legibilidade: com tantos blocos aninhados e níveis de alinhamento, fica fácil a gente se perder! Você já deve ter visto coisas parecidas com esse triângulo de “if”s: muitos programadores o chamam de “pyramid of doom”, ou “pirâmide da perdição”.
E tem solução?
Certo, mas como tornar esse código mais legível?
Em Swift, podemos inverter o pensamento e criar barreiras, ou “postos de verificação”, de modo a impedir a execução dos comandos seguintes se alguns critérios não forem obedecidos. No fim, o efeito final (no caso do exemplo, a impressão do título do filme) só ocorre se o filme conseguiu ser “aprovado” em todos os postos de verificação:
for movie in movieCatalog { // ... Testes para descartar os filmes que não interessam ... // Se chegar neste ponto, é porque o filme passou nos filtros: print("Uma alternativa é: \(movie.originalTitle).") }
Bem mais fácil de ler, certo? Bom, vamos ao código que está faltando:
for movie in movieCatalog { // Primeiro filtro: Filme anterior a 1990 guard movie.year != nil else { continue } let year = movie.year! guard year < 1990 else { continue } // Segundo filtro: Filme de comédia guard movie.genres != nil else { continue } let genres = movie.genres! guard genres.contains(.comedy) else { continue } // Terceiro filtro: nota mínima no iMDB guard movie.imdbRating != nil else { continue } let imdbRating = movie.imdbRating! guard imdbRating >= 8.5 else { continue } // Se chegar neste ponto, é porque o filme passou nos filtros: print("Uma alternativa é: \(movie.originalTitle).") }
Mais legível, não acham? O guard é uma espécie de controle de qualidade que diz: “Daqui pra frente essa condição tem que ser verdadeira, senão…” e um bloco de código que necessariamente impede a execução dos comandos seguintes.
Essa última parte é muito importante, pois é o que garante a desistência quando a condição falha. Assim, necessariamente o bloco dentro do “else” deve conter um dos seguintes comandos:
continue
, para desistir da iteração atual e passar para a próxima rodada do laço (é o que estou usando para dizer “desista deste filme, passe pro próximo”);break
, para desistir do laço atual;return
, para desistir da função ou método atual;throw
, para desistir de… bom, de qualquer esperança, chutar o balde e gerar uma exceção.
Aliás, o uso de um desses comandos é obrigatório: se a gente omitir, o Swift emite o seguinte erro: 'guard' body must not fall through, consider using a 'return' or 'throw' to exit the scope.
Subindo de nível: O “guard let”
Tem uma situação bastante comum em programação, quando a gente precisa fazer a seguinte sequência:
guard
preliminar: Testar se um optional está guardando algum valor (ou seja, se é diferente denil
);let
: Extrair o valor do optional com a exclamação (!
) e guardar em uma variável local;guard
s adicionais: Fazer testes em cima dessa variável.
Olhe o meu código de exemplo acima: Notou que essa situação aparece três vezes, uma pra cada campo testado?
Muito bem. A próxima dica é que os dois primeiros passos — o guard
do nil
e o let
para o valor extraído — podem ser comprimidos em um único passo!
Seja bem-vindo ao guard let
. Aqui está ele em ação:
for movie in movieCatalog { // Primeiro filtro: Filme anterior a 1990 guard let year = movie.year else { continue } guard year < 1990 else { continue } // Segundo filtro: Filme de comédia guard let genres = movie.genres else { continue } guard genres.contains(.comedy) else { continue } // Terceiro filtro: nota mínima no iMDB guard let imdbRating = movie.imdbRating else { continue } guard imdbRating >= 8.5 else { continue } // Se chegar neste ponto, é porque o filme passou nos filtros: print("Uma alternativa é: \(movie.originalTitle).") }
Entendeu como ele funciona? A ideia é: “Preciso que este valor não seja nil
e preciso dele nesta nova variável, senão…” e a condição de desistência. Em um único passo!
Várias condições em um único guard
Não quero estender muito este post, mas tem mais uma dica que eu queria deixar: O guard permite listar várias condições separadas por vírgula. A desistência ocorre se qualquer condição falhar.
Isso pode ser usado para condensar um pouco mais o meu exemplo acima. Dê uma olhada na versão a seguir (e preste atenção nas vírgulas para separar as condições):
for movie in movieCatalog { // Primeiro filtro: Filme anterior a 1990 guard let year = movie.year, year < 1990 else { continue } // Segundo filtro: Filme de comédia guard let genres = movie.genres, genres.contains(.comedy) else { continue } // Terceiro filtro: nota mínima no iMDB guard let imdbRating = movie.imdbRating, imdbRating >= 8.5 else { continue } // Se chegar neste ponto, é porque o filme passou nos filtros: print("Uma alternativa é: \(movie.originalTitle).") }
Será que isso significa que a gente pode entrar em modo insano e colocar todas as condições em um único comando? Bom, até pode…
for movie in movieCatalog { // Todos os filtros em cascata! guard let year = movie.year, year < 1990, let genres = movie.genres, genres.contains(.comedy), let imdbRating = movie.imdbRating, imdbRating >= 8.5 else { continue }
…mas o guard foi criado para aumentar a legibilidade, e chega um ponto em que muita coisa acontecendo em uma única linha de código acaba atrapalhando, não é mesmo? Assim, fica a dica: Use, mas não abuse!