Pull to refresh
89.6
Skillfactory
Онлайн-школа IT-профессий

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

Reading time5 min
Views7.3K
Original author: Juan M. Tirado

Когда человек начинает писать на непривычном языке программирования, он всегда обращает внимание на его особенности. Новичку бывает сложно понять причины такого дизайна языка. Своим студентам мы даем необходимый контекст, и постепенно они учатся программировать, учитывая и принимая то, что раньше выводило их из равновесия. Автор статьи разбирает особенности 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:



Tags:
Hubs:
-1
Comments22

Articles

Information

Website
www.skillfactory.ru
Registered
Founded
Employees
501–1,000 employees
Location
Россия
Representative
Skillfactory School