Kodowalne: obsługa niepoprawnych wartości wyliczeniowych

May 09 2023
Ochrona bazy kodu: radzenie sobie z nieoczekiwanymi wartościami wyliczeniowymi Czy kiedykolwiek zdarzyło Ci się, że Twoja aplikacja zaczęła się zawieszać znikąd, co po zbadaniu wykazało, że backend dostarczył wartość dla pewnego pola opartego na wyliczeniu, które nie istnieje w wyliczeniu w kodzie? Rozważmy następujący przykład: Ohhh, uwielbiam trochę herbaty Ale co, jeśli zmienimy ulubiony napój w danych JSON z Tea na Juice (wartość, która nie istnieje w enum, Beverage)? Skoncentruj się na części komunikatu: „Nie można zainicjować napoju z nieprawidłowej wartości ciągu Sok”. Gdyby była to rzeczywista aplikacja, a nie kod placu zabaw, aplikacja uległaby awarii. Jak więc zadbać o to, aby nawet jeśli backend wyśle ​​w odpowiedzi nieprawidłową wartość, nasza aplikacja powinna sobie z tym poradzić zamiast od razu się zawiesić? Jeden ze sposobów i mój ulubiony:

Ochrona bazy kodu: radzenie sobie z nieoczekiwanymi wartościami wyliczeniowymi

Zdjęcie Kostiantyna Li na Unsplash

Czy kiedykolwiek spotkałeś się z sytuacją, w której Twoja aplikacja zaczęła się zawieszać znikąd, co po zbadaniu wykazało, że backend dostarczył wartość dla pewnego pola opartego na wyliczeniu , które nie istnieje w wyliczeniu w kodzie?

Rozważ następujący przykład:

import Foundation

struct Person: Codable {
    let name: String
    let favouriteBeverage: Beverage
}

enum Beverage: String, Codable {
    case Tea
    case Coffee
}

let jsonString = """
{
    "name": "Shubham Bakshi",
    "favouriteBeverage": "Tea"
}
""".data(using: .utf8)!

let decodedJSON = try! JSONDecoder().decode(Person.self, from: jsonString)

switch decodedJSON.favouriteBeverage {
    
case .Tea:
    print("Ohhh I love some Tea")
    
case .Coffee:
    print("Coffee is no Tea but meh, I'll take it")
}

Ohhh uwielbiam herbatę

Ale co, jeśli zmienimy favouriteBeveragedane JSON z Tea na Juice (wartość, która nie istnieje w enum, Beverage )?

let jsonString = """
{
    "name": "Shubham Bakshi",
    "favouriteBeverage": "Juice"
}
""".data(using: .utf8)!

Skoncentruj się na części wiadomości: „Nie można zainicjować napoju z nieprawidłowej wartości ciągu Juice”

Gdyby była to rzeczywista aplikacja zamiast kodu placu zabaw, aplikacja uległaby awarii. Jak więc zadbać o to, aby nawet jeśli backend wyśle ​​nieprawidłową wartość w odpowiedzi, nasza aplikacja powinna sobie z tym poradzić zamiast od razu się zawiesić ?

Jeden ze sposobów i mój osobisty faworyt: Nieprawidłowy mechanizm awaryjny Enum.

Mówi mechanizm, ale jest mniej więcej protokołem, który zajmuje się wszystkim. Więc zaczynamy ….

/// This is useful in case the backend specifies some value that does not 
/// exist inside the enum as in such cases the app crashes since it fails to 
/// parse the enum.
/// 
/// This ensures that in case an invalid value is specified, the app will 
/// not crash but rather resort to a value specified for 
/// `fallbackForIncorrectEnumValue`
///
protocol IncorrectEnumFallbackMechanism: RawRepresentable, Codable {
    
    /// Fallback value in case the backend returns an incorrect value for 
    /// the enum
    static var fallbackForIncorrectEnumValue: Self { get }
}

extension IncorrectEnumFallbackMechanism where RawValue: Codable {
    
    init(from decoder: Decoder) throws {
        
        // Fetching the value specified inside the JSON
        let value = try decoder.singleValueContainer().decode(RawValue.self)
        
        // Creating the instance based on the provided value or falling back 
        // to a default value in case the provided value for creating is 
        // invalid
        self = Self(rawValue: value) ?? Self.fallbackForIncorrectEnumValue
    }
    
    /// Since `RawRepresentable` declares a `encode(coder:)` by default so we 
    /// don't need to implement that explicitly
}

enum Beverage: String, IncorrectEnumFallbackMechanism {
    
    static var fallbackForIncorrectEnumValue: Beverage = .SomeWeirdBeverage
    
    case Tea
    case Coffee
    
    case SomeWeirdBeverage
}

Czekać! Co to jest?

Bez względu na to, co wyśle ​​nam backend favouriteBeverage, będziemy w stanie sobie z tym poradzić, wypić to, cokolwiek!

Więc nasz kompletny kod będzie teraz wyglądał tak:

import Foundation

/// This is useful in case the backend specifies some value that does not
/// exist inside the enum as in such cases the app crashes since it fails to
/// parse the enum.
///
/// This ensures that in case an invalid value is specified, the app will
/// not crash but rather resort to a value specified for
/// `fallbackForIncorrectEnumValue`
///
protocol IncorrectEnumFallbackMechanism: RawRepresentable, Codable {
    
    /// Fallback value in case the backend returns an incorrect value for
    /// the enum
    static var fallbackForIncorrectEnumValue: Self { get }
}

extension IncorrectEnumFallbackMechanism where RawValue: Codable {
    
    init(from decoder: Decoder) throws {
        
        // Fetching the value specified inside the JSON
        let value = try decoder.singleValueContainer().decode(RawValue.self)
        
        // Creating the instance based on the provided value or falling back
        // to a default value in case the provided value for creating is
        // invalid
        self = Self(rawValue: value) ?? Self.fallbackForIncorrectEnumValue
    }
    
    /// Since `RawRepresentable` declares a `encode(coder:)` by default so we
    /// don't need to implement that explicitly
}

struct Person: Codable {
    let name: String
    let favouriteBeverage: Beverage
}

enum Beverage: String, IncorrectEnumFallbackMechanism {
    
    static var fallbackForIncorrectEnumValue: Beverage = .SomeWeirdBeverage
    
    case Tea
    case Coffee
    
    case SomeWeirdBeverage
}

let jsonString = """
{
    "name": "Shubham Bakshi",
    "favouriteBeverage": "Juice"
}
""".data(using: .utf8)!

let decodedJSON = try! JSONDecoder().decode(Person.self, from: jsonString)

switch decodedJSON.favouriteBeverage {
    
case .Tea:
    print("Ohhh I love some Tea")
    
case .Coffee:
    print("Coffee is no Tea but meh, I'll take it")
    
case .SomeWeirdBeverage:
    print("Wait! What is that?")
}

To wszystko, ludzie! Szczęśliwego kodowania!

Możesz połączyć się ze mną na LinkedIn lub możesz skontaktować się ze mną przez inne kanały