вторник, 30 июня 2020 г.

Пакет errors в Golang

Пакет errors реализует функции для манипулирования ошибками.

Функция New создает ошибки, единственным содержимым которых является текстовое сообщение.

Функции Unwrap, Is и As работают с ошибками, которые могут переносить другие ошибки. Ошибка переносит другую ошибку, если ее тип имеет метод

Unwrap() error

Если e.Unwrap() возвращает ненулевую ошибку w, то мы говорим, что e переносит w.

Unwrap распаковывает упакованные ошибки. Если тип его аргумента имеет метод Unwrap, он вызывает метод один раз. В противном случае возвращается nil.

Простой способ создать упакованные ошибки - вызвать fmt.Errorf и применить глагол %w к аргументу ошибки:

errors.Unwrap(fmt.Errorf("... %w ...", ..., err, ...))

возвращает err.

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

if errors.Is(err, os.ErrExist)

предпочтительнее чем

if err == os.ErrExist

потому что первый преуспеет, если err обернет os.ErrExist.

As разворачивает свой первый аргумент последовательно ищет ошибку, которая может быть назначена второму аргументу, который должен быть указателем. Если это успешно, выполняется назначение и возвращает true. В противном случае возвращается false. Форма

var perr *os.PathError
if errors.As(err, &perr) {
    fmt.Println(perr.Path)
}

предпочтительнее чем

if perr, ok := err.(*os.PathError); ok {
    fmt.Println(perr.Path)
}

потому что первый будет успешным, если err обернет *os.PathError.

Пример использования пользовательского типа ошибки

package main

import (
    "fmt"
    "time"
)

// MyError это реализация error 
// которая включает время и сообщение.
type MyError struct {
    When time.Time
    What string
}

func (e MyError) Error() string {
    return fmt.Sprintf("%v: %v", e.When, e.What)
}

func oops() error {
    return MyError{
        time.Date(1989, 3, 15, 22, 30, 0, 0, time.UTC),
        "the file system has gone away",
    }
}

func main() {
    if err := oops(); err != nil {
        fmt.Println(err)
    }
}

Вывод:

1989-03-15 22:30:00 +0000 UTC: the file system has gone away

Функция New

func New(text string) error

New возвращает ошибку, которая форматируется как заданный текст. Каждый вызов New возвращает отдельное значение ошибки, даже если текст идентичен.

package main

import (
    "errors"
    "fmt"
)

func main() {
    err := errors.New("Неожиданная ошибка")
    if err != nil {
        fmt.Print(err)
    }
}

Вывод:

Неожиданная ошибка

Функция Errorf пакета fmt позволяет использовать функции форматирования пакета для создания описательных сообщений об ошибках.

package main

import (
    "fmt"
)

func main() {
    const name, id = "бублик", 117
    err := fmt.Errorf("пользователь %q (id %d) не найден", name, id)
    if err != nil {
        fmt.Print(err)
    }
}

Вывод:

пользователь "бублик" (id 117) не найден

Функция Is (с версии Go 1.13)

func Is(err, target error) bool

Is сообщает, соответствует ли target ошибке какая-либо ошибка в цепочке err.

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

Считается, что ошибка соответствует цели, если она равна этой цели или если она реализует метод Is(error) bool, такой что Is(target) возвращает true.

Тип ошибки может предоставлять метод Is, поэтому его можно рассматривать как эквивалент существующей ошибки. Например, если MyError определяет

func (m MyError) Is(target error) bool { return target == os.ErrExist }

затем Is (MyError{}, os.ErrExist) возвращает значение true. syscall.Errno.Is - пример в стандартной библиотеке.

Функция As (с версии Go 1.13)

func As(err error, target interface{}) bool

As находит первую ошибку в цепочке err, которая соответствует target, и если находит, то устанавливает target равным этому значению ошибки и возвращает true. В противном случае возвращается false.

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

Ошибка соответствует цели, если конкретное значение ошибки присваивается значению, на которое указывает цель, или если ошибка имеет метод As(interface{}) bool, такой что As(target) возвращает true. В последнем случае метод As отвечает за установку цели.

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

As паникует, если target не является ненулевым указателем ни на тип, который реализует error, ни на любой тип интерфейса.

Пример использования As

package main

import (
    "errors"
    "fmt"
    "os"
)

func main() {
    if _, err := os.Open("non-existing"); err != nil {
        var pathError *os.PathError
        if errors.As(err, &pathError) {
            fmt.Println("Failed at path:", pathError.Path)
        } else {
            fmt.Println(err)
        }
    }

}

Вывод:

Failed at path: non-existing

Функция Unwrap

func Unwrap(err error) error

Unwrap возвращает результат вызова метода Unwrap для err, если тип err содержит метод Unwrap, возвращающий ошибку. В противном случае Unwrap возвращает nil.


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


понедельник, 29 июня 2020 г.

Пакет hex в Golang

Пакет hex реализует шестнадцатеричное кодирование и декодирование.

Функция Decode

func Decode(dst, src []byte) (int, error)

Decode декодирует src в DecodedLen(len(src)) байты, возвращая фактическое количество байтов, записанных в dst.

Decode ожидает, что src содержит только шестнадцатеричные символы, а src имеет четную длину. Если входные данные искажены, Decode возвращает количество байтов, декодированных до ошибки.

Пример использования hex.Decode

package main

import (
    "encoding/hex"
    "fmt"
    "log"
)

func main() {
    src := []byte("48656c6c6f20476f7068657221")

    dst := make([]byte, hex.DecodedLen(len(src)))
    n, err := hex.Decode(dst, src)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("%s\n", dst[:n])

}

Вывод:

Hello Gopher!

Функция DecodeString

func DecodeString(s string) ([]byte, error)

DecodeString возвращает байты, представленные шестнадцатеричной строкой s.

DecodeString ожидает, что src содержит только шестнадцатеричные символы и что src имеет четную длину. Если входные данные искажены, DecodeString возвращает байты, декодированные до ошибки.

Пример использования hex.DecodeString

package main

import (
    "encoding/hex"
    "fmt"
    "log"
)

func main() {
    const s = "48656c6c6f20476f7068657221"
    decoded, err := hex.DecodeString(s)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("%s\n", decoded)

}

Вывод:

Hello Gopher!

Функция Encode

func Encode(dst, src []byte) int

Encode кодирует src в EncodedLen(len(src)) байты dst. Для удобства он возвращает количество байтов, записанных в dst, но это значение всегда EncodedLen(len(src)). Кодирование реализует шестнадцатеричное кодирование.

Пример использования hex.Encode

package main

import (
    "encoding/hex"
    "fmt"
)

func main() {
    src := []byte("Hello Gopher!")

    dst := make([]byte, hex.EncodedLen(len(src)))
    hex.Encode(dst, src)

    fmt.Printf("%s\n", dst)

}

Вывод:

48656c6c6f20476f7068657221

Функция EncodeToString

func EncodeToString(src []byte) string

EncodeToString возвращает шестнадцатеричное кодирование src.

Пример использования hex.EncodeToString

package main

import (
    "encoding/hex"
    "fmt"
)

func main() {
    src := []byte("Hello")
    encodedStr := hex.EncodeToString(src)

    fmt.Printf("%s\n", encodedStr)

}

Вывод:

48656c6c6f

Функция Dump

func Dump(data []byte) string

Dump возвращает строку, содержащую шестнадцатеричный дамп данных. Формат шестнадцатеричного дампа совпадает с выводом `hexdump -C` в командной строке.

Пример использования hex.Dump

package main

import (
    "encoding/hex"
    "fmt"
)

func main() {
    content := []byte("Go is an open source programming language.")

    fmt.Printf("%s", hex.Dump(content))

}

Вывод:

