🇺🇦 Go for two :)
1.18K subscribers
22 photos
3 files
184 links
Telegram channel about tricks and engineering practices in the Go programming language over a cup of coffee ☕️.

author: @a_soldatenko
personal blog: https://asoldatenko.org

#golang #go #kubernetes #debugging
加入频道
Designing Go code with interface chaining vs requiring deps.

https://gist.github.com/joncalhoun/0cd99c9082d2ba210c5169082038a420

Note: этот gist от создателя https://gophercises.com/, он написал в твиттере: времени нет писать 2 статьи - ловите код :)
В одном из недавних постов я писал, что люди требуют крови!: https://yangx.top/golang_for_two/16

CloudFlare написал long read на эту тему, что пошло не так -> https://new.blog.cloudflare.com/details-of-the-cloudflare-outage-on-july-2-2019

Но самое интересно, там есть Gif в которой тестируют RegExp и Go комюнити в лице Nate Fintch прикинул, что можно написать так:

// Equivalent normal code is worst case len(s)+1 steps:
if !strings.HasSuffix(s, ";") {
return false
}
if !strings.Contains(s, "=") {
return false
}
Памятка Go разработчку про regexp!
Твит который набирает популярность!

src: https://twitter.com/eugen_yzeiri/status/1154400836862337025
В Go есть репозиторий "golang/proposal" который описывает процесс внесение изменений в язык. Так вот, Russ и ко. смекнули, (вероятно) после недавних зафейлиных предложений, что нужно что-то менять.
Мысли Russ'а можно прочитать тут, обещает опубликовать продолжение через пару дней🍿
💡Рубрика интересный факт:

Кто из вас слышал, что в Go до версии 1.0 был debugger под названием, барабанная дробь: OGLE.
Те задумка была такая:

> go ogle main.go

К сожалению, дебаггер не был готов к релизу go1.0 и его дропнули💩
Note #7 О стилистике Decode//UnMarshal 🐣

Недавно в твиттере возник вопрос, что лучше писать:

// Option A
var v T
v.Decode(someData)
Или так:
Option B
func (t *T) Decode(data []byte) {
// decode data into *t
}

На самом деле все зависит от контекста, а именно вариант В) или использование метода вместо функции - позволяет типам удовлетворять интерфейсам, т.е другими словами, если у вас есть где-то type Decoder interface{ Decode([]byte) error },
Если такого типа нет, оба варианта подойдут и как все знают на вкус и цвет фломастеры разные, хотя второй вариант более универсальный кмк.

Пару ссылок:
- Интересный gist о том как можно приводить типы между источниками
- Дока о том, что Decode - это обычно выражает Unmarshal BinaryUnmarshaler
Note #8 Ast T-shirt👕

Допустим вы решили себе напечатать футболку с куском кода который на Go, но hello-world это скучно. Вот вам идея, взять кусок синтаксического дерева из if err != nil. А если серьезно, то всегда интересно посмотреть - во что превратится ваша программа, когда компилятор ее парсит и строит ситаксическое дерево.

В общем получится что-то типа такого:
55  .  1: *ast.IfStmt {
56 . . If: 5:2
57 . . Cond: *ast.BinaryExpr {
58 . . . X: *ast.Ident {
59 . . . . NamePos: 5:5
60 . . . . Name: "err"
61 . . . . Obj: *(obj @ 38)
62 . . . }
63 . . . OpPos: 5:9
64 . . . Op: !=
65 . . . Y: *ast.Ident {
66 . . . . NamePos: 5:12
67 . . . . Name: "nil"
68 . . . }
69 . . }


Если чуток почистить, то можно и на чашку вместить:

&ast.IfStmt{
Cond: &ast.BinaryExpr{
X: &ast.Ident{Name: "err"},
Op: token.NEQ,
Y: &ast.Ident{Name: "nil"},
},
}


Ссылки:
- поиграться
- почитать
Слайды моего доклада “Advanced debugging techniques in different environments” на Kyiv Go Meetup July 2019:

- демо проект link
- слайды файлом link
- slideshare link

Вопросы пиши в личку: @a_soldatenko
Note #9 Когда лучше использовать именнованые параметры в функциях, определеных в интерфейсах?

Ели нам попадается такой код, то не совсем понятно что такое str или int O_O:

type runner interface {
run(context.Context, string, int)
}


Сразу же последует PR с чем-то вроде такого:

2c2
< run(context.Context, string, int)
---
> run(ctx context.Context, service string, instances int)
4d3
<

Так намного лучше :)

Но иногда есть более спорный кейс, в котором называть не объязательно:
type Enroller interface {
Enroll(*User, *Course) error
}


// P.S.
func main(){println("не забываем, что код пишется, еще и для наших коллеги, а не только для того, чтобы его можно было запустить на проде!")}

🍺
Note #10 Напоминание самому себе:

