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
}
// Se chegar neste ponto, é porque o filme passou nos filtros:
print("Uma alternativa é: \(movie.originalTitle).")
}
…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!