00000000  47 6f 20 69 73 20 61 6e  20 6f 70 65 6e 20 73 6f  |Go is an open so|
00000010  75 72 63 65 20 70 72 6f  67 72 61 6d 6d 69 6e 67  |urce programming|
00000020  20 6c 61 6e 67 75 61 67  65 2e                    | language.|

Функция Dumper

func Dumper(w io.Writer) io.WriteCloser

Dumper возвращает WriteCloser, который записывает шестнадцатеричный дамп всех записанных данных в w. Формат дампа соответствует выводу `hexdump -C` в командной строке.

Пример использования hex.Dumper

package main

import (
    "encoding/hex"
    "os"
)

func main() {
    lines := []string{
        "Go is an open source programming language.",
        "\n",
        "We encourage all Go users to subscribe to golang-announce.",
    }

    stdoutDumper := hex.Dumper(os.Stdout)

    defer stdoutDumper.Close()

    for _, line := range lines {
        stdoutDumper.Write([]byte(line))
    }

}

Вывод:

00000000  47 6f 20 69 73 20 61 6e  20 6f 70 65 6e 20 73 6f  |Go is an open so|
00000010  75 72 63 65 20 70 72 6f  67 72 61 6d 6d 69 6e 67  |urce programming|
00000020  20 6c 61 6e 67 75 61 67  65 2e 0a 57 65 20 65 6e  | language..We en|
00000030  63 6f 75 72 61 67 65 20  61 6c 6c 20 47 6f 20 75  |courage all Go u|
00000040  73 65 72 73 20 74 6f 20  73 75 62 73 63 72 69 62  |sers to subscrib|
00000050  65 20 74 6f 20 67 6f 6c  61 6e 67 2d 61 6e 6e 6f  |e to golang-anno|
00000060  75 6e 63 65 2e                                    |unce.|


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


воскресенье, 28 июня 2020 г.

Пакет xml в Golang - интерфейс Unmarshaler

Unmarshaler - это интерфейс, реализуемый объектами, которые могут демаршалировать описание своего XML элемента.

type Unmarshaler interface {
    UnmarshalXML(d *Decoder, start StartElement) error
}

UnmarshalXML декодирует один элемент XML, начиная с данного start элемента. Если он возвращает ошибку, внешний вызов Unmarshal останавливается и возвращает эту ошибку. UnmarshalXML должен использовать ровно один элемент XML. Одна из распространенных стратегий внедрения состоит в том, чтобы демаршалировать в отдельное значение с макетом, соответствующим ожидаемому XML используя d.DecodeElement, а затем копировать данные из этого значения в приемник. Другая распространенная стратегия - использовать d.Token для обработки XML объекта по одному токену за раз. UnmarshalXML не может использовать d.RawToken.

С помощью реализации интерфейса Unmarshaler можно задавать пользовательские способы демаршалинга XML объектов.

Пример реализации интерфейса Unmarshaler

 package main

import (
    "encoding/xml"
    "fmt"
    "log"
    "strings"
)

type Animal int

const (
    Unknown Animal = iota
    Gopher
    Zebra
)

func (a *Animal) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
    var s string
    if err := d.DecodeElement(&s, &start); err != nil {
        return err
    }
    switch strings.ToLower(s) {
    default:
        *a = Unknown
    case "gopher":
        *a = Gopher
    case "zebra":
        *a = Zebra
    }

    return nil
}

func main() {
    blob := `
    
        gopher
        armadillo
        zebra
        unknown
        gopher
        bee
        gopher
        zebra
    `
    var zoo struct {
        Animals []Animal `xml:"animal"`
    }
    if err := xml.Unmarshal([]byte(blob), &zoo); err != nil {
        log.Fatal(err)
    }

    census := make(map[Animal]int)
    for _, animal := range zoo.Animals {
        census[animal] += 1
    }

    fmt.Printf("Перепись зоопарка:\n* Суслики:     %d\n* Зебры:       %d\n* Неизвестные: %d\n",
        census[Gopher], census[Zebra], census[Unknown])

}

Вывод:

Перепись зоопарка:
* Суслики:     3
* Зебры:       2
* Неизвестные: 3


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


пятница, 26 июня 2020 г.

Пакет xml в Golang - метод Unmarshal

func Unmarshal(data []byte, v interface{}) error

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

Поскольку Unmarshal использует пакет reflect, он может назначать только экспортированные поля (в верхнем регистре). Unmarshal использует сравнение с учетом регистра для сопоставления имен элементов XML с тегами значений и структурных имен полей.

Unmarshal отображает XML-элемент в структуру, используя следующие правила. В правилах тег поля ссылается на значение, связанное с ключом 'xml' в теге поля структуры.

  • Если структура имеет поле типа []byte или string с тегом ",innerxml", Unmarshal накапливает сырой XML, вложенный в элемент в этой области. Остальные правила все еще применяются.
  • Если в структуре есть поле с именем XMLName типа Name, Unmarshal записывает имя элемента в этом поле.
  • Если у поля XMLName есть связанный тег формы "name" или "namespace-URL name", элемент XML должен иметь имя (и, возможно, пространство имен) или Unmarshal возвращает ошибку.
  • Если элемент XML имеет атрибут, имя которого соответствует имени структурного поля со связанным тегом, содержащим ",attr" или явное имя в теге структурного поля вида "name,attr", Unmarshal записывает значение атрибута в это поле.
  • Если элемент XML имеет атрибут, не обработанный предыдущим правилом и структура имеет поле со связанным тегом, содержащим ",any,attr", Unmarshal записывает значение атрибута в первое такое поле.
  • Если элемент XML содержит символьные данные, эти данные накапливается в первом структурном поле с тегом "chardata". Поле структуры может иметь тип []byte или string. Если такого поля нет, символьные данные отбрасываются.
  • Если элемент XML содержит комментарии, они накапливаются в первое поле структуры, которое имеет тег ",comment". Структурное поле может иметь тип []byte или string. Если нет такого поля, комментарии отбрасываются.
  • Если элемент XML содержит подэлемент, имя которого совпадает префикс тега, отформатированный как "a" или "a>b>c", unmarshal опустится в структуру XML в поисках элементов с заданными именами, и сопоставит самые внутренние элементы с этой структурой поле. Тег, начинающийся с ">", эквивалентен одному стартовому с именем поля, сопровождаемым ">".
  • Если элемент XML содержит подэлемент, имя которого совпадает с тегом XMLName поля структуры, а поле структуры не имеет явный тег name в соответствии с предыдущим правилом, демаршал картирует подэлемент в этом поле структуры.
  • Если элемент XML содержит подэлемент, имя которого соответствует поле без каких-либо флагов режима (",attr", ",chardata" и т. д.), Unmarshal сопоставляет подэлемент с этим структурным полем.
  • Если элемент XML содержит подэлемент, который не соответствует ни одному из вышеперечисленных правил и структура имеет поле с тегом ",any", unmarshal отображает вложенный элемент в это структурное поле.
  • Анонимное поле структуры обрабатывается так, как если бы поля его значения были частью внешней структуры.
  • Поле структуры с тэгом "-" никогда не записывается.

Если Unmarshal встречает тип поля, который реализует интерфейс Unmarshaler, Unmarshal вызывает свой метод UnmarshalXML для получения значения из элемента XML. В противном случае, если значение реализует encoding.TextUnmarshaler, Unmarshal вызывает метод UnmarshalText этого значения.

Unmarshal отображает элемент XML в string или []byte, сохраняя объединение символьных данных этого элемента в string или []byte. Сохраненный []byte никогда не равен nil.

Unmarshal отображает значение атрибута в string или []byte, сохраняя значение в строке или срезе.

Unmarshal отображает значение атрибута в Attr, сохраняя атрибут, включая его имя, в Attr.

Unmarshal отображает элемент XML или значение атрибута на срез, увеличивая длину среза и сопоставляя элемент или атрибут с вновь созданным значением.

Unmarshal отображает значение элемента XML или атрибута в логическое значение, устанавливая для него логическое значение, представленное строкой. Пустое пространство обрезается и игнорируется.

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

Unmarshal отображает элемент XML на имя путем записи имени элемента.

Unmarshal отображает элемент XML на указатель, устанавливая указатель на только что выделенное значение, а затем сопоставляя элемент с этим значением.

Отсутствующий элемент или пустое значение атрибута будет отменено как нулевое значение. Если поле является срезом, к нему будет добавлено нулевое значение. В противном случае поле будет установлено в его нулевое значение.

Пример использования Unmarshal

Этот пример демонстрирует демаршализацию выдержки XML в значение с некоторыми предустановленными полями. Обратите внимание, что поле Phone не изменено, а элемент XML <Company> игнорируется. Кроме того, поле Groups назначается с учетом пути элемента, указанного в его теге.

package main

import (
    "encoding/xml"
    "fmt"
)

func main() {
    type Email struct {
        Where string `xml:"where,attr"`
        Addr  string
    }
    type Address struct {
        City, State string
    }
    type Result struct {
        XMLName xml.Name `xml:"Person"`
        Name    string   `xml:"FullName"`
        Phone   string
        Email   []Email
        Groups  []string `xml:"Group>Value"`
        Address
    }
    v := Result{Name: "none", Phone: "none"}

    data := `
        <Person>
            <FullName>Grace R. Emlin</FullName>
            <Company>Example Inc.</Company>
            <Email where="home">
                <Addr>gre@example.com</Addr>
            </Email>
            <Email where='work'>
                <Addr>gre@work.com</Addr>
            </Email>
            <Group>
                <Value>Friends</Value>
                <Value>Squash</Value>
            </Group>
            <City>Hanga Roa</City>
            <State>Easter Island</State>
        </Person>
    `
    err := xml.Unmarshal([]byte(data), &v)
    if err != nil {
        fmt.Printf("error: %v", err)
        return
    }
    fmt.Printf("XMLName: %#v\n", v.XMLName)
    fmt.Printf("Name: %q\n", v.Name)
    fmt.Printf("Phone: %q\n", v.Phone)
    fmt.Printf("Email: %v\n", v.Email)
    fmt.Printf("Groups: %v\n", v.Groups)
    fmt.Printf("Address: %v\n", v.Address)
}

Вывод:

XMLName: xml.Name{Space:"", Local:"Person"}
Name: "Grace R. Emlin"
Phone: "none"
Email: [{home gre@example.com} {work gre@work.com}]
Groups: [Friends Squash]
Address: {Hanga Roa Easter Island}


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


четверг, 25 июня 2020 г.

Пакет xml в Golang - метод Marshal

Пакет xml реализует простой анализатор XML 1.0, который понимает пространства имен XML.

func Marshal(v interface{}) ([]byte, error)

Marshal возвращает XML кодированный v.

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

Имя для элементов XML взято в порядке предпочтения:

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

Элемент XML для структуры содержит маршалированные элементы для каждого из экспортируемых полей структуры, за исключением:

  • поле XMLName, описанное выше, опущено.
  • поле с тегом "-" опущено.
  • поле с тегом "name,attr" становится атрибутом с данным name в элементе XML.
  • поле с тегом "attr" становится атрибутом с именем поля в элементе XML.
  • поле с тэгом ",chardata" записывается как символьные данные, не как элемент XML.
  • поле с тегом ",cdata" записывается как символьные данные, обернутые в один или несколько тегов <![CDATA[ ... ]]>, а не как элемент XML.
  • поле с тэгом ",innerxml" записывается дословно, не подчинено к обычной процедуре маршалинга.
  • поле с тегом ",comment" записывается как комментарий XML, не объект для обычной процедуры маршалинга. Он не должен содержать строк "--" внутри него.
  • поле с тегом, включающим опцию "omitempty", опущено если значение поля пусто. Пустые значения false, 0, любой nil указатель или значение интерфейса и любой массив, срез, карта или строка нулевой длины.
  • анонимное структурное поле обрабатывается так, как если бы поля его значение было частью внешней структуры.
  • поле, реализующее Marshaler, пишется путем вызова его MarshalXML метода.
  • поле, реализующее encoding.TextMarshaler записывается путем кодирования результат его метода MarshalText как текст.

Если в поле используется тег"a>b>c", то элемент c будет вложен в родительские элементы a и b. Поля, которые появляются рядом друг с другом и называют одного и того же родителя, будут заключены в один элемент XML.

Если имя XML для поля структуры определяется как тегом поля, так и полем XMLName структуры, имена должны совпадать.

Marshal вернет ошибку, если будет предложено маршалировать канал, функцию или карту.

Метод MarshalIndent

func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)

MarshalIndent работает как Marshal, но каждый элемент XML начинается с новой строки с отступом, которая начинается с prefix и сопровождается одной или несколькими копиями indent в зависимости от глубины вложения.

Пример использования Marshal (MarshalIndent)

package main

import (
    "encoding/xml"
    "fmt"
    "os"
)

func main() {
    type Address struct {
        City, State string
    }
    type Person struct {
        XMLName   xml.Name `xml:"person"`
        Id        int      `xml:"id,attr"`
        FirstName string   `xml:"name>first"`
        LastName  string   `xml:"name>last"`
        Age       int      `xml:"age"`
        Height    float32  `xml:"height,omitempty"`
        Married   bool
        Address
        Comment string `xml:",comment"`
    }

    v := &Person{Id: 13, FirstName: "John", LastName: "Doe", Age: 42}
    v.Comment = " Need more details. "
    v.Address = Address{"Hanga Roa", "Easter Island"}

    output, err := xml.MarshalIndent(v, "  ", "    ")
    if err != nil {
        fmt.Printf("error: %v\n", err)
    }

    os.Stdout.Write(output)
}

Вывод:

  <person id="13">
      <name>
          <first>John</first>
          <last>Doe</last>
      </name>
      <age>42</age>
      <Married>false</Married>
      <City>Hanga Roa</City>
      <State>Easter Island</State>
      <!-- Need more details. -->
  </person>


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


понедельник, 22 июня 2020 г.

Пакет base64 в Golang

Пакет base64 реализует base64 кодирование, определенное в RFC 4648.

Самым простым способом использование данного пакета являются методы EncodeToString и DecodeString.

EncodeToString возвращает base64 кодированную строку из исходного среза байтов.

func (enc *Encoding) EncodeToString(src []byte) string

Пример использования EncodeToString:

package main

import (
    "encoding/base64"
    "fmt"
)

func main() {
    data := []byte("any + old & data")
    str := base64.StdEncoding.EncodeToString(data)
    fmt.Println(str)
}

Вывод:

YW55ICsgb2xkICYgZGF0YQ==

DecodeString возвращает байты, представляющие исходную base64 строку.

func (enc *Encoding) DecodeString(s string) ([]byte, error)

Пример использования DecodeString:

package main

import (
    "encoding/base64"
    "fmt"
)

func main() {
    str := "c29tZSBkYXRhIHdpdGggACBhbmQg77u/"
    data, err := base64.StdEncoding.DecodeString(str)
    if err != nil {
        fmt.Println("error:", err)
        return
    }
    fmt.Printf("%q\n", data)
}

Вывод:

"some data with \x00 and \ufeff"

Пример совместного использования EncodeToString и DecodeString:

package main

import (
    "encoding/base64"
    "fmt"
)

func main() {
    msg := "Hello, 世界"
    encoded := base64.StdEncoding.EncodeToString([]byte(msg))
    fmt.Println(encoded)
    decoded, err := base64.StdEncoding.DecodeString(encoded)
    if err != nil {
        fmt.Println("decode error:", err)
        return
    }
    fmt.Println(string(decoded))
}

Вывод:

SGVsbG8sIOS4lueVjA==
Hello, 世界

NewEncoder возвращает новый потоковый кодер base64. Данные, записанные в возвращенное устройство записи, будут закодированы с использованием enc, а затем записаны в w. Кодировки Base64 работают в 4-байтовых блоках; по окончании записи вызывающая сторона должна закрыть возвращаемый кодер, чтобы сбросить все частично записанные блоки.

func NewEncoder(enc *Encoding, w io.Writer) io.WriteCloser

Пример использования NewEncoder:

package main

import (
    "encoding/base64"
    "os"
)

func main() {
    input := []byte("foo\x00bar")
    encoder := base64.NewEncoder(base64.StdEncoding, os.Stdout)
    encoder.Write(input)
    // По окончании работы необходимо закрыть encoder, 
    // чтобы сбросить все частичные блоки.
    // Если вы закомментируете следующую строку, 
    // последний частичный блок "r" не будет закодирован.
    encoder.Close()
}

Вывод:

Zm9vAGJhcg==


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


пятница, 19 июня 2020 г.

Пакет csv в Golang

Пакет csv читает и записывает файлы значений, разделенных запятыми (CSV). Есть много видов файлов CSV; этот пакет поддерживает формат, описанный в RFC 4180.

CSV-файл содержит ноль или более записей из одного или нескольких полей на запись. Каждая запись отделяется символом новой строки. Финальная запись может опционально сопровождаться символом новой строки.

field1,field2,field3

Пустое пространство считается частью поля.

Символы возврата каретки до символов новой строки будут молча удалены.

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

Поля, которые начинаются и заканчиваются символом кавычки ", называются квотированными полями (quoted-field). Начальная и конечная кавычки не являются частью поля.

Источник:

normal string,"quoted-field"

получается в результате в полях

{`normal string`, `quoted-field`}

В кавычках символ кавычки, за которым следует второй символ кавычки, считается одинарной кавычкой.

"the ""word"" is true","a ""quoted-field"""

получается в результате

{`the "word" is true`, `a "quoted-field"`}

Новые строки и запятые могут быть включены в кавычки

"Multi-line
field","comma is ,"

получается в результате

{`Multi-line
field`, `comma is ,`}

csv.Reader

Reader читает записи из файла в формате CSV.

По возвращению NewReader, Reader ожидает ввод, соответствующий RFC 4180. Экспортированные поля можно изменить, чтобы настроить детали перед первым вызовом Read или ReadAll.

Reader преобразует все последовательности \r\n в своем входном файле в обычные \n, в том числе в значения многострочных полей, чтобы возвращаемые данные не зависели от того, какое соглашение о конце строки использует входной файл.

package main

import (
    "encoding/csv"
    "fmt"
    "io"
    "log"
    "strings"
)

func main() {
    in := `first_name,last_name,username
"Rob","Pike",rob
Ken,Thompson,ken
"Robert","Griesemer","gri"
`
    r := csv.NewReader(strings.NewReader(in))

    for {
        record, err := r.Read()
        if err == io.EOF {
            break
        }
        if err != nil {
            log.Fatal(err)
        }

        fmt.Println(record)
    }
}

Read читает одну запись (часть полей) из r. Если запись содержит неожиданное количество полей, Read возвращает запись вместе с ошибкой ErrFieldCount. За исключением этого случая, Read всегда возвращает либо ненулевую запись, либо ненулевую ошибку, но не обе. Если не осталось данных для чтения, Read возвращает nil, io.EOF. Если ReuseRecord имеет значение true, возвращенный срез может быть разделен между несколькими вызовами Read.

В этом примере показано, как можно настроить csv.Reader для обработки других типов файлов CSV.

package main

import (
    "encoding/csv"
    "fmt"
    "log"
    "strings"
)

func main() {
    in := `first_name;last_name;username
"Rob";"Pike";rob
# lines beginning with a # character are ignored
Ken;Thompson;ken
"Robert";"Griesemer";"gri"
`
    r := csv.NewReader(strings.NewReader(in))
    r.Comma = ';'
    r.Comment = '#'

    records, err := r.ReadAll()
    if err != nil {
        log.Fatal(err)
    }

    fmt.Print(records)
}

ReadAll читает все оставшиеся записи из r. Каждая запись представляет собой срез полей. Успешный вызов возвращает err == nil, а не err == io.EOF. Поскольку ReadAll определен для чтения до EOF, он не рассматривает конец файла как ошибку, о которой будет сообщено.

csv.Writer

type Writer struct {
    Comma   rune 
    UseCRLF bool 
}

Writer пишет записи с использованием CSV-кодировки.

Как возвращается NewWriter, Writer записывает записи, оканчивающиеся символом новой строки, и использует ',' в качестве разделителя полей. Экспортируемые поля можно изменить, чтобы настроить детали перед первым вызовом Write или WriteAll.

Comma - это разделитель полей.

Если UseCRLF имеет значение true, Writer заканчивает каждую выходную строку с \r\n вместо \n.

Записи отдельных записей буферизируются. После того, как все данные записаны, клиент должен вызвать метод Flush, чтобы гарантировать, что все данные были перенаправлены в базовый io.Writer. Любые возникшие ошибки следует проверить, вызвав метод Error.

Write записывает одну запись CSV в w вместе со всеми необходимыми кавычками. Запись представляет собой срез строк, где каждая строка представляет собой одно поле. Записи буферизуются, поэтому в конце концов необходимо вызвать Flush, чтобы запись была записана в базовый io.Writer.

package main

import (
    "encoding/csv"
    "log"
    "os"
)

func main() {
    records := [][]string{
        {"first_name", "last_name", "username"},
        {"Rob", "Pike", "rob"},
        {"Ken", "Thompson", "ken"},
        {"Robert", "Griesemer", "gri"},
    }

    w := csv.NewWriter(os.Stdout)

    for _, record := range records {
        if err := w.Write(record); err != nil {
            log.Fatalln("error writing record to csv:", err)
        }
    }

    // Записываем любые буферизованные данные в подлежащий writer (стандартный вывод).
    w.Flush()

    if err := w.Error(); err != nil {
        log.Fatal(err)
    }
}

WriteAll записывает несколько CSV-записей в w с помощью Write, а затем вызывает Flush, возвращая любую ошибку из Flush.

package main

import (
    "encoding/csv"
    "log"
    "os"
)

func main() {
    records := [][]string{
        {"first_name", "last_name", "username"},
        {"Rob", "Pike", "rob"},
        {"Ken", "Thompson", "ken"},
        {"Robert", "Griesemer", "gri"},
    }

    w := csv.NewWriter(os.Stdout)
    w.WriteAll(records) // вызывает Flush внутри

    if err := w.Error(); err != nil {
        log.Fatalln("error writing csv:", err)
    }
}


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


среда, 17 июня 2020 г.

Пакет bson для работы с MongoDB в Golang

Пакет bson (go.mongodb.org/mongo-driver/bson) - это библиотека для чтения, записи и управления BSON. BSON - это двоичный формат сериализации, используемый для хранения документов и выполнения удаленных вызовов процедур в MongoDB. Библиотека BSON обрабатывает маршалинг и демаршаллинг значений через настраиваемую систему кодеков.

Raw BSON

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

Пример:

var raw bson.Raw = ... // байты откуда-либо
err := raw.Validate()
if err != nil { return err }
val := raw.Lookup("foo")
i32, ok := val.Int32OK()
// делаем что-нибудь с i32...

Нативные Go типы

Типы D и M, определенные в этом пакете, могут использоваться для построения представлений BSON с использованием собственных типов Go. D - это срез, а M - это карта.

Пример:

bson.D{{"foo", "bar"}, {"hello", "world"}, {"pi", 3.14159}}
bson.M{"foo": "bar", "hello": "world", "pi": 3.14159}

При декодировании BSON в D или M применяются следующие сопоставления типов при демаршаллинге:

  1. BSON int32 демаршаллизируется в int32.
  2. BSON int64 демаршаллизируется в int64.
  3. BSON double демаршаллизируется в float64.
  4. BSON string демаршализируется в string.
  5. BSON boolean демаршализируется в bool.
  6. BSON embedded document демаршализируется в родительский тип (т.е. D для D, M для M).
  7. BSON array демаршализируется в bson.A.
  8. BSON ObjectId демаршализируется в primitive.ObjectID.
  9. BSON datetime демаршализируется в primitive.Datetime.
  10. BSON binary демаршализируется в primitive.Binary.
  11. BSON regular expression демаршализируется в primitive.Regex.
  12. BSON JavaScript демаршализируется в primitive.JavaScript.
  13. BSON code демаршализируется в primitive.CodeWithScope.
  14. BSON timestamp демаршализируется в primitive.Timestamp.
  15. BSON 128-bit decimal демаршализируется в primitive.Decimal128.
  16. BSON min key демаршализируется в primitive.MinKey.
  17. BSON max key демаршализируется в primitive.MaxKey.
  18. BSON undefined демаршализируется в primitive.Undefined.
  19. BSON null демаршализируется в primitive.Null.
  20. BSON DBPointer демаршализируется в primitive.DBPointer.
  21. BSON symbol демаршализируется в primitive.Symbol.

Вышеуказанные сопоставления также применяются при маршалинге D или M к BSON. Некоторые другие полезные сопоставления маршаллинга:

  1. time.Time маршализируется в BSON datetime.
  2. int8, int16, and int32 маршализируется в BSON int32.
  3. int маршалируется в BSON int32, если значение находится между math.MinInt32 и math.MaxInt32 включительно, и BSON int64 в противном случае.
  4. int64 маршалируется в BSON int64.
  5. uint8 и uint16 маршалируется в BSON int32.
  6. uint, uint32 и uint64 маршалируются в BSON int32, если значение находится между math.MinInt32 и math.MaxInt32, включительно, в BSON int64 в противном случае.
  7. BSON null значения будут разбиты на нулевое значение поля (например, демаршализирование BSON null в строку даст пустую строку.).

Структуры

Структуры можно маршалировать/демаршалировать в/из BSON. При преобразовании структур в/из BSON применяются следующие правила:

  1. Только экспортированные поля в структурах будут маршалированы или демаршалированы.
  2. При маршалинге структуры каждое поле будет в нижнем регистре генерировать ключ для соответствующего элемента BSON.
    Например, поле структуры с именем "Foo" будет генерировать ключ "foo". Это можно переопределить с помощью тега struct (например, `bson:"fooField"` для генерации ключа "fooField" вместо этого).
  3. Встроенное поле структуры маршалируется как вложенный документ. Ключом будет строчное имя типа поля.
  4. Поле указателя маршалируется как базовый тип, если указатель не nil. Если указатель равен nil, это маршалируется как BSON null значение.
  5. При отмене маршалинга поле типа interface{} будет соответствовать отображениям типа D/M, перечисленным выше. Документы BSON демаршалированные в поле interface{} будет демаршалированны как D.

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

  1. omitempty: если в поле указан тег структуры omitempty, поле не будет маршалироваться, если установлено нулевое значение. По умолчанию поле структуры считается пустым, только если тип поля реализует Zeroer интерфейс и метод IsZero возвращает true. Структурные поля типов, которые не реализуют Zeroer, всегда маршалируются как вложенные документы. Этот тег должен использоваться для всех значений среза и карты.
  2. minsize: если тег структуры minsize указан в поле типа int64, uint, uint32 или uint64 и значением поле может помещаться в int32 со знаком, поле будет сериализовано как BSON int32, а не BSON int64. Для других типов, этот тег игнорируется.
  3. truncate: если структурный тег truncate указан в поле с числовым типом, не являющимся float, BSON double демаршалированный в это поле будет окреглен к десятичной точке. Например, если 3.14 демаршалируется в поле типа int, он будет демаршалирован как 3. Если этот тег не указан, декодер выдаст ошибку, если значение не может быть декодировано без потери точности. Для float64 или нечисловых типов этот тег игнорируется.
  4. inline: если структурный тег inline указан для поля struct или map, поле будет "уплощено" при маршаллинге и "разуплощено" при демаршаллинге. Это означает, что все поля в этой структуре/карте будут поднято на один уровень и станет полями верхнего уровня, а не полями во вложенном документе. Например, если поле карты с именем "Map" со значением map map[string]interface{}{"foo": "bar"} является inline, результирующий документ будет {"foo": "bar"} вместо {"map": {"foo": "bar"}}. В структуре может быть только одно inline поле map. Если есть дублированные поля в результирующем документе, когда inline поле маршалируется, будет возвращена ошибка. Этот тег может использоваться с полями, которые являются указателями на структуры. Если поле встроенного указателя равно nil, оно не будет маршалировано. Для полей, которые не являются картами или структурами, этот тег игнорируется.

Маршаллинг и демаршаллинг

Маршаллинг и демаршаллинг можно выполнять вручную с помощью семейства функций Marshal и Unmarshal.


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


вторник, 16 июня 2020 г.

Работа с MongoDB в Golang

MongoDB поддерживает драйвер для Go - go.mongodb.org/mongo-driver.

Требования

  • Go 1.10 или выше
  • MongoDB 2.6 или выше

Установка

Рекомендуемый способ начать использовать драйвер MongoDB Go - использовать модули go для установки зависимости в вашем проекте. Это можно сделать, либо импортировав пакеты с go.mongodb.org/mongo-driver и выполнив шаг сборки, чтобы установить зависимость, либо явно запустив

go get go.mongodb.org/mongo-driver/mongo

При использовании версии Go, которая не поддерживает модули, драйвер можно установить с помощью команды dep, запустив

dep ensure -add "go.mongodb.org/mongo-driver/mongo"

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

Чтобы начать работу с драйвером, импортируйте пакет mongo, создайте mongo.Client:

import (
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)

client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://localhost:27017"))

И подключите его к вашему работающему серверу MongoDB:

ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
err = client.Connect(ctx)

Чтобы сделать это за один шаг, вы можете использовать функцию Connect:

ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017"))

Вызов Connect не блокирует обнаружение сервера. Если вы хотите узнать, был ли сервер MongoDB найден и подключен, используйте метод Ping:

ctx, _ = context.WithTimeout(context.Background(), 2*time.Second)
err = client.Ping(ctx, readpref.Primary())

Чтобы вставить документ в коллекцию, сначала получите базу данных, а затем экземпляр коллекции с клиента:

collection := client.Database("testing").Collection("numbers")

Затем экземпляр Collection можно использовать для вставки документов:

ctx, _ = context.WithTimeout(context.Background(), 5*time.Second)
res, err := collection.InsertOne(ctx, bson.M{"name": "pi", "value": 3.14159})
id := res.InsertedID

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

ctx, _ = context.WithTimeout(context.Background(), 30*time.Second)
cur, err := collection.Find(ctx, bson.D{})
if err != nil { log.Fatal(err) }
defer cur.Close(ctx)
for cur.Next(ctx) {
    var result bson.M
    err := cur.Decode(&result)
    if err != nil { log.Fatal(err) }
    // сделать что-то с результатом....
}
if err := cur.Err(); err != nil {
    log.Fatal(err)
}

Для методов, которые возвращают один элемент, возвращается экземпляр SingleResult:

var result struct {
    Value float64
}
filter := bson.M{"name": "pi"}
ctx, _ = context.WithTimeout(context.Background(), 5*time.Second)
err = collection.FindOne(ctx, filter).Decode(&result)
if err != nil {
    log.Fatal(err)
}
// Делаем что-то с результатом...


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


четверг, 11 июня 2020 г.

