15 вопросов с собеседований Golang из LinkedIn Assessment — подробные пояснения

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

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

@golang_interview/ – разбор практических вопросов Go в нашем телеграм канале.

1. Что нужно, чтобы две функции были одного типа?
 A. They should share the same signatures, including parameter types and return types.
 B. They should share the same parameter types but can return different types.
 C. All functions should be the same type.
 D. The functions should not be a first class type.

Если мы хотим, чтобы две функции в Go были одного типа, они должны иметь одинаковую сигнатуру функции .

type sigFunc func(a int, b float64) (bool, error)

func functionA(a int, b float64) (bool, error) {
  return true, nil
}

func functionB(a int, b float64) (bool, error) {
  return false, nil
}

func main() {
  var x sigFunc = functionA
  x = functionB
  print(x)
}

Это означает, что они должны иметь соответствующие параметры (количество, типы) и возвращаемые значения.

2. Что возвращает функция len(), если ей передаётся строка в кодировке UTF-8?
 A. the number of characters
 B. the number of bytes
 C. It does not accept string types.
 D. the number of code points

Вот вам небольшая идея: в Go строки на самом деле представляют собой последовательности байтов. Это означает, что когда вы передаёте строку в кодировке UTF-8 функции len(), она считает байты, а не символы:

func main() {
  s := "世界"
  fmt.Println("Byte length:", len(s)) // 6 bytes
  fmt.Println("Rune count:", utf8.RuneCountInString(s)) // 2 letters
}
3. Каково значение Read?
const (
  Write = iota
  Read
  Execute
)

A. 0
B. 1
C. 2
D. a random value

Идентификатор iota в Go — интересная функция — она представляет целочисленное значение, которое начинается с 0 и увеличивается на 1 для каждого элемента в блоке const.

4. Как вы выведите запущенные тесты с помощью go test?
A. go test
B. go test -x
C. go test --verbose
D. go test -v

При использовании команды go test вы можете включить флаг -v (“verbose”) для более подробного вывода.

go test -v

Запустив go test с флагом -v, вы увидите название каждого теста, его результат и продолжительность теста по мере его выполнения:

=== RUN   TestAdd
=== RUN   TestAdd/case_1_2_3
=== RUN   TestAdd/case_-1_-2
=== RUN   TestAdd/case_0
=== RUN   TestAdd/case_-1_2
--- PASS: TestAdd (0.00s)
    --- PASS: TestAdd/case_1_2_3 (0.00s)
    --- PASS: TestAdd/case_-1_-2 (0.00s)
    --- PASS: TestAdd/case_0 (0.00s)
    --- PASS: TestAdd/case_-1_2 (0.00s)
PASS
ok      command-line-arguments  0.523s
5. Что делает блок sync.Mutex, пока он заблокирован?
A. all goroutines
B. any other call to lock that Mutex
C. any reads or writes of the variable it is locking
D. any writes to the variable it is locking

В Go a sync.Mutex служит механизмом взаимного исключения, гарантируя, что только одна горутина может одновременно получить доступ к критическому разделу кода.

Фраза « любое чтение или запись переменной, которую он блокирует » соответствует нашему пониманию, поскольку мы используем a sync.Mutex для защиты доступа к общим переменным.

var mtx = sync.Mutex{}

func Add() {
  mtx.Lock()
  defer mtx.Unlock()
  a++
}
6. Каков идиоматический способ приостановить выполнение текущей области до тех пор, пока не будет возвращено произвольное количество подпрограмм?
A. Pass an int and Mutex to each and count when they return.
B. Loop over a select statement.
C. Sleep for a safe amount of time.
D. sync.WaitGroup

Если у вас была возможность изучить мою статью о пакете Go Sync, вы, возможно, помните, что сначала я использовал функцию time.Sleep, прежде чем перейти к более эффективному sync.WaitGroup:

func main() {
  wg := sync.WaitGroup{}
  for i := 0; i < 500; i++ {
    wg.Add(1)
    go func() {
      defer wg.Done()
      Add()
    }()
  }
  
  wg.Wait()
  fmt.Println(a)
}

In sync.WaitGroupGo — это удобный инструмент, который позволяет вам дождаться завершения группы горутин, прежде чем приступить к выполнению.

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

7. Каков побочный эффект использования time.After в утверждении select?
A. It blocks the other channels.
B. It is meant to be used in select statements without side effects.
C. It blocks the select statement until the time has passed.
D. The goroutine does not end until the time passes.

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

func After(d Duration) <-chan Time

Обычно она используется в операторах select для реализации тайм-аутов или задержек. Например, представьте, что вы ждёте 3 секунды, прежде чем вывести что-то на экране:

func main() {
  timeout := 3 * time.Second
  start := time.Now()
  done := make(chan bool)

  select {
  case <-done:
      fmt.Println("Operation completed.")
      return
  case <-time.After(timeout):
    fmt.Printf("Timeout after %v\n", time.Since(start))
  }
}

Теперь поговорим о побочном эффекте.

Для кратковременных time.After периодов это может не иметь большого значения, но рассмотрим сценарий, в котором тайм-аут установлен на 1 час, а работа завершается до истечения времени ожидания. В этой ситуации таймер всё ещё задерживается в памяти:

func main() {
 done := make(chan bool)

 go func() {
  time.Sleep(500 * time.Millisecond)
  done <- true
 }()

  for {
    select {
    case <-done:
      fmt.Println("Operation completed.")
      return
    case <-time.After(time.Hour):
      fmt.Println("Still waiting...")
    }
  }
}

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

8. Какие ограничения есть на тип var для компиляции i := myVal.(int)?
A. myVal must be an integer type, such as int, int64, int32, etc.
B. myVal must be able to be asserted as an int.
C. myVal must be an interface.
D. myVal must be a numeric type, such as float64 or int64.

В контексте утверждения типа i := myVal.(int) переменная myVal должна быть типом интерфейса для успешной компиляции кода.

Однако использование её таким образом может быть рискованным и неэффективным, так как это может вызвать панику во время выполнения, если myVal не типа int. Чтобы более изящно справиться с этой ситуацией, лучше использовать форму утверждения типа с двумя значениями, которая обеспечивает резервный механизм:

// BAD
i := myVal.(int)

// BETTER
i, ok := myVal.(int)
// ... doing something with ok 
9. Как правильно передать это как тело запроса HTTP POST?
data := "A group of Owls is called a parliament"

A. resp, err := http.Post("https://httpbin.org/post", "text/plain", []byte(data))
B. resp, err := http.Post("https://httpbin.org/post", "text/plain", data)
C. resp, err := http.Post("https://httpbin.org/post", "text/plain", strings.NewReader(data))
D. resp, err := http.Post("https://httpbin.org/post", "text/plain", &data)

Чтобы отправить данные в виде тела запроса HTTP POST, важно знать тип содержимого. Поскольку это необработанный текст, мы будем использовать тип содержимого «текст/обычный». Функция http.Post требует io.Reader в качестве тела, а не строку или байты:

Post(url string, contentType string, body io.Reader) (resp *http.Response, err error)

Интерфейс Reader определяется следующим образом:

type Reader interface {
  Read(p []byte) (n int, err error)
}

Чтобы выполнить требования, мы преобразуем тело в буфер, реализующий этот интерфейс:

func main() {
  data := "A group of Owls is called a parliament"
  
  contentType := "text/plain"
  
  body := strings.NewReader(data)
  // or 
  // body := bytes.NewBufferString(data)
  
  resp, err := http.Post("https://example.com", contentType, body)
 
  // ....
}

Таким образом, правильный способ отправки данных в виде HTTP-запроса POST:

resp, err := http.Post("https://httpbin.org/post", "text/plain", strings.NewReader(data))
10. Каким должно быть идиоматическое имя для интерфейса с одним методом и сигнатурой Save() error?
A. Saveable
B. SaveInterface
C. ISave
D. Saver

Основываясь на эффективном соглашении об именах Go для интерфейсов, интерфейсы с одним методом обычно именуются путём добавления суффикса «-er» или аналогичной модификации к имени метода, что приводит к ReaderWriterFormatter и CloseNotifier..

В этом случае идиоматическое название интерфейса будет Saver .

О, возможно, вы также сталкивались с интерфейсом Stringer(), который определяется следующим образом:

type Stringer interface {
  String() string
}

Это уловка, когда вы передаёте значение, реализующее интерфейс Stringer таким функциям, как fmt.Println() или fmt.Printf()метод String() автоматически вызывается для получения строкового представления значения.

11. Какова чувствительность к регистру по умолчанию для функции JSON Unmarshal?
A. The default behavior is case insensitive, but it can be overridden.
B. Fields are matched case sensitive.
C. Fields are matched case insensitive.
D. The default behavior is case sensitive, but it can be overridden.

Функция JSON Unmarshal в Go по умолчанию нечувствительна к регистру. Однако это поведение можно переопределить с помощью тегов JSON. Что происходит, когда есть два поля, такие как «title» и «Title»?

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

type Post struct {
  Title    string `json:"Title"`
  SubTitle string
}

func main() {
  p := Post{}
  json.Unmarshal([]byte(`{"title":"hello","subtitle":"world"}`), &p)
  fmt.Println(p)
}

// {hello world}

В этом примере поле Title имеет явный тег поля json:"Title", предписывающий функции json.Unmarshal сопоставлять поле JSON «Title» (с учётом регистра) с полем структуры Title. Поле SubTitle не имеет тега поля, поэтому оно будет использовать поведение по умолчанию без учёта регистра, чтобы сопоставить поле «subtitle» JSON с полем структуры SubTitle.

«Но мне интересно, почему поле «title» всё ещё совпадает, даже если оно перекрывается «Title»

Это связано с тем, что пакет encoding/json использует резервный механизм во время демаршалинга:

  1. Сначала он ищет точное совпадение между именами полей JSON и именами полей или тегами структуры.
  2. Если он не находит точного совпадения, он возвращается к линейному поиску без учёта регистра (который может быть дорогостоящим для больших наборов данных).
type Post struct {
  Title    string
  SubTitle string
  TitleSm  string `json:"title"`
}

func main() {
  p := Post{}
  json.Unmarshal([]byte(`{"title":"hello","subtitle":"world"}`), &p)
  fmt.Println(p)
}

// { world hello}
// p.Title is empty
12. Где полезен встроенный метод recover?
A. in the main function
B. immediately after a line that might panic
C. inside a deferred function
D. at the beginning of a function that might panic

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

// BAD 
func main() {
  defer recover() 
  
  panicCode() // <-- crash
}

// BETTER
func handlePanic() {
  if panicInfo := recover(); panicInfo != nil {
    fmt.Println(panicInfo)
  }
}

func main() {
  defer handlePanic()
  
  panicCode()
}
13. В чём разница между time пакетами Time.Sub() и методами Time.Add()?
A. Time.Add() is for performing addition while Time.Sub() is for nesting timestamps.
B. Time.Add() always returns a later time while time.Sub always returns an earlier time.
C. They are opposites. Time.Add(x) is the equivalent of Time.Sub(-x).
D. Time.Add() accepts a Duration parameter and returns a Time while Time.Sub() accepts a Time parameter and returns a Duration.

Основное различие между методами Time.Add()и Time.Sub() в пакете time заключается в их параметрах и возвращаемых значениях. Time.Add() принимает параметр Duration и возвращает значение Time, а Time.Sub() принимает параметр Time и возвращает Duration.

«Почему они оба не принимают Duration и не возвращаются Time

Причина в том, что Time.Add() может обрабатывать отрицательные аргументы, эффективно функционируя как операция вычитания. Следовательно, не имеет смысла иметь другой метод Time.Sub(), который также принимает файлы Duration.

Методы Time.Add() и Time.Sub() служат разным целям и имеют разные сигнатуры для конкретных вариантов использования:

func main() {
  now := time.Now()
  
  newTime := now.Add(2 * time.Hour)
  fmt.Println("Time after 2 hours:", newTime)
  newTime = now.Add(2 * time.Hour)
  fmt.Println("Time before 2 hours:", newTime)
  
  duration := newTime.Sub(now)
  fmt.Println("Duration newTime to now:", duration)
}
Time after 2 hours: 2023-05-09 03:05:03.177199 +0700 +07 m=+7200.000587876
Time before 2 hours: 2023-05-09 03:05:03.177199 +0700 +07 m=+7200.000587876
Duration newTime to now: 2h0m0s

Как показано в этом примере, Time.Add() используется для добавления или вычитания продолжительности из значения времени, а Time.Sub() используется для вычисления продолжительности между двумя значениями времени.

14. Каков риск использования нескольких тегов полей в одной структуре?
A. Every field must have all tags to compile.
B. It tightly couples different layers of your application.
C. Any tags after the first are ignored.
D. Missing tags panic at runtime.

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

Но что такое « пары разных слоёв»?

type Post struct {
  Title    string `json:"title" bson:"title"`
  SubTitle string `json:"subtitle" bson:"subtitle"`
}

В этом примере структура Post имеет два тега поля для каждого поля: json и bson. Эти теги могут использоваться для различных целей, таких как отправка HTTP-ответов (с помощью json) и обработка демаршалинга MongoDB (с помощью bson).

Когда используется несколько таких тегов, уровень ответа HTTP (веб-сервер) и уровень хранения (MongoDB) становятся тесно связанными. Если вы хотите изменить title, например, на shortTitle, вам потребуется обновить как ответ HTTP (что также может повлиять на клиентов, обрабатывающих ответ), так и хранилище MongoDB.

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

15. Если вы перебираете мапу в цикле for range, в каком порядке будет осуществляться доступ к парам ключ:значение?
A. in pseudo-random order that cannot be predicted
B. in reverse order of how they were added, last in first out
C. sorted by key in ascending order
D. in the order they were added, first in first out

При переборе мапы с использованием цикла for range в Go порядок доступа к парам ключ-значение не обязательно соответствует какой-либо конкретной последовательности и не обязательно соответствует порядку вставки:

func main() {
  m := map[string]int{}
  m["a"] = 1
  m["b"] = 2
  m["c"] = 3
  
  for key, value := range m {
    fmt.Printf("Key: %s, Value: %d\n", key, value)
  }
  
  for key, value := range m {
    fmt.Printf("Key: %s, Value: %d\n", key, value)
  }
}
Key: a, Value: 1
Key: b, Value: 2
Key: c, Value: 3
Key: c, Value: 3
Key: a, Value: 1
Key: b, Value: 2

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

+1
0
+1
7
+1
0
+1
2
+1
0

Ответить

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