Nil не всегда nil

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Nil не всегда nil


"Что? Что вообще здесь написано?" спросите вы. Сейчас все разложу.


Когда начинал изучать язык — не думал что зайду в этот узкий случай. Это также не рационально как и изменять итерируемую коллекцию.


На примере:

func Foo() error {
    var err *os.PathError = nil
    return err
}

func main() {
    err := Foo()
    fmt.Println(err)           // <nil>
    fmt.Println(err == nil) // false
}

WAT!


Что представляет собой интерфейс

Переходим в файл пакета go runtime/runtime2.go и видим:


type itab struct { // 40 bytes on a 64bit arch
    inter *interfacetype
    _type *_type
    ...
}

Интерфейс хранит в себе тип интерфейса и тип самого значение.


Значение любого интерфейса, не только error, является nil в случае когда И значение И тип являются nil.


Функция Foo возвращает nil типа *os.PathError, результат мы сравниваем с nil типа nil, откуда и следует их неравенство.


Возможно об этом многие знали, но мало кто думает как попасть в это на практике.


Мой пример

type Response struct {
    Result ResponseResult    `json:"result,omitempty"`
    Error  *ResponseError    `json:"error,omitempty"`
}

type ResponseError struct {
    Message string `json:"message"`
}

func (e *ResponseError) Error() string {
    return e.Message
}
...
func (s *NotificationService) NotifyIfError(w *ResponseWriter) error {
    ...
    var res handlers.Response
    _ = json.Unmarshal(body, &res)

    if res.Error == nil {
        return
    }

    return s.NotifyError(err)
}

Response всегда имеет результат или ошибку.


При наличии ошибки — отправляем ее куда необходимо через сервис нотификаций.
Внутри сервиса вызывается метод Error, а так как наше значение nil — получаем панику.


Что делать?

Возвращать интерфейс строго типа интерфейса.


В случае ошибки — типа ошибки.


  • Добавим обьявление типа error

func (s *NotificationService) NotifyIfError(w *ResponseWriter) error {
    ...
    var res handlers.Response
    _ = json.Unmarshal(body, &res)

        var err error = res.Error
    return s.NotifyError(err)
}

Этот прием тоже, к моему удивлению, не работает.


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


  • Попробуем получить из типа интерфейса наш исходный тип и проверить его значение.

func (s *NotificationService) NotifyIfError(w *ResponseWriter) error {
    ...
        if e, ok := err.(*handlers.ResponseError); ok && e == nil {
        return s.NotifyError(err)
    }

        return nil
}

Да, такой прием работает.


Но будем честны, мы не можем позволить себе проверить все типы ошибок которые мы будем передавать.


Это могут быть все ошибки из драйвера базы данных, все наши внутренние ошибки и прочий мусор.


Какой наиболее рациональный вариант я вижу:

func (s *NotificationService) NotifyIfError(w *ResponseWriter) error {
    var err error
        ...
        var res handlers.Response
        _ = json.Unmarshal(body, &res)
        if res.Error != nil {
            return s.NotifyError(err)
        }

    return nil
}

Вначале у нас объявленa переменная типа error, как оказывается со значением и типом nil.
И прежде чем передать наш тип и значение этой переменной — проверим наш тип и его значение на nil.


Это позволит нам не упасть с паникой.


Напоследок

Можно пойти еще дальше и реализовать "опциональную" ошибку у типа Response, OptionalError или ErrorOrNil, вроде такого:


func (r *Response) ErrorOrNil() error {
    if r.Error == nil {
        return nil
    }

    return r.Error
}

На заметку

В заметках Go wiki код ревью заметка в топике об интерфейса: Instead return a concrete type and let the consumer mock the producer implementation.


Отмечу, что приведенные мной выше пляски не про это.


Мои заметки позволяют не упасть с паникой когда вы знаете, что хотите вернуть интерефйс, а в случае ошибок — вы всегда хотите возвращать интерфейс.


Но если вы можете себе позволить вернуть определенный тип — возвращайте его.


Ссылки

Я

https://www.linkedin.com/in/%D0%B4%D0%B5%D0%BD%D0%B8%D1%81-%D0%B4%D0%B2%D0%BE%D1%80%D0%BD%D0%B8%D0%BA%D0%BE%D0%B2-33399812b/
https://t.me/it_patifon
https://twitter.com/denny_penta/followers
https://github.com/dennypenta

Источник: https://habr.com/ru/post/449714/


Интересные статьи

Интересные статьи

Часто от программистов PHP можно услышать: «О нет! Только не „Битрикс“!». Многие специалисты не хотят связываться фреймворком, считают его некрасивым и неудобным. Однако вакансий ...
Инфотейнмент-системы в автомобиле могут отвлекать водителей похуже запрещенных веществ и препаратов. Но есть мнение, что ситуацию способны исправить голосовые ассистенты, однако и с этой технолог...
В интернет-магазинах, в том числе сделанных на готовых решениях 1C-Битрикс, часто неправильно реализован функционал быстрого заказа «Купить в 1 клик».
Если Вы используете в своих проектах инфоблоки 2.0 и таблицы InnoDB, то есть шанс в один прекрасный момент столкнуться с ошибкой MySQL «SQL Error (1118): Row size too large. The maximum row si...
Основанная в 1998 году компания «Битрикс» заявила о себе в 2001 году, запустив первый в России интернет-магазин программного обеспечения Softkey.ru.