понедельник, 4 марта 2019 г.

Go Code Review Comments: Интерфейсы

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

Не определяйте интерфейсы на имплементирующей стороне API «для создания mock'ов»; вместо этого спроектируйте API так, чтобы его можно было протестировать с использованием открытого API реальной реализации.

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

package consumer  // consumer.go

type Thinger interface { Thing() bool }

func Foo(t Thinger) string { … }

package consumer // consumer_test.go

type fakeThinger struct{ … }
func (t fakeThinger) Thing() bool { … }
…
if Foo(fakeThinger{…}) == "x" { … }

// НЕ ДЕЛАЙТЕ ТАК!!!
package producer

type Thinger interface { Thing() bool }

type defaultThinger struct{ … }
func (t defaultThinger) Thing() bool { … }

func NewThinger() Thinger { return defaultThinger{ … } }

Вместо этого верните конкретный тип и позвольте потребителю создать mock реализации producer.

package producer

type Thinger struct{ … }
func (t Thinger) Thing() bool { … }

func NewThinger() Thinger { return Thinger{ … } }


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


Go Code Review Comments: Инициализмы

Слова в именах, которые являются инициализмами или аббревиатурами (например, «URL» или «NATO»), имеют последовательный регистр. Например, «URL» должен отображаться как «URL» или «url» (как в «urlPony» или «URLPony»), а не «Url». Как пример: ServeHTTP не ServeHttp. Для идентификаторов с несколькими инициализированными «словами» используйте, например, «xmlHTTPRequest» или «XMLHTTPRequest».

Это правило также применяется к «ID», когда оно сокращенно от «Identity Document» (что практически во всех случаях, когда это не «id», как в «ego», «superego»), поэтому пишите «appID» вместо «appId».

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


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


воскресенье, 3 марта 2019 г.

Go Code Review Comments: Отступы в ответвлениях ошибок

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

if err != nil {
    // обработка ошибки
} else {
    // нормальный код
}

Вместо этого напишите:

if err != nil {
    // обработка ошибки
    return // или continue, и т.д.
}
// нормальный код

Если в операторе if есть оператор инициализации, например:

if x, err := f(); err != nil {
    // обработка ошибки
    return
} else {
    // использование x
}

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

x, err := f()
if err != nil {
    // обработка ошибки
    return
}
// использование x


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


суббота, 2 марта 2019 г.

Go Code Review Comments: Внутренние ошибки

В C и аналогичных языках функции обычно возвращают значения, такие как -1 или ноль, чтобы сигнализировать об ошибках или пропущенных результатах:

// Lookup возвращает значение для ключа или "", 
// если нет соответствия для ключа.
func Lookup(key string) string

// Невозможность проверить значение внутренней ошибки 
// может привести к багам:
Parse(Lookup(key))  
// возвращает "parse failure for value" 
// вместо "no value for key" 

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

// Lookup возвращает значение для ключа или ok = false, 
// если для ключа нет соответствия.
func Lookup(key string) (value string, ok bool)

Это препятствует тому, чтобы вызывающая сторона использовала результат неправильно:

Parse(Lookup(key))  // compile-time error

И поощряет более надежный и читаемый код:

value, ok := Lookup(key)
if !ok  {
    return fmt.Errorf("no value for %q", key)
}
return Parse(value)

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

Возвращаемые значения, такие как nil, "", 0 и -1, хороши, когда они являются действительными результатами для функции, то есть когда вызывающей стороне нет необходимости обрабатывать их иначе, чем другие значения.

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


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


Go Code Review Comments: пустой импорт, импорт точки

Пустой импорт

Пакеты, которые импортируются только для их побочных эффектов (с использованием синтаксиса import _ "pkg"), следует импортировать только в основной пакет программы или в тесты, которые в них нуждаются.

Импорт точки

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

package foo_test

import (
    "bar/testutil" // также испортирует "foo"
    . "foo"
)

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


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


Go примеры: Fizz buzz

В серии постов Go примеры будут представлены примеры программ на Go.

Начнем мы с классической задачи Fizz buzz.

Напомню требования этой задачи. Необходимо реализовать функцию, которая будет проходить по последовательному ряду чисел, начинающемуся с нуля. В случае если число кратно трем будет выводиться слово "Fizz", в случае если число кратно пяти будет выводиться слово "Buzz".

package main

import "fmt"

func main() {
  fmt.Println("Start Fizz Buzz game")
  for i := 0; i < 100; i++ {
    fmt.Println(i)
    if i%3 == 0 {
      fmt.Println("Fizz")
    } else if i%5 == 0 {
      fmt.Println("Buzz")
    }
  }
  fmt.Println("Game over")
}

Этот пример Fizz buzz также доступен в Go песочнице https://play.golang.org/p/WTY4dMNJZtu.


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


пятница, 1 марта 2019 г.

Go Code Review Comments: импорт

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

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

package main

import (
  "fmt"
  "hash/adler32"
  "os"

  "appengine/foo"
  "appengine/user"

  "github.com/foo/bar"
  "rsc.io/goversion/version"
)

goimports выполняет все эти действия за вас.


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