Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Enums in Go

Enums in Go

Перечисляемые типы (Enum) в Go

Avatar for Andrew Chernov

Andrew Chernov

May 25, 2017

Other Decks in Programming

Transcript

  1. О компании Lazada • Основана в 2012 году • Крупнейшая

    e-commerce компания Юго- Восточной Азии • Работает в 6 странах Юго-Восточной Азии с суммарным населением 650M человек • Более 40 миллионов продуктов • 1.2M товаров за время последней распродажи (3 дня) • С 2016 года является частью Alibaba Group
  2. Команда Lazada • 4 TechHub: Вьетнам, Сингапур, Бангкок, Москва •

    Более 650 инженеров • Разработка платформы только на Go • 90% платформы разрабатывается в Москве • Более 100 Golang программистов в московской команде
  3. Введение Как известно, в Go нет перечисляемых типов в том

    виде, в котором они реализованы в таких языках, как Java или С++. В данном докладе будет описано, какие проблемы решаются с помощью перечисляемых типов, как перечисляемые типы реализованы в других языках и какие подходы и инструменты можно использовать в Go, чтобы получить все преимущества от использования Enum.
  4. Определение Перечисляемый тип (enumerated type) — тип данных, чьё множество

    значений представляет собой ограниченный список идентификаторов. - https://en.wikipedia.org/wiki/Enumerated_type Пример: месяцы в году, дни недели, планеты Солнечной системы.
  5. Список констант Сам по себе перечисляемый тип может быть описан

    с помощью набора числовых или строковых констант const ( Mercury = iota + 1 Venus Earth Mars )
  6. Это означает, что следующий код будет скомпилирован без каких-либо предупреждений

    компилятора: В данном случае переменной planet будет присвоено некорректное значение 42 nibiruPlanet := 42 var planet int if isFromPlanet("Mars”) { planet = Mars } else { planet = nibiruPlanet } Константы не типобезопасны
  7. Типизированное перечисление type Planet int const ( Mercury Planet =

    iota + 1 Venus Earth Mars ) nibiruPlanet := 42 var planet Planet if fromPlanet("Mars”) { planet = Mars } else { planet = nibiruPlanet }
  8. Теперь код из предыдущего примера не скомпилируется: Этот подход делает

    перечисление более типобезопасным, но не ограничивает список этих значений. Мы все еще можем присвоить нашему типу некорректное значение: cannot use nibiruPlanet (type int) as type Planet in assignment nibiruPlanet := 42 var planet Planet planet = Mars // ok planet = Planet(nibiruPlanet) // преобразование типа planet = 42 // числовой литерал
  9. Вероятность ошибки возрастает, если перечисляемый тип будет полем структуры: В

    данном случае полю структуры HomePlanet будет присвоено некорректное значение по умолчанию - 0. type Spaceship struct { Name string Captain string HomePlanet Planet } Enterprise := Spaceship{ Name: "USS Enterprise", Captain: "James T. Kirk", HomePlanet: Earth, } UFO := Spaceship{ Name: "Unknown", }
  10. JSON Enterprise := Spaceship{ Name: "USS Enterprise", Captain: "James T.

    Kirk", HomePlanet: Earth, } data, _ := json.Marshal(&Enterprise) fmt.Println(string(data)) { "Name": "USS Enterprise", "Captain": "James T. Kirk", "HomePlanet": 3 }
  11. Также для получения полного списка значений перечисляемого типа нам придется

    написать много boilerplate кода: type Planet int func (p Planet) MarshalJSON() ([]byte, error) { switch p { case Mercury: return []byte("\"Mercury\""), nil case Venus: return []byte("\"Venus\""), nil case Earth: return []byte("\"Earth\""), nil case Mars: return []byte("\"Mars\""), nil } return []byte("\"Unknown\""), nil } { "Name": "USS Enterprise", "Captain": "James T. Kirk", "HomePlanet": "Earth" }
  12. Кроме того, часто бывает так, что перечисляемые типы участвуют в

    логике ветвления, например, в switch операторе: При добавлении нового значения в перечисляемы тип можно легко забыть о том, что нового значение нужно обработать в разных местах кода. switch p { case Mercury: return "Mercury" case Venus: return "Venus" case Earth: return "Earth" case Mars: return "Mars" }
  13. В С перечисляемые типы не являются типобезопасными: enum week {

    sunday, monday, tuesday, wednesday, thursday, friday, saturday }; enum week today; today = wednesday; printf("Day %d\n",today+1); int a = 10; today = a; printf("Day %d\n",today+1); today = 15; printf("Day %d\n",today+1);
  14. При этом при обработке не всех значений в switch операторе

    компилятор показывает warning: switch (today) { case sunday: printf("sunday\n"); break; case monday: printf("monday\n"); break; case tuesday: printf("tuesday\n"); break; } enum.c:19:13: warning: 4 enumeration values not handled in switch: 'wednesday', 'thursday', 'friday'... [-Wswitch] switch (today) { ^ 1 warning generated.
  15. В Java перечисляемые типы являются типобезопасными: enum Day { SUNDAY,

    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY} Day today = Day.SUNDAY; today = 10; System.out.printf("Day %s", today); Enum.java:12: error: incompatible types: int cannot be converted to Day today = 10; ^ 1 error
  16. При обработке не всех значений в switch операторе компилятор выдает

    ошибку: Day today = Day.SUNDAY; switch (today) { case Day.MONDAY: System.out.println("Mondays are bad."); break; case Day.FRIDAY: System.out.println("Fridays are better."); break; case Day.SATURDAY: case Day.SUNDAY: System.out.println("Weekends are best."); break; } ERROR at line 16, col 1: switch must be exhaustive, consider adding a default clause } ^
  17. enum Planet { case mercury, venus, earth, mars, jupiter, saturn,

    uranus, neptune } В Swift можно также описывать перечисляемые типы с "сырым" (raw) значением: enum Planet: Int { case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune }
  18. В Swift перечисляемые типы являются типобезопасными: enum Planet: Int {

    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune } var planet = Planet.earth planet = 42 ERROR at line 7, col 10: cannot assign value of type 'Int' to type 'Planet' planet = 42 ^~ Planet(rawValue: )
  19. В Swift на уровне языка есть возможность безопасно преобразовать "raw"

    значение в перечисляемый тип: enum Planet: Int { case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune } let positionToFind = 42 if let somePlanet = Planet(rawValue: positionToFind) { switch somePlanet { case .earth: print("Mostly harmless") default: print("Not a safe place for humans") } } else { print("There isn't a planet at position \(positionToFind)") }
  20. При обработке не всех значений в switch операторе компилятор выдает

    ошибку: var planet = Planet.earth switch planet { case .mercury: print("Lots of planets have a north") case .venus: print("Watch out for penguins") case .earth: print("Where the sun rises") case .mars: print("Where the skies are blue") } ERROR at line 16, col 1: switch must be exhaustive, consider adding a default clause } ^
  21. В Rust перечисляемые типы являются типобезопасными: enum Person { Engineer,

    Scientist } fn main() { let mut person = Person::Engineer; let _ = Person::Scientist; // используем пустой // идентификатор чтобы // скомпилировалось J person = 42; match person { Person::Engineer => println!("Is an engineer!"), Person::Scientist => println!("Is a scientist!") } } error[E0308]: mismatched types --> <anon>:9:9 | 9 | person = 14; | ^^ expected enum `Person`, found integral variable | = note: expected type `Person` found type `{integer}`
  22. При обработке не всех значений в match операторе компилятор генерирует

    ошибку: enum Planet { Earth, Mars, Venus, Mercury } fn main() { let planet = Planet::Earth; let _ = Planet::Mars; let _ = Planet::Venus; let _ = Planet::Mercury; match planet { Planet::Earth => println!("Is Earth!"), Planet::Mars => println!("Is Mars!") } } error[E0004]: non-exhaustive patterns: `Venus` and `Mercury` not covered --> <anon>:12:11 | 12 | match planet { | ^ patterns `Venus` and `Mercury` not covered
  23. Что же мы хотим иметь в Go для работы с

    перечисляемыми типами? • Типобезопасность • Защиту от присвоения перечисляемому типу некорректного значения • Проверки на обработку всех значений перечисляемого типа в switch операторах • Автоматическое получение строкового представления • Интеграция с json/yaml/csv ...
  24. Enumer работает с типизированным перечисляемым типом: type Pill int const

    ( Placebo Pill = iota Aspirin Ibuprofen Paracetamol Acetaminophen = Paracetamol )
  25. Утилита Enumer генерирует следующие методы: $ enumer -type=Pill -json -transform=snake

    func (i Pill) String() string { //... } func PillString(s string) (Pill, error) { //... } func (i Pill) MarshalJSON() ([]byte, error) { //... } func (i *Pill) UnmarshalJSON(data []byte) error { //... }
  26. Enum linter https://github.com/THE108/enumlinter Утилита enumlinter находит следующие проблемы: • Присвоение

    перечисляемому типу некорректного значения • Обработка не всех значений перечисляемого типа в switch операторах
  27. type Enum int const ( Apple Enum = iota Pear

    Banana ) ... var f Enum f = 42 // line 1 f = Enum(42) // line 2 $ enumlinter -type Enum ./enum.go:1:6: Enum type constant must be used instead of a basic literal “42” ./enum.go:2:6: converting function must be used instead of type casting “Enum(42)”
  28. var p Enum switch p { case Apple : return

    "Apple" case Pear : return "Pear” } $ enumlinter -type Enum ./testdata/enum_assignment.go:45:6: non-exhaustive check for Enum type – Banana missed
  29. Итого Используя описанный выше подход и утилиты, можно добиться такой

    же безопасности и удобства использования перечисляемых типов в Go как и в других популярных ЯП.
  30. Just do it! • type Enum int • ./enumer –type

    Enum • ./enumlinter –type Enum • …. • PROFIT!!!