diff --git a/description.ru.yml b/description.ru.yml index 2f2a335..c4008b8 100644 --- a/description.ru.yml +++ b/description.ru.yml @@ -1,5 +1,5 @@ --- -header: 'Go: обучение программированию на языке go, бесплатно' +title: 'Go: обучение программированию на языке go, бесплатно' description: | Go — язык общего назначения с широкими возможностями и понятным синтаксисом. Мультиплатформенный, обладает надежной, хорошо документированной стандартной библиотекой и ориентирован на удобные подходы к самой разработке seo_description: | diff --git a/modules/10-basics/10-hello-world/description.ru.yml b/modules/10-basics/10-hello-world/description.ru.yml deleted file mode 100644 index b5779a8..0000000 --- a/modules/10-basics/10-hello-world/description.ru.yml +++ /dev/null @@ -1,48 +0,0 @@ ---- - -name: Привет, Мир! -theory: | - - Go (также часто его называют Golang) – это современный язык программирования общего назначения с открытым исходным кодом. Он был задуман в первую очередь для того, чтобы легко писать простые и надежные программы, которые эффективно утилизируют многопроцессорные системы с несколькими ядрами. - - Разработка Go началась в сентябре 2007 года в компании Google, его непосредственным проектированием занимались Роберт Гризмер, Роб Пайк и Кен Томпсон. Публично язык был анонсирован 10 ноября 2009 года. В последнее время популярность Go очень выросла и его активно используют во многих компаниях. - - В Go очень простой синтаксис, мало синтаксического сахара, строгие правила форматирования, что позволяет на нем писать код, который легко читать и понимать. За счет этого Go имеет достаточно низкий порог входа для новых программистов. - - Уже из коробки Go имеет набор инструментов для управления зависимостями в коде, тестирования, форматирования и оптимизации кода. Также в нем есть эффективный механизм сборки мусора, который постоянно совершенствуется и оптимизируется. - - Go – компилируемый язык, при этом компилятор в нем очень быстрый. Уже на стадии компиляции он позволяет предотвратить большинство ошибок в коде. Также программу, написанную на Go, можно скомпилировать под разные операционные системы, включая Linux, Mac OS, Windows и другие. - - В Go есть такие абстракции данных как структуры, интерфейсы и методы, которые позволяют писать код в объектно-ориентированном стиле. Однако его подход значительно отличается от того, который можно увидеть в Java, PHP или Ruby (но, возможно, в понимании Алана Кея, Go — более ООП-язык, чем другие). - - И, конечно же, главной особенностью Go, благодаря которой он набрал такую популярность, является его модель конкурентного программирования. В языке существуют такие абстракции как горутины и каналы, которые позволяют легко писать конкурентный код, при этом его можно легко масштабировать на несколько ядер процессора. - - В целом Go отлично подходит для системного программирования, инфраструктурных задач и написания высоконагруженных сервисов. Многие современные популярные инструменты, которые широко используются в разных компаниях, написаны на Go, например, Docker, Kubernetes, Prometheus, Terraform, Consul, Grafana и другие. Также многие крупные компании начали использовать Go для перехода на микросервисную архитектуру. - -instructions: | - Наберите в редакторе код из задания символ в символ и нажмите «Проверить». - - ```go - // Определение пакета main - package main - // Импорт пакета fmt - import "fmt" - // Определение функции main - func main() { - // Вызов функции Print из пакета fmt - // Отступ 1 таб - fmt.Print("Hello, World!") // В конце не нужна точка с запятой - } - ``` - -tips: - - | - [play.golang.org](https://play.golang.org/) — здесь вы можете экспериментировать с кодом на Go. - - | - [The Go Programming Language Specification](https://golang.org/ref/spec) - - | - [Go Wiki](https://github.com/golang/go/wiki) - - | - [How to Write Go Code](https://golang.org/doc/code.html) - - | - [Effective Go](https://golang.org/doc/effective_go.html) diff --git a/modules/10-basics/10-hello-world/ru/data.yml b/modules/10-basics/10-hello-world/ru/data.yml index 1ce65f2..e96a311 100644 --- a/modules/10-basics/10-hello-world/ru/data.yml +++ b/modules/10-basics/10-hello-world/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Привет, Мир! tips: - > diff --git a/modules/10-basics/15-hello-world-debriefing/description.ru.yml b/modules/10-basics/15-hello-world-debriefing/description.ru.yml deleted file mode 100644 index f05a3bf..0000000 --- a/modules/10-basics/15-hello-world-debriefing/description.ru.yml +++ /dev/null @@ -1,66 +0,0 @@ ---- - -name: Разбор Hello World -theory: | - - Разберем программу Hello world из предыдущего урока: - - ```go - // Определение пакета main - package main - // Импорт пакета fmt - import "fmt" - // Определение функции main - func main() { - // Вызов функции Print из пакета fmt - // Отступ 1 таб - fmt.Print("Hello, World!") // В конце не нужна точка с запятой - } - ``` - - Сначала мы определили пакет `main`: - - ```go - package main - ``` - - Пакеты выполняют роль неймспейсов (логически обособленные директории с локальным именованием) и используются для группировки функций. Все Go-файлы начинаются с объявления пакета, к которому они относятся. Внутри одного пакета может быть множество функций. - - После определения пакета мы написали функцию `main`. В ней описана вся логика программы: - - ```go - func main() { -     ... - } - ``` - - `main` — главная функция, которая выполняется при запуске любой Go-программы и является точкой входа в программу. Она не может принимать или возвращать какие-либо аргументы. - - Чтобы вывести строку в терминал, мы использовали внешний пакет `fmt`: - - ```go - import "fmt" - ``` - - Импорт сторонних пакетов описывается словом `import`. Блок импортов располагается в начале Go-файла сразу после названия пакета. - - После импорта мы можем вызывать функции пакета в своем коде. - - ```go - fmt.Print("Hello, World!") - ``` - - Сторонний пакет — это не объект, а неймспейс. Его можно использовать только как префикс к функциям. Обратиться к функции стороннего пакета можно через точку. Функции, как и во многих языках (например, Java, PHP, Python), вызываются через скобки и передачу аргумента внутрь.  - - Вы могли обратить внимание, что функция `Print` написана с большой буквы, а `main` — с маленькой. В Go функция публична, если ее первая буква заглавная. Публичность или экспортируемость означает, что мы можем использовать эту функцию в других пакетах. С другой стороны, если функция начинается с маленькой буквы, то она является приватной и может использоваться только внутри пакета. Функция `fmt.Print` — публичная, поэтому мы можем вызывать ее в своем коде. А функция `main` — приватная, она доступна только в рамках нашего пакета `main`. - - Рассмотрим аргумент `"Hello, World!"`. Строки практически всегда объявляются в двойных кавычках. Существует еще один способ описать многострочную строку через обратную кавычку `, но он используется в исключительных ситуациях. Мы пока будем использовать только двойные. - - Одной из особенностей языка Go является отсутствие точек с запятыми. Чтобы компилятор понял код, необходимы правильные переносы строк и отступы: табами, а не пробелами. В работе это не доставляет больших неудобств, поскольку в языке есть встроенная тулза для форматирования кода. Ссылка на тулзу лежит в разделе `Советы` в конце урока. - -instructions: | - Напишите функцию `main`, которая выводит на экран строчку "Hello, Friend!". - -tips: - - | - [Go fmt tool](https://go.dev/blog/gofmt) diff --git a/modules/10-basics/15-hello-world-debriefing/ru/data.yml b/modules/10-basics/15-hello-world-debriefing/ru/data.yml index fdfcfdd..67f870f 100644 --- a/modules/10-basics/15-hello-world-debriefing/ru/data.yml +++ b/modules/10-basics/15-hello-world-debriefing/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Разбор Hello World tips: - | diff --git a/modules/10-basics/20-go-go-go/description.ru.yml b/modules/10-basics/20-go-go-go/description.ru.yml deleted file mode 100644 index fa1fa4b..0000000 --- a/modules/10-basics/20-go-go-go/description.ru.yml +++ /dev/null @@ -1,125 +0,0 @@ ---- - -name: Go, Go, Go -theory: | - Go — это компилируемый строго типизированный язык программирования, разработанный в Google. Язык спроектирован для быстрой разработки высоконагруженных бэкендов. Если вы знакомы с императивными языками (например, C++, PHP, Java), то синтаксис Go будет понятен практически сразу: - - ```go - import ( - "encoding/json" - "errors" - "fmt" - ) - - type Message struct { - Sender string `json:"sender"` // ставим тег с описанием JSON поля - Text string `json:"text"` - } - - // инициализация ошибки через конструктор стандартного пакета errors - var errEmptyMessage = errors.New("empty message") - - // возвращаем ошибку в случае неожиданного поведения - func DecodeJSON(rawMsg string) (Message, error) { - // если нам передали пустую строку, возвращаем ошибку об этом - if len(rawMsg) == 0 { - return Message{}, errEmptyMessage - } - - msg := Message{} - - // декодируем строку в структуру - err := json.Unmarshal([]byte(rawMsg), &msg) - if err != nil { - return Message{}, fmt.Errorf("unmarshal: %w", err) - } - - return msg, nil - } - ``` - - В Go нет исключений. Вместо них используется встроенный интерфейс `error`. Ошибки возвращаются явно последним аргументом из функции. Поэтому Go-код выглядит как череда вызовов функций и проверок на ошибки: - - ```go - func main() { - msg, err := DecodeJSON("") - if errors.Is(err, errEmptyMessage) { - // { } empty message - fmt.Println(msg, err) - } - - msg, err = DecodeJSON("hello") - if err != nil { - // { } unmarshal: invalid character 'h' looking for beginning of value - fmt.Println(msg, err) - } - - msg, err = DecodeJSON(`{"sender":"hexlet","text":"Go,Go,Go"}`) - // {hexlet Go,Go,Go} - fmt.Println(msg, err) - } - ``` - - Такой подход может показаться «неизящным» из-за постоянного повторения условного блока `if err != nil`, однако он позволяет увидеть и контролировать все потенциальные ошибки в коде. - - Самая сильная сторона Go — простое написание конкурентных программ. Для этого в языке используются легковесные потоки — горутины. Мы разберем эту тему подробно в соответствующем модуле, а пока можем оценить синтаксис программы, которая суммирует 10 значений из разных внешних источников: - - ```go - import ( - "fmt" - "sync" - ) - - func main() { - mu := sync.Mutex{} - wg := sync.WaitGroup{} - - sum := 0 - for i := 0; i < 10; i++ { - wg.Add(1) - - // ставим перед любой функцией слово «go», и она выполняется конкурентно в горутине - go func() { - // делаем долгий вызов к стороннему API. Так как каждый вызов происходит в своей горутине, мы делаем 10 вызовов одновременно - n := externalHTTPNum() - - mu.Lock() - sum += n - mu.Unlock() - - wg.Done() - }() - } - - // ждем, пока все 10 горутин вернут ответ - wg.Wait() - - fmt.Println(sum) // 55 - } - ``` - - Не стоит расстраиваться, если сейчас что-то непонятно, или кажется сложным. После разбора концепций конкурентного программирования в Go и небольшой практики, вы будете с легкостью писать высокопроизводительный код. - - -instructions: | - Написать интересный код самостоятельно на текущем уровне будет затруднительно, поэтому скопируйте код ниже. - - ```go - wg := sync.WaitGroup{} - - for i := 0; i < 3; i++ { - wg.Add(1) - go func() { - fmt.Println("Go!") - wg.Done() - }() - } - - wg.Wait() - ``` - -tips: - - | - [Стандартные пакеты в Go](https://pkg.go.dev/std) - - | - [Awesome Go — библиотеки и фреймворки](https://github.com/avelino/awesome-go) diff --git a/modules/10-basics/20-go-go-go/ru/data.yml b/modules/10-basics/20-go-go-go/ru/data.yml index d61ab4e..4492e5b 100644 --- a/modules/10-basics/20-go-go-go/ru/data.yml +++ b/modules/10-basics/20-go-go-go/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Go, Go, Go tips: - | diff --git a/modules/10-basics/25-variables/description.ru.yml b/modules/10-basics/25-variables/description.ru.yml deleted file mode 100644 index 7630200..0000000 --- a/modules/10-basics/25-variables/description.ru.yml +++ /dev/null @@ -1,89 +0,0 @@ ---- - -name: Переменные -theory: | - - Переменные — это именованные значения. Они хранятся в памяти во время выполнения программы. - - Существует два способа объявить переменную в Go. Длинная запись с ключевым словом `var`: - - ```go - var num int = 11 - ``` - - И короткая запись: - - ```go - num := 22 - ``` - - Понимание, где лучше использовать короткую, а где – длинную инициализацию нарабатывается практикой. Пока же советуем придерживаться двух правил: - — Использовать короткую запись как можно чаще - — Если где-то необходимо написать через var (например, инициализировать несколько переменных за раз), то все переменные объявляются одинаково. - - Значение переменной можно изменять в любой момент: - - ```go - // двоеточие используется только при инициализации - num := 22 - num = 33 - ``` - - Однако из-за строгой типизации мы не можем записать в переменную значение другого типа данных: - - ```go - num := 22 - // получим ошибку: cannot use "string" (type untyped string) as type int in assignment - num = "string" - ``` - - Переменные принято называть в camelCase: - - ```go - longTrickyName := "Josefina" - ``` - - Если не задавать значение переменной при инициализации, она будет иметь «нулевое» значение: - - ```go - var ( - a string // "" - b bool // false - c int // 0 - ) - ``` - - Объявлять переменные можно на уровне функций и пакетов. Переменные на уровне пакета инициализируются при старте программы. Они используются не часто. Например, чтобы не тратить память и процессор на создание новой переменной, мы можем один раз описать статичные ошибки и использовать их в функциях пакета: - - ```go - package math - - import "errors" - - // статичная ошибка - var errCannotSum = errors.New("cannot sum") - - func sum(...) - ``` - - Стоит сказать пару слов об особенности именования переменных. Из-за стремления к простоте, переменные называются в максимально сокращенном виде, достаточном для понимания. Например: - - ```go - // НЕ Go way - message := "👎" - buffer := bytes.Buffer{} - - // Go way - msg := "👍" - buf := bytes.Buffer{} - ``` - -instructions: | - Объявите две переменные `firstName` и `lastName`. Переменная `firstName` должна содержать строку «John», переменная `lastName` — «Smith». - Выведите значения переменных `firstName` и `lastName` через пробел, чтобы получилась строка «John Smith». Используйте функцию `Println` из пакета `fmt` - -tips: - - | - [The Go Programming Language Specification - Variables](https://go.dev/ref/spec#Variables) - - | - [Effective Go - Variables](https://go.dev/doc/effective_go#variables) diff --git a/modules/10-basics/25-variables/ru/data.yml b/modules/10-basics/25-variables/ru/data.yml index ed34784..7615f4d 100644 --- a/modules/10-basics/25-variables/ru/data.yml +++ b/modules/10-basics/25-variables/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Переменные tips: - > diff --git a/modules/10-basics/30-funcs/description.ru.yml b/modules/10-basics/30-funcs/description.ru.yml deleted file mode 100644 index de33990..0000000 --- a/modules/10-basics/30-funcs/description.ru.yml +++ /dev/null @@ -1,92 +0,0 @@ ---- - -name: Функции -theory: | - - Функции в Go объявляются через ключевое слово `func`: - - ```go - func multiply(x int, y int) int { - return x * y - } - ``` - - Объявление функции состоит из следующих частей: - — ключевое слово `func` - — название функции `multiply` - — входящие аргументы `(x int, y int)`. Тип аргументов пишется после названия переменной. Если несколько аргументов подряд имеют одинаковый тип, то можно написать сокращенно `(x, y int)` - — тип возвращаемого значения `int` - — тело функции `{ return x * y }` - - Функции именуются в `camelCase`. Если первая буква заглавная, то функция экспортируемая (публичная) и доступна в других пакетах. Функции с маленькой буквы используются только в рамках текущего пакета: - - ```go - package math - - // публичная функция, можно вызвать извне как math.Multiply(5,7) - func Multiply(x int, y int) int { - ... - } - - // приватная функция, извне не вызвать - func divide(x int, y int) int { - ... - } - ``` - - Из одной функции можно возвращать несколько значений. Чаще всего это используется для возвращения ошибок: - - ```go - package math - - import "errors" - - func divide(x, y int) (int, error) { - if y == 0 { - return 0, errors.New("cannot divide on zero") - } - - return x / y, nil - } - ``` - - Возвращаемые значения могут быть именованными: - - ```go - func multiply(x, y int) (res int) { - res = x * y - return - } - ``` - - Использовать именованные возвращаемые аргументы — плохая практика. На это есть несколько причин: - - — увеличенное расстояние между объявлением и использованием переменной ведет к сложности в чтении и понимании кода - — переменная может быть несколько раз переопределена в теле функции, что приводит к неожиданному поведению - — пустой `return` неявно возвращает аргументы - - Естественно, код состоит не только из функций: чаще всего используется множество внешних библиотек. Чтобы использовать внешнюю функцию, нужно указать пакет и через `.` вызвать публичную функцию. Например: - - ```go - import "fmt" - - func myPrint(msg string) { - // пакет.функция - fmt.Println(msg) - } - ``` - -instructions: | - В Go есть стандартная библиотека `strconv` для конвертации чисел в строки и наоборот. Пример использования: - - ```go - s := strconv.Itoa(-42) // "-42" - ``` - - Напишите функцию IntToString, которая преобразует и возвращает входящее число в строку - -tips: - - | - [The Go Programming Language Specification — Functions](https://go.dev/ref/spec#Function_declarations) - - | - [Awesome Go - библиотеки и фреймворки](https://github.com/avelino/awesome-go) diff --git a/modules/10-basics/30-funcs/ru/data.yml b/modules/10-basics/30-funcs/ru/data.yml index 4832045..5620164 100644 --- a/modules/10-basics/30-funcs/ru/data.yml +++ b/modules/10-basics/30-funcs/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Функции tips: - > diff --git a/modules/10-basics/35-math/description.ru.yml b/modules/10-basics/35-math/description.ru.yml deleted file mode 100644 index 60e1528..0000000 --- a/modules/10-basics/35-math/description.ru.yml +++ /dev/null @@ -1,83 +0,0 @@ ---- - -name: Математика и числа -theory: | - - В Go представлено много числовых типов данных: *uint*, *uint8*, *uint16*, *uint32*, *uint64*, *int*, *int8*, *int16*, *int32*, *int64*, *float32*, *float64*, *complex64*, *complex128*. Зачем же столько типов для чисел? Одна из главных особенностей Go — это кроссплатформенность. Пишется один код, который компилируется и запускается на любой архитектуре процессора. В своем коде предпочтительнее использовать *int* и *uint*, так как они являются архитектурно-независимыми: в момент компиляции принимают значение 32 или 64 битов под процессор. - - На самом деле вам не нужно запоминать все эти типы. В ежедневной работе вы будете сталкиваться с: - - *int* — основной кросплатформенный тип целых чисел, может быть отрицательным - - *int64* нужен для больших чисел из внешних систем. Например, ID в СУБД имеет тип *bigint*. Чтобы правильно распарсить такой ID, используется *int64* - - *float64* — число с плавающей точкой. Чаще всего используются для математических операций. Например, функция `math.Max`, определяющая наибольшее число, принимает аргументы в виде *float64* - - В Go имеется стандартный набор арифметических операций: - - ```go - x := 10 - y := 5 - - // сложение - x + y // 15 - - // вычитание - x - y // 5 - - // деление - x / y // 2 - - // умножение - x * y // 50 - ``` - - Любые операции осуществляются только над числами одинакового типа: - - ```go - x := 5.05 - y := 10 - - x + y // invalid operation: x + y (mismatched types float64 and int) - ``` - - Чтобы осуществить сложение из прошлого примера, нам нужно конвертировать значения к одному типу - - ```go - x := 5.05 - y := 10 - - x + float64(y) // 15.05 - ``` - - Числовые типы конвертируются без проблем между собой, однако есть нюансы, о которых стоит помнить: - - ```go - // нельзя конвертировать float64 к целому числу, если после точки не только нули - x := int64(5.05) // ошибка компиляции: constant 5.05 truncated to integer - - x := int64(5.00) // OK - - // uint не может быть отрицательным - x := uint(-5) // constant -5 overflows uint - ``` - - Приведенные выше примеры вызовут ошибки компиляции, поэтому вам не удастся «выстрелить себе в ногу». Однако существуют способы обмануть компилятор, и тогда вы получите неявное поведение в коде: - - ```go - a, _ := strconv.Atoi("-42") - - // ошибки компиляции нет, но число было преобразовано в положительное путем прибавления MAX_UINT+1. MAX_UINT = 18446744073709551615 - x := uint(a) // 18446744073709551574 - - a, _ := strconv.Atoi("5.05") - - // ошибки компиляции нет, но значение всегда будет равно 0 - x := int(a) // 0 - ``` - -instructions: | - - В уроке упоминалась функция `math.Max`, которая сравнивает два числа и возвращает наибольшее. В этом задании следует использовать противоположную функцию `math.Min`, определяющую наименьшее число. - Напишите функцию `MinInt(x, y int) int`, которая возвращает наименьшее целое число - -tips: - - | - [The Go Programming Language Specification - Numeric Types](https://go.dev/ref/spec#Numeric_types) diff --git a/modules/10-basics/35-math/ru/data.yml b/modules/10-basics/35-math/ru/data.yml index e3cea0b..d98274e 100644 --- a/modules/10-basics/35-math/ru/data.yml +++ b/modules/10-basics/35-math/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Математика и числа tips: - > diff --git a/modules/10-basics/40-bool/description.ru.yml b/modules/10-basics/40-bool/description.ru.yml deleted file mode 100644 index 2e69c75..0000000 --- a/modules/10-basics/40-bool/description.ru.yml +++ /dev/null @@ -1,65 +0,0 @@ ---- - -name: Логический тип -theory: | - - Логический тип в Go представлен привычными значениями `true` и `false` c операторами: - — `&&` (и) - — `==` (равно) - — `||` (или) - — `!` (не): - - ```go - true && false // false - false || true // true - ``` - - Объявление переменных происходит через ключевое слово `bool`: - - ```go - var b bool = true - - // сокращенная запись - bs := false - ``` - - Из-за строгой типизации в Go можно сравнивать только одинаковые типы данных: - - ```go - true == false // false - - false == false // true - ``` - - То есть нельзя сравнить строку с логическим типом, как это происходит в динамических языках: - - ```go - true == "hello" // invalid operation: false == "hello" (mismatched types untyped bool and untyped string) - ``` - - Когда необходимо проверить на истинность разные значения, нелогические типы нужно привести к логическому: - - ```go - flag := true - text := "hello" - - // вариант не сработает, потому что нельзя конвертировать строку в bool - flag && bool(text) // cannot convert text (type string) to type bool - - // правильный вариант: если строка не пустая, то в ней есть текст - flag && text != "" // true - ``` - -instructions: | - Реализуйте функцию `IsValid(id int, text string) bool`, которая проверяет, что переданный идентификатор `id` является положительным числом и текст `text` не пустой. - Например: - - ```go - IsValid(0, "hello world") // false - IsValid(-22, "hello world") // false - IsValid(22, "") // false - IsValid(225, "hello world") // true - ``` - - -tips: [] diff --git a/modules/10-basics/40-bool/ru/data.yml b/modules/10-basics/40-bool/ru/data.yml index 2c3a791..f34ced2 100644 --- a/modules/10-basics/40-bool/ru/data.yml +++ b/modules/10-basics/40-bool/ru/data.yml @@ -1,2 +1,3 @@ +--- name: Логический тип tips: [] diff --git a/modules/10-basics/45-strings/description.ru.yml b/modules/10-basics/45-strings/description.ru.yml deleted file mode 100644 index 5c13ec5..0000000 --- a/modules/10-basics/45-strings/description.ru.yml +++ /dev/null @@ -1,83 +0,0 @@ ---- - -name: Строки -theory: | - - Строки в Go объявляются с типом `string`: - - ```go - var s string = "hello" - - // сокращенная запись - s := "hey" - ``` - - Практически всегда для строк используются двойные кавычки. Однако они не подходят, когда нужно написать несколько строк. Для этого используются обратные кавычки: - - ```go - q := ` - SELECT * - FROM person - WHERE age > 18 - ` - ``` - - Строки можно сравнивать операторами: `==`, `>`, `<`, `<=`, `>=`, где строки сравниваются посимвольно в лексическом порядке и по длине. Это свойство часто используется при сортировке массива строк: - - ```go - "привет" == "привет" // true - "golang" > "go" // true - "golang" > "lang" // false - "go" > "foobar" // true - ``` - - Базовые операции со строками в любом языке — это конкатенация и интерполяция. Конкатенация осуществляется с помощью знака `+`: - - ```go - "hello " + "world" // "hello world" - ``` - - В Go нет привычной интерполяции, как в динамических языках. Она реализуется через форматирующую функцию `fmt.Sprintf`: - - ```go - username := "Ivan" - - greetings := fmt.Sprintf("hello, %s", username) // "hello, Ivan" - ``` - - Узнать длину строки можно с помощью встроенной функции `len`: - - ```go - len("go") // 2 - - // будьте внимательны! Функция считает кол-во байт, а не кол-во символов - len("го") // 4 - ``` - -instructions: | - Для работы со строками часто используется стандартная библиотека `strings`. В данном задании вам понадобятся следующие функции: - - ```go - // обрезает символы, переданные вторым аргументом, с обеих сторон строки - strings.Trim(s, cutset string) string - // пример - strings.Trim(" hello ", " ") // "hello" - - // преобразует все буквы в строке в нижний регистр - strings.ToLower(s string) string - // пример - strings.ToLower("пРиВеТ") // "привет" - - // озаглавливает первую букву в каждом слове в строке - strings.Title(s string) string - // пример - strings.Title("привет, джон") // "Привет, Джон" - ``` - - Реализуйте функцию `Greetings(name string) string`, которая вернет строку-приветствие. Например, при передаче имени `Иван`, возвращается `Привет, Иван!`. Учтите, что имя может быть написано в разном регистре и содержать пробелы. - -tips: - - | - [The Go Programming Language Specification — String types](https://go.dev/ref/spec#String_types) - - | - [Go Standard Library — strings](https://pkg.go.dev/strings) diff --git a/modules/10-basics/45-strings/ru/data.yml b/modules/10-basics/45-strings/ru/data.yml index 2885a1c..df25ced 100644 --- a/modules/10-basics/45-strings/ru/data.yml +++ b/modules/10-basics/45-strings/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Строки tips: - > diff --git a/modules/10-basics/50-if-else/description.ru.yml b/modules/10-basics/50-if-else/description.ru.yml deleted file mode 100644 index 102a16e..0000000 --- a/modules/10-basics/50-if-else/description.ru.yml +++ /dev/null @@ -1,61 +0,0 @@ ---- - -name: Условные конструкции -theory: | - - Условия в Go представлены привычной конструкцией `if else`. В условии должно быть строго выражение логического типа. Следующий пример вернет ошибку компиляции: - - ```go - if "hi" { // non-bool "hi" (type string) used as if condition - } - ``` - - Корректный пример: - - ```go - package main - - import ( - "fmt" - "strings" - ) - - func statusByName(name string) string { - // функция проверяет, что строка name начинается с подстроки "Mr." - if strings.HasPrefix(name, "Mr.") { - return "married man" - } else if strings.HasPrefix(name, "Mrs.") { - return "married woman" - } else { - return "single person" - } - } - - func main() { - n := "Mr. Doe" - fmt.Println(n + " is a " + statusByName(n)) // Mr. Doe is a married man - - n = "Mrs. Berry" - fmt.Println(n + " is a " + statusByName(n)) // Mrs. Berry is a married woman - - n = "Karl" - fmt.Println(n + " is a " + statusByName(n)) // Karl is a single person - } - ``` - - Логическое выражение пишется после `if` без скобок. `else if` можно написать только раздельно. - - -instructions: | - На веб-сайтах часто используются разные поддомены для языков. Например, сайт *site.com* на английском располагается по адресу *en.site.com*, а на русском — *ru.site.com*. - - Реализуйте функцию `DomainForLocale(domain, locale string) string`, которая добавляет язык `locale` как поддомен к домену `domain`. Язык может прийти пустым, тогда нужно добавить поддомен *en.*. Например: - - ```go - DomainForLocale("site.com", "") // en.site.com - DomainForLocale("site.com", "ru") // ru.site.com - ``` - -tips: - - | - [The Go Programming Language Specification — If statements](https://go.dev/ref/spec#If_statements) diff --git a/modules/10-basics/50-if-else/ru/data.yml b/modules/10-basics/50-if-else/ru/data.yml index d9368dd..fe97ecd 100644 --- a/modules/10-basics/50-if-else/ru/data.yml +++ b/modules/10-basics/50-if-else/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Условные конструкции tips: - > diff --git a/modules/10-basics/55-switch/description.ru.yml b/modules/10-basics/55-switch/description.ru.yml deleted file mode 100644 index 2f2cce1..0000000 --- a/modules/10-basics/55-switch/description.ru.yml +++ /dev/null @@ -1,74 +0,0 @@ ---- - -name: Альтернативная условная конструкция -theory: | - - В Go присутствует единственная альтернатива `if` — конструкция `switch`. Для этой конструкции используется стандартный синтаксис, но логика работы отличается от С-подобных языков. Когда срабатывает условие какого-либо `case`, программа выполняет блок и выходит из конструкции `switch` без необходимости писать `break`: - - ```go - x := 10 - - switch x { - default: // default всегда выполняется последним независимо от расположения в конструкции - fmt.Println("default case") - case 10: - fmt.Println("case 10") - } - ``` - - Output: - - ```go - case 10 - ``` - - Однако при необходимости можно реализовать логику С-подобных языков и «провалиться» в следующий `case`: - - ```go - x := 10 - - switch { // выражение отсутствует. Для компилятора выглядит как: switch true - default: - fmt.Println("default case") - case x == 10: - fmt.Println("equal 10 case") - fallthrough - case x <= 10: - fmt.Println("less or equal 10 case") - } - ``` - - Output: - - ```go - equal 10 case - less or equal 10 case - ``` - - -instructions: | - Реализуйте функцию `ModifySpaces(s, mode string) string`, которая изменяет строку `s` в зависимости от переданного режима `mode`. Подробности в примере: - - ```go - // когда передается mode "dash", нужно заменить все пробелы на дефисы "-" - ModifySpaces("hello world", "dash") // hello-world - - // когда передается mode "underscore", нужно заменить все пробелы на нижние подчеркивания "_" - ModifySpaces("hello world", "underscore") // hello_world - - // когда передается неизвестный или пустой mode, заменяем все пробелы на звездочки "*" - ModifySpaces("hello world", "unknown") // hello*world - ModifySpaces("hello world", "") // hello*world - ``` - - Для замены символов в строке существует функция `ReplaceAll(s, old, new string) string` из пакета `strings`: - - ```go - strings.ReplaceAll("hello world!", "world!", "buddy!") // hello buddy! - - strings.ReplaceAll("one two three", " ", "_") // one_two_three - ``` - -tips: - - | - [The Go Programming Language Specification — Switch statements](https://go.dev/ref/spec#Switch_statements) diff --git a/modules/10-basics/55-switch/ru/data.yml b/modules/10-basics/55-switch/ru/data.yml index 3ca2e56..9154372 100644 --- a/modules/10-basics/55-switch/ru/data.yml +++ b/modules/10-basics/55-switch/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Альтернативная условная конструкция tips: - > diff --git a/modules/10-basics/60-structs/description.ru.yml b/modules/10-basics/60-structs/description.ru.yml deleted file mode 100644 index 161f8d6..0000000 --- a/modules/10-basics/60-structs/description.ru.yml +++ /dev/null @@ -1,138 +0,0 @@ ---- - -name: Структуры -theory: | - - В Go нет классов и привычной реализации ООП. Вместо классов в языке используются структуры — наборы полей, имеющих название и тип данных. Объявление структуры имеет следующий вид: - - ```go - type Person struct { - // [название поля] [тип данных] - Name string - Age int - } - - func main() { - p := Person{Name: "John", Age: 25} - - p.Name // "John" - p.Age // 25 - } - ``` - - Структуру можно инициализировать, не передавая значения. В этом случае каждое поле примет свое «нулевое» значение: - - ```go - func main() { - p := Person{} - - p.Name // "" - p.Age // 0 - } - ``` - - Регистр первой буквы в названии структуры и полей означает публичность точно так же, как в переменных и функциях. Если первая буква заглавная, то структуру можно инициализировать во внешних пакетах. В противном случае она доступна только в рамках текущего пакета: - - ```go - type Person struct { // структура публична - Name string // поле публично - - wallet wallet // поле приватно: можно обращаться только внутри текущего пакета - } - - type wallet struct { // структура приватна: можно инициализировать только внутри текущего пакета - id string - moneyAmount float64 - } - ``` - - У любого поля структуры можно указать теги. Они используются для метаинформации о поле для сериализации, валидации, маппинга данных из БД и тд. Тег указывается после типа данных через бектики: - - ```go - type User struct { - ID int64 `json:"id" validate:"required"` - Email string `json:"email" validate:"required,email"` - FirstName string `json:"first_name" validate:"required"` - } - ``` - - Тег `json` используется для названий полей при сериализации/десериализации структуры в json и обратно: - - ```go - package main - - import ( - "encoding/json" - "fmt" - ) - - type User struct { - ID int64 `json:"id"` - Email string `json:"email"` - FirstName string `json:"first_name"` - } - - func main() { - u := User{} - u.ID = 22 - u.Email = "test@test.com" - u.FirstName = "John" - - bs, _ := json.Marshal(u) - - fmt.Println(string(bs)) // {"id":22,"email":"test@test.com","first_name":"John"} - } - ``` - - Тег `validate` используется Go-валидатором. В следующем примере присутствует вызов функции у структуры `v.Struct(u)`. Функции структур — методы — мы разберем подробно в соответствующем уроке, а пока просто посмотрите, как происходит вызов: - - ```go - package main - - import ( - "fmt" - "github.com/go-playground/validator/v10" - ) - - type User struct { - ID int64 `validate:"required"` - Email string `validate:"required,email"` - FirstName string `validate:"required"` - } - - func main() { - // создали пустую структуру, чтобы проверить валидацию - u := User{} - - // создаем валидатор - v := validator.New() - - // метод Struct валидирует переданную структуру и возвращает ошибку `error`, если какое-то поле некорректно - fmt.Println(v.Struct(u)) - } - ``` - - Вывод программы: - - ``` - Key: 'User.ID' Error:Field validation for 'ID' failed on the 'required' tag - Key: 'User.Email' Error:Field validation for 'Email' failed on the 'required' tag - Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'required' tag - ``` - -instructions: | - На сервер приходит HTTP-запрос. Тело запроса парсится и мапится в модель. Сразу работать с моделью нельзя, потому что данные могут быть неверными. - Реализуйте функцию `Validate(req UserCreateRequest) string`, которая валидирует запрос и возвращает строку с ошибкой "invalid request", если модель невалидна. Если запрос корректный, то функция возвращает пустую строку. Правила валидации и структура модели описаны ниже. Не используйте готовые библиотеки и опишите правила самостоятельно. - - ```go - type UserCreateRequest struct { - FirstName string // не может быть пустым; не может содержать пробелы - Age int // не может быть 0 или отрицательным; не может быть больше 150 - } - ``` - - Наличие пробелов можно проверить с помощью функции `strings.Contains(firstName, " ")`. - -tips: - - | - [The Go Programming Language Specification — Struct types](https://go.dev/ref/spec#Struct_types) diff --git a/modules/10-basics/60-structs/ru/data.yml b/modules/10-basics/60-structs/ru/data.yml index f29f075..a861bbe 100644 --- a/modules/10-basics/60-structs/ru/data.yml +++ b/modules/10-basics/60-structs/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Структуры tips: - > diff --git a/modules/10-basics/65-const/description.ru.yml b/modules/10-basics/65-const/description.ru.yml deleted file mode 100644 index 524322c..0000000 --- a/modules/10-basics/65-const/description.ru.yml +++ /dev/null @@ -1,120 +0,0 @@ ---- - -name: Константы -theory: | - - Константы — это постоянные значения, которые инициализируются один раз и не изменяются в течение всего выполнения программы. В Go константы объявляются через ключевое слово `const`: - - ```go - const [название] [тип данных] = [значение] - - const StatusOk int = 200 - ``` - - На практике тип данных не указывается, и несколько констант принято объявлять в рамках одного блока `const`: - - ```go - const ( - StatusOk = 200 - StatusNotFound = 404 - ) - ``` - - Только некоторые типы данных можно присвоить константе: строки, символы, числа, логический тип: - - ```go - package main - - type Person struct { - } - - func main() { - // такие константы допустимы - const ( - num = 20 - str = "hey" - isValid = true - ) - - // нельзя объявить структуру как константу - const p = Person{} // ошибка компиляции: const initializer Person{} is not a constant - } - - ``` - - Регистр первой буквы указывает на публичность/приватность константы: - - ```go - const ( - // публичная константа, которую можно использовать во внешних пакетах - StatusOk = 200 - - // приватная константа, доступная только в рамках текущего пакета - statusInternalError = 500 - ) - ``` - - Константы можно объявлять на уровне функции, либо пакета: - - ```go - package main - - import "fmt" - - const defaultStatus = 200 - - func main() { - const status = 404 - - fmt.Println("default status:", defaultStatus) // default status: 200 - fmt.Println("current status:", status) // current status: 404 - } - ``` - - Для последовательных числовых констант следует использовать идентификатор iota, который присвоит для списка чисел значения от его текущей позиции: - - ```go - package main - - import "fmt" - - const ( - zero = iota - one - two - three - ) - - const ( - a = iota - b = 42 - c = iota - d - ) - - func main() { - fmt.Println(zero, one, two, three) // 0 1 2 3 - fmt.Println(a, b, c, d) // 0 42 2 3 - } - ``` - -instructions: | - В больших проектах часто используется gRPC — высокопроизводительный RPC (Remote Procedure Call)-фреймворк для коммуникации между микросервисами. Ошибки в gRPC стандартизированы и представлены в виде строк для пользователя, либо в виде чисел для компьютера. - - Представим, что нам нужно написать конвертер ошибок в числовой формат для gRPC. Реализуйте функцию `ErrorMessageToCode(msg string) int`, которая возвращает числовой код для заданного значения. Список сообщений и соответствующих кодов: - - ``` - OK = 0 - CANCELLED = 1 - UNKNOWN = 2 - ``` - - В реальности список намного шире. Мы для простоты ограничимся тремя ошибками. Учтите, что если в функцию передать неизвестную ошибку, она должна вернуть код ошибки для сообщения *UNKNOWN*. - -tips: - - | - [The Go Programming Language Specification — Constant](https://golang.org/ref/spec#Constants) - - | - [The Go Programming Language Specification — Constant declarations](https://golang.org/ref/spec#Constant_declarations) - - | - [The Go Programming Language Specification — Iota](https://golang.org/ref/spec#Iota) diff --git a/modules/10-basics/65-const/ru/data.yml b/modules/10-basics/65-const/ru/data.yml index 2ea556a..605f750 100644 --- a/modules/10-basics/65-const/ru/data.yml +++ b/modules/10-basics/65-const/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Константы tips: - > diff --git a/modules/10-basics/70-inheritance-interfaces/description.ru.yml b/modules/10-basics/70-inheritance-interfaces/description.ru.yml deleted file mode 100644 index 790a455..0000000 --- a/modules/10-basics/70-inheritance-interfaces/description.ru.yml +++ /dev/null @@ -1,140 +0,0 @@ ---- - -name: Наследование и интерфейсы -theory: | - - В языке Go отсутствует понятие наследования в классическом виде (нет ключевого слова extends, как, например, в Java). - Однако терять преимущества, которые даёт механизм наследования, разработчики Go не хотели. - Поэтому всё тоже самое, что можно сделать в других языках программирования за счёт наследования классов, можно реализовать в Golang другими средствами. - - Для начала определим, что даёт нам наследование: - 1. Повторное использование кода: класс-наследник получает всё содержимое класса-предка и добавляет своё. - 2. Динамический полиморфизм: переменная, у которой типом данных является некий базовый класс, может ссылаться как на объекты этого базового класса, так и на объекты его класса-наследника. - 3. Динамическая диспетчеризация: Метод с одним и тем же названием может иметь разную реализацию в классе-предке и классе-наследнике. - - В качестве элегантного решения проблемы повторного использования кода Golang предлагает использование композиции и встраивания. - Функционал полиморфизма и динамической диспетчеризации достигается за счёт использования интерфейсов. - - ## Композиция - - Рассмотрим пример использования композиции в качестве инструмента повторного использования кода. - Допустим мы имеем структуру, которая описывает Машину (`Сar`). - Если нам необходимо получить все возможности структуры `Car` и дополнить их в классе Пожарная машина (`FireEngine`), то мы можем использовать композицию (сделать `FireEngine` членом `Car`): - - ```go - type Car struct { - // … содержимое - } - - type FireEngine struct { - basis Car - // … дополнение - } - ``` - - ## Встраивание - - Теперь рассмотрим решение проблемы повторного использования кода через Встраивание. - Допустим структура `Car` имеет метод `Drive`. Мы должны скопировать точное поведение метода `Drive` в структуре `FireEngine`. - Для этого мы можем применить делегирование: - - ```go - type Car struct { - // … содержимое - } - - func (c *Car) Drive() { … } - - type FireEngine struct { - basis Car - // … дополнение - } - - func (fe *FireEngine) Drive() { fe.basis.Drive() } - ``` - - Однако оно ведёт к дублированию кода. Поэтому имеет механизм Встраивание, что позволяет значительно сократить код: - - ```go - type Car struct { - // … содержимое - } - - func (c *Car) Drive() { … } - - type FireEngine struct { - Car - // … дополнение - } - ``` - - ## Интерфейсы - - Теперь на очереди функционал полиморфизма и динамической диспетчеризации. - Допустим, что наше приложение расширяется и в нем появляется всё больше видов специализированных машин: - Полицейская Машина (`PoliceCar`), Машина Скорой Помощи (`AmbulanceCar`), Поливомоечная машина (`WateringCar`). - Все они должны иметь метод `Drive`, однако реализует его каждая по-разному. - Например, `PoliceCar` едет со звуком сирены, а `WateringCar` во время поездки поливает дорогу водой. - То есть, мы должны определить "поведение", которое должно присутствовать в каждой из этих структур, но реализовано оно может быть по-разному. - В таком случае на сцену и выходят интерфейсы (`interfaces`). - - Интерфейсы определяют, что тип делает, а не кем он является. - Методы должны отражать поведение типа, поэтому интерфейсы объявляются с набором методов. - В нашем случае каждая из указанных выше структур должна иметь метод `Drive`. - - ```go - type IDriveable interface { - Drive() - } - - type Car struct { - // … - } - - type PoliceCar struct { - // … - } - - func (c Car) Drive() { - fmt.Println("Просто еду по дороге") - } - - func (pc PoliceCar) Drive() { - fmt.Println("Еду по дороге с мигалкой. Виу-виу!") - } - - func main() { - cars := []IDriveable{&Car{}, &PoliceCar{}} - for _, vehicle := range cars { - vehicle.Drive() - // => Просто еду по дороге - // => Еду по дороге с мигалкой. Виу-виу! - } - } - ``` - - Именование интерфейсов в виде "глагол + able" стандартно для большинства языков программирования. - Однако в Go интерфейсы именуются немного по-другому. В данном случае интерфейс должен называться Driver. - Подробнее про нейминг можно почитать в официальной документации Golang. - - Так никакого явного указания реализации не требуется. - Любой тип, который предоставляет методы, которые указаны в интерфейсе, можно считать реализующим интерфейс. - -instructions: | - - Реализуйте интерфейс `Voicer` для структур `Cat`, `Cow` и `Dog` так, чтобы при вызове метода `Voice` экземпляр структуры `Cat` возвращал строку "Мяу", экземпляр `Cow` строку "Мууу", а экземпляр `Dog` сообщение `Гав`: - - ```go - cat := Cat{} - dog := Dog{} - cow := Cow{} - - fmt.Println(cat.Voice()) // Мяу - fmt.Println(dog.Voice()) // Гав - fmt.Println(cow.Voice()) // Мууу - ``` - -tips: - - | - [Interfaces and other types](https://go.dev/doc/effective_go#interfaces_and_types) - [Interface names](https://go.dev/doc/effective_go#interface-names) diff --git a/modules/10-basics/70-inheritance-interfaces/ru/data.yml b/modules/10-basics/70-inheritance-interfaces/ru/data.yml index ad36500..24b4f03 100644 --- a/modules/10-basics/70-inheritance-interfaces/ru/data.yml +++ b/modules/10-basics/70-inheritance-interfaces/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Наследование и интерфейсы tips: - > diff --git a/modules/20-array-slice-map/10-array/description.ru.yml b/modules/20-array-slice-map/10-array/description.ru.yml deleted file mode 100644 index 8cf59ff..0000000 --- a/modules/20-array-slice-map/10-array/description.ru.yml +++ /dev/null @@ -1,84 +0,0 @@ ---- - -name: Массивы -theory: | - - Массив — это коллекция однотипных значений фиксированной длины: - - ```go - nums := [5]int{} - ``` - - Длина массива указывается в квадратных скобках. Если не заполнить массив при инициализации, то массив будет состоять из нулевых значений данного типа: - - ```go - nums := [5]int{} // [0, 0, 0, 0, 0] - - // длинная инициализация - var nums [5]int // [0, 0, 0, 0, 0] - ``` - - При попытке инициализации элементов за границей массива выходит ошибка компиляции: - - ```go - nums := [1]int{1,2} // array index 1 out of bounds [0:1] - ``` - - Чтение и запись элементов массива происходит через квадратные скобки: - - ```go - nums := [3]int{1, 2, 3} - - fmt.Println(nums[1]) // 2 - - nums[2] = 33 - - fmt.Println(nums) // [1, 2, 33] - ``` - - Нумерация элементов массива начинается с 0. При попытке чтения/записи элементов за границей массива выходит ошибка компиляции: - - ```go - words := [2]string{} - - words[2] // invalid array index 2 (out of bounds for 2-element array) - ``` - - Массивы в Go передаются по значению, следовательно, любое изменение внутри функции не влияет на исходный массив: - - ```go - package main - - import ( - "fmt" - ) - - func main() { - a := [3]int{1,2,3} - - modifyArr(a) - - fmt.Println(a) // 1, 2, 3 - } - - func modifyArr(nums [3]int) { - nums[0] = 35 - } - ``` - - В Go есть встроенная функция `len()`, которая возвращает длину массива: - - ```go - fmt.Println(len([5]int{1,2,3})) // 5 - fmt.Println(len([10]int{})) // 10 - ``` - - Так как массивы инициализируются с фиксированной длинной, то функция `len()` всегда возвращает одно и то же значение. - -instructions: | - - Реализуйте функцию `SafeWrite(nums [5]int, i, val int) [5]int`, которая записывает значение `val` в массив `nums` по индексу `i`, если индекс находится в рамках массива. В противном случае массив возвращается без изменения. - -tips: - - | - [The Go Programming Language Specification — Array types](https://golang.org/ref/spec#Array_types) diff --git a/modules/20-array-slice-map/10-array/ru/data.yml b/modules/20-array-slice-map/10-array/ru/data.yml index 82a49d3..0171902 100644 --- a/modules/20-array-slice-map/10-array/ru/data.yml +++ b/modules/20-array-slice-map/10-array/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Массивы tips: - > diff --git a/modules/20-array-slice-map/15-slice/description.ru.yml b/modules/20-array-slice-map/15-slice/description.ru.yml deleted file mode 100644 index 060e61a..0000000 --- a/modules/20-array-slice-map/15-slice/description.ru.yml +++ /dev/null @@ -1,77 +0,0 @@ ---- - -name: Слайсы -theory: | - - На практике не часто сталкиваешься с массивами из-за ограниченной длины при строгой типизации. Вместо этого повсеместно используются слайсы. Слайс — это массив *неопределенной* длины (или динамический массив): - - ```go - var nums = []int{1,2,3} - - nums := []int{1,2,3} - ``` - - Чтение и запись осуществляется точно так же как в массивах: - - ```go - nums := []int{1,2,3} - - nums[2] // 3 - - nums[0] = 10 // [10, 2, 3] - - // с помощью оператора : можно получить элементы от нижней до верхней границы - nums[1:3] // [2, 3] - nums[:2] // [10, 2] - nums[2:] // [3] - ``` - - В слайсы можно добавлять элементы с помощью встроенной функции `func append(slice []Type, elems ...Type) []Type`, которая возвращает новый слайс с добавленным элементом: - - ```go - words := []string{"hello"} - - words = append(words, "world") // ["hello", "world"] - ``` - - Так как слайс имеет нефиксированную длину, "под капотом" лежит более сложная структура, чем у массива. Помимо самих значений слайс хранит 2 дополнительных свойства: длину массива *len* (длина) и *cap* (вместимость). Благодаря этому возможно инициализировать слайс нужной длины с помощью встроенной функции `func make(t Type, len, cap IntegerType) Type`. Понимание, где лучше использовать какой способ инициализации, приходит с опытом, но для старта рекомендуется использовать `make` везде, где можно: - - ```go - // len = 5. Массив сразу будет заполнен 5-ю нулевыми значениями - nums := make([]int, 5, 5) // [0, 0, 0, 0, 0] - - // len = 0, но cap = 5. Массив будет пустым, однако заполнение слайса через append будет эффективным, потому что в памяти уже выделен массив нужной длины - nums := make([]int, 0, 5) // [] - ``` - - Передача слайса как аргумента функции происходит хитро. Длина и вместимость передаются по значению, но массив значений передается по ссылке. Вследствие этого получается неявное поведение: добавленные элементы не сохранятся в исходный слайс, но изменение существующих останется: - - ```go - package main - - import ( - "fmt" - ) - - func main() { - nums := []int{1, 2, 3, 4, 5} - - modifySlice(nums) - - fmt.Println(nums) // [1 2 10 4 5] - } - - func modifySlice(nums []int) { - nums[2] = 10 // элемент будет и в исходном слайсе - nums = append(nums, 6) // элемент не добавится в исходный слайс - } - ``` - - -instructions: | - - В Go нет встроенной функции удаления элемента из слайса. Реализуйте функцию `Remove(nums []int, i int) []int`, которая удаляет элемент по индексу `i` из слайса `nums`. Если приходит несуществующий индекс, то из функции возвращается исходный слайс. Порядок элементов может быть нарушен после удаления элемента. - -tips: - - | - [The Go Programming Language Specification — Slice types](https://golang.org/ref/spec#Slice_types) diff --git a/modules/20-array-slice-map/15-slice/ru/data.yml b/modules/20-array-slice-map/15-slice/ru/data.yml index 0ae384b..87336c2 100644 --- a/modules/20-array-slice-map/15-slice/ru/data.yml +++ b/modules/20-array-slice-map/15-slice/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Слайсы tips: - > diff --git a/modules/20-array-slice-map/20-for-loop/description.ru.yml b/modules/20-array-slice-map/20-for-loop/description.ru.yml deleted file mode 100644 index 885f420..0000000 --- a/modules/20-array-slice-map/20-for-loop/description.ru.yml +++ /dev/null @@ -1,129 +0,0 @@ ---- - -name: Цикл for -theory: | - - Из-за стремления создателей Go к простоте и однозначности обход коллекций осуществляется только через цикл `for`. Условное выражение пишется без скобок и тело цикла всегда должно быть внутри `{ }`: - - ```go - nums := make([]int, 0, 10) - - // начиная с 0; пока i меньше 10; инкрементим i после каждого шага - for i := 0; i < 10; i++ { - nums = append(nums, i) - } - - fmt.Println(nums) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - ``` - - При необходимости `for` используется как цикл `while` в других языках: - - ```go - i := 0 - nums := make([]int, 0, 10) - - for i < 10 { - nums = append(nums, i) - i++ - } - - fmt.Println(nums) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - ``` - - Если опустить условное выражение, можно уйти в бесконечный цикл: - - ```go - i := 0 - - nums := make([]int, 0, 10) - - for { - if i == 10 { - break - } - - nums = append(nums, i) - i++ - } - - fmt.Println(nums) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - ``` - - Если условное выражение возвращает `false` или был вызван `break` в теле, то происходит остановка цикла. Перейти на следующую итерацию можно с помощью ключевого слова `continue`: - - ```go - nums := make([]int, 0, 10) - - // добавляем только четные числа в слайс - for i := 0; i < 10; i++ { - if i % 2 != 0 { - continue - } - - nums = append(nums, i) - } - - fmt.Println(nums) // [0 2 4 6 8] - ``` - - Для обхода коллекции в Go есть "синтаксический сахар" `range`. Эта конструкция обходит слайс, возвращая пару - индекс и элемент на каждом шаге: - - ```go - names := []string{"John", "Harold", "Vince"} - - // i — это индекс, name — это значение на текущем шаге цикла - for i, name := range names { - fmt.Println("Hello ", name, " at index ", i) - } - - // => Hello John at index 0 - // => Hello Harold at index 1 - // => Hello Vince at index 2 - ``` - - Если пропустить вторую переменную, то получим только индексы: - - ```go - for i := range names { - fmt.Println("index = ", i) - } - - // => index = 0 - // => index = 1 - // => index = 2 - ``` - - Можно пропустить первую переменную, это можно сделать с помощью `_`: - - ```go - for _, name := range names { - fmt.Println("Hello ", name) - } - - // => Hello John - // => Hello Harold - // => Hello Vince - ``` - - Пропуск сразу двух переменных не сработает. На этапе компиляции произойдет ошибка: - - ```go - for _,_ := range names { - fmt.Println("Nothing") - } - ``` - - ```text - # command-line-arguments - ./main.go:21:14: no new variables on left side of := - ``` - - - -instructions: | - - Реализуйте функцию `func Map(strs []string, mapFunc func(s string) string) []string`, которая преобразует каждый элемент слайса `strs` с помощью функции `mapFunc` и возвращает новый слайс. Учтите, что исходный слайс, который передается как `strs`, не должен измениться в процессе выполнения. - -tips: - - | - [The Go Programming Language Specification — For statement](https://golang.org/ref/spec#For_statements) diff --git a/modules/20-array-slice-map/20-for-loop/ru/data.yml b/modules/20-array-slice-map/20-for-loop/ru/data.yml index 1d00f1d..bf99f41 100644 --- a/modules/20-array-slice-map/20-for-loop/ru/data.yml +++ b/modules/20-array-slice-map/20-for-loop/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Цикл for tips: - > diff --git a/modules/20-array-slice-map/25-slice-copy/description.ru.yml b/modules/20-array-slice-map/25-slice-copy/description.ru.yml deleted file mode 100644 index a04adac..0000000 --- a/modules/20-array-slice-map/25-slice-copy/description.ru.yml +++ /dev/null @@ -1,53 +0,0 @@ ---- - -name: Копирование слайсов -theory: | - - Допустим, в вашей функции происходят изменения элементов, но вы не хотите затронуть входной слайс. В языке есть встроенная функция `func copy(dst, src []Type) int`, которая копирует слайс `src` в слайс `dst` и возвращает кол-во скопированных элементов: - - ```go - nums := []int{1,2,3,4,5} - - // важно инициализировать слайс той же длины - numsCp := make([]int, len(nums)) - - copy(numsCp, nums) - - fmt.Println(numsCp) // [1,2,3,4,5] - ``` - - Почему мы не можем просто перезаписать слайс в другую переменную и изменять ее? Как и с функциями, при присваивании слайса к переменной, копируется только длина и вместимость, но массив передается по ссылке: - - ```go - nums := []int{1,2,3,4,5} - - numsCp := nums - - // исходный слайс nums тоже будет изменен - numsCp[0] = 10 - - fmt.Println(nums) // [10,2,3,4,5] - ``` - - Существует распространенная ошибка, когда пытаются скопировать слайсы различной длины. В этом случае элементы, выходящие за рамки слайса `dst`, не будут скопированы: - - ```go - nums := []int{1, 2, 3, 4, 5} - - // создали слайс с длиной 0 - numsCp := make([]int, 0) - - // при копировании в пустой слайс ничего не произойдет - copy(numsCp, nums) - - fmt.Println(numsCp) // [] - ``` - -instructions: | - - Реализуйте функцию `IntsCopy(src []int, maxLen int) []int`, которая создает копию слайса `src` с длиной `maxLen`. Если `maxLen` равен нулю или отрицательный, то функция возвращает пустой слайс `[]int{}`. Если `maxLen` больше длины `src`, то возвращается полная копия `src`. - - -tips: - - | - [The Go Programming Language Specification — Appending to and copying slices](https://golang.org/ref/spec#Appending_and_copying_slices) diff --git a/modules/20-array-slice-map/25-slice-copy/ru/data.yml b/modules/20-array-slice-map/25-slice-copy/ru/data.yml index 7ef4b40..e5e4948 100644 --- a/modules/20-array-slice-map/25-slice-copy/ru/data.yml +++ b/modules/20-array-slice-map/25-slice-copy/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Копирование слайсов tips: - > diff --git a/modules/20-array-slice-map/30-slice-sort/description.ru.yml b/modules/20-array-slice-map/30-slice-sort/description.ru.yml deleted file mode 100644 index 4ad25fb..0000000 --- a/modules/20-array-slice-map/30-slice-sort/description.ru.yml +++ /dev/null @@ -1,61 +0,0 @@ ---- - -name: Сортировка слайсов -theory: | - - Сортировка массива — распространненая задача в программировании. Во всех языках существуют готовые решения для этой задачи, и Go — не исключение. Стандартный пакет `sort` предоставляет функции для сортировки: - - ```go - nums := []int{2,1,6,5,3,4} - - sort.Slice(nums, func(i, j int) bool { - return nums[i] < nums[j] - }) - - fmt.Println(nums) // [1 2 3 4 5 6] - ``` - - Рассмотрим функцию `Slice(x interface{}, less func(i, j int) bool)`. В описании функции присутствует неизвестный тип данных `interface{}`. Понятие интерфейса будет рассмотренно в следующих модулях курса. Следует запомнить, что пустой интерфейс `interface{}` в Go означает тип данных, под который подходит любой другой тип. Например: - - ```go - func Print(arg interface{}) { - fmt.Println(arg) - } - - func main() { - Print("hello!") - Print(123) - Print([]int{1,5,10}) - } - ``` - - Вывод: - - ```go - hello! - 123 - [1 5 10] - ``` - - То есть в функцию `Slice(x interface{}, less func(i, j int) bool)` передается слайс любого типа данных, как первый аргумент. Вторым аргументом передается функция, которая берет элементы по индексу и определяет должен ли элемент по индексу `i` находиться перед элементом по индексу `j`. - - "Под капотом" в функции `sort.Slice` используется быстрая сортировка. В пакете также присутствует сортировка вставками `sort.SliceStable`: - - ```go - nums := []int{2,1,6,5,3,4} - - sort.SliceStable(nums, func(i, j int) bool { - return nums[i] < nums[j] - }) - - fmt.Println(nums) // [1 2 3 4 5 6] - ``` - - Выбор алгоритма зависит от набора и размера данных, архитектуры процессора, скорости доступа к памяти, то есть от многих факторов. Для большинства стандартных случаев используется `sort.Slice`, пока производительность или нестабильность алгоритма не станет "узким горлышком". - -instructions: | - Реализуйте функцию `UniqueSortedUserIDs(userIDs []int64) []int64`, которая возвращает отсортированный слайс, состоящий из уникальных идентификаторов `userIDs`. Обработка должна происходить in-place, то есть без выделения доп. памяти. - -tips: - - | - [Стандартные пакеты в Go — sort](https://pkg.go.dev/sort) diff --git a/modules/20-array-slice-map/30-slice-sort/ru/data.yml b/modules/20-array-slice-map/30-slice-sort/ru/data.yml index ee283ee..838fd5c 100644 --- a/modules/20-array-slice-map/30-slice-sort/ru/data.yml +++ b/modules/20-array-slice-map/30-slice-sort/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Сортировка слайсов tips: - | diff --git a/modules/20-array-slice-map/35-map/description.ru.yml b/modules/20-array-slice-map/35-map/description.ru.yml deleted file mode 100644 index fef9096..0000000 --- a/modules/20-array-slice-map/35-map/description.ru.yml +++ /dev/null @@ -1,101 +0,0 @@ ---- - -name: Мапа -theory: | - - Map — тип данных, предназначенный для хранения пар *ключ-значение*. В других языках эту структуру так же называют: хэш-таблица, словарь, ассоциативный массив. Запись и чтение элементов происходят в основном за O(1): - - ```go - // создание пустой мапы - var m map[int]string - - // сокращенное создание пустой мапы - m := map[int]string{} - - // рекомендуемое создание с обозначением размера - m := make(map[int]string, 10) - - // создание мапы с элементами - m := map[int]string{1: "hello", 2: "world"} - - // добавление элемента - m[3] = "!" // map[1:hello, 2:world, 3:!] - - // чтение элемента - word := m[1] // "hello" - ``` - - При чтении элемента по несуществующему ключу возвращается нулевое значение данного типа. Это приводит к ошибкам логики, когда используется `bool` как значение. Для решения данной проблемы при чтении используется вторая переменная, в которую записывается наличие элемента в мапе: - - ```go - elements := map[int64]bool{1: true, 2: true} - - element, elementExists := elements[1] // true, true - element, elementExists := elements[2] // true, true - - element, elementExists := elements[225] // false, false - ``` - - Для проверки существования ключа можно использовать мапу с пустыми структурами - - ```go - // пустая структура struct{} — это тип данных, который занимает 0 байт - // используется, когда нужно проверять в мапе только наличие ключа - cache := make(map[string]struct{}) - - // проверяем есть ли ключ `key` в мапе - _, ok = cache["key"] - fmt.Println(ok) // false - - // добавим ключ и проверим вновь - cache["key"] = struct{}{} - _, ok = cache["key"] - fmt.Println(ok) // true - ``` - - Элементы удаляются с помощью встроенной функции `delete(m map[Type]Type1, key Type)`: - - ```go - engToRus := map[string]string{"hello":"привет", "world":"мир"} - - delete(engToRus, "world") - - fmt.Println(engToRus) // map[hello:привет] - ``` - - Мапы в Go всегда передаются по ссылке: - - ```go - package main - - import ( - "fmt" - ) - - func main() { - m := map[int]string{1: "hello", 2: "world"} - - modifyMap(m) - - fmt.Println(m) // вывод: map[1:changed 2:world 200:added] - } - - func modifyMap(m map[int]string) { - m[200] = "added" - - m[1] = "changed" - } - ``` - -instructions: | - - Реализуйте функцию `UniqueUserIDs(userIDs []int64) []int64`, которая возвращает слайс, состоящий из уникальных идентификаторов `userIDs`. Порядок слайса должен сохраниться. - - -tips: - - | - [The Go Programming Language Specification — Map types](https://golang.org/ref/spec#Map_types) - - | - [The Go Programming Language Specification — Making maps](https://golang.org/ref/spec#Making_slices_maps_and_channels) - - | - [The Go Programming Language Specification — Deleting map elements](https://golang.org/ref/spec#Deletion_of_map_elements) diff --git a/modules/20-array-slice-map/35-map/ru/data.yml b/modules/20-array-slice-map/35-map/ru/data.yml index 3411064..eedd29b 100644 --- a/modules/20-array-slice-map/35-map/ru/data.yml +++ b/modules/20-array-slice-map/35-map/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Мапа tips: - > diff --git a/modules/20-array-slice-map/40-map-for/description.ru.yml b/modules/20-array-slice-map/40-map-for/description.ru.yml deleted file mode 100644 index e87e7b9..0000000 --- a/modules/20-array-slice-map/40-map-for/description.ru.yml +++ /dev/null @@ -1,62 +0,0 @@ ---- - -name: Обход мапы -theory: | - - Как и слайс, мапу можно обойти с помощью конструкции `for range`: - - ```go - idToName := map[int64]string{1: "Alex", 2: "Dan", 3: "George"} - - // первый аргумент — ключ, второй — значение - for id, name := range idToName { - fmt.Println("id: ", id, "name: ", name) - } - ``` - - Вывод: - - ```go - id: 1 name: Alex - id: 2 name: Dan - id: 3 name: George - ``` - - Стоит учитывать, что порядок ключей в мапе **рандомизирован**: - - ```go - numExistence := make(map[int]bool, 0) - - // записали по порядку числа от 0 до 9 - for i := 0; i < 10; i++ { - numExistence[i] = true - } - - // обходим мапу и выводим ключи - for num := range numExistence { - fmt.Println(num) - } - ``` - - Вывод: - - ```go - 8 - 1 - 2 - 3 - 6 - 7 - 9 - 0 - 4 - 5 - ``` - -instructions: | - - Реализуйте функцию `MostPopularWord(words []string) string`, которая возвращает самое часто встречаемое слово в слайсе. Если таких слов несколько, то возвращается первое из них. - -tips: - - | - [Go by Example: Range](https://gobyexample.com/range) diff --git a/modules/20-array-slice-map/40-map-for/ru/data.yml b/modules/20-array-slice-map/40-map-for/ru/data.yml index 4d634c9..1cef2a4 100644 --- a/modules/20-array-slice-map/40-map-for/ru/data.yml +++ b/modules/20-array-slice-map/40-map-for/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Обход мапы tips: - | diff --git a/modules/30-strings/10-strings-bytes/description.ru.yml b/modules/30-strings/10-strings-bytes/description.ru.yml deleted file mode 100644 index 9a1d3fc..0000000 --- a/modules/30-strings/10-strings-bytes/description.ru.yml +++ /dev/null @@ -1,88 +0,0 @@ ---- - -name: Строки и байты -theory: | - - Строки в Go — это иммутабельные массивы байт. Для стандартного компилятора Go внутренняя структура строки описана как: - - ```go - type _string struct { - elements *byte // байты - len int // кол-во байт - } - ``` - - После инициализации строку нельзя изменить и такая иммутабельность позволяет избежать побочных эффектов в коде. - - ```go - s := "hello" - s[4] = "" // ошибка компиляции: cannot assign to s[4] (strings are immutable) - ``` - - Стоит отметить, что тип данных `byte` — это алиас к типу `uint8` (0-255). Во-первых, потому что нужно абстрактно отличать типы в коде. Во-вторых, байты представляют ASCII символы, а в кодовой таблице ASCII символов 256 кодов: - - ```go - package main - - import "fmt" - - func main() { - s := "hey" - - fmt.Println(s[0], s[1], s[2]) // 104 101 121 - - fmt.Println(string(s[0]), string(s[1]), string(s[2])) // h e y - } - ``` - - Большинство библиотечных функций работают со слайсами байт `[]byte` для производительности. Конвертация строки в слайс байт описывается в коде явно: - - ```go - package main - - import "fmt" - - func main() { - s := "hey" - bs := []byte(s) - - fmt.Println([]byte(s)) // [104 101 121] - - fmt.Println(string(bs)) // hey - } - ``` - - Отдельные ASCII символы можно объявлять сразу с типом `byte`. Для этого нужно обернуть символ в одинарные кавычки и указать тип `byte`: - - ```go - package main - - import ( - "fmt" - "reflect" - ) - - func main() { - asciiCh := byte('Z') - asciiChStr := string(asciiCh) - - fmt.Println(reflect.TypeOf(asciiCh), asciiCh) // uint8 90 - - fmt.Println(reflect.TypeOf(asciiChStr), asciiChStr) // string Z - } - ``` - -instructions: | - - Реализуйте функции `nextASCII(b byte) byte` и `prevASCII(b byte) byte`, которые возвращают следующий или предыдущий символ ASCII таблицы соответственно. Например: - - ```go - nextASCII(byte('a')) // 'b' - prevASCII(byte('b')) // 'a' - ``` - - Допускаем, что в функцию `prevASCII` *не* может прийти нулевой символ, а в функцию `nextASCII` — последний символ ASCII таблицы. - -tips: - - | - [The Go Programming Language Specification — String](https://golang.org/ref/spec#String_types) diff --git a/modules/30-strings/10-strings-bytes/ru/data.yml b/modules/30-strings/10-strings-bytes/ru/data.yml index 1872ea8..7045e38 100644 --- a/modules/30-strings/10-strings-bytes/ru/data.yml +++ b/modules/30-strings/10-strings-bytes/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Строки и байты tips: - > diff --git a/modules/30-strings/15-strings-for/description.ru.yml b/modules/30-strings/15-strings-for/description.ru.yml deleted file mode 100644 index 97f6a8e..0000000 --- a/modules/30-strings/15-strings-for/description.ru.yml +++ /dev/null @@ -1,73 +0,0 @@ ---- - -name: Обход строки -theory: | - - Так как строка — это **массив** байт, ее можно обойти с помощью цикла `for`: - - ```go - package main - - import ( - "fmt" - ) - - func main() { - s := "hello" - for i := 0; i < len(s); i++ { - fmt.Println(string(s[i])) - } - - } - ``` - - Вывод: - - ``` - h - e - l - l - o - ``` - - Таким способом можно обойти только строки, состоящие из ASCII символов. Если строка содержит мультибайтовые символы, вывод будет некорректен: - - ```go - package main - - import ( - "fmt" - ) - - func main() { - s := "привет" - for i := 0; i < len(s); i++ { - fmt.Println(string(s[i])) - } - - } - ``` - - Вывод проверьте сами в [Go Playground](https://play.golang.org/p/-G3ygH0rTIv) - -instructions: | - - Реализуйте функцию `shiftASCII(s string, step int) string`, которая принимает на вход состоящую из ASCII символов строку `s` и возвращает новую строку, где каждый символ из входящей строки сдвинут вперед на число `step`. Например: - - ```go - shiftASCII("abc", 0) // "abc" - shiftASCII("abc1", 1) // "bcd2" - shiftASCII("bcd2", -1) // "abc1" - shiftASCII("hi", 10) // "rs" - ``` - - Если после сдвига код символа выходит за рамки ASCII, то число должно быть взято по модулю 256: - - ```go - shiftASCII("abc", 256) // "abc" - shiftASCII("abc", -511) // "bcd" - ``` - - -tips: [] diff --git a/modules/30-strings/15-strings-for/ru/data.yml b/modules/30-strings/15-strings-for/ru/data.yml index b614e03..6ff5ab0 100644 --- a/modules/30-strings/15-strings-for/ru/data.yml +++ b/modules/30-strings/15-strings-for/ru/data.yml @@ -1,2 +1,3 @@ +--- name: Обход строки tips: [] diff --git a/modules/30-strings/20-strings-runes/description.ru.yml b/modules/30-strings/20-strings-runes/description.ru.yml deleted file mode 100644 index 112fb7b..0000000 --- a/modules/30-strings/20-strings-runes/description.ru.yml +++ /dev/null @@ -1,80 +0,0 @@ ---- - -name: Руны -theory: | - - В современном мире невозможно работать только со строками, состоящими исключительно из ASCII символов. Везде используются нестандартные знаки, языки отличные от латиницы и эмодзи. Для работы с такими Юникод символами в Go представлен тип данных `rune`: - - ```go - package main - - import ( - "fmt" - ) - - func main() { - emoji := []rune("привет😀") - - for i := 0; i < len(emoji); i++ { - fmt.Println(emoji[i], string(emoji[i])) // выводим код символа и его строковое представление - } - } - ``` - - Вывод: - - ```go - 1087 п - 1088 р - 1080 и - 1074 в - 1077 е - 1090 т - 128512 😀 - ``` - - `rune` — это алиас к `int32`. Как и байты, руны были созданы для отличия от встроенного типа данных. Каждая руна представляет собой код символа стандарта Юникод. Строка свободно преобразуется в `[]byte` и `[]rune`, но эти 2 типа данных не конвертируются между собой напрямую: - - ```go - s := "hey😉" - - rs := []rune([]byte(s)) // cannot convert ([]byte)(s) (type []byte) to type []rune - - bs := []byte([]rune(s)]) // cannot convert ([]rune)(s) (type []rune) to type []byte - ``` - - В Go присутствует синтаксический сахар при обходе строки. Если использовать конструкцию `for range`, строка автоматически будет преобразована в `[]rune`, то есть обход будет по Юникод символам: - - ```go - package main - - import ( - "fmt" - ) - - func main() { - emoji := []rune("cool😀") - - for _, ch := range emoji { - fmt.Println(ch, string(ch)) // выводим код символа и его строковое представление - } - } - ``` - - Вывод: - - ```go - 99 c - 111 o - 111 o - 108 l - 128512 😀 - ``` - -instructions: | - - Реализуйте функцию `isASCII(s string) bool`, которая возвращает `true`, если строка `s` состоит только из ASCII символов. - -tips: - - | - [The Go Programming Language Specification — Rune literals](https://golang.org/ref/spec#Rune_literals) diff --git a/modules/30-strings/20-strings-runes/ru/data.yml b/modules/30-strings/20-strings-runes/ru/data.yml index 3cb6e9d..2ac379d 100644 --- a/modules/30-strings/20-strings-runes/ru/data.yml +++ b/modules/30-strings/20-strings-runes/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Руны tips: - > diff --git a/modules/30-strings/25-strings-standard-pkg/description.ru.yml b/modules/30-strings/25-strings-standard-pkg/description.ru.yml deleted file mode 100644 index 320fd19..0000000 --- a/modules/30-strings/25-strings-standard-pkg/description.ru.yml +++ /dev/null @@ -1,50 +0,0 @@ ---- - -name: Стандартный пакет strings -theory: | - - Для работы со строками в Go существует стандартный пакет `strings`, который содержит основные функции. С некоторыми мы уже встречались в первом модуле (например `strings.ReplaceAll`). Теперь рассмотрим список самых часто встречающихся функций: - - ```go - import "strings" - - // проверяет наличие подстроки в строке - strings.Contains("hello", "h") // true - - // разбивает строку по Юникод символам или по переданному разделителю - strings.Split("hello", "") // ["h", "e", "l", "l", "o"] - - // склеивает строки из слайса с разделителем - strings.Join([]string{"hello","world!"}, " ") // "hello world!" - - // обрезает начальные и конечные символы строки, содержащиеся во втором аргументе - strings.Trim(" hey !", " ") // "hey !" - ``` - - Очень важная часть пакета `strings` — это *Builder*. Когда необходимо собрать большую строку по каким-то правилам, использование конкатенации — не лучшее решение, потому что каждая операция создает новую строку, что сильно влияет на производительность при большом количестве операций. Такая задача решается с помощью билдера: - - ```go - import "strings" - - sb := &strings.Builder{} - - sb.WriteString("hello") - sb.WriteString(" ") - sb.WriteString("world") - - sb.String() // "hello world" - ``` - - -instructions: | - В пакете unicode есть [функция `unicode.Is(unicode.Latin, rune)`](https://pkg.go.dev/unicode#Is), которая проверяет, что руна является латинским символом или нет. - - Реализуйте функцию `latinLetters(s string) string`, которая возвращает только латинские символы из строки `s`. Например: - - ```go - latinLetters("привет world!") // "world" - ``` - -tips: - - | - [Go standard package — strings](https://pkg.go.dev/strings) diff --git a/modules/30-strings/25-strings-standard-pkg/ru/data.yml b/modules/30-strings/25-strings-standard-pkg/ru/data.yml index ff44760..c55d1b2 100644 --- a/modules/30-strings/25-strings-standard-pkg/ru/data.yml +++ b/modules/30-strings/25-strings-standard-pkg/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Стандартный пакет strings tips: - | diff --git a/modules/30-strings/30-strings-fmt/description.ru.yml b/modules/30-strings/30-strings-fmt/description.ru.yml deleted file mode 100644 index 5e089d5..0000000 --- a/modules/30-strings/30-strings-fmt/description.ru.yml +++ /dev/null @@ -1,79 +0,0 @@ ---- - -name: Форматирование строк -theory: | - - В предыдущих уроках мы использовали пакет `fmt` для вывода переменных или результатов функций: - - ```go - s := "hello world" - - // печатает вывод на следующей строке - fmt.Println(s) // "hello world" - ``` - - Пакет `fmt` так же используется для форматирования строк. Плейсхолдеры разных типов данных в основном не отличаются от других языков: - - ```go - name := "Andy" - - // подставляем строку - fmt.Sprintf("hello %s", name) // "hello Andy" - - // число - fmt.Sprintf("there are %d kittens", 10) // "there are 10 kittens" - - // логический тип - fmt.Sprintf("your story is %t", true) // "your story is true" - ``` - - Так же существуют специализированные плейсхолдеры, которые преобразуют сложные структуры: - - ```go - package main - - import ( - "fmt" - ) - - type Person struct { - Name string - Age int - } - - func main() { - p := Person{Name: "Andy", Age: 18} - - // вывод значений структуры - fmt.Println("simple struct:", p) - - // вывод названий полей и их значений - fmt.Printf("detailed struct: %+v\n", p) - - // вывод названий полей и их значений в виде инициализации - fmt.Printf("Golang struct: %#v\n", p) - } - ``` - - Вывод: - - ```go - simple struct: {Andy 18} - detailed struct: {Name:Andy Age:18} - Golang struct: main.Person{Name:"Andy", Age:18} - ``` - - -instructions: | - - Реализуйте функцию `generateSelfStory(name string, age int, money float64) string`, которая генерирует предложение, подставляя переданные данные в возвращаемую строку. Например: - - ```go - generateSelfStory("Vlad", 25, 10.00000025) // "Hello! My name is Vlad. I'm 25 y.o. And I also have $10.00 in my wallet right now." - ``` - - Шаблон возвращаемой строки: *Hello! My name is [name]. I'm [age] y.o. And I also have $[money with precision 2] in my wallet right now.* - -tips: - - | - [Go Formatting](https://pkg.go.dev/fmt#hdr-Printing) diff --git a/modules/30-strings/30-strings-fmt/ru/data.yml b/modules/30-strings/30-strings-fmt/ru/data.yml index 8b23050..6d588a0 100644 --- a/modules/30-strings/30-strings-fmt/ru/data.yml +++ b/modules/30-strings/30-strings-fmt/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Форматирование строк tips: - | diff --git a/modules/40-func/10-variadic-func/description.ru.yml b/modules/40-func/10-variadic-func/description.ru.yml deleted file mode 100644 index 2235b74..0000000 --- a/modules/40-func/10-variadic-func/description.ru.yml +++ /dev/null @@ -1,47 +0,0 @@ ---- - -name: Вариативные функции -theory: | - - Последний аргумент функции может быть вариативным. Функция может иметь максимум один вариативный аргумент и этот аргумент всегда слайс. Чтобы обозначить аргумент вариативным, нужно поставить три точки `...` перед его типом: - - ```go - package main - - import ( - "fmt" - ) - - func main() { - // кол-во аргументов может быть любым - PrintNums(1, 2, 3) - } - - - func PrintNums(nums ...int) { - for _, n := range nums { - fmt.Println(n) - } - } - ``` - - Также тремя точками можно разбить слайс на элементы при передаче в вариативную функцию. Например, встроенный метод `append(slice []Type, elems ...Type) []Type`, который добавляет последний элемент в слайс, принимает вариативный аргумент `elems ...Type`. Чтобы добавить один слайс в конец другого, нужно разбить второй слайс на элементы путем добавления трех точек `...` после переменной: - - ```go - nums1 := []int{1,2,3,4,5} - - nums2 := []int{6,7,8,9,10} - - res := append(nums1, nums2...) // [1 2 3 4 5 6 7 8 9 10] - ``` - -instructions: | - Реализуйте функцию `MergeNumberLists(numberLists ...[]int) []int`, которая принимает вариативный список слайсов чисел и объединяет их в 1, сохраняя последовательность: - - ```go - MergeNumberLists([]int{1, 2}, []int{3}, []int{4}) // [1, 2, 3, 4] - ``` - -tips: - - | - [The Go Programming Language Specification — Function types](https://golang.org/ref/spec#Function_types) diff --git a/modules/40-func/10-variadic-func/ru/data.yml b/modules/40-func/10-variadic-func/ru/data.yml index d644744..629cdb4 100644 --- a/modules/40-func/10-variadic-func/ru/data.yml +++ b/modules/40-func/10-variadic-func/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Вариативные функции tips: - > diff --git a/modules/40-func/15-arg-pointers/description.ru.yml b/modules/40-func/15-arg-pointers/description.ru.yml deleted file mode 100644 index a226d7a..0000000 --- a/modules/40-func/15-arg-pointers/description.ru.yml +++ /dev/null @@ -1,130 +0,0 @@ ---- - -name: Аргументы с указателем -theory: | - - Указатели — очень обширная и непростая тема, выходящая за рамки данного курса. В этом уроке будут рассмотренны только основы передачи указателей на аргументы в функции: - - ```go - package main - - import ( - "fmt" - ) - - type User struct { - email string - password string - } - - // при объявлении указываем, - // что переменная должна быть указателем. - // Для этого ставим звездочку * перед типом данных - func fillUserData(u *User, email string, pass string) { - u.email = email - u.password = pass - } - - func main() { - u := User{} - - // передаем указатель с помощью амперсанда - // & перед переменной - fillUserData(&u, "test@test.com", "qwerty") - - fmt.Printf("points on func call %+v\n", u) - // points on func call {email:test@test.com password:qwerty} - - // сразу инициализируем переменную с указателем - up := &User{} - - fillUserData(up, "test@test.com", "qwerty") - - fmt.Printf("points on init %+v\n", up) - // points on init {email:test@test.com password:qwerty} - } - - ``` - - Мапы по умолчанию передаются с указателем: - - ```go - package main - - import ( - "fmt" - ) - - func main() { - m := map[string]int{} - - fillMap(m) - - fmt.Println(m) // map[random:1] - } - - func fillMap(m map[string]int) { - m["random"] = 1 - } - ``` - - Разработчики, пришедшие из других языков, часто используют фразы "передача по ссылке" или "ссылка на переменную". Строго говоря, в Go нет ссылок, только указатели: - - ```go - package main - - import "fmt" - - func main() { - a := 1 - b := &a - c := &b - - fmt.Printf("%p %p %p\n", &a, &b, &c) - // 0xc000018030 0xc00000e028 0xc00000e030 - } - ``` - - В этом примере *b* и *c* содержат одинаковые значения — адрес переменной *a*, однако *b* и *c* хранятся в разных адресах. Из-за этого обновление переменной *b* не изменит *c*. Поэтому если кто-то говорит про ссылки в Go, он имеет в виду указатели. - -instructions: | - Реализуйте функцию `CopyParent(p *Parent) Parent`, которая создает копию структуры Parent и возвращает ее: - - ```go - type Parent struct { - Name string - Children []Child - } - - type Child struct { - Name string - Age int - } - - cp := CopyParent(nil) // Parent{} - - p := &Parent{ - Name: "Harry", - Children: []Child{ - { - Name: "Andy", - Age: 18, - }, - }, - } - cp := CopyParent(p) - - // при мутациях в копии "cp" - // изначальная структура "p" не изменяется - cp.Children[0] = Child{} - fmt.Println(p.Children) // [{Andy 18}] - ``` - -tips: - - | - [The Go Programming Language Specification — Pointer types](https://golang.org/ref/spec#Pointer_types) - - | - [Указатели в Go](https://gobyexample.com/pointers) - - | - [Еще об указателях](https://www.digitalocean.com/community/conceptual-articles/understanding-pointers-in-go) - diff --git a/modules/40-func/15-arg-pointers/ru/data.yml b/modules/40-func/15-arg-pointers/ru/data.yml index 7955f1e..28c9a93 100644 --- a/modules/40-func/15-arg-pointers/ru/data.yml +++ b/modules/40-func/15-arg-pointers/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Аргументы с указателем tips: - > diff --git a/modules/40-func/20-struct-methods/description.ru.yml b/modules/40-func/20-struct-methods/description.ru.yml deleted file mode 100644 index 20ef0e3..0000000 --- a/modules/40-func/20-struct-methods/description.ru.yml +++ /dev/null @@ -1,104 +0,0 @@ ---- - -name: Методы структур -theory: | - - В Go нет классов, но существуют структуры с методами. Метод — это функция с дополнительным аргументом, который указывается в скобках между `func` и названием функции: - - ```go - package main - - import ( - "fmt" - ) - - type Dog struct{} - - // сначала объявляется дополнительный аргумент "(d Dog)", а следом идет обычное описание функции - func (d Dog) Bark() { - fmt.Println("woof!") - } - - func main() { - d := Dog{} - d.Bark() // woof! - } - ``` - - В примере выше структура *Dog* передается по значению, то есть копируется. Если изменятся любые свойства внутри метода *Bark*, они останутся неизменными в исходной структуре: - - ```go - package main - - import ( - "fmt" - ) - - type Dog struct { - IsBarked bool - } - - func (d Dog) Bark() { - fmt.Println("woof!") - d.IsBarked = true - } - - func main() { - d := Dog{} - d.Bark() // woof! - - fmt.Println(d.IsBarked) // false - } - ``` - - Если есть необходимость в изменении состояния, структура должна передаваться указателем: - - ```go - package main - - import ( - "fmt" - ) - - type Dog struct { - IsBarked bool - } - - func (d *Dog) Bark() { - fmt.Println("woof!") - d.IsBarked = true - } - - func main() { - d := &Dog{} - d.Bark() // woof! - - fmt.Println(d.IsBarked) // true - } - ``` - -instructions: | - - Реализуйте методы структуры `Counter`, представляющую собой счётчик, хранящий *неотрицательное целочисленное значение* и позволяющий это значение изменять: - - - метод `Inc(delta int)` должен увеличивать текущее значение на `delta` единиц (на 1 по умолчанию), - - метод `Dec(delta int)` должен уменьшать текущее значение на `delta` единиц. - - ```go - c := Counter{} - c.Inc(0) - c.Inc(0) - c.Inc(40) - c.Value // 42 - - c.Dec(0) - c.Dec(30) - c.Value // 11 - - c.Dec(100) - c.Value // 0 - ``` - -tips: - - | - [The Go Programming Language Specification — Method Declarations](https://golang.org/ref/spec#Method_declarations) diff --git a/modules/40-func/20-struct-methods/ru/data.yml b/modules/40-func/20-struct-methods/ru/data.yml index 24fbf51..de6fe86 100644 --- a/modules/40-func/20-struct-methods/ru/data.yml +++ b/modules/40-func/20-struct-methods/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Методы структур tips: - > diff --git a/modules/40-func/25-custom-types/description.ru.yml b/modules/40-func/25-custom-types/description.ru.yml deleted file mode 100644 index 8f27ad8..0000000 --- a/modules/40-func/25-custom-types/description.ru.yml +++ /dev/null @@ -1,76 +0,0 @@ ---- - -name: Пользовательские типы и методы -theory: | - - В Go можно объявить алиас на существующий тип данных для выразительности и абстракции. Например, тип *byte* из модуля "строки" — это алиас *uint8*. Алиас объявляется через ключевое слово `type`: - - ```go - type NumCount int - - func main() { - nc := NumCount(len([]int{1, 2, 3})) - - fmt.Println(nc) // 3 - } - ``` - - Алиас можно конвертировать в оригинальный тип и обратно: - - ```go - type errorCode string - - func main() { - ec := errorCode("internal") - - fmt.Println(ec) // internal - - fmt.Println(string(ec)) // internal - } - ``` - - Также у алиасов могут быть методы. Объявление метода происходит так же, как и со структурами: - - ```go - type counter int - - - // передается указатель, чтобы можно было изменить состояние счетчика "c" - func (c *counter) inc() { - *c++ - } - - func main() { - c := counter(0) - (&c).inc() // передается указатель на счетчик &c, так как функция "inc()" работает с указателями - (&c).inc() - - fmt.Println(c) // 2 - } - ``` - -instructions: | - - Представим, что есть структура *Person*, содержащая возраст человека: - - ```go - type Person struct { - Age uint8 - } - ``` - - Реализуйте тип `PersonList` (слайс структур *Person*), с методом `(pl PersonList) GetAgePopularity() map[uint8]int`, который возвращает мапу, где ключ — возраст, а значение — кол-во таких возрастов: - - ```go - pl := PersonList{ - {Age: 18}, - {Age: 44}, - {Age: 18}, - } - - pl.GetAgePopularity() // map[18:2 44:1] - ``` - -tips: - - | - [The Go Programming Language Specification — Type Declarations](https://golang.org/ref/spec#Type_declarations) diff --git a/modules/40-func/25-custom-types/ru/data.yml b/modules/40-func/25-custom-types/ru/data.yml index fa625e4..2ac655d 100644 --- a/modules/40-func/25-custom-types/ru/data.yml +++ b/modules/40-func/25-custom-types/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Пользовательские типы и методы tips: - > diff --git a/modules/40-func/30-errors/description.ru.yml b/modules/40-func/30-errors/description.ru.yml deleted file mode 100644 index 6bb47c6..0000000 --- a/modules/40-func/30-errors/description.ru.yml +++ /dev/null @@ -1,155 +0,0 @@ ---- - -name: Ошибки -theory: | - - Ошибки в Go — это особенность языка, которая позволяет работать с неожиданным поведением кода в явном виде: - - ```go - import "errors" - - func validateName(name string) error { - if name == "" { - // errors.New создает новый объект ошибки - return errors.New("empty name") - } - - if len([]rune(name)) > 50 { - return errors.New("a name cannot be more than 50 characters") - } - - return nil - } - ``` - - Тип `error` является интерфейсом. Интерфейс — это отдельный тип данных в Go, представляющий набор методов. Любая структура реализует интерфейс неявно через структурную типизацию. Структурная типизация (в динамических языках это называют *утиной типизацией*) — это связывание типа с реализацией во время компиляции без явного указания связи в коде: - - ```go - package main - - import ( - "fmt" - ) - - // объявление интерфейса - type Printer interface { - Print() - } - - // нигде не указано, что User реализует интерфейс Printer - type User struct { - email string - } - - // структура User имеет метод Print, как в интерфейсе Printer. Следовательно, во время компиляции запишется связь между User и Printer - func (u *User) Print() { - fmt.Println("My email is", u.email) - } - - // функция принимает как аргумент интерфейс Printer - func TestPrint(p Printer) { - p.Print() - } - - func main() { - // в функцию TestPrint передается структура User, и так как она реализует интерфейс Printer, все работает без ошибок - TestPrint(&User{email: "test@test.com"}) - } - ``` - - Интерфейс `error` содержит только один метод *Error*, который возвращает строковое представление ошибки: - - ```go - // The error built-in interface type is the conventional interface for - // representing an error condition, with the nil value representing no error. - type error interface { - Error() string - } - ``` - - Следовательно, легко можно создавать свои реализации ошибок: - - ```go - type TimeoutErr struct { - msg string - } - - // структура TimeoutErr реализует интерфейс error и может быть использована как обычная ошибка - func (e *TimeoutErr) Error() string { - return e.msg - } - ``` - - Следует запомнить, что если функция возвращает ошибку, то она всегда возвращается последним аргументом: - - ```go - // функция возвращает несколько аргументов, и ошибка возвращается последней - func DoHTTPCall(r Request) (Response, error) { - ... - } - ``` - - Нулевое значение для интерфейса — это пустое значение `nil`. Следовательно, когда код работает верно, возвращается `nil` вместо ошибки. - -instructions: | - - Для выполнения этого задания потребуется функция `json.Unmarshal`, которая декодирует JSON байты в структуру: - - ```go - package main - - import ( - "encoding/json" - "fmt" - ) - - type HelloWorld struct { - Hello string - } - - func main() { - hw := HelloWorld{} - - // первым аргументом передается JSON-строка в виде слайса байт. Вторым аргументом указатель на структуру, в которую нужно декодировать результат. - err := json.Unmarshal([]byte("{\"hello\":\"world\"}"), &hw) - - fmt.Printf("error: %s, struct: %+v\n", err, hw) // error: %!s(), struct: {Hello:world} - } - ``` - - В API методах часто используются запросы с телом в виде JSON. Такие тела нужно декодировать в структуры и валидировать. Хоть это и не лучшая практика делать функции, в которых происходит несколько действий, но для простоты примера реализуйте функцию `DecodeAndValidateRequest(requestBody []byte) (CreateUserRequest, error)`, которая декодирует тело запроса из JSON в структуру `CreateUserRequest` и валидирует ее. Если приходит невалидный JSON или структура заполнена неверно, функция должна вернуть ошибку. - Структура запроса: - - ```go - type CreateUserRequest struct { - Email string `json:"email"` - Password string `json:"password"` - PasswordConfirmation string `json:"password_confirmation"` - } - ``` - - Список ошибок, которые нужно возвращать из функции: - - ```go - // validation errors - var ( - errEmailRequired = errors.New("email is required") // когда поле email не заполнено - errPasswordRequired = errors.New("password is required") // когда поле password не заполнено - errPasswordConfirmationRequired = errors.New("password confirmation is required") // когда поле password_confirmation не заполнено - errPasswordDoesNotMatch = errors.New("password does not match with the confirmation") // когда поля password и password_confirmation не совпадают - ) - ``` - - Примеры работы функции `DecodeAndValidateRequest`: - - ```go - DecodeAndValidateRequest([]byte("{\"email\":\"\",\"password\":\"test\",\"password_confirmation\":\"test\"}")) // CreateUserRequest{}, "email is required" - DecodeAndValidateRequest([]byte("{\"email\":\"test\",\"password\":\"\",\"password_confirmation\":\"test\"}")) // CreateUserRequest{}, "password is required" - DecodeAndValidateRequest([]byte("{\"email\":\"test\",\"password\":\"test\",\"password_confirmation\":\"\"}")) // CreateUserRequest{}, "password confirmation is required" - DecodeAndValidateRequest([]byte("{\"email\":\"test\",\"password\":\"test\",\"password_confirmation\":\"test2\"}")) // CreateUserRequest{}, "password does not match with the confirmation" - DecodeAndValidateRequest([]byte("{\"email\":\"email@test.com\",\"password\":\"passwordtest\",\"password_confirmation\":\"passwordtest\"}")) // CreateUserRequest{Email:"email@test.com", Password:"passwordtest", PasswordConfirmation:"passwordtest"}, nil - ``` - -tips: - - | - [The Go Programming Language Specification — Errors](https://golang.org/ref/spec#Errors) diff --git a/modules/40-func/30-errors/ru/data.yml b/modules/40-func/30-errors/ru/data.yml index 2b8b4f5..0c5498b 100644 --- a/modules/40-func/30-errors/ru/data.yml +++ b/modules/40-func/30-errors/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Ошибки tips: - > diff --git a/modules/40-func/35-errors-handling/description.ru.yml b/modules/40-func/35-errors-handling/description.ru.yml deleted file mode 100644 index e077aa9..0000000 --- a/modules/40-func/35-errors-handling/description.ru.yml +++ /dev/null @@ -1,157 +0,0 @@ ---- - -name: Обработка ошибок -theory: | - - Возвращаемые ошибки принято проверять при каждом вызове: - - ```go - import "log" - - response, err := DoHTTPCall() - if err != nil { - log.Println(err) - } - - // только после проверки на ошибку можно делать что-то с объектом response - ``` - - При этом логика обработки отличается от места и типа ошибки. Ошибки можно оборачивать и прокидывать в функцию выше, логировать или делать любые фоллбек действия. - - Оборачивание ошибок — важная часть написания кода на Go. Это позволяет явно видеть трейс вызова и место возникновения ошибки. Для оборачивания используется функция `fmt.Errorf`: - - ```go - package main - - import ( - "errors" - "fmt" - ) - - // для простоты примера опускаем аргументы запроса и ответа - func DoHTTPCall() error { - err := SendTCP() - if err != nil { - // оборачивается в виде "[название метода]: %w". %w — это плейсхолдер для ошибки - return fmt.Errorf("send tcp: %w", err) - } - - return nil - } - - var errTCPConnectionIssue = errors.New("TCP connect issue") - - func SendTCP() error { - return errTCPConnectionIssue - } - - func main() { - fmt.Println(DoHTTPCall()) // send tcp: TCP connect issue - } - ``` - - В современном Go существуют функции для проверки типов конкретных ошибок. Например, ошибку из примера выше можно проверить с помощью функции `errors.Is`. В данном случае *errTCPConnectionIssue* обернута другой ошибкой, но функция `errors.Is` найдет ее при проверке: - - ```go - err := DoHTTPCall() - if err != nil { - if errors.Is(err, errTCPConnectionIssue) { - // в случае ошибки соединения ждем 1 секунду и пытаемся сделать запрос снова - time.Sleep(1 * time.Second) - return DoHTTPCall() - } - - // обработка неизвестной ошибки - log.Println("unknown error on HTTP call", err) - } - ``` - - `errors.Is` подходит для проверки статичных ошибок, хранящихся в переменных. Иногда нужно проверить не конкретную ошибку, а целый тип. Для этого используется функция `errors.As`: - - ```go - package main - - import ( - "errors" - "log" - "time" - ) - - // ошибка подключения к базе данных - type ConnectionErr struct{} - - func (e ConnectionErr) Error() string { - return "connection err" - } - - func main() { - // цикл подключения к БД. Пытаемся 3 раза, если не удалось подсоединиться с первого раза. - tries := 0 - for { - if tries > 2 { - log.Println("Can't connect to DB") - break - } - - err := connectDB() - if err != nil { - // если ошибка подключения, то ждем 1 секунду и пытаемся снова - if errors.As(err, &ConnectionErr{}) { - log.Println("Connection error. Trying to reconnect...") - time.Sleep(1 * time.Second) - tries++ - continue - } - - // в противном случае ошибка критичная, логируем и выходим из цикла - log.Println("connect DB critical error", err) - } - - break - } - } - - // для простоты функция всегда возвращает ошибку подключения - func connectDB() error { - return ConnectionErr{} - } - ``` - - Вывод программы спустя 3 секунды: - - ``` - Connection error. Trying to reconnect... - Connection error. Trying to reconnect... - Connection error. Trying to reconnect... - Can't connect to DB - ``` - -instructions: | - - Какая-то функция возвращает критичные и некритичные ошибки: - - ```go - // некритичная ошибка валидации - type nonCriticalError struct{} - - func (e nonCriticalError) Error() string { - return "validation error" - } - - // критичные ошибки - var ( - errBadConnection = errors.New("bad connection") - errBadRequest = errors.New("bad request") - ) - ``` - - Реализуйте функцию `GetErrorMsg(err error) string`, которая возвращает текст ошибки, если она критичная. В случае неизвестной ошибки возвращается строка *unknown error*: - - ```go - GetErrorMsg(errBadConnection) // "bad connection" - GetErrorMsg(errBadRequest) // "bad request" - GetErrorMsg(nonCriticalError{}) // "" - GetErrorMsg(errors.New("random error")) // "unknown error" - ``` - -tips: [] diff --git a/modules/40-func/35-errors-handling/ru/data.yml b/modules/40-func/35-errors-handling/ru/data.yml index 53672ef..f976a12 100644 --- a/modules/40-func/35-errors-handling/ru/data.yml +++ b/modules/40-func/35-errors-handling/ru/data.yml @@ -1,2 +1,3 @@ +--- name: Обработка ошибок tips: [] diff --git a/modules/40-func/40-defer/description.ru.yml b/modules/40-func/40-defer/description.ru.yml deleted file mode 100644 index ed424f1..0000000 --- a/modules/40-func/40-defer/description.ru.yml +++ /dev/null @@ -1,78 +0,0 @@ ---- - -name: Отложенные функции -theory: | - - В Go есть полезная конструкция `defer`, которая позволяет выполнять функции в фазе выхода из текущей функции. Например: - - ```go - package main - - import ( - "fmt" - ) - - func main() { - // функция выполнится в самом конце при выходе из main - defer fmt.Println("finish") - - fmt.Println("start") - } - ``` - - Вывод: - - ```go - start - finish - ``` - - Такие функции называются *отложенными*. Каждая такая функция добавляется в стек отложенных функций и будет выполнена в порядке LIFO (Last In First Out): - - ```go - package main - - import ( - "fmt" - ) - - func main() { - defer fmt.Println("3rd") - defer fmt.Println("2nd") - - fmt.Println("1st") - } - ``` - - Вывод: - - ```go - 1st - 2nd - 3rd - ``` - - Использование отложенных функций достаточно распространено. Например: - - закрытие дескриптора файла после работы - - возвращение соединения с базой данных в общий пул после чтения всех строк - - закрытие TCP соединения после полного прочтения тела ответа - -instructions: | - - Реализуйте функцию `ExecuteMergeDictsJob(job *MergeDictsJob) (*MergeDictsJob, error)`, которая выполняет джобу *MergeDictsJob* и возвращает ее. Алгоритм обработки джобы следующий: - - перебрать по порядку все словари *job.Dicts* и записать каждое ключ-значение в результирующую мапу *job.Merged* - - если в структуре *job.Dicts* меньше 2-х словарей, возвращается ошибка `errNotEnoughDicts = errors.New("at least 2 dictionaries are required")` - - если в структуре *job.Dicts* встречается словарь в виде нулевого значения `nil`, то возвращается ошибка `errNilDict = errors.New("nil dictionary")` - - независимо от успешного выполнения или ошибки в возвращаемой структуре *MergeDictsJob* поле *IsFinished* должно быть заполнено как `true` - - Пример работы: - - ```go - ExecuteMergeDictsJob(&MergeDictsJob{}) // &MergeDictsJob{IsFinished: true}, "at least 2 dictionaries are required" - ExecuteMergeDictsJob(&MergeDictsJob{Dicts: []map[string]string{{"a": "b"},nil}}) // &MergeDictsJob{IsFinished: true, Dicts: []map[string]string{{"a": "b"},nil}}, "nil dictionary" - ExecuteMergeDictsJob(&MergeDictsJob{Dicts: []map[string]string{{"a": "b"},{"b": "c"}}}) // &MergeDictsJob{IsFinished: true, Dicts: []map[string]string{{"a": "b", "b": "c"}}}, nil - ``` - -tips: - - | - [The Go Programming Language Specification — Defer statements](https://golang.org/ref/spec#Defer_statements) diff --git a/modules/40-func/40-defer/ru/data.yml b/modules/40-func/40-defer/ru/data.yml index f640590..d028022 100644 --- a/modules/40-func/40-defer/ru/data.yml +++ b/modules/40-func/40-defer/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Отложенные функции tips: - > diff --git a/modules/50-concurrency/10-concurrency-intro/description.ru.yml b/modules/50-concurrency/10-concurrency-intro/description.ru.yml deleted file mode 100644 index 7542ee9..0000000 --- a/modules/50-concurrency/10-concurrency-intro/description.ru.yml +++ /dev/null @@ -1,39 +0,0 @@ ---- - -name: Введение в конкурентность -theory: | - - Современные процессоры (CPU) имеют несколько ядер, и некоторые поддерживают гиперпоточность, поэтому они могут обрабатывать несколько инструкций одновременно. Чтобы полностью утилизировать современные CPU, нужно использовать конкурентное программирование. - - *Конкурентные вычисления* — это форма вычислений, когда несколько инструкций выполняются, пересекаясь, в течение одного временного периода. - - Например есть 2 инструкции, которые нужно выполнить: *А* и *B*. При выполнении инструкций конкурентно ядро процессора вычисляет инструкцию *A*, при ожидающей *B*. Когда первая инструкция *A* заканчивает активное вычисление, она ставится на паузу, и ядро переключается на вычисление инструкции *B*. - - Рассмотрим простой пример конкурентности в реальной жизни: HTTP запрос к стороннему сервису по сети: - - вычисление *А* делает HTTP запрос - - пока *А* ждет ответа, начинает выполняться вычисление *B* - - когда ответ пришел, *А* возвращается в работу - - Если в процессоре присутствует более одного ядра, обработка происходит *параллельно*. Возвращаясь к примеру выше, инструкции *A* и *B* будут выполняться независимо в один момент времени. - - Конкурентные вычисления могут происходить в программе, компьютере или сети. В данном курсе рассматривается только уровень программ. - - Как это все относится к Go? Разберемся в следующем уроке. - -instructions: | - - Написать конкурентный код самостоятельно в данном уроке не получится, поэтому давайте подготовимся к следующему уроку реализацией синхронного кода. - - Реализуйте функцию `MaxSum(nums1, nums2 []int) []int`, которая суммирует числа в каждом слайсе и возвращает слайс с наибольшей суммой. Если сумма одинаковая, то возвращается первый слайс. - - Пример работы: - - ```go - MaxSum([]int{1, 2, 3}, []int{10, 20, 50}) // [10 20 50] - - MaxSum([]int{3, 2, 1}, []int{1, 2, 3}) // [3 2 1] - ``` - -tips: - - | - [Concurrency vs Parallelism](https://devopedia.org/concurrency-vs-parallelism) diff --git a/modules/50-concurrency/10-concurrency-intro/ru/data.yml b/modules/50-concurrency/10-concurrency-intro/ru/data.yml index 369c560..c492435 100644 --- a/modules/50-concurrency/10-concurrency-intro/ru/data.yml +++ b/modules/50-concurrency/10-concurrency-intro/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Введение в конкурентность tips: - > diff --git a/modules/50-concurrency/20-goroutines/description.ru.yml b/modules/50-concurrency/20-goroutines/description.ru.yml deleted file mode 100644 index 2b67d70..0000000 --- a/modules/50-concurrency/20-goroutines/description.ru.yml +++ /dev/null @@ -1,98 +0,0 @@ ---- - -name: Горутины -theory: | - - Вот и подошло время познакомиться с самой сильной стороной языка Go — горутинами. Горутины — это легковесные потоки, которые реализуют конкурентное программирование в Go. Их называют *легковесными потоками*, потому что они управляются рантаймом языка, а не операционной системой. Стоимость переключения контекста и расход памяти намного ниже, чем у потоков ОС. Следовательно, для Go — не проблема поддерживать одновременно десятки тысяч горутин. - - Запустить функцию в горутине — супер легко. Для этого достаточно написать слово `go` перед вызовом функции: - - ```go - package main - - import ( - "fmt" - "time" - ) - - func main() { - // выведет сообщение в горутине - go fmt.Println("Hello concurrent world") - - // если не подождать, то программа закончится, не успев, вывести сообщение - time.Sleep(100 * time.Millisecond) - } - ``` - - При написании конкурентного кода возникают новые моменты, которые нужно учитывать: состояние гонки, блокировки, коммуникация между горутинами. Пример программы, которая работает не так, как ожидается: - - ```go - package main - - import ( - "fmt" - "time" - ) - - func main() { - for i := 0; i < 5; i++ { - go func() { - fmt.Println(i) - }() - } - - time.Sleep(100 * time.Millisecond) - } - ``` - - Сперва может показаться, что должны вывестись числа от 0 до 4, но на самом деле вывод будет следующим: - - ```go - 5 - 5 - 5 - 5 - 5 - ``` - - Все потому что *i* передается в общем скоупе, следовательно, когда горутины будут выполняться, цикл уже закончится и *i* будет равно *5*. В данном случае нужно передать копию *i*: - - ```go - package main - - import ( - "fmt" - "time" - ) - - func main() { - for i := 0; i < 5; i++ { - go func(i int) { - fmt.Println(i) - }(i) - } - - time.Sleep(100 * time.Millisecond) - } - ``` - - Вывод: - - ```go - 0 - 4 - 3 - 1 - 2 - ``` - - Также можно заметить, что числа вывелись не в порядке вызова. Горутины выполняются независимо и не гарантируют порядка. При необходимости последовательность в выполнении придется реализовывать самостоятельно. - -instructions: | - - Реализуйте функцию `MaxSum(nums1, nums2 []int) []int` из прошлого задания, используя горутины для расчета каждой суммы слайса. - Не забудьте использовать функцию `time.Sleep(100 * time.Millisecond)`, чтобы сумма успела посчитаться. В настоящих приложениях используются специальные инструменты, чтобы дожидаться исполнения асинхронного кода, но для простоты здесь будет использоваться обычный сон. - -tips: - - | - [Effective Go — Goroutines](https://golang.org/doc/effective_go#goroutines) diff --git a/modules/50-concurrency/20-goroutines/ru/data.yml b/modules/50-concurrency/20-goroutines/ru/data.yml index 12a6027..c0007d4 100644 --- a/modules/50-concurrency/20-goroutines/ru/data.yml +++ b/modules/50-concurrency/20-goroutines/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Горутины tips: - | diff --git a/modules/50-concurrency/30-channels/description.ru.yml b/modules/50-concurrency/30-channels/description.ru.yml deleted file mode 100644 index ec7f286..0000000 --- a/modules/50-concurrency/30-channels/description.ru.yml +++ /dev/null @@ -1,142 +0,0 @@ ---- - -name: Каналы -theory: | - - В Go существует постулат: "Do not communicate by sharing memory; instead, share memory by communicating" (Не общайтесь разделением памяти. Разделяйте память через общение). Для безопасной коммуникации между горутинами используется специальный тип данных: `chan` (канал). - - Как слайсы и мапы, каналы инициализируются с помощью функции `make`: - - ```go - numCh := make(chan int) - ``` - - Чтение и запись в канал происходит через конструкцию `<-`. Стрелка ставится перед, если канал читается и после, если записывается: - - ```go - numCh := make(chan int) - - numCh <- 10 // записали значение в канал - - num := <- numCh // прочитали значение из канала и записали в переменную "num" - ``` - - Чтение из канала блокирует текущую горутину, пока не вернется значение: - - ```go - package main - - import ( - "fmt" - ) - - func main() { - numCh := make(chan int) - - <-numCh // программа зависнет здесь и будет ошибка: fatal error: all goroutines are asleep - deadlock! - - fmt.Println("program has ended") // эта строка никогда не выведется - } - ``` - - Запись в канал так же блокирует текущую горутину, пока кто-то не прочтет значение. - - Каналы также можно использовать для задачи из прошлого урока: - - ```go - package main - - import ( - "fmt" - ) - - func main() { - fmt.Println(maxSum([]int{1, 2, 3}, []int{10, 20, 50})) // [10 20 50] - } - - // суммирует значения каждого слайса nums и возвращает тот, который имеет наибольшую сумму - func maxSum(nums1, nums2 []int) []int { - // канал для результата первой суммы - s1Ch := make(chan int) - go sumParallel(nums1, s1Ch) - - // канал для результата второй суммы - s2Ch := make(chan int) - go sumParallel(nums2, s2Ch) - - // присваиваем результаты в переменные. Здесь программа будет заблокирована, пока не придут результаты из обоих каналов. - s1, s2 := <-s1Ch, <-s2Ch - - if s1 > s2 { - return nums1 - } - - return nums2 - } - - func sumParallel(nums []int, resCh chan int) { - s := 0 - for _, n := range nums { - s += n - } - - // результат суммы передаем в канал - resCh <- s - } - ``` - - Иногда требуется запустить обработчика в отдельной горутине, который будет выполнять работу на протяжении всего цикла жизни программы. С помощью конструкции `for range` можно читать из канала до того момента, пока он не будет закрыт: - - ```go - package main - - import ( - "fmt" - "time" - ) - - func main() { - // создаем канал, в который будем отправлять сообщения - msgCh := make(chan string) - - // вызываем функцию асинхронно в горутине - go printer(msgCh) - - msgCh <- "hello" - msgCh <- "concurrent" - msgCh <- "world" - - // закрываем канал - close(msgCh) - - // и ждем, пока printer закончит работу - time.Sleep(100 * time.Millisecond) - } - - func printer(msgCh chan string) { - // читаем из канала, пока он открыт - for msg := range msgCh { - fmt.Println(msg) - } - - fmt.Println("printer has finished") - } - ``` - -instructions: | - - Реализуйте функцию-воркера `SumWorker(numsCh chan []int, sumCh chan int)`, которая суммирует переданные числа из канала *numsCh* и передает результат в канал *sumCh*: - - ```go - numsCh := make(chan []int) - sumCh := make(chan int) - - go SumWorker(numsCh, sumCh) - numsCh <- []int{10, 10, 10} - - res := <- sumCh // 30 - ``` - -tips: - - | - [Effective Go — Channels](https://golang.org/doc/effective_go#channels) diff --git a/modules/50-concurrency/30-channels/ru/data.yml b/modules/50-concurrency/30-channels/ru/data.yml index fa9091a..46fc6dd 100644 --- a/modules/50-concurrency/30-channels/ru/data.yml +++ b/modules/50-concurrency/30-channels/ru/data.yml @@ -1,3 +1,4 @@ +--- name: Каналы tips: - |