12 приёмов Go, которые помогут в работе

12 приёмов Go, которые изменят вашу продуктивность

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

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

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

@Chatgpturbobot – chatgpt 4 версии для программистов, бесплатно и без регистрации.

1. Отслеживание времени выполнения функции

Если вы заинтересованы в отслеживании времени выполнения функции в Go, существует простой и эффективный способ, который вы можете реализовать всего одной строкой кода. Всё, что вам нужно, это функция TrackTime:

// Utility
func TrackTime(pre time.Time) time.Duration {
  elapsed := time.Since(pre)
  fmt.Println("elapsed:", elapsed)

  return elapsed
}

func TestTrackTime(t *testing.T) {
 defer TrackTime(time.Now()) // <--- THIS

 time.Sleep(500 * time.Millisecond)
}

// elapsed: 501.11125ms

2. Стоит ли указывать размер массива заранее?

Согласно выводам, изложенным в статье “Ускорители производительности Go”, заранее указанная размерность массива может значительно повысить производительность программ, написанных на языке программирования Go.

Однако стоит отметить, что такой подход иногда может привести к ошибкам, если мы непреднамеренно используем “append” вместо индексации (например, a[i]).

Знаете ли вы, что можно использовать данную функцию без указания длины массива (zero), как описано в вышеупомянутой статье?

// instead of
a := make([]int, 10)
a[0] = 1

// use this
b := make([]int, 0, 10)
b = b.append(1)

3. Метод Chaining

Метод Chaining может быть применён к указателям. Чтобы проиллюстрировать это, давайте рассмотрим структуру Person с двумя функциями, AddAge и Rename, которые можно использовать для её изменения.

type Person struct {
  Name string
  Age  int
}

func (p *Person) AddAge() {
  p.Age++
}

func (p *Person) Rename(name string) {
  p.Name = name
}

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

func main() {
  p := Person{Name: "Aiden", Age: 30}

  p.AddAge()
  p.Rename("Aiden 2")
}

В качестве альтернативы мы можем изменить функции AddAge и Rename, чтобы они возвращали изменённый объект, даже если ничего не должны возвращать.

func (p *Person) AddAge() *Person {
  p.Age++
  return p
}

func (p *Person) Rename(name string) *Person {
  p.Name = name
  return p
}

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

p = p.AddAge().Rename("Aiden 2")

4. Go 1.20 работа со Слайсами

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

a := []int{0, 1, 2, 3, 4, 5}
var b[3]int = a[0:3]

// cannot use a[0:3] (value of type []int) as [3]int value in variable 
// declaration compiler(IncompatibleAssign)

Чтобы пользователи могли преобразовать слайс в массив, команда Go обновила эту функцию в Go 1.17. А с выпуском Go 1.20 процесс преобразования стал ещё проще:

// go 1.20
func Test(t *testing.T) {
   a := []int{0, 1, 2, 3, 4, 5}
   b := [3]int(a[0:3])

  fmt.Println(b) // [0 1 2]
}

// go 1.17
func TestM2e(t *testing.T) {
  a := []int{0, 1, 2, 3, 4, 5}
  b := *(*[3]int)(a[0:3])

  fmt.Println(b) // [0 1 2]
}

Краткое примечание: вы можете использовать [:3] вместо [0:3]. Я упоминаю об этом для ясности.

5. Использование импорта с ‘_’ для

Иногда в библиотеках вы можете столкнуться с инструкциями импорта, которые объединяют символ подчёркивания (_) следующим образом:

import (
  _ "google.golang.org/genproto/googleapis/api/annotations" 
)

Этот символ приведёт к выполнению инициализации кода (функции init) без создания ссылки на его имя. Данная схема позволит вам инициализировать пакеты, регистрировать подключения и выполнять другие задачи перед запуском кода.

Давайте рассмотрим пример, чтобы лучше понять, как это работает:

// underscore
package underscore

func init() {
  fmt.Println("init called from underscore package")
}

// main
package main 

import (
  _ "lab/underscore"
)

func main() {}

// log: init called from underscore package

6. Используйте импорт с точкой.

Изучив, как мы можем использовать импорт с подчёркиванием, давайте теперь посмотрим, как работает точка “.” . Данный оператор, без сомнений, используется чаще.

Оператор “." можно использовать для того, чтобы сделать доступными экспортированные идентификаторы импортированного пакета без необходимости указывать имя пакета, что может быть полезным ярлыком для ленивых разработчиков.

Довольно круто, не так ли? Это особенно полезно при работе с длинными именами пакетов в наших проектах, такими как ‘externalmodel’ или ‘doingsomethinglonglib

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

package main

