Concrete | Swift Guard
Meu primeiro contato com guard não foi lá dos mais compreensivos. Entendi o conceito mas não entendi por que utilizá-lo ao invés de um simples if..let, que me parecia muito mais simples e objetivo. Porém, depois de alguns exemplos e alguns casos mais complexos, vi o potencial de tal condicional.
A finalidade do guard é validar alguma condição e forçar uma parada à execução se essa condição não for atendida :
func checkAge(age: Int) -> Bool { return age > 18 } func isAgeValid(input: Int) { // start of guard guard checkAge(input) else { return } // end of guard print("mais que 18 anos") }
func checkAge(age: Int) -> Bool {
func isAgeValid(input: Int) {
guard checkAge(input) else {
print("mais que 18 anos")
O conceito do guard segue dois padrões de programação. O primeiro é o "Happy Path" que, como o próprio nome (caminho feliz) diz, é um cenário padrão que não é caracterizado por condições excepcionais ou erros, ou seja, o "Happy Path" da nossa função é o print("mais que 18 anos"), no qual tudo correu como esperado. O segundo padrão é o "Bouncer Pattern": imagine que só entra quem tem nome na lista em uma festa fechada. O "Bouncer Pattern" checa na entrada se seu nome está na lista, obviamente te colocando no olho da rua caso contrário. Utilizando ambos os padrões, o código fica mais limpo e lida com as verificações de condicional em apenas um lugar em vez de vários if..else espalhados pelo código.
Evitando a tão temida Pyramid of Doom
O primeiro uso valioso que vi com guard foi evitar a temida "Pyramid of Doom". Não que eu já tenha feito isso, mas algumas vezes precisamos criar alguns if..else para verificar se uma ou mais condições são atendidas, o que deixa o seu código confuso:
enum AreaCalculationError: ErrorType { case RadiusNotSpecified case RadiusNotValid } func calculateCirlceArea(radius:Double?) throws -> Double { if let radius = radius { if radius > 0 { return radius*radius*M_PI } else { throw AreaCalculationError.RadiusNotValid } } else { throw AreaCalculationError.RadiusNotSpecified } }
enum AreaCalculationError: ErrorType {
case RadiusNotValid
if radius > 0 {
throw AreaCalculationError.RadiusNotValid
throw AreaCalculationError.RadiusNotSpecified
Com guard, o entendimento do código é mais claro e evitamos algunsif..else :
func calculateCirleArea(radius:Double?) throws -> Double { guard let radius = radius else { throw AreaCalculationError.RadiusNotSpecified } guard radius > 0 else { throw AreaCalculationError.RadiusNotValid } return radius*radius*M_PI }
Acesso a valores fora das chaves
Guard é uma instrução condicional que exige uma execução para sair do bloco atual. Se uma condição não for atendida, o bloco de retorno é executado e qualquer variável opcional criada dentro desse escopo pode ser acessada pelo resto da função ou bloco (eis o primeiro ponto tão gracioso do guard):
func parseUserFromJson(jsonData: NSData) throws -> User { guard let json = decodeJSON(jsonData) as? [String: AnyObject] else { throw ParsingError.InvalidJSON } guard let id = json["id"] as? Int, let name = json["name"] as? String else { throw ParsingError.MissingData } return User(id: id, name: name) }
func parseUserFromJson(jsonData: NSData) throws -> User {
Vantagens no exemplo acima :
Colocando o guard no topo da função em vez de colocar todo o escopo dentro de um grande if estamos garantindo que a condição não é parte do trabalho fundamental da função;
Utilizando o guard, fica claro a qualquer outra pessoa que ler o código que essa se essa condição for falsa, o retorno deverá ser imediato. E se por acaso a condicional for retirada, o código não irá compilar pela permanência apenas do else;
Unwrapping optionals: diferente do if let, que faz o unwrapping do valor apenas dentro das chaves, o guard adiciona valores unwrapped fora do escopo, assim você pode utilizá-los depois do guard.
Caso de um grande Happy Path
No caso de um grande "Happy Path", no qual cada condição deve retornar um erro:
func buyOneSoda(sodaName: String) throws { guard machineIsOn else { throw VendingMachineError.Off } let items = getItemByName(itemName) guard items.count > 0 else { throw VendingMachineError.OutOfStock } guard coinsDeposited >= totalPrice else { throw VendingMachineError.InsufficientFunds } removeItemFromInventory(itemName) dispenseSoda(items) }
func buyOneSoda(sodaName: String) throws {
guard machineIsOn else {
throw VendingMachineError.Off
let items = getItemByName(itemName)
removeItemFromInventory(itemName)
dispenseSoda(items)
Quando não utilizar o guard
If..else simples
// Don't: var userName: String? { guard let project = NSUserDefaults.standardUserDefaults().user where user.isValid else { return nil } return user.name }
guard let project = NSUserDefaults.standardUserDefaults().user where user.isValid else {
Para tais casos simples, é muito mais fácil entender e conceituar um ou dois if..else.
// Better! var userName: String? { if let user = NSUserDefaults.standardUserDefaults().user where user.isValid { return user.name } else { return nil } }
if let user = NSUserDefaults.standardUserDefaults().user where user.isValid {
Não colocar o código complexo dentro do else
Guard não deve ser mais do que uma saída precoce simples. Ok se for necessário fazer registros de diagnóstico para debug, mas raramente outra coisa, por isso a utilização apenas do throw nos exemplos anteriores.
Regra de ouro: um guard não deve nunca ter mais do que duas ou três linhas de código.
Entendido? Se ficou alguma dúvida ou tem alguma outra dica sobre o Guard, deixe nos comentários! Até a próxima!