7 странных особенностей Go

—

от автора


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


Сразу скажу, что эта статья: мое личное, полностью субъективное мнение. Список ниже — только небольшая выдержка без каких-либо критериев выбора. Для ясности расскажу о себе: у меня около 20 лет опыта работы, я работал с C, C++, Java, Scala, Python, R (если смотреть на R как на язык).
Я нахожу Go легким в изучении. Наверное, благодаря четко определенному замыслу, который устраняет особенности, подразумевающие сложный синтаксис. Так или иначе, я начинаю список.

1. Нежелательное импортирование и лишние переменные

Go заставляет придерживаться минимализма. Это означает, что бесполезное импортирование и лишние переменные вызовут ошибку компиляции. Например:

import (     "fmt"     "os" //not used )  func main() {     fmt.Println("Hola") } 

Компилятор возвращает:

imported and not used: "os" 

2. Итерация по коллекциям

Функция range, используемая при итерации по коллекции, возвращает два значения. Первое значение — это позиция элемента в коллекции. Второе значение — это значение самого элемента.

x := [4]string{"one","two","three","four"} for i, entry := range(x) {    fmt.Printf("Element at position %d is %s\n", i, entry) } 

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

x := [4]string{"one","two","three","four"} for i, entry := range(x) {   fmt.Printf("Element %s\n", entry) } 

И такой код вызовет ошибку компиляции:

i declared but not used 

Или даже хуже, вы пропустите i. Вот так:

x := [4]string{"one","two","three","four"} for entry := range(x) {    fmt.Printf("Element %s\n", entry) } 

Это может запутать. В переменной возвращается позиция элемента, но можно ожидать его значение.

Element %!s(int=0) Element %!s(int=1) Element %!s(int=2) Element %!s(int=3) 

Как решить проблему? Нужно просто обозначить неиспользуемую переменную i вот так:

x := [4]string{"one","two","three","four"}     for _, entry := range(x) {        fmt.Printf("Element %s\n", entry)     } 

3. Видимость атрибутов

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

type Message struct {  Text string // This is public  text string // This is private } 

4. Что с перегрузкой методов?

Никакой перегрузки методов нет. Если вы пришли из мира Java, скорее всего вы применяли перегрузку методов. В Golang перегрузки методов нет.

5. А наследование?

Наследования тоже нет. Эту особенность можно обойти, как описано здесь. Но я не думаю, что это действительно наследование.

6. Интерфейсы в Go

В отличие от перегрузки методов и наследования, интерфейсы в Go есть. Вы можете определить их как набор из сигнатур методов. Но они странные в сравнении с интерфейсами в других языках. Почему? Потому что вы не указываете программно, что структура реализует интерфейс. Структура автоматически удовлетворяет интерфейсу, когда реализует перечисленные в интерфейсе методы. Это проще понять на примере:

package main import (     "fmt" )  type Speaker interface {     SayYourName() string     SayHello(b Speaker) string }  type HappySpeaker struct {} func(hs HappySpeaker) SayYourName() string {     return "Happy" }  func(hs HappySpeaker) SayHello(b Speaker) string {     return fmt.Sprintf("Hello %s!",b.SayYourName()) }  type AngrySpeaker struct {} func(as AngrySpeaker) SayYourName() string {     return "Angry" }  func(as AngrySpeaker) SayHello(b Speaker) string {     return fmt.Sprintf("I'm not going to say hello to %s!",b.SayYourName()) }  func main() {     // We have two different structs     happy := HappySpeaker{}     angry := AngrySpeaker{}     // they can say their names     fmt.Println(happy.SayYourName())     fmt.Println(angry.SayYourName())      // But they are also speakers     fmt.Println(happy.SayHello(angry))     fmt.Println(angry.SayHello(happy))      // This is also valid     var mrSpeaker Speaker = happy     fmt.Println(mrSpeaker.SayHello(angry)) } 

Вполне понятно, что такое поведение языка влияет на код. Интерфейсы в Go — тема для подробной дискуссии. Вы найдете множество обсуждений достоинств и недостатков этой особенности языка.

7. Конструкторы

В Go нет конструкторов, подобных тем, которые вы найдете в объектно-ориентированном языке. Определение структуры на Go очень похоже на определение структуры в языке C. Но есть одна потенциальная проблема: вы можете пропустить инициализацию атрибутов. В коде ниже у halfMessage1 и halfMessage2 пустые атрибуты.

import (     "fmt" )  type Message struct {     MsgA string     MsgB string }  func(m Message) SayIt() {   fmt.Printf("[%s] - [%s]\n",m.MsgA, m.MsgB) }  func main() {     fullMessage1 := Message{"hello","bye"}     fullMessage2 := Message{MsgA: "hello", MsgB: "bye"}     halfMessage1 := Message{"hello",""}     halfMessage2 := Message{MsgA: "hello"}     emptyMessage := Message{}     fullMessage1.SayIt()     fullMessage2.SayIt()     halfMessage1.SayIt()     halfMessage2.SayIt()         emptyMessage.SayIt()         }

Код выше выведет:

[hello] - [bye] [hello] - [bye] [hello] - [] [hello] - [] [] - []  

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

package main  import (     "fmt" )  type Message struct {     MsgA string     MsgB string }  func(m Message) SayIt() {   fmt.Printf("[%s] - [%s]\n",m.MsgA, m.MsgB) }  func NewMessage(msgA string, msgB string) *Message{   if len(msgA) * len(msgB) == 0 {      return nil   }     return &Message{MsgA: msgA, MsgB: msgB} }  func main() {    // A correct message    msg1 := NewMessage("hello","bye")        if msg1 != nil {       msg1.SayIt()    } else {       fmt.Println("There was an error")    }    // An incorrect message    msg2 := NewMessage("","")    if msg2 != nil {       msg2.SayIt()    } else {       fmt.Println("There was an error")    } } 

Заключение

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

image


Получить востребованную профессию с нуля или Level Up по навыкам и зарплате, можно пройдя онлайн-курсы SkillFactory:

ссылка на оригинал статьи https://habr.com/ru/company/skillfactory/blog/522222/


Комментарии

Добавить комментарий

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