Обережно кодогенерація


збільшення швидкодії та зменшення використання пам'яті після використання кодогенераціїgithub.com/protocolbuffers/protobuf は、私が最も重要な問題であると考えています.

Protobuf, перша помилка яка показала себе через пару тижнів після змін в коді



≥looflyするまで.
А потім я дізнався про "Protocol Buffers for Go with Gadgets" github.com/gogo/protobuf , бібліотеку-fork яка генерує додатковий код щоб прибрати рефексію підчас серіалізації і вже записує в слайс байтів по індексу бо так швидше.
Коли змінював одну бібліотеку на іншу то важливим вважав що працювати швидше написані раніше тести проуши.
І все було гаразд але в проекті латка яка через пару тижнів після заміни перезапустила мікросервіс через паніку:

panic: runtime error: index out of range

Латка виглядала приблизно так:

import (
    "github.com/golang/protobuf/proto"
    google "gitlab.com/go-yp/go-warning-codegeneration/models/protos/google/advertisement"
)

func example() {
    var popup = &google.Popup{
        Id:      uuid(),
        Viewed:  true,
        Clicked: false,
    }

    // some deep nested function
    go func() {
        var content, err = proto.Marshal(popup)

        if err != nil {
            // log error

            return
        }

        // store to database
        store(content)
    }()

    // some delay with other actions

    // @temporary hack
    go func() {
        popup.Clicked = true

        var content, err = proto.Marshal(popup)

        if err != nil {
            // log error

            return
        }

        // store to database again
        store(content)
    }()
}

私はあなたのことを知っています:

import (
    "github.com/golang/protobuf/proto"
    google "gitlab.com/go-yp/go-warning-codegeneration/models/protos/google/advertisement"
    "testing"
)

const (
    n = 1000000
)

func TestGoogleProtoMarshal(t *testing.T) {
    for i := 0; i < n; i++ {
        var popup = &google.Popup{
            Id:      uint32(i),
            Viewed:  true,
            Clicked: false,
        }

        // some deep nested function
        go func() {
            _, _ = proto.Marshal(popup)
        }()

        // @temporary hack
        go func() {
            popup.Clicked = true

            _, _ = proto.Marshal(popup)
        }()
    }
}

А от з github.com/gogo/protobuf при аналогічному тесті вже видає паніку.
Якщо розглянути згенерований код:

func (m *Popup) Marshal() (dAtA []byte, err error) {
    size := m.Size()
    dAtA = make([]byte, size)
    n, err := m.MarshalToSizedBuffer(dAtA[:size])
    if err != nil {
        return nil, err
    }
    return dAtA[:n], nil
}

func (m *Popup) MarshalToSizedBuffer(dAtA []byte) (int, error) {
    i := len(dAtA)

    //...

    if m.Clicked {
        i--
        dAtA[i] = 1
        i--
        dAtA[i] = 0x18
    }

    //...

    return len(dAtA) - i, nil
}

. Clicked = false, а серіалізація за умов m.Clicked = true is таким чином пориа 範囲外"
Звісно латку ми виправили і стало працювати навіть краще.

JSON はベンダーによって異なります



Бібліотека easyjson теж для серіалізації працює через додатковий код замість використання рефлексії.
簡単な JSON の使用は簡単ではありません.

package tests

import (
    "github.com/stretchr/testify/require"
    "gitlab.com/go-yp/go-warning-codegeneration/models/jsons/easy"
    "testing"
)

const (
    // language=JSON
    popupWithUnicodeContent = `{
        "title": "Some title with symbol \u201Dt",
        "description": "Any description"
    }`

    // language=JSON
    popupContent = `{
        "title": "Some title",
        "description": "Any description"
    }`
)

func TestEasyjsonUnmarshalJSON(t *testing.T) {
    content := make([]byte, 0, 1024)

    content = append(content[:0], popupWithUnicodeContent...)

    var popup easy.Popup

    unmarshalErr := popup.UnmarshalJSON(content)

    require.NoError(t, unmarshalErr)

    var expected = easy.Popup{
        Title:       "Some title with symbol \u201Dt",
        Description: "Any description",
    }

    require.Equal(t, expected, popup)

    content = append(content[:0], popupContent...)

    /**
    Failed:
    expected: easy.Popup{Title:"Some title with symbol ”t", Description:"Any description"}
    actual  : easy.Popup{Title:"Some title with symbol ”t", Description:" }y description"}
    */
    require.Equal(t, expected, popup)
}

easyjson を使用すると、問題が解決されます.

回答:



私はあなたのことをよく知っています.
Pриклади доступні в репозиторії .