import (
  "fmt"
  . "math"
)

func main() {
  fmt.Println(Pi) // 3.141592653589793
  fmt.Println(Sin(Pi / 2)) // 1
}

7. Несколько ошибок теперь можно объединить в одну в Go 1.20

Go 1.20 вводит новые особенности, включая поддержку множественных ошибок и внесение изменений в errors.Is и errors.As .

Ниже представлен подробный разбор функции Join:

var (
  err1 = errors.New("Error 1st")
  err2 = errors.New("Error 2nd")
)

func main() {
  err := err1
  err = errors.Join(err, err2)
  
  fmt.Println(errors.Is(err, err1)) // true
  fmt.Println(errors.Is(err, err2)) // true
}

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

8. Хитрость для проверки интерфейса во время компиляции

Предположим, у вас есть интерфейс под названием Buffer, который содержит функцию Write(). Кроме того, у вас есть структура с именем StringBuffer, которая реализует этот интерфейс.

Однако, что, если вы допустите опечатку и напишете Writeee() вместо Write()?

type Buffer interface {
  Write(p []byte) (n int, err error)
}

type StringBuffer struct{}

func (s *StringBuffer) Writeee(p []byte) (n int, err error) {
  return 0, nil
}

Вы не можете проверить, правильно ли StringBuffer реализовал интерфейс до выполнения программы. Однако, используя этот трюк, компилятор предупредит вас с помощью сообщения об ошибке IDE:

var _ Buffer = (*StringBuffer)(nil)

// cannot use (*StringBuffer)(nil) (value of type *StringBuffer) 
// as Buffer value in variable declaration: *StringBuffer 
// does not implement Buffer (missing method Write)

9. Использование тернарного оператора в Go

Go не имеет встроенной поддержки тернарных операторов, как многие другие языки программирования:

# python 
min = a if a < b else b

# c#
min = x < y ? x : y

С generics от Go, в версии 1.18, у нас появилась возможность создать утилиту, которая обеспечивает тернарную функциональность всего в одной строке кода:

// our utility
func Ter[T any](cond bool, a, b T) T {
  if cond {
    return a
  }
  
  return b
}

func main() {
  fmt.Println(Ter(true, 1, 2)) // 1 
  fmt.Println(Ter(false, 1, 2)) // 2
}

10. Работа с параметрами

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

printInfo("foo", true, true)

Что означают первое “true” и второе “true“, если вы не проверяете printInfo? Когда у вас есть функция с несколькими аргументами, понимание значения параметра может привести к путанице.

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

// func printInfo(name string, isLocal, done bool)

printInfo("foo", true /* isLocal */, true /* done */)

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

11. Способы проверки того, действительно ли интерфейс имеет значение nil

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

func main() {
  var x interface{}
  var y *int = nil
  x = y
  
  if x != nil {
    fmt.Println("x != nil") // <-- actual
  } else {
    fmt.Println("x == nil")
  }
  
  fmt.Println(x)
}

// x != nil
// <nil>

Как мы можем определить, равно ли значение interface{} нулю? К счастью, существует простая утилита, которая может помочь нам достичь этого:

func IsNil(x interface{}) bool {
  if x == nil {
    return true
  }

  return reflect.ValueOf(x).IsNil()
}

12. time.Duration в JSON

При парсинге JSON использование time.Duration может быть неприятным процессом, поскольку для этого потребуется добавить 9 нулей с интервалом в 1 секунду (т.е. 1000000000). Чтобы упростить этот процесс, я создал новый тип под названием Duration:

type Duration time.Duration

Чтобы включить синтаксический анализ строк с длительностью 1s или 20h5m типа int64, мне пришлось реализовать новую пользовательскую логику:

func (d *Duration) UnmarshalJSON(b []byte) error {
  var s string
  if err := json.Unmarshal(b, &s); err != nil {
    return err
  }
  dur, err := time.ParseDuration(s)
  if err != nil {
    return err
  }
  *d = Duration(dur)
  return nil
}

Однако, важно отметить, что переменная ‘d’ не должна быть равна нулю, поскольку это может привести к ошибкам. В качестве альтернативы, вы также можете включить проверку на ”d” в начале функции.

Хмм… Я предполагал поделиться большим количеством своих утилит, но я не хотел делать пост слишком длинным и трудным для понимания, поскольку эти приёмы не структурированы (охватывают абсолютно разные темы).

Если вы нашли эти приёмы полезными или у вас есть какие-либо собственные идеи, которыми вы хотели бы поделиться, пожалуйста, не стесняйтесь оставлять комментарий!

+1
2
+1
16
+1
0
+1
2
+1
1

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *