Когда человек начинает писать на непривычном языке программирования, он всегда обращает внимание на его особенности. Новичку бывает сложно понять причины такого дизайна языка. Своим студентам мы даем необходимый контекст, и постепенно они учатся программировать, учитывая и принимая то, что раньше выводило их из равновесия. Автор статьи разбирает особенности 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?
Получить востребованную профессию с нуля или Level Up по навыкам и зарплате, можно пройдя онлайн-курсы SkillFactory:
- Курс «Backend-разработчик на Go»
- Курс «Python для веб-разработки»
- Профессия Веб-разработчик
- Курс по JavaScript
Eще курсы
- Обучение профессии Data Science с нуля
- Онлайн-буткемп по Data Science
- Онлайн-буткемп по Data Analytics
- Профессия аналитика с любым стартовым уровнем
- Курс по Machine Learning
- Курс «Математика и Machine Learning для Data Science»
- Продвинутый курс «Machine Learning Pro + Deep Learning»
- Курс по аналитике данных
- Курс по DevOps
- Профессия iOS-разработчик с нуля
- Профессия Android-разработчик с нуля
- Профессия Java-разработчик с нуля
- Профессия UX-дизайнер с нуля
- Профессия Web-дизайнер
dark_ruby
Зачем ожидать наследования, если авторы языка явно декларируют что хотели его избежать.
AndreySu
Как для чего? Только для рекламы онлайн-курсов SkillFactory.
kvaps
Ну стоит признать что описанный метод демонстрирует не наследование а самую что ни на есть композицию.