Golang "Что выведет код?" задачи для собеседования.

Проходя собеседование на позицию Golang разработчика мы часто сталкиваемся с задачами по типу “что выведет код на экране?”. Цель подобных задач – определить насколько хорошо кандидат понимает синтаксис своего языка и основы работы с базовыми типами и функциями. Ниже я набросал несколько, которые чаще всего попадались мне на собеседованиях. Эти задачи затрагивают темы: slice, map, struct, goroutine и их синхронизации. Так же в конце я добавил одно бонусное задание, которое давали мне на собеседование в один небезызвестный маркетплейс.

Что выведет данный код?

1. Слайсы – определение, аллокации и указатели.

func main() { a := []int{1, 2, 3} p := &a[1] b := append([]int(nil), a[:2]...) a[0] = 9 *p = 8 fmt.Println(a, b, *p) }

Ответ:

[9 8 3] [1 2] 8

2. Map – определение, перебор.

func main() { m := map[string][]int{ "a": {1, 2}, "b": {3, 4}, } for k, v := range m { v[0] = 9 m[k] = append(v, 7) } fmt.Println(m["a"]) fmt.Println(m["b"]) }

Ответ:

[9 2 7]
[9 4 7]

3. Struct – встраивание и receiver method.

type Parent struct{} func (c *Parent) Print() { fmt.Println("parent") } type Child struct { Parent } func (p *Child) Print() { fmt.Println("child") } func main() { var child Child child.Print() }

Ответ:

child

4. Goroutine – гонка данных.

func main() { runtime.GOMAXPROCS(1) done := false go func() { done = true }() for !done {} fmt.Println("finished") }

Ответ:

Код не имеет детерминированного результата, это data race, поэтому программа может зависнуть навсегда или напечатать finished.

5. Goroutine – deadlock?

func main() { ch := make(chan int) go func() { ch <- 1 }() go func() { t := time.NewTicker(200 * time.Millisecond) defer t.Stop() for range t.C { fmt.Print(".") } }() for v := range ch { fmt.Println("\nvalue:", v) } fmt.Println("done") }

Ответ:

Вывод у этой программы никогда не дойдёт до done. Она зависнет после получения первого значения и дальше будет печатать точки.

6. Goroutine – интересное поведение.

func main() { var wg sync.WaitGroup for i := 0; i < 3; i++ { go func() { wg.Add(1) defer wg.Done() fmt.Print(i) }() } wg.Wait() fmt.Println("\ndone") }

Ответ:

У данного кода существует 3 варианта поведения:
1. программа выведет только done;
2. программа успеет вывести от одной до трех цифр в случайном порядке;
3. программа завершится с паникой sync: WaitGroup misuse: Add called concurrently with Wait. Из за того что Add(1) находится внутри горутины а Wait() вызывается снаружи сразу после запуска эти функции могут выполняться параллельно.

Бонус

Дополнительная задача о которой упоминал вначале. К “что выведет” она не относится, но я все равно решил включить её в статью, потому что она интересная и мне встречалась в части секции с устными задачами. Суть задачи – проверить, понимает ли кандидат интерфейсы в Go.

Надо реализовать функцию getError, которая выбрасывает ошибку без сторонних импортов (в тч. без пакета fmt).

func getError() error { ... }

Решение:

type myErr struct{} func (myErr) Error() string { return "boom" } func getError() error { var e *myErr = nil return e }

Пояснение:

error – это интерфейс
У error внутри две части:
- тип (dynamic type);
- значение (dynamic value).
Интерфейс считается nil только если обе части nil: нет типа и нет значения.
Что возвращает getError.

var e *myErr = nil return e

e – это nil-указатель типа *myErr.
Когда программа возвращает e как error, Go упаковывает это в интерфейс:
dynamic type = *myErr (НЕ nil)
dynamic value = nil
То есть интерфейс не nil, потому что у него есть тип.

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

Спасибо за внимание!

22