> For the complete documentation index, see [llms.txt](https://eda-1.gitbook.io/lgwt/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://eda-1.gitbook.io/lgwt/osnovy-go/hello-world.md).

# Hello, World

[**Весь код для этой главы можно найти здесь**](https://github.com/quii/learn-go-with-tests/tree/main/hello-world)

Традиционно вашей первой программой на новом языке является [Hello, World](https://en.m.wikipedia.org/wiki/%22Hello,_World!%22_program).

* Создайте папку в любом удобном для вас месте
* Поместите в нее новый файл с именем `hello.go` и вставьте в него следующий код

```go
package main

import "fmt"

func main() {
	fmt.Println("Hello, world")
}
```

Чтобы запустить ее, наберите `go run hello.go`.

## Как это работает

Когда вы пишете программу на Go, у вас будет определен пакет `main` с функцией `main` внутри него. Пакеты — это способы группировки связанного Go-кода.

Ключевое слово `func` определяет функцию с именем и телом.

С помощью `import "fmt"` мы импортируем пакет, который содержит функцию `Println`, используемую для вывода.

## Как тестировать

Как это протестировать? Хорошо отделять ваш "доменный" код от внешнего мира (побочных эффектов). `fmt.Println` является побочным эффектом (печать в stdout), а строка, которую мы передаем, является нашим доменом.

Итак, давайте разделим наш код, чтобы было легче тестировать

```go
package main

import "fmt"

func Hello() string {
	return "Hello, world"
}

func main() {
	fmt.Println(Hello())
}
```

Мы создали новую функцию с помощью `func`, но на этот раз мы добавили еще одно ключевое слово `string` к определению. Это означает, что эта функция возвращает строку.

Теперь создайте новый файл `hello_test.go`, где мы будем писать тест для нашей функции `Hello`

```go
package main

import "testing"

func TestHello(t *testing.T) {
	got := Hello()
	want := "Hello, world"

	if got != want {
		t.Errorf("got %q want %q", got, want)
	}
}
```

## Go-модули?

Следующий шаг — запустить тесты. Введите `go test` в вашем терминале. Если тесты проходят, то вы, вероятно, используете более раннюю версию Go. Однако, если вы используете Go 1.16 или новее, тесты, скорее всего, не запустятся. Вместо этого вы увидите сообщение об ошибке, подобное этому, в терминале:

```shell
$ go test
go: cannot find main module; see 'go help modules'
```

В чем проблема? Одним словом, [модули](https://blog.golang.org/go116-module-changes). К счастью, проблему легко исправить. Введите `go mod init example.com/hello` в вашем терминале. Это создаст новый файл со следующим содержимым:

```
module example.com/hello

go 1.16
```

Этот файл сообщает инструментам `go` важную информацию о вашем коде. Если бы вы планировали распространять свое приложение, вы бы включили информацию о том, где код доступен для загрузки, а также информацию о зависимостях. Имя модуля, example.com/hello, обычно относится к URL-адресу, где модуль можно найти и загрузить. Для совместимости с инструментами, которые мы скоро начнем использовать, убедитесь, что имя вашего модуля содержит точку, как, например, точка в .com в example.com/hello. Пока ваш файл модуля минимален, и вы можете оставить его таким. Чтобы узнать больше о модулях, [вы можете ознакомиться с документацией в Golang](https://golang.org/doc/modules/gomod-ref). Теперь мы можем вернуться к тестированию и изучению Go, поскольку тесты должны работать, даже на Go 1.16.

В будущих главах вам нужно будет запускать `go mod init SOMENAME` в каждой новой папке перед выполнением таких команд, как `go test` или `go build`.

Стоит уточнить, что `SOMENAME`, имя модуля, не связано с `package main` (который каждый файл `.go` в этой книге пока объявляет в начале). Имя модуля — это просто идентификатор для вашего проекта в целом — его не обязательно называть `main`, и `go run`, `go test`, `go build` будут работать корректно с любым выбранным вами именем, если запускать их из той же папки.

## Возвращаемся к тестированию

Запустите `go test` в вашем терминале. Тесты должны были пройти! Просто для проверки, попробуйте специально сломать тест, изменив строку `want`.

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

### Написание тестов

Написание теста похоже на написание функции, с несколькими правилами:

* Он должен находиться в файле с именем вроде `xxx_test.go`.
* Тестовая функция должна начинаться со слова `Test`.
* Тестовая функция принимает только один аргумент: `t *testing.T`.
* Чтобы использовать тип `*testing.T`, вам нужно `import "testing"`, как мы это делали с `fmt` в другом файле.

На данный момент достаточно знать, что ваш `t` типа `*testing.T` — это ваш "крючок" к фреймворку тестирования, чтобы вы могли делать такие вещи, как `t.Fail()`, когда хотите вызвать ошибку.

Мы рассмотрели несколько новых тем:

#### `if`

Операторы `if` в Go очень похожи на операторы в других языках программирования.

#### Объявление переменных

Мы объявляем некоторые переменные с синтаксисом `varName := value`, что позволяет нам повторно использовать некоторые значения в нашем тесте для читаемости.

#### `t.Errorf`

Мы вызываем *метод* `Errorf` у нашего `t`, который выведет сообщение и завершит тест с ошибкой. `f` означает "format" (формат), что позволяет нам создавать строку с значениями, вставленными в плейсхолдеры `%q`. Когда вы намеренно вызываете ошибку теста, должно быть ясно, как это работает.

Вы можете прочитать больше о строках-плейсхолдерах в [документации fmt](https://pkg.go.dev/fmt#hdr-Printing). Для тестов `%q` очень полезен, так как он заключает ваши значения в двойные кавычки.

Позже мы рассмотрим разницу между методами и функциями.

### Документация Go

Еще одна функция Go, повышающая удобство работы, — это документация. Мы только что видели документацию для пакета `fmt` на официальном сайте просмотра пакетов, и Go также предоставляет способы быстрого получения документации в офлайн-режиме.

В Go есть встроенный инструмент `doc`, который позволяет изучать любой пакет, установленный в вашей системе, или модуль, над которым вы в данный момент работаете. Чтобы просмотреть ту же документацию для глаголов форматирования (Printing verbs):

```
$ go doc fmt
package fmt // import "fmt"

Package fmt implements formatted I/O with functions analogous to C's printf and
scanf. The format 'verbs' are derived from C's but are simpler.

# Printing

The verbs:

General:

    %v	the value in a default format
    	when printing structs, the plus flag (%+v) adds field names
    %#v	a Go-syntax representation of the value
    %T	a Go-syntax representation of the type of the value
    %%	a literal percent sign; consumes no value
...
```

Второй инструмент Go для просмотра документации — это команда `pkgsite`, которая лежит в основе официального веб-сайта Go для просмотра пакетов. Вы можете установить `pkgsite` с помощью `go install golang.org/x/pkgsite/cmd/pkgsite@latest`, а затем запустить его с помощью `pkgsite -open .`. Команда установки Go загрузит исходные файлы из этого репозитория и соберет их в исполняемый бинарный файл. Для стандартной установки Go этот исполняемый файл будет находиться в `$HOME/go/bin` для Linux и macOS и `%USERPROFILE%\go/bin` для Windows. Если вы еще не добавили эти пути в свою переменную $PATH, возможно, вы захотите это сделать, чтобы упростить запуск команд, установленных Go.

Подавляющее большинство стандартной библиотеки имеет отличную документацию с примерами. Стоит перейти по ссылке <http://localhost:8080/testing>, чтобы увидеть, что вам доступно.

### Hello, YOU

Теперь, когда у нас есть тест, мы можем безопасно итерировать наше программное обеспечение.

В последнем примере мы написали тест *после* того, как код был написан, чтобы вы могли получить пример того, как писать тест и объявлять функцию. С этого момента мы будем *сначала писать тесты*.

Наше следующее требование — позволить нам указывать получателя приветствия.

Давайте начнем с фиксации этих требований в тесте. Это базовый подход к разработке через тестирование (TDD), который позволяет нам убедиться, что наш тест *действительно* проверяет то, что мы хотим. При ретроспективном написании тестов существует риск того, что ваш тест может продолжать проходить, даже если код работает не так, как задумано.

```go
package main

import "testing"

func TestHello(t *testing.T) {
	got := Hello("Chris")
	want := "Hello, Chris"

	if got != want {
		t.Errorf("got %q want %q", got, want)
	}
}
```

Теперь запустите `go test`, у вас должна быть ошибка компиляции

```
./hello_test.go:6:18: too many arguments in call to Hello
    have (string)
    want ()
```

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

В этом случае компилятор говорит вам, что нужно сделать, чтобы продолжить. Мы должны изменить нашу функцию `Hello`, чтобы она принимала аргумент.

Отредактируйте функцию `Hello`, чтобы она принимала аргумент типа `string`

```go
func Hello(name string) string {
	return "Hello, world"
}
```

Если вы снова попытаетесь запустить свои тесты, ваш `hello.go` не скомпилируется, потому что вы не передаете аргумент. Передайте "world", чтобы он скомпилировался.

```go
func main() {
	fmt.Println(Hello("world"))
}
```

Теперь, когда вы запустите свои тесты, вы должны увидеть что-то вроде

```
hello_test.go:10: got 'Hello, world' want 'Hello, Chris''
```

Наконец-то у нас есть компилирующаяся программа, но она не соответствует нашим требованиям согласно тесту.

Давайте заставим тест пройти, используя аргумент `name` и объединив его с `Hello,`

```go
func Hello(name string) string {
	return "Hello, " + name
}
```

Когда вы запустите тесты, они теперь должны пройти. Обычно, в рамках цикла TDD, мы должны теперь *рефакторить*.

### Примечание о системе контроля версий

На этом этапе, если вы используете систему контроля версий (а вы должны!), я бы `commit`-нул код как есть. У нас есть работающее программное обеспечение, подкрепленное тестом.

Я бы *не* стал пушить в `main`, потому что планирую рефакторинг следующим. Приятно сделать коммит на этом этапе на случай, если вы как-то запутаетесь с рефакторингом — вы всегда сможете вернуться к рабочей версии.

Здесь не так много для рефакторинга, но мы можем ввести еще одну языковую особенность — *константы*.

### Константы

Константы определяются так:

```go
const englishHelloPrefix = "Hello, "
```

Теперь мы можем рефакторить наш код

```go
const englishHelloPrefix = "Hello, "

func Hello(name string) string {
	return englishHelloPrefix + name
}
```

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

Стоит подумать о создании констант для фиксации значения и иногда для повышения производительности.

## Hello, world... снова

Следующее требование заключается в том, что если наша функция вызывается с пустой строкой, она по умолчанию должна выводить "Hello, World", а не "Hello, ".

Начните с написания нового ошибочного теста

```go
func TestHello(t *testing.T) {
	t.Run("saying hello to people", func(t *testing.T) {
		got := Hello("Chris")
		want := "Hello, Chris"

		if got != want {
			t.Errorf("got %q want %q", got, want)
		}
	})
	t.Run("say 'Hello, World' when an empty string is supplied", func(t *testing.T) {
		got := Hello("")
		want := "Hello, World"

		if got != want {
			t.Errorf("got %q want %q", got, want)
		}
	})
}
```

Здесь мы вводим еще один инструмент в наш арсенал тестирования: подтесты. Иногда полезно группировать тесты вокруг "вещи", а затем иметь подтесты, описывающие различные сценарии.

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

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

```go
const englishHelloPrefix = "Hello, "

func Hello(name string) string {
	if name == "" {
		name = "World"
	}
	return englishHelloPrefix + name
}
```

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

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

Рефакторинг предназначен не *только* для production-кода!

Теперь, когда тесты проходят, мы можем и должны рефакторить наши тесты.

```go
func TestHello(t *testing.T) {
	t.Run("saying hello to people", func(t *testing.T) {
		got := Hello("Chris")
		want := "Hello, Chris"
		assertCorrectMessage(t, got, want)
	})

	t.Run("empty string defaults to 'world'", func(t *testing.T) {
		got := Hello("")
		want := "Hello, World"
		assertCorrectMessage(t, got, want)
	})

}

func assertCorrectMessage(t testing.TB, got, want string) {
	t.Helper()
	if got != want {
		t.Errorf("got %q want %q", got, want)
	}
}
```

Что мы здесь сделали?

Мы рефакторили наше утверждение (assertion) в новую функцию. Это уменьшает дублирование и улучшает читаемость наших тестов. Нам нужно передать `t *testing.T`, чтобы мы могли указать тестовому коду завершиться ошибкой, когда нам это нужно.

Для вспомогательных функций рекомендуется принимать `testing.TB`, который является интерфейсом, удовлетворяющим как `*testing.T`, так и `*testing.B`, поэтому вы можете вызывать вспомогательные функции из теста или бенчмарка (не беспокойтесь, если слова вроде "интерфейс" пока ничего для вас не значат, это будет рассмотрено позже).

`t.Helper()` необходим для того, чтобы сообщить тестовому набору, что этот метод является вспомогательным. Делая это, при возникновении ошибки номер строки, указанный в отчете, будет находиться в нашем *вызове функции*, а не внутри нашей вспомогательной функции теста. Это поможет другим разработчикам легче отслеживать проблемы. Если вы всё ещё не понимаете, закомментируйте его, заставьте тест завершиться ошибкой и понаблюдайте за выводом теста. Комментарии в Go — отличный способ добавить дополнительную информацию к вашему коду, или, в данном случае, быстрый способ сказать компилятору игнорировать строку. Вы можете закомментировать код `t.Helper()`, добавив два косых слэша `//` в начале строки. Вы должны увидеть, как эта строка станет серой или изменит цвет по сравнению с остальным кодом, чтобы указать, что она теперь закомментирована.

Когда у вас есть более одного аргумента одного и того же типа (в нашем случае две строки), вместо `(got string, want string)` вы можете сократить это до `(got, want string)`.

### Возвращаемся к системе контроля версий

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

### Дисциплина

Давайте еще раз рассмотрим цикл:

* Напишите тест
* Убедитесь, что компилятор проходит
* Запустите тест, убедитесь, что он завершается ошибкой, и проверьте, что сообщение об ошибке является осмысленным
* Напишите достаточно кода, чтобы тест прошел
* Рефакторинг

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

Это не только гарантирует, что у вас есть *релевантные тесты*, но и помогает *проектировать хорошее программное обеспечение* путем рефакторинга с безопасностью тестов.

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

Обеспечивая *быстроту* ваших тестов и настраивая свои инструменты таким образом, чтобы запуск тестов был простым, вы можете войти в состояние потока при написании кода.

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

## Продолжаем! Больше требований

Боже мой, у нас еще больше требований. Теперь нам нужно поддерживать второй параметр, указывающий язык приветствия. Если передается язык, который мы не распознаем, по умолчанию используется английский.

Мы должны быть уверены, что можем легко использовать TDD для реализации этой функциональности!

Напишите тест для пользователя, передающего испанский язык. Добавьте его в существующий набор тестов.

```go
	t.Run("in Spanish", func(t *testing.T) {
		got := Hello("Elodie", "Spanish")
		want := "Hola, Elodie"
		assertCorrectMessage(t, got, want)
	})
```

Помните, не жульничайте! *Сначала тест*. Когда вы попытаетесь запустить тест, компилятор *должен* пожаловаться, потому что вы вызываете `Hello` с двумя аргументами, а не с одним.

```
./hello_test.go:27:19: too many arguments in call to Hello
    have (string, string)
    want (string)
```

Исправьте проблемы компиляции, добавив еще один строковый аргумент в `Hello`

```go
func Hello(name string, language string) string {
	if name == "" {
		name = "World"
	}
	return englishHelloPrefix + name
}
```

Когда вы снова попытаетесь запустить тест, он пожалуется на то, что в других тестах и в `hello.go` вы не передаете достаточно аргументов в `Hello`.

```
./hello.go:15:19: not enough arguments in call to Hello
    have (string)
    want (string, string)
```

Исправьте их, передав пустые строки. Теперь все ваши тесты должны компилироваться *и* проходить, кроме нашего нового сценария.

```
hello_test.go:29: got 'Hello, Elodie' want 'Hola, Elodie'
```

Мы можем использовать `if` здесь, чтобы проверить, равен ли язык "Spanish", и если да, изменить сообщение.

```go
func Hello(name string, language string) string {
	if name == "" {
		name = "World"
	}

	if language == "Spanish" {
		return "Hola, " + name
	}
	return englishHelloPrefix + name
}
```

Теперь тесты должны пройти.

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

```go
	const spanish = "Spanish"
	const englishHelloPrefix = "Hello, "
	const spanishHelloPrefix = "Hola, "

	func Hello(name string, language string) string {
		if name == "" {
			name = "World"
		}

		if language == spanish {
			return spanishHelloPrefix + name
		}
		return englishHelloPrefix + name
	}
```

### Французский

* Напишите тест, утверждающий, что если вы передадите `"French"`, вы получите `"Bonjour, "`
* Убедитесь, что он завершается ошибкой, проверьте, что сообщение об ошибке легко читается
* Внесите наименьшее разумное изменение в код

Вы могли написать что-то, что выглядит примерно так:

```go
func Hello(name string, language string) string {
	if name == "" {
		name = "World"
	}

	if language == spanish {
		return spanishHelloPrefix + name
	}
	if language == french {
		return frenchHelloPrefix + name
	}
	return englishHelloPrefix + name
}
```

## `switch`

Когда у вас много операторов `if`, проверяющих конкретное значение, вместо них часто используется оператор `switch`. Мы можем использовать `switch` для рефакторинга кода, чтобы сделать его более читаемым и расширяемым, если мы захотим добавить поддержку других языков позже.

```go
func Hello(name string, language string) string {
	if name == "" {
		name = "World"
	}

	prefix := englishHelloPrefix

	switch language {
	case spanish:
		prefix = spanishHelloPrefix
	case french:
		prefix = frenchHelloPrefix
	}

	return prefix + name
}
```

Обратите внимание, что мы используем `:=` один раз для `prefix` (объявляя новую переменную и присваивая ей начальное значение), но простой `=` везде, где значение изменяется, как для `name` выше, так и для `prefix` внутри `switch`. `:=` — это [короткое объявление переменной](https://go.dev/ref/spec#Short_variable_declarations) в Go — оно создает новую переменную. `=` — это простое [присваивание](https://go.dev/ref/spec#Assignment_statements) — оно изменяет значение переменной, которая уже существует (`name` уже существует как параметр, а `prefix` уже была объявлена парой строк выше с помощью `:=`). Использование `:=` для переменной, которая уже существует в той же области видимости, или `=` для той, которой еще не существует, является ошибкой компиляции.

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

### Еще... один... рефакторинг?

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

```go

const (
	spanish = "Spanish"
	french  = "French"

	englishHelloPrefix = "Hello, "
	spanishHelloPrefix = "Hola, "
	frenchHelloPrefix  = "Bonjour, "
)

func Hello(name string, language string) string {
	if name == "" {
		name = "World"
	}

	return greetingPrefix(language) + name
}

func greetingPrefix(language string) (prefix string) {
	switch language {
	case french:
		prefix = frenchHelloPrefix
	case spanish:
		prefix = spanishHelloPrefix
	default:
		prefix = englishHelloPrefix
	}
	return
}
```

Несколько новых концепций:

* В сигнатуре нашей функции мы сделали *именованный возвращаемый параметр* `(prefix string)`.
* Это создаст переменную с именем `prefix` в вашей функции.
  * Ей будет присвоено "нулевое" значение. Оно зависит от типа, например, для `int` это 0, а для `string` это `""`.
    * Вы можете вернуть всё, что в ней установлено, просто вызвав `return`, а не `return prefix`.
  * Это будет отображаться в Go Doc для вашей функции, что может сделать намерение вашего кода более ясным.
* `default` в операторе `switch` будет выбран, если ни один из других операторов `case` не совпадает.
* Имя функции начинается со строчной буквы. В Go публичные функции начинаются с заглавной буквы, а приватные — со строчной. Мы не хотим, чтобы внутренности нашего алгоритма были видны всему миру, поэтому мы сделали эту функцию приватной.
* Также мы можем группировать константы в блоке вместо объявления их на отдельной строке. Для читаемости рекомендуется использовать пустую строку между наборами связанных констант.

## Подводим итоги

Кто бы мог подумать, что из `Hello, world` можно извлечь так много?

К этому моменту вы должны иметь некоторое понимание:

### Некоторых синтаксических особенностей Go, касающихся

* Написания тестов
* Объявления функций, с аргументами и типами возвращаемых значений
* `if`, `const` и `switch`
* Объявления переменных и констант

### Процесса TDD и *почему* эти шаги важны

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

В нашем случае мы перешли от `Hello()` к `Hello("name")`, а затем к `Hello("name", "French")` небольшими, легко понятными шагами.

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


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://eda-1.gitbook.io/lgwt/osnovy-go/hello-world.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
