Codable: การจัดการค่า Enum ที่ไม่ถูกต้อง

May 09 2023
ปกป้อง codebase ของคุณ: จัดการกับค่า enum ที่ไม่คาดคิด เคยเผชิญกับสถานการณ์ที่แอปของคุณเริ่มทำงานล้มเหลวโดยไม่รู้ตัว ซึ่งจากการตรวจสอบพบว่า backend ให้ค่าสำหรับฟิลด์ตาม enum บางฟิลด์ที่ไม่มีอยู่ใน enum ในโค้ด? ลองพิจารณาตัวอย่างต่อไปนี้: Ohhh I love some Tea แต่ถ้าเราเปลี่ยนเครื่องดื่มที่ชื่นชอบในข้อมูล JSON จาก Tea เป็น Juice (ค่าที่ไม่มีอยู่ใน enum, Beverage) ล่ะ โฟกัสที่ส่วนของข้อความ: “ไม่สามารถเริ่มต้นเครื่องดื่มจากน้ำผลไม้ที่มีค่าสตริงไม่ถูกต้อง” หากเป็นแอปจริงแทนที่จะเป็นโค้ด Playground แอปอาจขัดข้อง แล้วเราจะดูแลได้อย่างไรว่าแม้ว่าแบ็กเอนด์จะส่งค่าที่ไม่ถูกต้องในการตอบกลับ แอปของเราควรจะสามารถจัดการกับมันได้แทนที่จะหยุดทำงานทันที หนึ่งในวิธีที่ฉันชอบเป็นการส่วนตัว:

ปกป้อง codebase ของคุณ: จัดการกับค่า enum ที่ไม่คาดคิด

ภาพถ่ายโดย Kostiantyn Li บน Unsplash

เคยเผชิญกับสถานการณ์ที่แอปของคุณเริ่มหยุดทำงานโดยไม่มีที่ไหนเลย ซึ่งจากการตรวจสอบพบว่าแบ็กเอนด์ให้ค่าสำหรับฟิลด์ ที่ใช้ enum บาง ฟิลด์ที่ไม่มีอยู่ใน enum ในโค้ด

พิจารณาตัวอย่างต่อไปนี้:

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

โอ้ ฉันรักชา

แต่ถ้าเราเปลี่ยนfavouriteBeverageข้อมูล JSON จากTeaเป็นJuice (ค่าที่ไม่มีอยู่ใน enum, Beverage ) ล่ะ?

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

โฟกัสที่ข้อความ: “ไม่สามารถเริ่มต้นเครื่องดื่มจากน้ำผลไม้ค่าสตริงที่ไม่ถูกต้อง”

หากเป็นแอปจริงแทนที่จะเป็นโค้ด Playground แอปนั้นน่าจะขัดข้อง แล้วเราจะดูแลได้อย่างไรว่าแม้ว่าแบ็กเอนด์จะส่งค่าที่ไม่ถูกต้องในการตอบกลับแอปของเราควรจะสามารถจัดการกับมันได้แทนที่จะหยุดทำงานทันที ?

หนึ่งในวิธีที่ฉันชอบเป็นการส่วนตัว: กลไก Enum Fallback ที่ไม่ถูกต้อง

มันบอกว่ากลไก แต่เป็นโปรโตคอลที่ดูแลทุกอย่างไม่มากก็น้อย เอาล่ะ ไปเลย….

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

รอ! นั่นคืออะไร?

ไม่ว่าแบ็กเอนด์จะส่งเรามาในฐานะอะไรfavouriteBeverageเราก็สามารถจัดการมันได้ หรือดื่มมัน อะไรก็ตาม!

ดังนั้นรหัสที่สมบูรณ์ของเราจะมีลักษณะดังนี้:

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

แค่นั้นแหละ! มีความสุขในการเข้ารหัส!

คุณสามารถเชื่อมต่อกับฉันบนLinkedIn หรือติดต่อฉันผ่านช่องทางอื่น ๆ