Руководство по стилю для пакетов Golang

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

Цель этого поста - рассказать об общепринятых практиках, а не устанавливать правила. Вы всегда должны будете использовать свое собственное суждение, чтобы выбрать наиболее элегантное решение для вашего конкретного случая.

Пакеты

Весь код Go организован в пакеты. Пакет в Go - это просто каталог/папка с одним или несколькими файлами .go внутри. Пакеты Go обеспечивают изоляцию и организацию кода, аналогично тому, как каталоги/папки организуют файлы на компьютере.

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

Организация пакетов

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

Используйте несколько файлов

Пакет - это каталог с одним или несколькими файлами Go. Не стесняйтесь разделить ваш код на столько файлов, сколько логически целесообразно для оптимальной читабельности.

Например, пакет HTTP может быть разделен на разные файлы в соответствии с аспектом HTTP, который обрабатывает файл. В следующем примере пакет HTTP разбит на несколько файлов: типы и код заголовка, типы и код cookie, фактическая реализация HTTP и документация пакета.

- doc.go     // пакетная документация
- headers.go // Типы и код заголовков HTTP
- cookies.go // Типы и код cookies HTTP
- http.go    // Реализация HTTP-клиента, 
             // типы запросов и ответов и т. д.

Держите типы близко

Как правило, держите типы ближе к месту их использования. Это позволяет любому сопровождающему (а не только первоначальному автору) легко найти тип. Хорошее место для типа структуры Header может быть в headers.go.

$ cat headers.go
package http

// Header represents an HTTP header.
type Header struct {...}

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

Организовать по ответственности

Обычной практикой в ​​других языках является объединение типов в пакет, называемый models или types. В Go мы организуем код по их функциональным обязанностям.

package models // НЕ ДЕЛАЙТЕ ЭТОГО !!!

// User represents a user in the system.
type User struct {...}

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

package mngtservice

// User represents a user in the system.
type User struct {...}

func UsersByQuery(ctx context.Context, q *Query) ([]*User, *Iterator, error)

func UserIDByEmail(ctx context.Context, email string) (int64, error)

Оптимизировать для godoc

Это хорошее упражнение - использовать godoc на ранних этапах разработки API вашего пакета, чтобы увидеть, как ваши концепции будут отображаться в документации. Иногда визуализация также влияет на дизайн. Godoc - это способ, которым ваши пользователи будут потреблять пакет, поэтому можно настроить вещи, чтобы сделать их более доступными. Запустите godoc -http=<hostport>, чтобы запустить сервер godoc локально.

Приведите примеры, чтобы заполнить пробелы

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

$ godoc cloud.google.com/go/datastore
func NewClient(ctx context.Context, projectID string, opts ...option.ClientOption) (*Client, error)
...

NewClient работает с option.ClientOptions, но ни пакет datastore, ни пакет option не экспортируют все типы option.

$ godoc google.golang.org/extraoption
func WithCustomValue(v string) option.ClientOption
...

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

Примеры - это хороший способ улучшить видимость менее обнаруживаемого пакета. Например, пример для datastore.NewClient может ссылаться на пакет extraoption.

Не экспортировать из main

Идентификатор может быть экспортирован, чтобы разрешить доступ к нему из другого пакета.

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

Исключениями из этого правила могут быть main пакеты, встроенные в плагин .so, .a или Go. В таких случаях код Go может использоваться из других языков с помощью функции экспорта cgo, и для этого требуются идентификаторы экспорта.

Наименование пакета

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

Только строчные

Имена пакетов должны быть строчными. Не используйте snake_case или camelCase в именах пакетов.

Короткие, но представительные имена

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

Избегайте слишком широких имен пакетов, таких как "common" и "util".

import "pkgs.org/common" // НЕ ДЕЛАЙТЕ ТАК !!!

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

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

Очистите пути импорта

Старайтесь не раскрывать свою пользовательскую структуру хранилища пользователям. Избегайте использования разделов src/, pkg/ в ваших путях импорта.

github.com/user/repo/src/httputil   // НЕ ДЕЛАЙТЕ ЭТОГО, ИЗБЕГАЙТЕ SRC!!

github.com/user/repo/gosrc/httputil // НЕ ДЕЛАЙТЕ ЭТОГО, ИЗБЕГАЙТЕ GOSRC!!

Нет множественного числа

На самом деле, имена пакетов не во множественном числе. Это удивительно для программистов, которые пришли из других языков и сохраняют старую привычку использовать множественные имена. Не называйте пакет httputils, а используйте httputil

package httputils  // НЕ ДЕЛАЙТЕ ЭТОГО, ИСПОЛЬЗУЙТЕ ЕДИНСТВЕННОЕ ЧИСЛО!!

Переименования должны следовать тем же правилам

Если вы импортируете более одного пакета с одинаковым именем, вы можете локально переименовать имена пакетов. Переименования должны следовать тем же правилам, которые указаны в этом посте. Нет правила, какой пакет вы должны переименовать. Если вы переименовываете стандартную библиотеку пакетов, было бы неплохо добавить префикс go, чтобы сделать самодостаточным документом, что это пакет "стандартной библиотеки Go", например, gourl, goioutil.

import (
    gourl "net/url"

    "myother.com/url"
)

Обеспечить vanity URL

go get поддерживает получение пакетов по URL, который отличается от URL репозитория пакета. Эти URL-адреса называются vanity и требуют, чтобы вы обслуживали страницу с конкретными метатегами, которые распознают инструменты Go. Вы можете обслуживать пакет с пользовательским доменом и путем, используя vanity URL.

Например,

$ go get cloud.google.com/go/datastore

за кулисами извлекает исходный код из https://code.googlesource.com/gocloud и помещает его в свое рабочее пространство в папке $GOPATH/src/cloud.google.com/go/datastore.

Учитывая, что code.googlesource.com/gocloud уже обслуживает этот пакет, можно ли получить пакет с этого URL? Ответ - нет, если вы введете vanity URL.

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

package datastore // import "cloud.google.com/go/datastore"

Пакетная документация

Всегда документируйте пакет. Документация пакета - это комментарий верхнего уровня, непосредственно предшествующий предложению пакета. Для неосновных пакетов godoc всегда начинается с "Package {pkgname}" и следует с описанием. Для основных (main) пакетов документация должна объяснять двоичный файл.

// Package ioutil implements some I/O utility functions.
package ioutil

// Command gops lists all the processes running on your system.
package main

// Sample helloworld demonstrates how to use x.
package main

Используйте doc.go

Иногда пакетные документы могут быть очень длинными, особенно если они содержат подробную информацию об использовании и рекомендации. Переместите godoc пакета в файл doc.go.


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


вторник, 9 июня 2020 г.

Система типов Golang

Работать с новым языком - настоящая борьба, особенно если тип не похож на тот, что вы видели ранее.

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

Ход программы в первую очередь, типы потом

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

Роб Пайк, один из создателей Go, поделился своими мыслями о разделении данных и поведения:

... более важной идеей является разделение понятий: данные и поведение - это две разные концепции в Go, не объединенные в одно понятие "класс". - Роб Пайк

Go уделяет большое внимание модели данных. Структуры (которые являются агрегатными типами) обеспечивают легкий способ представления данных. Отсутствие иерархии типов помогает структурам оставаться тонкими, структуры никогда не представляют слои и уровни унаследованного поведения, а только поля данных. Это делает их ближе к структурам данных, которые они представляют, а не к поведению, которое они дополнительно обеспечивают.

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

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

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

Встраивание на самом деле мало чем отличается от наличия обычного поля, но позволяет встраивать методы встроенного типа непосредственно в новый тип.

Рассмотрим следующую структуру:

type File struct {
    sync.Mutex
    rw io.ReadWriter
}

Затем объекты File будут напрямую иметь доступ к методам sync.Mutex:

f := File{}
f.Lock()

