Статьи

Первые шаги в мир го

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

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

Существует также плагин Eclipse, который поддерживает Go. Хотя это не так полно, как поддержка Java (например, очень мало быстрых исправлений или рефакторингов), это намного лучше, чем для некоторых других языков, которые я пробовал.

Лучший способ научиться чему-либо — это учиться на практике , поэтому я начал с простого ката : разработка набора типов. Стандартная библиотека для Go не предлагает наборы , но это не главное; Я просто хочу выучить язык, построив что-то знакомое.

Итак, начнем с первого теста. Соглашение в Go заключается в создании файла с именем foo_test.go если вы хотите протестировать foo.go

01
02
03
04
05
06
07
08
09
10
11
12
package set
  
import (
  "testing"
)
  
func TestEmpty(t *testing.T) {
  empty := NewSet()
  if !empty.IsEmpty() {
    t.Errorf("Set without elements should be empty")
  }
}

(WordPress в настоящее время не поддерживает подсветку синтаксиса Go, поэтому ключевое слово func не отображается как таковое.)

Об этом куске кода следует отметить несколько вещей:

  • Go поддерживает пакеты, используя оператор package
  • Операторы заканчиваются точкой с запятой ( ; ), но вы можете опустить их в конце строки, как в Groovy
  • Вы импортируете пакет, используя инструкцию import . Пакет testing является частью стандартной библиотеки
  • Все, что начинается с буквы в нижнем регистре, является закрытым для пакета, все, что начинается с буквы в верхнем регистре, является открытым
  • Код в Go идет внутри функции, как указано ключевым словом func
  • Имена переменных пишутся перед типом
  • Синтаксис := является сокращением для объявления и инициализации переменной; Go определит правильный тип
  • Go не имеет конструкторов , но использует фабричные функции для достижения того же
  • if операторы не требуют скобок вокруг условия, но требуют скобок
  • Пакет testing довольно мал и не имеет утверждений. Хотя есть пакеты, которые предоставляют их, я решил остаться здесь по умолчанию

Итак, давайте пройдем тестирование:

01
02
03
04
05
06
07
08
09
10
11
12
package set
  
type set struct {
}
  
func NewSet() *set {
  return new(set)
}
  
func (s *set) IsEmpty() bool {
  return true
}

Отличительной особенностью плагина Eclipse является то, что он автоматически запускает тесты всякий раз, когда вы сохраняете файл, во многом как InfiniTest для Java. Это действительно хорошо, когда вы делаете тест-ориентированную разработку.

Конечно, теперь это не слишком сложный тест, поскольку он проверяет только одну сторону монеты IsEmpty() . Что и позволяет нам подделать реализацию. Итак, давайте исправим тест:

01
02
03
04
05
06
07
08
09
10
11
func TestEmpty(t *testing.T) {
  empty := NewSet()
  one := NewSet()
  one.Add("A")
  if !empty.IsEmpty() {
    t.Errorf("Set without elements should be empty")
  }
  if one.IsEmpty() {
    t.Errorf("Set with one element should not be empty")
  }
}

Который мы можем легко сделать пропуск:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
type set struct {
  empty bool
}
  
func NewSet() *set {
  s := new(set)
  s.empty = true
  return s
}
  
func (s *set) IsEmpty() bool {
  return s.empty
}
  
func (s *set) Add(item string) {
  s.empty = false
}

Обратите внимание, что я использовал string тип в качестве аргумента Add() . Очевидно, мы бы хотели что-то более общее, но в Go нет Object как в Java. Я вернусь к этому решению позже.

Следующий тест проверяет количество предметов в наборе:

01
02
03
04
05
06
07
08
09
10
11
func TestSize(t *testing.T) {
  empty := NewSet()
  one := NewSet()
  one.Add("A")
  if empty.Size() != 0 {
    t.Errorf("Set without elements should have size 0")
  }
  if one.Size() != 1 {
    t.Errorf("Set with one element should have size 1")
  }
}

Который мы пропускаем путем обобщения empty size

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
type set struct {
  size int
}
  
func NewSet() *set {
  s := new(set)
  s.size = 0
  return s
}
  
func (s *set) IsEmpty() bool {
  return s.Size() == 0
}
  
func (s *set) Add(item string) {
  s.size++
}
  
func (s *set) Size() int {
  return s.size
}

Теперь, когда тесты пройдены, нам нужно немного их очистить:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var empty *set
var one *set
  
func setUp() {
  empty = NewSet()
  one = NewSet()
  one.Add("A")
}
  
func TestEmpty(t *testing.T) {
  setUp()
  if !empty.IsEmpty() {
    t.Errorf("Set without elements should be empty")
  }
  if one.IsEmpty() {
    t.Errorf("Set with one element should not be empty")
  }
}
  
func TestSize(t *testing.T) {
  setUp()
  if empty.Size() != 0 {
    t.Errorf("Set without elements should have size 0")
  }
  if one.Size() != 1 {
    t.Errorf("Set with one element should have size 1")
  }
}

