Как проверить значение во вложенном указателе

Как я могу легко проверить вложенный указатель?

type inner1 struct {
    value string
}

type inner2 struct {
    value *inner1
}

type outter struct {
    value *inner2
}

У меня есть такие данные:

o := &outter{
    value: &inner2{
        value: &inner1{
            value: "I need this data",
        },
    },
} 

Если я хочу получить данные из этого, мне нужно проверить нулевой указатель.

func printValue(o *outter) (string, bool) {
    if o.value != nil {
        v1 := o.value
        if v1.value != nil {
            v2 := v1.value
            return v2.value, true
        }
    }
    return "", false
}

Да, я могу сделать это. Но в моем реальном сценарии эта вложенная часть указателя намного длиннее. Я предпочту другой путь.

Я проверил этот ответ, Тест для нулевых значений в вложенных структурах Голанга. Но мне нужна альтернатива.

Как я могу сделать это. Любой эффективный и лучший вариант?

3 ответа

Решение

Паника и выздоровление для исключительных случаев. Проверка, если указатель nil обычно нет, поэтому вы должны держаться подальше от этого. Проверка, если указатель nil используя if утверждение гораздо чище, чем вводить отложенную функцию с recover(), Тем более что recover() остановит все другие виды паники, даже те, которые возникнут по другим причинам, чем попытка разыменования nil указатель. (С помощью defer а также recover() также медленнее, чем простой nil проверить с помощью if.)

Если данные всегда должны быть неnil значение, вы должны рассмотреть возможность не использовать указатель для него. И если данные всегда должны быть неnil но по какой-то причине вы обязаны использовать указатель, тогда это может быть допустимым случаем, чтобы позволить вашему коду паниковать, если какой-либо из указателей все еще nil,

Если это может быть nilтогда вы должны проверить nil дело и обращаться с ним соответствующим образом. Вы должны быть откровенными об этом, Go не поможет вам пропустить эту проверку.

Чтобы избежать проверки nil в любом месте полезна функция или метод полезности. Обратите внимание, что методы могут быть вызваны, даже если получатель nil что может быть полезно в таких случаях.

Например, вы можете прикрепить следующие методы:

func (i *inner1) Get() (string, bool) {
    if i == nil {
        return "", false
    }
    return i.value, true
}

func (i *inner2) Get() (string, bool) {
    if i == nil {
        return "", false
    }
    return i.value.Get()
}

func (o *outter) Get() (string, bool) {
    if o == nil {
        return "", false
    }
    return o.value.Get()
}

Обратите внимание, что каждый Get() Метод требует проверки одного указателя, не имеет значения, насколько сложна структура данных.

Тестирование это:

o := &outter{
    value: &inner2{
        value: &inner1{
            value: "I need this data",
        },
    },
}
fmt.Println(o.Get())

o.value.value = nil
fmt.Println(o.Get())

o.value = nil
fmt.Println(o.Get())

o = nil
fmt.Println(o.Get())

Вывод (попробуйте на Go Playground):

I need this data true
 false
 false
 false

Вышеупомянутое решение скрывает внутренние органы outter что полезно для тех, кто использует outter (не требует обновления клиентов, если внутренние outter изменить, только outter.Get() метод).

Аналогичный подход заключается в добавлении методов, которые только возвращают поле структуры получателя:

func (i *inner1) Value() (string, bool) {
    if i == nil {
        return "", false
    }
    return i.value, true
}

func (i *inner2) Inner1() *inner1 {
    if i == nil {
        return nil
    }
    return i.value
}

func (o *outter) Inner2() *inner2 {
    if o == nil {
        return nil
    }
    return o.value
}

Этот подход требует, чтобы клиенты знали внутренние outter, но так же не требует nil проверяет при использовании:

o := &outter{
    value: &inner2{
        value: &inner1{
            value: "I need this data",
        },
    },
}
fmt.Println(o.Inner2().Inner1().Value())

o.value.value = nil
fmt.Println(o.Inner2().Inner1().Value())

o.value = nil
fmt.Println(o.Inner2().Inner1().Value())

o = nil
fmt.Println(o.Inner2().Inner1().Value())

Выход такой же. Попробуйте это на Go Playground.

Использование отражения

func getFieldByName(v interface{}, fields string, sep string) interface{} {
    r := reflect.ValueOf(v)
    s := strings.Split(fields, sep)
    for _, field := range s {
        r = reflect.Indirect(r)
        if r.IsValid() {
            r = reflect.Indirect(r).FieldByName(field)
        } else {
            return nil
        }
    }
    if r.IsValid() {
        return r.Interface()
    }
    return nil
}

Использование Panic Recovery

type safeGetFn func() interface{}

func safeGet(f safeGetFn, d interface{}) (ret interface{}) {
    defer func() interface{} {
        if r := recover(); r != nil {
            ret = d
        }
        return ret
    }()
    return f()
}

Пример отражения: https://play.golang.org/p/m0_zQqJm7MY

Пример восстановления после паники: https://play.golang.org/p/PNPPBXCvHxJ

Я могу использовать метод восстановления паники. Это решит мою проблему. Но это кажется мне счастливым.

func printValue(o *outter) (string, bool) {
    defer func() (string, bool) {
        if r := recover(); r != nil {
            return "", false
        }
        return "", false
    }()
    return o.value.value.value, true
}

Легкого пути нет. Вы можете пойти по пути восстановления, но это не идиоматическое IMO, и вы должны убедиться, что вы не ловите другие несвязанные ошибки.

Я предпочитаю один if вместо нескольких. Я не думаю о коде ниже уродливом или многословном.

func printValue(o *outter) (string, bool) {
    if o.value != nil and o.value.value != nil and o.value.value.value != nil {
        return *o.value.value.value, true
    }
    return "", false
}
Другие вопросы по тегам