Это ничем не отличается от предоставления методов Lock и Unlock из File и заставляет их работать в поле sync.Mutex. Это не использование подклассов.

Полиморфизм

Из-за отсутствия подкласса полиморфизм в Go достигается только с помощью интерфейсов. Методы отправляются во время выполнения в зависимости от конкретного типа.

var r io.Reader

r = bytes.NewBufferString("hello")

buf := make([]byte, 2048)
if _, err := r.Read(buf); err != nil {
    log.Fatal(err)
}

Выше r.Read будет отправлен в (*Buffer).Read.

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

type Animal struct {}

type Dog struct {
    Animal
}


func main() {
    var a Animal
    a = Dog{}
}
// НЕ КОМПИЛИРУЕТСЯ

Нет явных реализаций интерфейса

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

Как это масштабируется? Возможно ли случайно реализовать интерфейсы, которые вы не хотели реализовывать? Хотя механически, возможно, для пользовательской базы никогда не было проблемой, чтобы реализация одного интерфейса была ошибочно принята за другой. Интерфейсы часто сильно различаются, или это признак того, что может не потребоваться второй интерфейс, если два интерфейса очень похожи.

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

Нет заголовочных файлов

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

Шаблоны именования, основанные на соглашениях об инверсии зависимостей других языков, являются анти-шаблонами в Go. Присвоение имен таким стилем не вписывается в экосистему Go.

type Banana interface {
    //...
}
type BananaImpl struct {}
// АНТИ-ШАБЛОН В GO

Еще одна вещь ... Go предпочитают небольшие интерфейсы. Вы всегда можете встраивать интерфейсы позже, но вы не можете разложить большие.

Нет конструкторов

Go не имеет конструкторов, поэтому не позволяет переопределить конструктор по умолчанию. Конструкция по умолчанию всегда приводит к полям с нулевым значением.

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

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

func NewRequest(method, url string, body io.Reader) (*Request, error)

NewRequest проверяет method и url, устанавливает правильные внутренние параметры для чтения из данного тела и возвращает запрос.

Nil приемники

Nil - это значение, nil-значение типа может реализовывать поведение. Разработчики не должны предоставлять конкретные типы для реализации отсуствия операций (noop).

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

Ниже регистрация событий будет noop для значений nil.

type Event struct {}

func (e *Event) Log(msg string) {
    if e == nil {
        return
    }
    // Записать сообщение о событии...
}

Затем пользователь может использовать значение nil для поведения noop:

var e *Event
e.Log("this is a message")

Нет дженериков

В Go нет дженериков.

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


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


воскресенье, 7 июня 2020 г.

Встроенные отсрочки в Golang

Ключевое слово defer в Go позволяет нам запланировать запуск функций до возврата родительской функции. Несколько функций могут быть отложены (deferred) в родительской функции. defer часто используется для очистки ресурсов, завершения задач в области функций и т. п. Отложенные функции отлично подходят для удобства обслуживания. Откладывая, например, мы уменьшаем риск забыть закрыть файл в остальной части программы:

func main() {
    f, err := os.Open("hello.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()

    // Остальная часть программы...
}

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

Как работает defer

Defer обрабатывает несколько функций, складывая их, и, следовательно, запускает их в порядке LIFO. Чем больше у вас отложенных функций, тем больше будет стек.

func main() {
    for i := 0; i < 5; i++ {
        defer fmt.Printf("%v ", i)
    }
}

Приведенная выше программа выведет “4 3 2 1 0 ”, потому что последняя отложенная функция будет выполняться первой.

Когда функция откладывается, переменные, к которым она обращается, сохраняются в качестве аргументов. Для каждой отложенной функции компилятор генерирует вызов runtime.deferproc на месте вызова и вызывает в runtime.deferreturn точку возврата функции.

0: func run() {
1:    defer foo()
2:    defer bar()
3:
4:    fmt.Println("hello")
5: }

Компилятор сгенерирует код, подобный приведенному ниже, для программы выше:

runtime.deferproc(foo) // генерируется для строки 1
runtime.deferproc(bar) // генерируется для строки 2

// Другой код...

runtime.deferreturn() // генерируется для строки 5

Производительность defer

defer раньше требовала двух дорогих вызовов времени выполнения, описанных выше. Это делало отложенные функции значительно более дорогими, чем неотложенные функции. Например, рассмотрим блокировку и разблокировку sync.Mutex с отложенным и не отложенным использованием.

var mu sync.Mutex
mu.Lock()

defer mu.Unlock()

Программа выше будет работать в 1,7 раза медленнее, чем не отложенная версия. Несмотря на то, что для блокировки и разблокировки мьютекса с помощью отсрочки требуется всего ~25-30 наносекунд, это имеет значение при широкомасштабном использовании или в случаях, когда вызов функции должен быть завершен менее чем за 10 наносекунд.

BenchmarkMutexNotDeferred-8    125341258          9.55 ns/op        0 B/op        0 allocs/op
BenchmarkMutexDeferred-8       45980846         26.6 ns/op        0 B/op        0 allocs/op

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

Встраивание отложенных функций

В последних нескольких версиях Go были постепенные улучшения производительности defer. Но с Go 1.14 в некоторых распространенных случаях значительно улучшится производительность. Компилятор сгенерирует код для встраивания некоторых отложенных функций в точках возврата. С этим улучшением вызов некоторых отложенных функций будет стоить столько же, сколько и обычный вызов функции.

0: func run() {
1:    defer foo()
2:    defer bar()
3:
4:    fmt.Println("hello")
5: }

С новыми улучшениями приведенный выше код сгенерирует:

// Другой код...

bar() // генерируется для строки 5
foo() // генерируется для строки 5

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

Вот пример выполнения на Go 1.14beta с примером блокировки/разблокировки мьютекса выше. Отложенные и неотложенные версии теперь работают очень похоже:

BenchmarkMutexNotDeferred-8    123710856          9.64 ns/op        0 B/op        0 allocs/op
BenchmarkMutexDeferred-8       104815354         11.5 ns/op        0 B/op        0 allocs/op

Go 1.14 - это хорошая версия для переоценки отсрочки, если вы избегаете defer для увеличения производительности.


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


пятница, 5 июня 2020 г.

Пакет strconv в Golang

Пакет strconv реализует преобразования в и из строковых представлений основных типов данных.

Числовые преобразования

Наиболее распространенные числовые преобразования - это Atoi (string в int) и Itoa (int в string).

i, err := strconv.Atoi("-42")
s := strconv.Itoa(-42)

Они предполагают десятичность и тип Go int.

ParseBool, ParseFloat, ParseInt и ParseUint преобразуют строки в значения:

b, err := strconv.ParseBool("true")
f, err := strconv.ParseFloat("3.1415", 64)
i, err := strconv.ParseInt("-42", 10, 64)
u, err := strconv.ParseUint("42", 10, 64)

Функции parse возвращают самый широкий тип (float64, int64 и uint64), но если аргумент size указывает более узкую ширину, результат может быть преобразован в этот более узкий тип без потери данных:

s := "2147483647" // наибольшее int32
i64, err := strconv.ParseInt(s, 10, 32)
...
i := int32(i64)

FormatBool, FormatFloat, FormatInt и FormatUint преобразуют значения в строки:

s := strconv.FormatBool(true)
s := strconv.FormatFloat(3.1415, 'E', -1, 64)
s := strconv.FormatInt(-42, 16)
s := strconv.FormatUint(42, 16)

AppendBool, AppendFloat, AppendInt и AppendUint похожи, но добавляют отформатированное значение к целевому срезу.

Строковые преобразования

Quote и QuoteToASCII конвертируют строки в экранированные строковые литералы Go. Последний гарантирует, что результатом является строка ASCII, экранируя любой не-ASCII Unicode с \u:

q := strconv.Quote("Hello, 世界")
q := strconv.QuoteToASCII("Hello, 世界")

QuoteRune и QuoteRuneToASCII похожи, но принимают руны и возвращают экранированные литералы рун Go.

Unquote и UnquoteChar удаляют кавычки из строковых литералов и литералов рун.


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


четверг, 4 июня 2020 г.

Пакет testing в Golang

Пакет testing обеспечивает поддержку автоматического тестирования пакетов Go. Он предназначен для использования совместно с командой "go test", которая автоматизирует выполнение любой функции формы

func TestXxx(*testing.T)

где Xxx не начинается со строчной буквы. Имя функции служит для идентификации процедуры тестирования.

В этих функциях используйте Error, Fail или связанные методы, чтобы сигнализировать о сбое.

Чтобы написать новый набор тестов, создайте файл, имя которого заканчивается _test.go и содержит функции TestXxx, как описано здесь. Поместите файл в тот же пакет, что и тестируемый. Файл будет исключен из обычных сборок пакетов, но будет включен при запуске команды "go test".

Простая тестовая функция выглядит так:

func TestAbs(t *testing.T) {
    got := Abs(-1)
    if got != 1 {
        t.Errorf("Abs(-1) = %d; want 1", got)
    }
}

Бенчмарки

Функции формы

func BenchmarkXxx(*testing.B)

считаются бенчмарками и выполняются командой "go test", если указан флаг -bench. Тесты запускаются последовательно.

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

func BenchmarkHello(b *testing.B) {
    for i := 0; i < b.N; i++ {
        fmt.Sprintf("hello")
    }
}

Бенчмарк функция должна запускать целевой код b.N раз. Во время выполнения бенчмарка b.N корректируется до тех пор, пока функция бенчмарка не продержится достаточно долго, чтобы быть надежно синхронизированной. Вывод

BenchmarkHello    10000000    282 ns/op

означает, что цикл выполнялся 10000000 раз со скоростью 282 нс на цикл.

Если перед запуском бенчмарка требуется дорогостоящая настройка, таймер может быть сброшен:

func BenchmarkBigLen(b *testing.B) {
    big := NewBig()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        big.Len()
    }
}