Еще раз отметим отсутствие поддержки тестовой инфраструктуры по сравнению, скажем, с JUnit. Мы должны вручную вызвать setUp() .

С кодом в лучшей форме давайте добавим следующий тест:

1
2
3
4
5
6
7
8
9
func TestContains(t *testing.T) {
  setUp()
  if empty.Contains("A") {
    t.Errorf("Empty set should not contain element")
  }
  if !one.Contains("A") {
    t.Errorf("Set should contain added element")
  }
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
type set struct {
  items []string
}
  
func NewSet() *set {
  s := new(set)
  s.items = make([]string, 0, 10)
  return s
}
  
func (s *set) Add(item string) {
  s.items = append(s.items, item)
}
  
func (s *set) Size() int {
  return len(s.items)
}
  
func (s *set) Contains(item string) bool {
  for _, value := range s.items {
    if (value == item) {
      return true
    }
  }
  return false
}

Срез — это удобная структура данных в виде массива, которая поддерживается настоящим массивом. Массивы не могут изменить размер, но они могут быть больше, чем фрагменты, которые они возвращают. Это позволяет добавлять элементы к срезу эффективно.

Цикл for является единственной циклической конструкцией в Go, но он немного более мощный, чем for большинства других языков. Он дает как индекс, так и значение, первое из которых мы игнорируем, используя подчеркивание ( _ ). Он перебирает все элементы в срезе, используя ключевое слово range .

Итак, теперь у нас есть коллекция сортов, но еще не совсем набор:

1
2
3
4
5
6
7
func TestIgnoresDuplicates(t *testing.T) {
  setUp()
  one.Add("A")
  if one.Size() != 1 {
    t.Errorf("Set should ignore adding an existing element")
  }
}
1
2
3
4
5
func (s *set) Add(item string) {
  if !s.Contains(item) {
    s.items = append(s.items, item)
  }
}

Все, что нам осталось сделать, чтобы сделать этот полнофункциональный набор, это разрешить удаление элементов:

1
2
3
4
5
6
7
8
func TestRemove(t *testing.T) {
  setUp()
  one.Remove("A")
  
  if one.Contains("A") {
    t.Errorf("Set still contains element after removing it")
  }
}
1
2
3
4
5
6
7
8
func (s *set) Remove(item string) {
  for index, value := range s.items {
    if value == item {
      s.items[index] = s.items[s.Size() - 1]
      s.items = s.items[0:s.Size() - 1]
    }
  }
}

Здесь мы видим полную форму цикла for , как с индексом, так и со значением. Этот цикл очень похож на цикл Contains() , поэтому мы можем извлечь метод, чтобы избавиться от дублирования:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
func (s *set) Contains(item string) bool {
  return s.indexOf(item) >= 0
}
  
func (s *set) indexOf(item string) int {
  for index, value := range s.items {
    if value == item {
      return index
    }
  }
  return -1
}
  
func (s *set) Remove(item string) {
  index := s.indexOf(item)
  if index >= 0 {
    s.items[index] = s.items[s.Size()-1]
    s.items = s.items[0 : s.Size()-1]
  }
}

Обратите внимание на indexOf() букву в indexOf() которая делает его закрытым методом. Поскольку наш набор неупорядочен, не имеет смысла раскрывать эту функциональность.

Наконец, нам нужно обобщить набор, чтобы он мог содержать элементы любого типа:

01
02
03
04
05
06
07
08
09
10
11
12
13
func TestNonStrings(t *testing.T) {
  set := NewSet()
  
  set.Add(1)
  if !set.Contains(1) {
    t.Errorf("Set does not contain added integer")
  }
  
  set.Remove(1)
  if set.Contains(1) {
    t.Errorf("Set still contains removed integer")
  }
}

Некоторые раскопки показывают, что мы можем имитировать Object Java в Go с пустым интерфейсом :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
type set struct {
  items []interface{}
}
  
func NewSet() *set {
  s := new(set)
  s.items = make([]interface{}, 0, 10)
  return s
}
  
func (s *set) IsEmpty() bool {
  return s.Size() == 0
}
  
func (s *set) Add(item interface{}) {
  if !s.Contains(item) {
    s.items = append(s.items, item)
  }
}
  
func (s *set) Size() int {
  return len(s.items)
}
  
func (s *set) Contains(item interface{}) bool {
  return s.indexOf(item) >= 0
}
  
func (s *set) indexOf(item interface{}) int {
  for index, value := range s.items {
    if value == item {
      return index
    }
  }
  return -1
}
  
func (s *set) Remove(item interface{}) {
  index := s.indexOf(item)
  if index >= 0 {
    s.items[index] = s.items[s.Size()-1]
    s.items = s.items[0 : s.Size()-1]
  }
}

В целом, я нашел работу в Go довольно приятной. Язык простой, но мощный. Команда go fmt убивает дискуссии о компоновке кода, а также настойчивость компилятора в скобках с if. Где Go действительно сияет, так это параллельное программирование, но это нечто другое.

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

Ссылка: Первые шаги в мир перехода от нашего партнера по JCG Ремона Синнема в блоге по безопасной разработке программного обеспечения .