7 дивних особливостей Go

Alex Alex 19 жовтня
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?

Джерело: 7 Golang Features You Might Find Weird

Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Війти / Зареєструватися