Обережно, кодогенерація
Раніше вже писав про збільшення швидкодії та зменшення використання пам’яті після використання кодогенерації і ця історія має продовження, а саме розбір помилок.
Protobuf, перша помилка яка показала себе через пару тижнів після змін в коді
В проекті ми використовуємо офіційну бібліотеку Protobuf, яка підчас серіалізації використовує рефлексію і будує слайс байтів через append.
А потім я дізнався про Protocol Buffers for Go with Gadgets, бібліотеку-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 }
то стає зрозуміло, що розрахунок ємності слайсу відбувався за умов m.Clicked = false, а серіалізація за умов m.Clicked = true і таким чином отримав паніку «index out of range».
Звісно латку ми виправили і стало працювати навіть краще.
JSON, помилка у vendor бібліотеці
Бібліотека easyjson теж для серіалізації працює через додатковий код замість використання рефлексії.
Але після внесення в 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 цю помилку вже виправили.
Висновки
Звісно, хочеться використовувати оптимізовані бібліотеки, але стандартні краще протестовані та мають менше помилок.
Приклади доступні в репозиторії.
36 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів