Skip to content

Latest commit

 

History

History
107 lines (88 loc) · 12.5 KB

11. Порожній ідентифікатор.md

File metadata and controls

107 lines (88 loc) · 12.5 KB

Зміст

Порожній ідентифікатор

Ми вже кілька разів згадували про порожній ідентифікатор у контексті циклів for із range та мап. Порожній ідентифікатор можна призначити або оголосити з будь-яким значенням будь-якого типу, при цьому значення буде безболісно відкинуто. Це трохи схоже на запис до файлу Unix /dev/null: він являє собою значення, доступне лише для запису, яке можна використовувати як заповнювач там, де потрібна змінна, але фактичне її значення є нерелевантним. Порожній ідентифікатор має багато інших застосувань, окрім тих, що ми вже розглянули.

Порожній ідентифікатор у множинному присвоюванні

Використання порожнього ідентифікатора в циклі for із range є окремим випадком загальної ситуації: множинного присвоювання.

Якщо присвоювання вимагає декількох значень у лівій частині, але одне зі значень не буде використовуватися програмою, порожній ідентифікатор у лівій частині присвоювання дозволяє уникнути необхідності створювати фіктивну змінну і дає зрозуміти, що це значення буде відкинуто. Наприклад, при виклику функції, яка повертає значення і помилку, але важлива лише помилка, використовуйте порожній ідентифікатор, щоб відкинути несуттєве значення.

if _, err := os.Stat(path); os.IsNotExist(err) {
    fmt.Printf("%s does not exist\n", path)
}

Іноді ви можете побачити код, який відкидає значення помилки, щоб ігнорувати її; це жахлива практика. Завжди перевіряйте повідомлення про помилки; вони надаються не просто так.

// Погано! Цей код спровокує креш, якщо шляху не існує.
fi, _ := os.Stat(path)
if fi.IsDir() {
    fmt.Printf("%s is a directory\n", path)
}

Невикористовувані імпортування й значення

Помилкою є імпорт пакета або оголошення змінної без її використання. Невикористаний імпорт роздуває програму і сповільнює її компіляцію, тоді як ініціалізована, але невикористана змінна — це щонайменше даремні обчислення і, можливо, свідчення серйознішої помилки. Однак, коли програма перебуває на стадії активної розробки, часто виникають ситуації невикористання імпортованих даних та змінних, і видаляти їх, щоб продовжити компіляцію, буває набридливо, оскільки вони можуть знадобитися згодом. Порожній ідентифікатор забезпечує обхідний шлях.

Ця напівнаписана програма має два невикористані імпорти (fmt і io) і невикористану змінну (fd), тому вона не скомпілюється, але було б непогано перевірити, чи правильним є код на цей час.

package main

import (
    "fmt"
    "io"
    "log"
    "os"
)

func main() {
    fd, err := os.Open("test.go")
    if err != nil {
        log.Fatal(err)
    }
    // TODO: використати fd.
}

Щоб заглушити скарги на невикористаний імпорт, використовуйте порожній ідентифікатор як посилання на імпортований пакет. Аналогічно, присвоєння невикористаної змінної fd порожньому ідентифікатору приглушить відповідну помилку. Цю версію програми буде скомпільовано.

package main

import (
    "fmt"
    "io"
    "log"
    "os"
)

var _ = fmt.Printf // Для дебагу; видалити після завершення.
var _ io.Reader    // Для дебагу; видалити після завершення.

func main() {
    fd, err := os.Open("test.go")
    if err != nil {
        log.Fatal(err)
    }
    // TODO: використати fd.
    _ = fd
}

За домовленістю, глобальні декларації про ігнорування помилок при імпорті повинні з'являтися одразу після імпорту й коментуватися, щоб їх було легко знайти, а також як нагадування про необхідність виправлення помилок пізніше.

Імпортування для сторонніх ефектів

Невикористаний імпорт, такий як fmt або io у попередньому прикладі, з часом слід використати або вилучити: порожні призначення ідентифікують код як незавершений. Але іноді корисно імпортувати пакет лише для його побічних ефектів, без будь-якого явного використання. Наприклад, під час функції init пакет net/http/prof реєструє HTTP-обробники, які надають налагоджувальну інформацію. Він має експортований API, але більшості клієнтів потрібна лише реєстрація обробників і доступ до даних через вебсторінку. Щоб імпортувати пакет лише для його побічних ефектів, перейменуйте посилання на пакет на порожній ідентифікатор:

import _ "net/http/pprof"

Ця форма імпорту дає зрозуміти, що пакет імпортується заради його побічних ефектів, оскільки немає іншого можливого використання пакета: у цьому файлі він не має назви. (Якби він мав, а ми не використали її, компілятор відхилив би програму).

Перевірки інтерфейсів

Як ми вже звернули увагу в обговоренні інтерфейсів, тип не повинен явно оголошувати, що він реалізує інтерфейс. Натомість тип реалізує інтерфейс, просто реалізуючи методи інтерфейсу. На практиці більшість перетворень інтерфейсів є статичними й тому перевіряються під час компіляції. Наприклад, передача *os.File у функцію, яка очікує io.Reader, не скомпілюється, якщо *os.File не реалізує інтерфейс io.Reader.

Деякі перевірки інтерфейсів все ж таки відбуваються під час виконання. Одна з них знаходиться у пакеті encoding/json, який визначає інтерфейс Marshaler. Коли кодувальник JSON отримує значення, яке реалізує цей інтерфейс, він викликає метод маршалування значення, щоб перетворити його в JSON, замість того, щоб виконувати стандартне перетворення. Кодувальник перевіряє цю властивість під час виконання за допомогою твердження типу:

m, ok := val.(json.Marshaler)

Якщо потрібно лише запитати, чи тип реалізує інтерфейс, без використання самого інтерфейсу, можливо, як частина перевірки помилок, використовуйте порожній ідентифікатор, щоб ігнорувати значення, стверджене типом:

if _, ok := val.(json.Marshaler); ok {
    fmt.Printf("value %v of type %T implements json.Marshaler\n", val, val)
}

Така ситуація виникає, коли необхідно гарантувати в пакеті, що реалізує тип, що він дійсно задовольняє інтерфейсу. Якщо тип — наприклад, json.RawMessage, потребує спеціального представлення JSON, він повинен реалізувати json.Marshaler. Проте, не існує статичних перетворень, які б змусили компілятор перевіряти це автоматично. Якщо тип ненавмисно не задовольняє інтерфейсу, кодувальник JSON все одно працюватиме, але не використовуватиме користувацьку реалізацію. Щоб гарантувати коректність реалізації, у пакеті можна використати глобальне оголошення з порожнім ідентифікатором:

var _ json.Marshaler = (*RawMessage)(nil)

У цьому оголошенні присвоєння, що включає перетворення *RawMessage у Marshaler, вимагає, щоб *RawMessage реалізовував Marshaler, і ця властивість буде перевірена під час компіляції. Якщо інтерфейс json.Marshaler зміниться, цей пакунок більше не компілюватиметься, і ми отримаємо повідомлення про необхідність його оновлення.

Поява порожнього ідентифікатора у цій конструкції вказує на те, що оголошення існує лише для перевірки типу, а не для створення змінної. Однак не варто робити це для кожного типу, який задовольняє інтерфейсу. За домовленістю, такі оголошення використовуються лише тоді, коли в коді немає статичних перетворень, що є рідкісною подією.