golang

К слову об именах переменных в Go

  • воскресенье, 12 января 2025 г. в 00:00:10
https://habr.com/ru/articles/872940/

Субботним утречком решил поговорить о кое-чем действительно важном. Управление памятью, сборщик мусора — это всё недостойная обсуждения фигня. Имена переменных — вот это действительно стоящая тема. Не вижу, почему бы трем благородным донам её не обсудить.

Для тех, кто пишет на Go давно, изложенное ниже может показаться очевидным, но буду рад вашим комментам (панамку за некоторую сумбурность изложения приготовил).

Одной из ключевых особенностей Go является ориентация на читаемость и краткость кода. Это проявляется как в конструкциях языка, так и в стилевых рекомендациях, принятых сообществом и разработчиками языка.

Короткие имена переменных — важная часть этой философии. В отличие от языков, где длинные и описательные имена переменных могут быть нормой (например, PHP или Java), Go поощряет использование коротких имен, особенно в случаях, когда их смысл легко понять из контекста.

Роб Пайк (надеюсь, не надо представлять этого благородного дона) в одном из старых своих постов писал…

Ah, variable names. Length is not a virtue in a name; clarity of expression is.

Андрю Джеррард (он чуть менее известен, так что вот ссылка на его гитхаб) Посвятил в 2014-м году этой теме один из первых своих докладов по go, который буквально так и называется, What's in a name

Эти идеи прослеживаются в стандартной библиотеке и Go’s Code Review comments где прямо сказано

вы должны предпочитать c вместо lineCount

Я немножко забежал вперед, просто сразу хотелось сослаться на авторитеты. Школьная учительница по риторике, всегда говорила, что ссылка на авторитеты в начале, это хорошо и надежно (спасибо вам, Марианна Юрьевна)

func countLines() int {
    // do stuff
    var linesCount int
    for i := 0; i < lines; i++ {
        linesCount += 1
    }
    return linesCount
}

// Не читается лучше чем 

func countLines() int {
    // do stuff
    var c int
    for i := 0; i < lines; i++ {
        c += 1
    }
    return c
}

Почему так?

В обоих случаях, переменные i и c/linesCount являются кратковременными. Их область видимости очень мала, блок кода, где они используются, тоже небольшой. Вы сразу видите, где они заканчиваются, и вам не нужно прокручивать слишком много.

Верхняя версия содержит больше текста, но смысл не стал понятнее. Напротив, дополнительная длина мешает восприятию.

В стандартной библиотеке Go короткие имена используются повсеместно. Например, функция io.Copy:

func Copy(dst Writer, src Reader) (written int64, err error) {
    var buf [32 * 1024]byte
    for {
        n, err := src.Read(buf[:])
        if n > 0 {
            nw, ew := dst.Write(buf[0:n])
            if nw > 0 {
                written += int64(nw)
            }
            if ew != nil {
                err = ew
                break
            }
            if n != nw {
                err = io.ErrShortWrite
                break
            }
        }
        if err != nil {
            if err == io.EOF {
                err = nil
            }
            break
        }
    }
    return written, err
}

Здесь используются короткие имена: dst (destination), src (source), n (number of bytes). Они интуитивно понятны, потому что:

  • Их смысл ясен из контекста функции.

  • Область видимости переменных ограничена циклом или функцией.

Или в пакете math

func Max(x, y float64) float64 {
    if x > y {
        return x
    }
    return y
}

Имена x и y передают математическую сущность, а не логику программы. Более длинные имена, такие как firstNumber и secondNumber, только усложнили бы восприятие.

На самом деле любой файл из src можно в качестве примера приводить, так что не буду эту часть растягивать

Когда оправдано использовать короткие имена переменных?

Если вкратце - когда они помогают снизить визуальный шум, и улучшить читаемость, за счет лаконичности

  • Когда переменная используется в небольшой функции

  • В общепринятых сокращениях, которые широко используются и понятны большинству разработчиков: err для ошибок, ctx для контекста, i, j для индексов в циклах, ok для булевых вовращаемых параметров функции и тп

  • Для временных переменных, которые используются в одном месте или в короткой области (1-7 строк) часто достаточной однобуквенной перменной, а для средней области видимости (7-15 строк) короткого слова

  • Для ресивера метода, почти никогда не нужно больше двух символов, лучше один

  • Для распространенных типов: r для io.Reader или *http.Request, w для io.Writer или http.ResponseWriter и тп

Стоит помнить, что имя локальной переменной должно отражать её содержимое и то, как она используется в текущем контексте, а не то, откуда произошло значение.

Тем не менее

Имя переменной, которое может быть совершенно понятным (например, c для счетчика) в пределах небольшой области видимости, может оказаться недостаточным в более широкой области. В этом случае потребуется более ясное название, чтобы напомнить читателю о назначении переменной в дальнейших частях кода.

Простое правило (примерно в таком виде оно изложено в Go code review comments)

чем дальше от места объявление используется переменная, тем подробнее должно быть имя

Короткие имена создают путаницу, если используются в глобальных переменных или экспортируемых идентификаторах.

var cfg string // непонятно, что это за конфигурация
var databaseConfig string // так понятнее

Если переменная хранит сложную структуру, которая используется во многих местах, длинные имена помогают понять её назначение.

type User struct {
    ID        int
    FirstName string
    LastName  string
}

Имена FirstName и LastName однозначно описывают содержимое переменной, в отличие от кратких Fn и Ln.

Если смысл переменной не ясен из контекста, лучше использовать описательное имя.

func CalculateDiscount(price float64, percentage float64) float64 {
    discountAmount := price * percentage / 100
    return price - discountAmount
}

В конечном счете, цель использования коротких имен переменных заключается в том, чтобы сконцентрироваться на функциональности кода, избегая лишней детализации там, где она не нужна. Это делает Go тем языком, который мы любим за его простоту и выразительность.

Помните:

Лаконичность кода — это не цель, а средство для достижения читаемости и понимания.

P.S. Я немого устал под конец статьи и пожалуй опубликую ее в таком виде, чтобы она не ушла в страну недописанных черновиков. Нужность статьи была продиктована обсуждениями, с которыми иногда я (полагаю не я один) сталкиваюсь на ревью. Всем хорошего дня =)