Если бенчмарку нужно тестировать производительность в параллельном режиме, он может использовать вспомогательную функцию RunParallel; такие тесты предназначены для использования с флагом go test -cpu:

func BenchmarkTemplateParallel(b *testing.B) {
    templ := template.Must(template.New("test").Parse("Hello, {{.}}!"))
    b.RunParallel(func(pb *testing.PB) {
        var buf bytes.Buffer
        for pb.Next() {
            buf.Reset()
            templ.Execute(&buf, "World")
        }
    })
}

Примеры (Examples)

Пакет также запускается и проверяет пример кода. Функции example могут включать заключительную строку комментария, которая начинается с "Output:" и сравнивается со стандартным выводом функции при выполнении тестов. (Сравнение игнорирует начальные и конечные пробелы.) Это примеры example:

func ExampleHello() {
    fmt.Println("hello")
    // Output: hello
}

func ExampleSalutations() {
    fmt.Println("hello, and")
    fmt.Println("goodbye")
    // Output:
    // hello, and
    // goodbye
}

Префикс комментария "Unordered output:" похож на "Output:", но соответствует любому порядку строк:

func ExamplePerm() {
    for _, value := range Perm(5) {
        fmt.Println(value)
    }
    // Unordered output: 4
    // 2
    // 1
    // 3
    // 0
}

Example функции без выходных комментариев компилируются, но не выполняются.

Соглашение об именах для объявления примеров для пакета, функции F, типа T и метода M для типа T:

func Example() { ... }
func ExampleF() { ... }
func ExampleT() { ... }
func ExampleT_M() { ... }

Несколько примеров функций для пакета/типа/функции/метода могут быть предоставлены путем добавления отдельного суффикса к имени. Суффикс должен начинаться со строчной буквы.

func Example_suffix() { ... }
func ExampleF_suffix() { ... }
func ExampleT_suffix() { ... }
func ExampleT_M_suffix() { ... }

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

Пропуски

Тесты или бенчмарки могут быть пропущены во время выполнения с помощью вызова Skip метода *T или *B:

func TestTimeConsuming(t *testing.T) {
    if testing.Short() {
        t.Skip("пропуск теста в сокращенном режиме.")
    }
    ...
}

Субтесты и суб-бенчмарки

Методы Run для T и B позволяют определять субтесты и суб-бенчмарки без необходимости определять отдельные функции для каждого. Это позволяет использовать табличные бенчмарки и создавать иерархические тесты. Это также предоставляет возможность поделиться общим кодом установки и удаления (очистки):

func TestFoo(t *testing.T) {
    // установочный код
    t.Run("A=1", func(t *testing.T) { ... })
    t.Run("A=2", func(t *testing.T) { ... })
    t.Run("B=1", func(t *testing.T) { ... })
    // код очистки
}

Каждый субтест и суб-бенчмарк имеет уникальное имя: сочетание имени теста верхнего уровня и последовательности имен, передаваемых в Run, разделенных косой чертой, с необязательным конечным порядковым номером для устранения неоднозначности.

Аргумент для флагов командной строки -run и -bench - это нефиксированное регулярное выражение, которое соответствует имени теста. Для тестов с несколькими элементами, разделенными слешем, такими как субтесты, аргумент сам разделен слэшем, а выражения по очереди соответствуют каждому элементу имени. Поскольку оно не занято, пустое выражение соответствует любой строке. Например, использование "соответствия" ("matching") означает "чье имя содержит":

go test -run ''      # запускает все тесты
go test -run Foo     # запускает верхнеуровневые тесты соотвествующие "Foo", такие как "TestFooBar".
go test -run Foo/A=  # запускает верхнеуровневые тесты соотвествующие "Foo", запускает субтесты соотвествующие "A=".
go test -run /A=1    # запускает все верхнеуровневые тесты, запускает субтесты соотвествующие "A=1".

Субтесты также могут быть использованы для контроля параллелизма. Родительский тест будет завершен только после завершения всех его субтестов. В этом примере все тесты выполняются параллельно друг другу и только друг с другом, независимо от других тестов верхнего уровня, которые могут быть определены:

func TestGroupedParallel(t *testing.T) {
    for _, tc := range tests {
        tc := tc // захватить переменную диапазона
        t.Run(tc.Name, func(t *testing.T) {
            t.Parallel()
            ...
        })
    }
}

Детектор гонки убивает программу, если она превышает 8192 конкурентных goroutine, поэтому будьте осторожны при выполнении параллельных тестов с установленным флагом -race.

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

func TestTeardownParallel(t *testing.T) {
    // Этот Run не вернется, пока не завершатся параллельные тесты.
    t.Run("group", func(t *testing.T) {
        t.Run("Test1", parallelTest1)
        t.Run("Test2", parallelTest2)
        t.Run("Test3", parallelTest3)
    })
    // код очистки
}

Main

Иногда тестовой программе необходимо выполнить дополнительную настройку или демонтаж до или после тестирования. Иногда тесту также необходимо контролировать, какой код выполняется в основном (main) потоке. Для поддержки этих и других случаев, если тестовый файл содержит функцию:

func TestMain(m *testing.M)

тогда сгенерированный тест вызовет TestMain(m) вместо непосредственного запуска тестов. TestMain работает в основной программе и может выполнять любые настройки и разборки, необходимые для вызова m.Run. Затем он должен вызвать os.Exit с результатом m.Run. Когда вызывается TestMain, flag.Parse не был запущен. Если TestMain зависит от флагов командной строки, включая флаги пакета тестирования, он должен явно вызывать flag.Parse.

Простая реализация TestMain:

func TestMain(m *testing.M) {
    // вызываем flag.Parse() здесь, 
    // если TestMain использует флаги
    os.Exit(m.Run())
}


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