Показаны сообщения с ярлыком структуры (struct) в Go. Показать все сообщения
Показаны сообщения с ярлыком структуры (struct) в Go. Показать все сообщения

среда, 30 декабря 2020 г.

Go style guides: необработанные строки, инициализация ссылок на структуры и карт

Используйте необработанные строковые литералы, чтобы избежать экранирования

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

Менее удачный вариант:

wantError := "unknown name:\"test\""

Более удачный вариант:

wantError := `unknown error:"test"`

Инициализация ссылок на структуры

Используйте &T{} вместо new(T) при инициализации ссылок на структуру, чтобы это было консистентно с инициализацией структуры.

Менее удачный вариант:

sval := T{Name: "foo"}

// неконсистентно
sptr := new(T)
sptr.Name = "bar"

Более удачный вариант:

sval := T{Name: "foo"}

sptr := &T{Name: "bar"}

Инициализация карт

Предпочитайте make(..) для пустых карт и карт, заполняемых программно. Это делает инициализацию карты визуально отличимой от объявления и упрощает добавление подсказок размера позже, если они доступны.

Менее удачный вариант:

// Объявление и инициализация визуально похожи.

var (
    // m1 безопасно читать и записывать;
    // m2 запаникует при записи.
    m1 = map[T1]T2{}
    m2 map[T1]T2
)

Более удачный вариант:

// Объявление и инициализация визуально различны.

var (
    // m1 безопасно читать и записывать;
    // m2 запаникует при записи.
    m1 = make(map[T1]T2)
    m2 map[T1]T2
)

По возможности предоставляйте подсказки емкости при инициализации карт с помощью make().

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

Менее удачный вариант:

m := make(map[T1]T2, 3)
m[k1] = v1
m[k2] = v2
m[k3] = v3

Более удачный вариант:

m := map[T1]T2{
    k1: v1,
    k2: v2,
    k3: v3,
}

Основное практическое правило - использовать литералы карты при добавлении фиксированного набора элементов во время инициализации, в противном случае используйте make (и укажите подсказку размера, если она доступна).


Читайте также:


вторник, 22 декабря 2020 г.

Go style guides: используйте имена полей для инициализации структур

Вы почти всегда должны указывать имена полей при инициализации структур. Теперь это навязывается командой go vet.

Менее удачный пример:

k := User{"John", "Doe", true}

Более удачный пример:

k := User{
    FirstName: "John",
    LastName: "Doe",
    Admin: true,
}

Исключение: имена полей могут быть опущены в тестовых таблицах, если имеется 3 или меньше полей.

tests := []struct{
    op Operation
    want string
}{
    {Add, "add"},
    {Subtract, "subtract"},
}


Читайте также:


понедельник, 21 декабря 2020 г.

Go style guides: встраивание в структуры

Встроенные типы (например, мьютексы) должны находиться в верхней части списка полей структуры, и должна быть пустая строка, отделяющая встроенные поля от обычных полей.

Менее удачный пример:

type Client struct {
    version int
    http.Client
}

Более удачный пример:

type Client struct {
    http.Client

    version int
}

Встраивание должно приносить ощутимые преимущества, например, добавление или расширение функциональности семантически подходящим способом. Оно должно делать это с нулевым негативным воздействием на пользователя (избегайте встраивания типов в общедоступные структуры).

Встраивание не должно:

  • Быть чисто косметическим или ориентированным на удобство.
  • Делать внешние типы более сложными для создания или использования.
  • Влиять на нулевые значения внешних типов. Если внешний тип имеет полезное нулевое значение, он все равно должен иметь полезное нулевое значение после внедрения внутреннего типа.
  • Предоставлять несвязанные функции или поля внешнего типа в качестве побочного эффекта внедрения внутреннего типа.
  • Выставлять неэкспортированные типы.
  • Влиять на семантику копирования внешних типов.
  • Изменять API внешнего типа или семантику типа.
  • Вставлять неканоническую форму внутреннего типа.
  • Раскрывать детали реализации внешнего типа.
  • Разрешать пользователям наблюдать или контролировать внутренние элементы типа.
  • Изменять общее поведение внутренних функций с помощью обертки таким образом, чтобы это могло удивить пользователей.

Проще говоря, внедряйте сознательно и намеренно. Хорошая лакмусовая бумажка: "будут ли все эти экспортированные внутренние методы/поля добавлены непосредственно во внешний тип"; если ответ - "некоторые" или "нет", не вставляйте внутренний тип - вместо этого используйте поле.

Менее удачный пример:

type A struct {
    // Неудачно: 
    // A.Lock() и A.Unlock() теперь доступны, 
    // не предоставляет функциональной выгоды,
    // разрешает пользователям контролировать 
    // детали внутреннего устройства A.
    sync.Mutex
}

Более удачный пример:

type countingWriteCloser struct {
    // Хорошо: функция Write() предоставлена
    // во внешнем слое для определенной
    // цели, а делегаты работают
    // с Write() внутреннего типа.
    io.WriteCloser

    count int
}

func (w *countingWriteCloser) Write(bs []byte) (int, error) {
    w.count += len(bs)
    return w.WriteCloser.Write(bs)
}

Менее удачный пример:

type Book struct {
    // Плохо: указатель 
    // изменяет полезность нулевого значения
    io.ReadWriter

    // другие поля
}

// позже

var b Book
b.Read(...)  // panic: nil pointer
b.String()   // panic: nil pointer
b.Write(...) // panic: nil pointer

Более удачный пример:

type Book struct {
    // Хорошо: имеет полезное нулевое значение
    bytes.Buffer

    // другие поля
}

// позже

var b Book
b.Read(...)  // ok
b.String()   // ok
b.Write(...) // ok

Менее удачный пример:

type Client struct {
    sync.Mutex
    sync.WaitGroup
    bytes.Buffer
    url.URL
}

Более удачный пример:

type Client struct {
    mtx sync.Mutex
    wg  sync.WaitGroup
    buf bytes.Buffer
    url url.URL
}


Читайте также:


пятница, 4 декабря 2020 г.

Go style guides: избегайте встраивания типов в общедоступные структуры

Встроенные типы содержат информацию о реализации, препятствуют развитию типов и приводят к неясной документации.

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

type AbstractList struct {}

// Add добавляет объект в список.
func (l *AbstractList) Add(e Entity) {
    // ...
}

// Remove удаляет объект из списка.
func (l *AbstractList) Remove(e Entity) {
    // ...
}

Неудачный вариант:

// ConcreteList - это список сущностей.
type ConcreteList struct {
    *AbstractList
}

Более удачный вариант:

// ConcreteList - это список сущностей.
type ConcreteList struct {
    list *AbstractList
}

// Add добавляет объект в список.
func (l *ConcreteList) Add(e Entity) {
    l.list.Add(e)
}

// Remove удаляет объект из списка.
func (l *ConcreteList) Remove(e Entity) {
    l.list.Remove(e)
}

Go позволяет встраивание типов как компромисс между наследованием и композицией. Внешний тип получает неявные копии методов встроенного типа. Эти методы по умолчанию делегируются тому же методу встроенного экземпляра.

Структура также получает поле с тем же именем, что и тип. Итак, если встроенный тип является общедоступным, поле является общедоступным. Для обеспечения обратной совместимости каждая будущая версия внешнего типа должна сохранять встроенный тип.

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

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

Менее удачный вариант:

// AbstractList - это обобщенная реализация
// для разного рода списков сущностей.
type AbstractList interface {
    Add(Entity)
    Remove(Entity)
}

// ConcreteList - это список сущностей.
type ConcreteList struct {
    AbstractList
}

Более удачный вариант:

// ConcreteList - это список сущностей.
type ConcreteList struct {
    list AbstractList
}

// Add добавляет объект в список.
func (l *ConcreteList) Add(e Entity) {
    l.list.Add(e)
}

// Remove удаляет объект из списка.
func (l *ConcreteList) Remove(e Entity) {
    l.list.Remove(e)
}

Либо со встроенной структурой, либо со встроенным интерфейсом, встроенный тип накладывает ограничения на развитие типа.

  • Добавление методов во встроенный интерфейс - критическое изменение.
  • Удаление методов из встроенной структуры - критическое изменение.
  • Удаление встроенного типа - критическое изменение.
  • Замена встроенного типа, даже альтернативой, удовлетворяющей тому же интерфейсу, является критическим изменением.

Хотя написание методов делегирования утомительно, дополнительные усилия скрывают детали реализации, оставляют больше возможностей для изменений, а также устраняют косвенное указание на обнаружение полного интерфейса List в документации.


Читайте также:


четверг, 28 мая 2020 г.

Нет JSON после маршалинга

Почему json.Marshal создает пустые структуры в текстовом выводе JSON?

type Person struct {
    name string
    age  int
}

p := Person{"Alice", 22}
jsonData, _ := json.Marshal(p)
fmt.Println(string(jsonData))

{}

Ответ

В выводе JSON будут присутствовать только экспортированные поля структуры Go.

type Person struct {
    Name string // Изменено на заглавную N
    Age  int    // Изменено на заглавную A
}

p := Person{"Alice", 22}

jsonData, _ := json.Marshal(p)
fmt.Println(string(jsonData))

{"Name":"Alice","Age":22}

Вы можете указать имя поля JSON явно с помощью тега json: .

type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

p := Person{"Alice", 22}

jsonData, _ := json.Marshal(p)
fmt.Println(string(jsonData))

{"name":"Alice","age":22}


Читайте также:


понедельник, 17 февраля 2020 г.

Создание, инициализация и сравнение структур в Golang

Структура (struct) - это типизированная коллекция полей, полезная для группировки данных в записи.

type Student struct {
    Name string
    Age  int
}

var a Student    // a == Student{"", 0}
a.Name = "Ivan"  // a == Student{"Ivan", 0}

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

Нулевое значение структуры по умолчанию задает нулевыми значениями соответствующих типов все ее поля.

Вы можете получить доступ к отдельным полям с точечной нотацией.

Два способа создания и инициализации новой структуры

Ключевое слово new может быть использовано для создания новой структуры. Возвращает указатель на вновь созданную структуру.

var pa *Student   // pa == nil
pa = new(Student) // pa == &Student{"", 0}
pa.Name = "Ivan"  // pa == &Student{"Ivan", 0}

Вы также можете создать и инициализировать структуру с помощью литерала структуры.

b := Student{ // b == Student{"Anton", 0}
    Name: "Anton",
}
    
pb := &Student{ // pb == &Student{"Anton", 8}
    Name: "Anton",
    Age:  8,
}

c := Student{"Kate", 5}    // c == Student{"Kate", 5}
d := Student{}             // d == Student{"", 0}

Список элементов, содержащий ключи, не обязательно должен иметь элемент для каждого поля структуры. Пропущенные поля получают нулевое значение для этого поля.

Список элементов, который не содержит ключей, должен перечислять элемент для каждого поля структуры в порядке, в котором поля объявлены.

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

Сравненение структур

Вы можете сравнивать значения структуры с операторами сравнения == и !=. Два значения равны, если их соответствующие поля равны.

d1 := Student{"Albert", 1}
d2 := Student{"Albert", 2}
fmt.Println(d1 == d2) // false


Читайте также:


понедельник, 27 мая 2019 г.

Спецификация Go: типы структур (struct types)

Структура (struct) - это последовательность именованных элементов, называемых полями, каждый из которых имеет имя и тип. Имена полей могут быть указаны явно (IdentifierList) или неявно (EmbeddedField). В структуре непустые имена полей должны быть уникальными.

StructType    = "struct" "{" { FieldDecl ";" } "}" .
FieldDecl     = (IdentifierList Type | EmbeddedField) [ Tag ] .
EmbeddedField = [ "*" ] TypeName .
Tag           = string_lit .

// Пустая структура.
struct {}

// Структура с 6 полями.
struct {
  x, y int
  u float32
  _ float32  // padding
  A *[]int
  F func()
}

Поле, объявленное с типом, но без явного имени поля, называется встроенным полем. Встроенное поле должно быть указано как имя типа T или как указатель на имя не интерфейсного типа *T, а само T может не быть указателем типа. Неквалифицированное (unqualified) имя типа действует как имя поля.

// Структура с 4 встроенными (embedded) полями типов T1, *T2, P.T3 и *P.T4
struct {
  T1        // имя поля T1
  *T2       // имя поля T2
  P.T3      // имя поля T3
  *P.T4     // имя поля T4
  x, y int  // имена полей x и y
}

Следующее объявление недопустимо, потому что имена полей должны быть уникальными в типе структуры:

struct {
  T     // конфликтует с встроенным (embedded) полем *T и *P.T
  *T    // конфликтует с встроенным (embedded) полем T и *P.T
  *P.T  // конфликтует с встроенным (embedded) полем T и *T
}

Поле или метод f встроенного поля в структуре x называется продвигаемым (promoted), если x.f является допустимым селектором, который обозначает это поле или метод f.

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

При заданном типе структуры S и определенном типе T продвигаемые методы включаются в набор методов структуры следующим образом:

  • Если S содержит встроенное поле T, наборы методов S и *S включают в себя продвигаемые методы с получателем T. Набор методов *S также включает продвигаемые методы с получателем *T.
  • Если S содержит встроенное поле *T, наборы методов S и *S оба включают продвигаемые методы с получателем T или *T.

За объявлением поля может следовать необязательный тег из строкового литерала, который становится атрибутом для всех полей в соответствующем объявлении поля. Пустая строка тега эквивалентна отсутствующему тегу. Теги становятся видимыми через интерфейс отражения (reflection interface) и принимают участие в идентификации типов для структур, но в остальном игнорируются.

struct {
  x, y float64 ""  // пустая строка в виде тега эквивалентна отсутствию тега
  name string  "люба строка допустима в виде тега"
  _    [4]byte "ceci n'est pas un champ de structure"
}

// Структура соответствующая буферу TimeStamp протокола.
// Теговые строки определяют номера поля буфера протокола;
// они следуют соглашению обозначенному в reflect пакете.
struct {
  microsec  uint64 `protobuf:"1"`
  serverIP6 uint64 `protobuf:"2"`
}


Читайте также: