Codierbar: Umgang mit falschen Enum-Werten

May 09 2023
Schutz Ihrer Codebasis: Umgang mit unerwarteten Enum-Werten Haben Sie jemals eine Situation erlebt, in der Ihre App aus dem Nichts abstürzte, was bei einer Untersuchung ergab, dass das Backend einen Wert für ein bestimmtes Enum-basiertes Feld bereitstellte, das nicht innerhalb des Enum im Code vorhanden ist? Betrachten Sie das folgende Beispiel: Ohhh, ich liebe Tee. Aber was ist, wenn wir favouriteBeverage in den JSON-Daten von Tea in Juice ändern (ein Wert, der in der Aufzählung Beverage nicht vorhanden ist)? Konzentrieren Sie sich auf den Teil der Meldung: „Beverage kann nicht von ungültigem String-Wert Juice initialisiert werden.“ Wenn es sich um eine tatsächliche App anstelle eines Playground-Codes gehandelt hätte, wäre die App abgestürzt. Wie sorgen wir also dafür, dass unsere App, selbst wenn das Backend einen ungültigen Wert in der Antwort sendet, damit umgehen kann, anstatt direkt abzustürzen? Einer der Wege und mein persönlicher Favorit:

Schutz Ihrer Codebasis: Umgang mit unerwarteten Enum-Werten

Foto von Kostiantyn Li auf Unsplash

Haben Sie jemals eine Situation erlebt, in der Ihre App aus dem Nichts abstürzte, was bei einer Untersuchung ergab, dass das Backend einen Wert für ein bestimmtes Enum- basiertes Feld bereitgestellt hat, das nicht innerhalb des Enums im Code vorhanden ist?

Betrachten Sie das folgende Beispiel:

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, ich liebe etwas Tee

favouriteBeverageAber was ist, wenn wir die JSON-Daten von Tea in Juice ändern (ein Wert, der in der Aufzählung Beverage nicht vorhanden ist )?

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

Konzentrieren Sie sich auf den Teil der Meldung: „Beverage kann nicht mit ungültigem String-Wert Juice initialisiert werden“

Wenn es sich um eine echte App statt um einen Playground-Code gehandelt hätte, wäre die App abgestürzt. Wie sorgen wir also dafür, dass selbst wenn das Backend einen ungültigen Wert in der Antwort sendet, unsere App in der Lage sein sollte, damit umzugehen, anstatt direkt abzustürzen ?

Einer der Wege und mein persönlicher Favorit: Falscher Enum-Fallback-Mechanismus.

Es sagt Mechanismus, ist aber mehr oder weniger ein Protokoll, das sich um alles kümmert. Auf geht's ….

/// 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
}

Warten! Was ist das?

Egal, was das Backend uns als sendet favouriteBeverage, wir können damit umgehen oder es trinken, was auch immer!

Unser vollständiger Code sieht nun also so aus:

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?")
}

Das ist es, Leute! Viel Spaß beim Codieren!

Sie können sich mit mir auf LinkedIn verbinden oder über andere Kanäle mit mir in Kontakt treten