В Go можно делать вызов метода, напрямую используя тип:

package main

import (
"fmt"
)

type Person struct {
Name string
}

func (p Person) Say() {
fmt.Println("Go for two me and", p.Name)
}

func main() {
p := Person{"%username%"}
// Все варианты эквивалентны
p.Say()
Person.Say(p)
(Person).Say(p)
f1 := Person.Say; f1(p)
f2 := (Person).Say; f2(p)
}

links:
[1] https://play.golang.org/p/5TbFC1DsZyy
[2] https://golang.org/ref/spec#Method_expressions
Note #11: Go 2 и числовые литералы

Tl;dr в Go версии 2 собираются добавить некоторые изменения, которые затронут числовые литералы. Так это и так все знают: подумали Вы?
Давайте посмотрим, что же уже сейчас есть в go репо в главной ветке (master branch):

$ git clone [email protected]:golang/go.git
$ cd go/src && ./all.bash
$ go version devel +eee07a8e68 Wed Aug 21 15:20:00 2019 +0000 darwin/amd64

Один из самых заметных для меня это конечно _ в цифрах).

Небольшой примерчик:
package main

func main() {
println("1_200_000 -> ", 1_200_000)
println("3.1415_9265 -> ", 3.1415_9265)
println("0b0110_1101_1101_0010 -> ",0b0110_1101_1101_0010)
// println("0___ -> ", 0___) invalid example from discussion
// println(" 0__.0__e-0__ -> ",0__.0__e-0__) invalid example from discussion
// println("1__2", 1__2) invalid
}


Итак:
$ go version
$ go1.12.7 darwin/amd64
$ go run main.go
# command-line-arguments
./main.go:4:28: syntax error: unexpected _200_000, expecting comma or )
./main.go:5:35: syntax error: unexpected _9265, expecting comma or )
./main.go:6:39: syntax error: unexpected b0110_1101_1101_0010, expecting comma or )

Oops 😬


Попытка номер 2:
./go version 
devel +eee07a8e68 Wed Aug 21 15:20:00 2019 +0000 darwin/amd64
./go run main.go
1_200_000 -> 1200000
3.1415_9265 -> +3.141593e+000
0b0110_1101_1101_0010 -> 28114


Go 1.13RC1:
go get golang.org/dl/go1.13rc1
go1.13rc1 run main.go
1_200_000 -> 1200000
3.1415_9265 -> +3.141593e+000
0b0110_1101_1101_0010 -> 28114

🎉🎉🎉🎉

P.S. Не совсем понятно почему это вроде как должно быть в Go2, а попало в Go 1.13 … наверное я что-то упустил, пойду читать GIthub…

[1] https://go.googlesource.com/proposal/+/master/design/19308-number-literals.md
Note #12: defer statements in infinite loops

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

$ cat main.go

package main

func g()

func f() {
for {
defer g()
}
}

func main() {
f()
}


Отловить данную проблему поможет статический анализатор:

deferlint:
go vet -vettool=$(which deferlint) .
# github.com/andriisoldatenko/golang_for_two
./main.go:10:4: defer in loop found "defer g()"


либо staticcheck
staticcheck
main.go:10:4: defers in this infinite loop will never run (SA5003)


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

Кстати, deferlint - отличный пример, как написать свой линтер[1].
Исправить данную ситуацию возможно, обернув defer в анонимную функцию, чтобы гарантированно ваша отложенная функция была вызвана вовремя.

func f() {
for {
func(){
defer g()
}()
}
}


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

Links:
[1] Если вдруг вы пропустили статью о том, как написать свой линтер использую go/analysis, то вот https://arslan.io/2019/06/13/using-go-analysis-to-write-a-custom-linter/
Note #13: go doc with colors

Если вы используете go doc, для быстрого доступа к go документации, есть интересная идея как это все разукрасить:

func main() {
args := []string{"doc"}
args = append(args, os.Args[1:]...)
cmd := exec.Command("go", args...)
cmd.Env = os.Environ()

output, _ := cmd.CombinedOutput()

stdout := colorable.NewColorableStdout()
err := quick.Highlight(stdout, string(output),
"go", "terminal256", style)

stdout.Write([]byte("\033[0m\n"))
}

Код очень простой и не странно, что принимает те же параметры что и go doc :trollface:, как описал автор :)

Link: https://github.com/inancgumus/godocc

Установить можно так:
go get -u github.com/inancgumus/godocc

GODOCC_STYLE=monokai godocc time Unix
Note #14 argument reusing in fmt.Printf

Бывает необходимость напечатать несколько раз одно и тоже значение или значение и тип:
fmt.Printf("Num: %d %d\n", 2, 2)
// or
fmt.Printf("Num: %d %T\n", 2, 2)


Это легко переписать, используя индексы аргументов:
fmt.Printf("Num: %d\n", 42)
fmt.Printf("Num: %d %[1]d %[1]d %[2]d %[2]d", 2, 3)
}


more details: https://golang.org/pkg/fmt/
Note #15 Go и зарезервированные ключевые слова

Ключевые слова в Go можно переопределять, НО никогда не стоит так делать!
Такой код, как Вы уже догадались, вполне себе валидный код в Go:

package main

import (
"fmt"
)

func main() {
const iota = iota
fmt.Println(iota, iota, iota)
}
И мы получим в результате:😱: 0 0 0.

Как следствие:
const (
a = iota
b
c
)
fmt.Println(a, b, c)
0 0 0

И staticcheck или go vet ничего нам не сказали :(

И в конце, чтобы запутать своих коллег еще сильнее, возможно написать так:
type int struct{}

func main() {
var i int
i++
fmt.Println(i)
}

Компилятор подскажет ошибку:
invalid operation: i++ (non-numeric type int) o_O

P.S. SyntaxError: invalid syntax было бы лучше :)

Links:
[1] https://golang.org/ref/spec#Predeclared_identifiers
[2] https://play.golang.org/p/6OW0pJMvgQk
Note #16 GopherCon 2019 videos

Подвезли видео с GopherCon 2019 🎉🎉🎉

https://www.youtube.com/playlist?list=PL2ntRZ1ySWBdDyspRTNBIKES1Y-P__59_

Update: Лайтинг талкс: https://www.youtube.com/playlist?list=PL2ntRZ1ySWBedT1MDWF4xAD39vAad0DBT

P.S. пишите в личку ,если что-то по вашему мнению must have посмотреть, сделаем отдельный обзор! Enjoy!
Note # 17 Скомпилируется или нет? Или игра с ссылками.

Есть пример программы, которая скорее всего не скомпилируется?! :)

Program:
package main

import (
"fmt"
)

var g *int

func f(p *****int) { g = ****p }

func main() {
var i int
j := &i
k := &j
l := &k
m := &l
f(&m)
fmt.Printf("%T\n", &m)
fmt.Printf("%T", f)
}


Итак перед тем как копировать код и запускать на play.golang.org, я решил немного освежить как же работают ссылки в Go. Есть небольшой пример:

func main() {
var i = 42
var j = &i
fmt.Println("i = ", i)
fmt.Println("j = ", j)
fmt.Println("*j = ", *j)
}


Думаю тут все и так понятно:
// Output
i = 100
j = 0xc0000160b8
*j = 100


Проблема лишь в том, что пока, я не приблизились к нашему первоначальному примеру ни на шаг.
И тут я вспомнил, то факт, что ссылка, может ссылаться на любой тип, в том числе, и быть ссылкой на ссылку, т.е. можно написать **p (dereferencing a pointer to pointer).


func main() {
var i = 42
var p = &i
var pp = &p
fmt.Println("pp = ", pp)
// Dereferencing a pointer to pointer
fmt.Println("*pp = ", *pp)
fmt.Println("**pp = ", **pp)
}
pp = 0xc0000bc010
*pp = 0xc0000c2000
**pp = 42

P.S. я думаю теперь вы догадались, какой правильный ответ.
Goto Program
Note #18 gotip 🔨

gotip консольная утилита, которая помогает скомпилировать go из мастера и очень быстро проверить, пофиксили ли баг в мастере или совместимость проекта с последней версией (origin/master).

$ go get -v golang.org/dl/gotip
$ gotip download
………………………….
…. Long output…. Here…. ☕️☕️☕️
………………………….
$ gotip version
go version devel +307544f Wed Aug 28 15:49:59 2019 +0000 darwin/amd64

$ gotip run main.go


P.S. код gotip довольно простой -> https://github.com/golang/dl/blob/master/gotip/main.go#L37
Note #19 Reduce sturct size (maligned) 📄

Недавно много говорили, о том что одна и та же структура (type struct), с разными типами полей и с разным порядком, может занимать разное кол-во памяти в зависимости от того, как расположить в памяти компьютера с учетом всех отступов. Более детально прочитать можно в вики [1], почему именно так происходит.

Хотелось бы, чтобы компилятор самостоятельно делал такие оптимизации.

Возьмем структура A:
$ cat main.go
// A is my struct
type A struct {
a bool // 1 byte
b float64 // 8 bytes
c int32 // 4 bytes
}


Хорошо что есть отличный линтер[2], который напомнит нам, что можно что-то улучшить:

$ golangci-lint run --enable=maligned main.go
main.go:4:8: struct of size 24 bytes could be of size 16 bytes (maligned)
type A struct {
^


либо

go get github.com/mdempsky/maligned
$ maligned .
/Users/andrii/work/golang_for_two/main.go:4:8: struct of size 24 could be 16


Links:
- https://en.wikipedia.org/wiki/Data_structure_alignment
- https://github.com/mdempsky/maligned