diff --git a/commentary/commentary.md b/commentary/commentary.md new file mode 100644 index 0000000..c79cfcf --- /dev/null +++ b/commentary/commentary.md @@ -0,0 +1,6 @@ +# Комментирование + +В Go предусмотрены блочные комментарии `/* */` в стиле `C` и строчные комментарии `//` в стиле `C++`. Строчные комментарии являются нормой; блочные комментарии появляются в основном как комментарии к пакетам, но они полезны внутри выражения или для отключения больших участков кода. + +Комментарии, появляющиеся перед декларациями верхнего уровня, без промежуточных строк, считаются документированием самой декларации. Эти «doc-комментарии» являются основной документацией для данного пакета или команды Go. Подробнее о комментариях см. в разделе [Комментарии к документам Go](https://go.dev/doc/comment). + diff --git a/formatting/formatting.md b/formatting/formatting.md new file mode 100644 index 0000000..4ebd37d --- /dev/null +++ b/formatting/formatting.md @@ -0,0 +1,75 @@ +# Форматирование + +Вопросы форматирования - самые спорные, но **наименее значимые**. Люди могут приспособиться к разным стилям форматирования, но лучше, если им не придется этого делать, и меньше времени будет уделено теме, если все будут придерживаться одного стиля. Проблема в том, как подойти к этой утопии без длинного предписывающего руководства по стилю. + +В Go мы используем необычный подход и позволяем машине позаботиться о большинстве вопросов форматирования. Программа `gofmt` (также доступная как `go fmt`, которая работает на уровне пакетов, а не исходных файлов) читает программу на Go и выдает исходный текст в стандартном стиле с отступами и вертикальным выравниванием, сохраняя и при необходимости переформатируя комментарии. Если вы хотите узнать, как поступить в новой ситуации с версткой, запустите `gofmt`; если ответ покажется вам неправильным, перестройте свою программу (или напишите об ошибке в `gofmt`), а не обходите ее. + +Например, не нужно тратить время на выстраивание комментариев к полям структуры. `Gofmt` сделает это за вас. Учитывая тэту структуру + +```go +type T struct { + name string // имя объекта + value int // его значение +} +``` + +gofmt выстроит столбцы в ряд: + +```go +type T struct { + name string // имя объекта + value int // его значение +} +``` + +Весь код Go в стандартных пакетах был отформатирован с помощью gofmt. + +Некоторые детали форматирования остались. **Очень коротко:** + +#### Отступы + +Мы используем табуляции для отступов, и `gofmt` выдает их по умолчанию. Используйте пробелы только в случае необходимости. + +#### Длина строки + +В Go нет ограничений на длину строки. Не бойтесь переполнить перфокарту. Если строка кажется слишком длинной, оберните ее и сделайте отступ с помощью дополнительной табуляции. + +Пример: + +```go +package main + +import "fmt" + +func main() { + // Длинная строка без переноса + fmt.Println("This is an example of a very long line of code in Go that exceeds the usual length limit and should be wrapped according to best practices for readability and maintainability.") +} + +``` + +```go +package main + +import "fmt" + +func main() { + // Длинная строка с множественными переносами + fmt.Println( + "This is an example of a very long line of code in Go that exceeds " + + "the usual length limit and should be wrapped "+ + "according to best practices for readability " + + "and maintainability.", + ) +} + + +``` + +#### Круглые скобки + +В Go требуется меньше круглых скобок, чем в C и Java: управляющие структуры (if, for, switch) не содержат круглых скобок в своем синтаксисе. Кроме того, иерархия старшинства операторов короче и понятнее, так что + + x<<8 + y<<16 + +означает то, что подразумевает интервал, в отличие от других языков. \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..51e1d3f --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/0x0FACED/effective-go-ru + +go 1.23.0 diff --git a/names/names.md b/names/names.md new file mode 100644 index 0000000..92c8ce8 --- /dev/null +++ b/names/names.md @@ -0,0 +1,125 @@ +# Имена + +Имена важны в Go, как и в любом другом языке. Они даже имеют семантический эффект: **видимость имени вне пакета определяется тем, является ли его первый символ верхним регистром**. Поэтому стоит уделить немного времени обсуждению соглашений об именовании в программах на Go. + +### Имена пакетов + +Когда пакет импортируется, его имя становится указателем доступа к его содержимому. После импорта пакета: + +```go +import "strings" +``` + +Мы можем использовать его содержимое: + +```go +package main + +import "strings" + +func main() { + str := "This is the new string!" + words := strings.SplitN(str, " ", 3) + // words = ["This","is","the new string!"] +} +bytes.Clone() + +``` + +Мы обращаемся к **экспортируемым** функциям/переменным/структурам через имя пакета. Это удобно, так как все, кто будут использовать этот пакет, будут использовать одинаковое обращение к его содержимому, что подразумевает, что имя пакета должно быть хорошим: коротким, лаконичным, вызывающим. По соглашению, пакетам присваиваются имена в нижнем регистре, состоящие из одного слова; подчеркивания или смешанные прописные буквы не нужны. Отдавайте предпочтение краткости, поскольку все, кто будет использовать ваш пакет, будут набирать это имя. И не беспокойтесь о коллизиях априори. Имя пакета - это только имя по умолчанию для импорта; оно не обязательно должно быть уникальным во всем исходном коде, и в редких случаях коллизии импортирующий пакет может выбрать другое имя для локального использования. + +Например: + +```go +import ( + "strings" + "github.com/exampleuser/examplelibrary/strings" + // Будет конфликт, так как оба пакета имеют одинаковые имена указателей доступа. + // strings и strings +) +``` + +Сделаем так: + +```go +import ( + "strings" + strings2 "github.com/exampleuser/examplelibrary/strings" + // Конфликта не будет, как ты локально дали другое название пакету. +) +``` + +В любом случае, путаница возникает редко, поскольку имя файла в импорте определяет, какой именно пакет используется. + +Другое соглашение заключается в том, что имя пакета является базовым именем его исходного каталога; пакет в `src/encoding/base64` импортируется как `"encoding/base64"`, но имеет имя `base64`, а не `encoding_base64` и не `encodingBase64`. + +Импортер пакета будет использовать это имя для ссылки на его содержимое, поэтому экспортируемые имена в пакете могут использовать этот факт, чтобы избежать повторений. (Не используйте нотацию `import .`, которая может упростить тесты, которые должны выполняться вне проверяемого пакета, но в остальном ее следует избегать). Например, `buffered reader type` в пакете `bufio` называется `Reader`, а не `BufReader`, потому что пользователи видят его как `bufio.Reader`, **что является ясным и кратким именем**. Более того, поскольку к импортируемым сущностям всегда обращаются по имени их пакета, `bufio.Reader` не конфликтует с `io.Reader`. Аналогично, функция создания новых экземпляров `ring.Ring` - а это определение конструктора в Go - обычно называется `NewRing`, но поскольку `Ring` - единственный тип, экспортируемый пакетом, и поскольку пакет называется `ring`, она называется просто `New`, что клиенты пакета видят как `ring.New`. Используйте структуру пакета, чтобы помочь вам выбрать хорошие имена. + +Например, у нас есть пакет `internal/server`, где лежит структура сервера и есть функция, которая создает объект этого сервера. так как мы будем обращаться к содержимому пакета через `server`, во всех экспортируемых именах не стоит ипсользовать `server`. + +```go +package server + +type server struct { + // ... +} + +func New(...) *server { + return &server{ + ... + } +} + +// ... +``` + +```go +package client + +import ".../internal/server" + +func main() { + s := server.New(...) + // ... +} +``` + +#### *Что здесь у нас?* + +У нас есть пакет со структурой сервера и функция, которая инициализирует объект сервера и возвращает указатель на него. **Структура НЕэкспортируемая, а функция экспортируемая**. В другом месте мы импортируем пакет сервера и хотим создать сервер + получить на него указатель. Мы вызываем `server.New()`, **что уже явно говорит нам, что создается именно сервер**. Не надо делать никаких `NewServer()` и других. + +Конечно, если будет еще другая структура в этом пакете и у нее будет функция `New()`, то тогда придется выбрать другое название. **А вообще надо просто создать отдельный пакет для этой структуры и всего, что с ней связано.** + +### Геттеры (Getters) + +Go не предоставляет автоматической поддержки геттеров и сеттеров. Нет ничего плохого в том, чтобы предоставлять геттеры и сеттеры самостоятельно, и часто это бывает уместно, **но нет ни идиоматизма, ни необходимости добавлять Get в имя геттера**. Если у вас есть поле с именем `owner` (**нижний регистр, неэкспортированное**), **метод геттера должен называться `Owner`** (**верхний регистр, экспортированное**), **а не** `GetOwner`. Использование имен в верхнем регистре при экспорте дает крючок для различения поля и метода. Функция сеттера, если она необходима, скорее всего, будет называться `SetOwner`. Оба имени хорошо читаются на практике: +```go +owner := obj.Owner() +if owner != user { + obj.SetOwner(user) +} +``` + +### Имена интерфейсов + +По соглашению, интерфейсы с одним методом называются по имени метода с добавлением суффикса `-er` или аналогичного изменения для образования существительного-агента: `Reader`, `Writer`, `Formatter`, `CloseNotifier` и т.д. + +Существует множество таких имен, и важно уважать их и функции, которые они описывают. `Read`, `Write`, `Close`, `Flush`, `String` и так далее **имеют канонические сигнатуры и значения**. Чтобы избежать путаницы, не давайте вашему методу одно из этих имен, если у него нет такой же сигнатуры и значения. Напротив, если ваш тип реализует метод с тем же значением, что и метод в известном типе, дайте ему такое же имя и сигнатуру; назовите ваш метод преобразования строки `String`, а не `ToString`. + +#### *Что за канонические сигнатуры и значения?* + +В Go существуют **стандартные (канонические) имена и сигнатуры методов для интерфейсов**. Например, методы `Read`, `Write`, `Close`, и `String` имеют общепринятые значения и способы использования в языке. + +Вот что это означает: + +1. *Стандартные имена и сигнатуры*. Методы с определенными именами имеют ожидаемые сигнатуры (определение параметров и возвращаемых значений). Например, метод `Read` в стандартной библиотеке имеет сигнатуру `Read(p []byte) (n int, err error)`, **что означает, что он читает данные в срез байтов и возвращает количество прочитанных байтов и ошибку**. + +2. *Согласованность и предсказуемость*. Когда вы используете стандартные имена и сигнатуры, это делает ваш код предсказуемым и совместимым с другими пакетами и библиотеками, которые следуют тем же стандартам. Это помогает другим разработчикам быстрее понять ваш код и использовать его без необходимости разбираться в нестандартных соглашениях. + +Не используйте стандартные имена без соответствия сигнатурам: не называйте ваш метод, например, `Read`, **если он не реализует ту же функциональность или сигнатуру, что и стандартный метод `Read`**. Если вы используете стандартное имя, оно должно точно соответствовать общепринятым стандартам, чтобы избежать путаницы. Например, метод `Read` должен принимать **срез байтов** и **возвращать количество прочитанных байтов и ошибку**, как это предусмотрено стандартом. + +Соблюдайте соглашения для методов с известными именами: если ваш тип реализует метод, аналогичный методу в известных типах (например, `String` для метода, возвращающего строку), используйте те же имена и сигнатуры. Это делает ваш код согласованным с другими библиотеками и типами, что упрощает его использование и понимание. Например, метод, **который возвращает строковое представление вашего типа, должен называться `String`, а не `ToString`.** + +### MixedCaps + +Наконец, в Go принято использовать **MixedCaps** или **mixedCaps**, **а не подчеркивание для записи многословных имен**. \ No newline at end of file