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

Для тех, кто пишет на 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, где прямо сказано:

вместо lineCount лучше использовать просто c

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

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. Я немого устал под конец статьи и пожалуй, опубликую ее в таком виде, чтобы она не ушла в страну недописанных черновиков. Нужность статьи была продиктована обсуждениями, с которыми иногда я (полагаю не я один) сталкиваюсь на ревью. Всем хорошего дня =)

Комментарии (57)


  1. Vitimbo
    11.01.2025 11:59

                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
                }

    Мимопроходящий шарпист негодует. В приведенном примере я не могу просто посмотреть на 9 строчку и понять что такое nw и с чем сравнивается. Надо возвращаться до объявления, смотреть что за метод вызывается, что он возвращает и только потом прыгать обратно на 9 строку, где есть еще какое-то n, которое описано еще выше. При написании с нуля, когда контекст и задача полностью в голове, это не составит проблем, но читать чужое, или свое, через какое-то время, может вызвать определенные сложности.

    Согласиться можно только с Max и то, можно было бы заменить x и y на first и second. Если код будет побольше и не очень хорошо структурирован, то придется сидеть и расшифровывать, что же хотел сказать автор.

    ИМХО, буквы ведь бесплатные, почему бы не воспользоваться ими? Ведь код не всегда будет короткий и грамотно разбит на функции.


    1. skovpen
      11.01.2025 11:59

      В современной версии пакета n переименовали в nr и стало логичней и понятней :-)
      В шарпах тоже любят FileStream fs = ...


      1. Vitimbo
        11.01.2025 11:59

        В шарпах за такое дважды должны ударить по рукам. 1 раз за FileStream вместо var, второй раз за само название. У меня на такое название даже ide заругается, что "тут опечатка, поправь, кожаный".


        1. segment
          11.01.2025 11:59

          Если уже говорить в таком ключе, то "бить по рукам" нужно как раз таки за использование var.


          1. tolyanski
            11.01.2025 11:59

            Почему? В C# var никакого негативного влияния на перформанс не имеет, и никакого отношения к т.н. динамическим типам тоже не имеет.

            Это всего лишь полный аналог auto в C++ и := в Go. Реальный тип переменной определяется на этапе компиляции.


            1. segment
              11.01.2025 11:59

              Иногда довольно сложно понять с каким типом имеешь дело. Ну вот к пример, открыл я код в bitbucket/github/notepad, и как мне посмотреть глазами на тип данных? Не всегда же используется IDE, да и не всегда удобно наводить курсор тогда, когда можно было этого не делать. Сам набор текста, даже с явными типами, занимает сильно меньше времени чем отладка и сопровождение.


              1. tolyanski
                11.01.2025 11:59

                Аргумент. Но в то же время, когда пишете код, и нужно в новую переменную положить результат какой-то библиотечной фукнции, полную сигнатуру которой вы не помните, вам IDE подсказала только ее название и список аргументов, но возвращаемый тип у нее например std::map<time_t, const SomeType&> - такое вводить при каждом объявлении новой переменной (а сначала зайти в библиотечную функцию и скопировать этот тип) - довольно таки нудное занятие :)

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

                А если создаете новый объект, то как бы и совсем смысл теряется два раза повторять тип:

                std::map<time_t, const SomeType&> var1 = std::map<time_t, const SomeType&>();
                
                // VS
                auto var2 = std::map<time_t, const SomeType&>();


                1. segment
                  11.01.2025 11:59

                  Если говорим о C++, то там действительно тип может иметь сложную сигнатуру и прописывать его будет нудно. Или там какие-нибудь итераторы, где в целом может быть понятен хранимый тип. Еще бывают случаи, когда программист задумывает возможность "безболезненной" смены типа данных где-то выше по коду и во всех местах auto останется без изменений, мне такой подход не нравится, но я могу согласится с некоторыми доводами в случае C++. Когда мы говорим о C#, то сейчас можно сместить тип в объявлении в левую сторону, оставив в правой только new(), таким образом код будет последователен если придерживаться отсутствия var.


                  1. tolyanski
                    11.01.2025 11:59

                    аргумент)


          1. Vitimbo
            11.01.2025 11:59

            Открываем стайлгайды от майкрософт и видим рекомендацию всегда использовать var, кроме случаев, когда тип надо указать явно или компилятор не может вывести его из контекста. Также, все ide будут предлагать заменить явный тип на var.

            Аргумент, что сложно понять тип не из ide мне не очень понятен, это не баш скрипты, чтобы их ковырять в блокноте. Оценить качество кода можно и без знания типов или вообще пользоваться инструментами для ревью в самой ide.


            1. segment
              11.01.2025 11:59

              Не совсем понял о какой такой рекомендации Вы говорите. Открываем что находится из последнего, и тут читаем:

              • Use var only when a reader can infer the type from the expression. Readers view our samples on the docs platform. They don't have hover or tool tips that display the type of variables.

              И эти рекомендации выглядят нормально, речь в них в том, что можно (и даже не обязательно) использовать var, когда явно понятен тип из выражения.

              Аргумент, что сложно понять тип не из ide мне не очень понятен, это не баш скрипты, чтобы их ковырять в блокноте. Оценить качество кода можно и без знания типов или вообще пользоваться инструментами для ревью в самой ide.

              Возможно, характер работы у Вас это позволяет. Мне же иногда нужно смотреть в историю разных репозиториев, держать код открытым не только в IDE (например, сохраненный участок кода во время рефакторинга). И так как кодовая база может быть довольно большой и старой, то не всегда помнишь контекст, поэтому во время поиска и решения проблем я хочу смотреть на код как на полноценный текст. Для меня явное лучше неявного, поэтому указание типа (а с codepilot это еще и быстрее) наоборот является хорошим тоном.


    1. sl4mmer Автор
      11.01.2025 11:59

      ну на самом деле, из контекста там понятно, что nw это сокращение он number of bytes written

      Буквы безусловно бесплатные, но вся идея в том, что не должно быть лишнего визуального шума, усложняющего восприятие - безусловно тут нужен сбалансированный поход конечно, исходя главным образом из здравого смысла

      >Ведь код не всегда будет короткий и грамотно разбит на функции

      А вот к этому стотит стремиться - го располагает к этому, даже гранулярность обработки паник (читай эксепшенов) на уровне функций. Естественно если функция длинная и переменная используется в широком контексте (более 15 строк) то стоило бы например в примере из io. написать numWritten например


      1. dopusteam
        11.01.2025 11:59

        ну на самом деле, из контекста там понятно, что nw это сокращение он number of bytes written

        А почему не bw? Кажется логичнее было бы

        А ew что значит?


        1. sl4mmer Автор
          11.01.2025 11:59

          >А почему не bw? Кажется логичнее было бы

          Возможно, тут уже кому-как

          >А ew что значит?

          Ошибка записи, стандартное имя err не использовали, чтобы не затенять ошибку чтения, при успешной записи


          1. dopusteam
            11.01.2025 11:59

            Возможно, тут уже кому-как

            Хороший комментарий, каждый придумывает своё сокращение, но 'из контекста понятно' всем.

            Ошибка записи, стандартное имя err не использовали, чтобы не затенять ошибку чтения, при успешной записи

            Какие то костыли в десятке строк кода) и я про сокращение спрашивал, а не про смысл) ew - error of writing? Почему не we?)


            1. sl4mmer Автор
              11.01.2025 11:59

              С еw понятно - ошибка если уж не err, то с e начинается, хотя бы я назвал errw, например


            1. tolyanski
              11.01.2025 11:59

              Какие то костыли в десятке строк кода)

              Еще в таких случаях, чтобы не затенять переменную err, могут лепить переменные err2, err3 и тд )


              1. sl4mmer Автор
                11.01.2025 11:59

                цифры в именах переменных это мрак, конечно, очень давно такого не видал


      1. Vitimbo
        11.01.2025 11:59

        ну на самом деле, из контекста там понятно, что nw это сокращение он number of bytes written

        Понял это только после вашего уточнения, изначально читал 9 строку как "ЕСЛИ Н НЕ РАВЕН СевероЗапад, ТО ..."

        не должно быть лишнего визуального шума

        Это не означает, что стоит уходить в крайности. Переменная типа countOfBytesReadedFromRecentOpenedFile (хотя, я видел и даже пару раз писал и по длиннее имена) также ужасно как и nw/nbf. В данной ситуации идеально подошло бы readedBytesCount. Лаконично и полностью отражает что тут лежит.

        А вот к этому стотит стремиться - го располагает к этому

        Стремиться, может быть, и стоит, только любой успешный продукт рано или поздно начинает разрастаться. Мне доводилось повидать проекты, превратившиеся в монстров. Уверен и на go такие уже существуют. И то, что функция сегодня складывает 2 числа не защитит ее от того, что завтра прилетит менеджер и попросит прикрутить туда логи и тонкую настройку доступов, чтобы для проверяющих 2 + 2 было 4, а для бухгалтера столько, сколько надо. И вот, ваша функция на 2 строки с переменными a b c мутировало во что-то, чего будут бояться даже чеченцы.

        Итого, мы получаем какой-то набор аббревиатур, которые известны только их создателю, и то, пока он их не забыл. И то, у одного в голове логично будет смотреться nw, а у другого bc или wbc. Вы друг друга даже не поймете. Это примерно одинаковый уровень упоротости с параметрами утилиты tar в линуксе.

        Жить намного проще, когда код можно читать как простое предложение на английском языке.


        1. bogolt
          11.01.2025 11:59

          Кхе-кхе, read это неправильный глагол, поэтому формы readed не существует. Примерно как по-русски кто-то бы попытался написать "победить" в будущем времени ( победю, побежду... ? )


          1. Vitimbo
            11.01.2025 11:59

            Как-то я упустил этот момент. Аббревиатура спасает от неправильной формы глагола.


        1. sl4mmer Автор
          11.01.2025 11:59

          >Это не означает, что стоит уходить в крайности.

          Абсолютно согласен, в применении любого подхода, нужен в первую очередь здравый смысл


      1. tolyanski
        11.01.2025 11:59

        Вы серьезно про "не должно быть лишнего визуального шума", говоря о языке Go? смешно)


        1. Noah1
          11.01.2025 11:59

          В Go очень мало визуального шума. Удивляюсь что кто-то Go называет плохо читаемым.


        1. sl4mmer Автор
          11.01.2025 11:59

          любопытно, разовьете мысль?


          1. tolyanski
            11.01.2025 11:59

            Сравните два куска псевдокода ниже, которые делают одно и то же:

            func DoGoodThing() {
              DoThis();
              DoThat();
              MakeThemHappy();
            }
            func DoGoodThing() {
              err := DoThis()
              if err != nil {
                return err
              }
              err = DoThat()
              if err != nil {
                return err
              }
              err = MakeThemHappy()
              if err != nil {
                return err
              }
            }


            1. Noah1
              11.01.2025 11:59

              Это подмена понятий, 2 примера кода делают НЕ одно и тоже. Во втором примере обрабатывается ошибка на каждый вызов функции, в первом - ошибки игнорируются.

              В классическом языке с исключениями, обработка каждой ошибки была бы ещё хуже:

              func DoGoodThing() {
                try {
                  DoThis();
                } catch {
                  return
                }
                try {
                  DoThat();
                } catch {
                  return
                }
                try {
                  MakeThemHappy();
                } catch {
                  return
                }
              }

              И не нужно писать про то, что в try можно обернуть все 3 функции - это уже не будет обработкой индивидуальных ошибок.

              `if err != nil` не идеальное, но элегантное в своей простоте решение.


              1. tolyanski
                11.01.2025 11:59

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

                Иными словами, у меня должна быть такая возможность, обрабатывать либо индивидуальные ошибки, либо общие, при этом чтобы гарантировалась остановка выполнения в любом случае.

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


                1. Lezvix
                  11.01.2025 11:59

                  В Go решения принимали не наобум, исключения эта штука неоднозначная, ими часто злоупотребляют, по этому решили передавать ошибки явно, всегда проще обработать ошибку там, где она случилась. И многие современные языки придерживаются такого подхода: Rust, Zig
                  При этом действительно исключительные исключения, которые не позволяют продолжить исполнение без их обработки в Go вполне себе есть, и реализуются через panic и recover


                  1. dopusteam
                    11.01.2025 11:59

                    всегда проще обработать ошибку там, где она случилась

                    В примерах из статьи вся обработка заключается в том, чтоб вернуть ошибку в вызывающий метод


                    1. Lezvix
                      11.01.2025 11:59

                      Мне кажется пример tolyanski более показательный, нежели пример из статьи, в статье всё таки EOF обрабатывается прямо в методе.
                      В Go действительно для таких случаев не помешал бы какой-нибудь оператор возврата ошибки по типу "?" из Rust


              1. eee
                11.01.2025 11:59

                Лукавите, в приведенном коде на go никакой обработки ошибок нет, а просто делается бабблинг ошибки наверх. В языке с исключениями не потребовалось делать ничего, т.к. исключения на то и исключения, что они автоматически бабблятся и тот кто захочет обработать, тот и обработает try-catch-ем.


              1. kotlomoy
                11.01.2025 11:59

                В данном случае никакой индивидуальной обработки нет, поэтому try...catch будет на вызывающей стороне.

                Но я согласен, что исключения ещё хуже. Но что авторам языка мешало сделать синтаксический сахар, как в Rust, с неявным возвратом err в случае отсутствия обработки ошибки на месте? Тогда код был бы с первого примера, но под капотом бы работал код второго примера


                1. tolyanski
                  11.01.2025 11:59

                  Именно. Я не топлю за исключения как таковые, я топлю за читаемость кода


      1. kotlomoy
        11.01.2025 11:59

        Наша команда недавно начала писать на Го один из наших сервисов. Мы уже задались вопросом, почему в Го приняты сокращения в названиях переменных. Моя версия - чтобы компенсировать зубодробительные названия функций)

        В Го мало того, что нет перегрузки функций, так ещё и нет file scope для названий функций. Поэтому даже если функция используется в пределах одного файла, ей нужно давать имя, неконфликтующее с другими функциями во всем пакете. Почему так? Чем авторам языка так насолил обычный static из C?


        1. tolyanski
          11.01.2025 11:59

          так ещё и нет file scope для названий функций

          Выход один: больше пакетов хороших и разных)

          Почему так? Чем авторам языка так насолил обычный static из C?

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


  1. alabamaa
    11.01.2025 11:59

    Примерно, такую же концепцию имен использую и на C++, и по этому поводу даже были разногласия с партнерами по разработке. Благодаря вашей статье усилилось желание познакомиться с языком Go.


    1. sl4mmer Автор
      11.01.2025 11:59

      Ну Роб Пайк (которого я в начале упомянул), он свои идеи о стиле, ставшие основой философии Go вынес из своего С опыта, есть его статья, 1989-го года, "Notes on Programming in C" - вам возможно будет любопытно, там и про именование есть


      1. cupraer
        11.01.2025 11:59

        Основа философии Го была напрямую сформулирована Пайком: язык для тупых, но старательных. Вот прямая цитата:

        They’re [Googlers] not capable of understanding a brilliant language but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt.

        Ещё Пайк не использует подсветку синтаксиса:

        Syntax highlighting is juvenile. When I was a child, I was taught arithmetic using colored rods. I grew up and today I use monochromatic numerals.

        Есть гипотеза, что для использование такой концепции наименования, надо бы и от IDE отказаться, и писать на чем-нибудь посерьезнее, типа си, хотя бы.


  1. Arnowt
    11.01.2025 11:59

    Неужели кто-то на самом деле уверен в том, что именование(длина) переменных, зависит от языка? И что для го не тоже самое что для пыха или питона...


    1. Noah1
      11.01.2025 11:59

      Зависит. У каждого языка(комьюнити) свои общепринятые стандарты, линтеры и common knowledge.


  1. Mur466
    11.01.2025 11:59

    Мне плевать на авторитеты. Тем более в цитате Пайка приветствуется ясность, а автор выдаёт ее за краткость.

    Что такое nw и ew сам не догадался.

    Го с его обработкой ошибок через err совсем не лаконичен, а наоборот.

    Детализация может казаться излишней писателю, так как для него всё очевидно. Но никто не знает, во что этот код разрастется с годами, и кто его будет читать и поддерживать. И там станет всё совсем не так очевидно.

    Стадартные сокращения типа err и ctx надо соблюдать, но даже в указанном примере вместо errWrite сделали ew и сразу стало непонятно. Если это стандарт в модуле, и встрчается многократно с этим можно жить. Но если сокращения бессистемные или неоднозначные, это ужас. Например переменная c может одначать и char и count и client и черта лысого.

    Поэтому уважайте тех кто придёт за вами, и не жалейте буковок для названий переменных.


    1. sl4mmer Автор
      11.01.2025 11:59

      вопрос здравого смысла, если с используется только строкой-двумя ниже, то это подходящее имя, если строками пятью ниже и больше то уже есть смысл в cnt или count

      >Поэтому уважайте тех кто придёт за вами

      В том и смысл,краткость (там где она уместна) и нужна для ясности, чтоб читающему было удобнее, не нужно задерживаться взглядом на каждой yetAnotherVar,

      >Детализация может казаться излишней писателю, так как для него всё очевидно

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

      Я понимаю вашу позицию, и во многом вы правы, сам я до этого очень долго писал на php/java/delphi/python и привык считать говорящие имена хорошим тоном, но стоит сказать, что за последние годы скорректировал свою позицию в сторону более взвешенной - краткость, там где она к месту, прекрасна. А бездумным применением практик - можно любой подход дискредетировать

      Касаемо обработки ошибок, ну мне уже трудно сказать - сначала подход казался непривычным и не очень удобным, сейчас после почти 10 лет на go, он для меня уже как родной, у него есть свои плюсы


      1. tolyanski
        11.01.2025 11:59

        Касаемо обработки ошибок, ну мне уже трудно сказать - сначала подход казался непривычным и не очень удобным, сейчас после почти 10 лет на go, он для меня уже как родной, у него есть свои плюсы

        Соглашусь, same here. Правда в моем случае не 10 а 5 лет.
        Но я считаю, что мы просто уже привыкли к этому. Человек ведь такая сволочь, ко всему привыкает)

        Но регулярное продолжение пописывания мной то на C++, то на C#, то на PHP, напоминает мне, от чего я отказался...


  1. tolyanski
    11.01.2025 11:59

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

    Каждый раз, когда кто-то где-то упоминает "ориентацию на читаемость в Go", я со слезами пересчитываю, сколько раз я за сегодня споткнулся об `if err != nil`, разбирая очередной спагетти...


    1. Noah1
      11.01.2025 11:59

      Как 'if err != nil' мешает читаемости? Предельно простая конструкция, ничуть хуже не делает


      1. tolyanski
        11.01.2025 11:59

        Так же как и обсуждаемая здесь захламляемость пространства длинными именами.
        if err != nil через каждые 3-5 строчек, это ни что иное как нагромождение, мешающее визуально быстро распознавать полезную логику.

        В особых случаях несколько помогает IDE, у которой есть возможность "сворачивать" этих монстриков, чтобы вычитывать полезный код. Но даже сам факт, что GoLand от JetBrains сделал такую фичу под названием code folding для таких кейсов, говорит о том, что сам по себе код на Go - не идеально читаемый, если открыть его в простом блокноте. Помню похожую фичу в CLion для C++ - так она там только для автоматического сворачивания портянки импортов. А в Go прям по самому коду у каждого столба надо сворачивать мусор.

        Разумеется, все познается в сравнении. Я говорю про это потому что повидал достаточно других языков, где с этим делом проблем поменьше. Да, разумеется, у других языков своих собственных проблем хватает. Но я и не утверждаю, что язык X объективно лучше языка Y.


        1. sl4mmer Автор
          11.01.2025 11:59

          я выше уже писал, мне чтоб полюбить if err!=nil понадобилось наверное лет 5-6, использования го как основного языка и должен сказать у этого подхода есть определенные плюсы

          Как минимум, не возникает сложных и скрытых механизмов обработки ошибок, магии которую легко создать эксепшенами. Вместо этого ошибки - обычные значения, которые можно проверять и обрабатывать явно. Поведение становиться более очевидным

          С точки зрения читаемости if err != nil делает обработку ошибок явной и легко заметной. Ошибки не “заглушаются” и не остаются незамеченными (ну в идеале)

          В целом такая обработка ошибок это фича дизайна языка - сознательное решение, направленное на упрощение кода, увеличение его читаемости и контроль предсказуемости.


          1. tolyanski
            11.01.2025 11:59

            В целом такая обработка ошибок это фича дизайна языка - сознательное решение, направленное на упрощение кода

            Я вроде где-то читал, что это не фича дизайна, а именно что реализовать обработку ошибок по-другому (более привычному) способу сложно из-за ориентированности языка на многопоточность и ее оптимизацию и тд. Не помню точные аргументы, что именно пошло не так / не получилось, надо будет на досуге погуглить еще раз


            1. sl4mmer Автор
              11.01.2025 11:59

              ну и это тоже, да и момент с производительностью, исключения требуют дополнительной нагрузки на систему (например, из-за управления стеком).


          1. h1pp0
            11.01.2025 11:59

            чтоб полюбить if err!=nil понадобилось наверное лет 5-6

            Значит ли это, что разработчик, похожий на вас, будет не любить самый популярный паттерн языка первые 4 года? Звучит не очень хорошо


            1. sl4mmer Автор
              11.01.2025 11:59

              Скажем так, он был мне непривычен и я не сразу понимал все его плюсы


        1. Noah1
          11.01.2025 11:59

          Но ведь это необходимое "зло". Как иначе могла бы выглядеть индивидуальная обработка ошибок? Максимум можно на одну строку сократить с вложенным if. Стандартные try catch - гораздо хуже.

          Go добился подробной и предельно простой обработки ошибок при помощи стандартной конструкции, без спец синтаксиса, по-моему весьма элегантно.


          1. tolyanski
            11.01.2025 11:59

            В том и дело, что "индивидуальная" обработка ошибок не во всех случаях нужна, но при этом нужна гарантированная остановка выполнения сразу после появления ошибки.

            Но то что это необходимо зло - полностью согласен.


          1. LiveRock
            11.01.2025 11:59

            Как уже заметили выше, в большинстве случаев в Go имеет место не «индивидуальная обработка» ошибок, а «выталкивание» их наверх — в вызывающий код. И в этом случае не помешал бы какой-нибудь синтаксический сахар, что-нибудь вроде:

            func findPatternInFile(pattern string, filePath string) ([]string, error) {
              var err! error
              default return nil, err
            
              re, err! := regexp.Compile(pattern) // err != nil: default return
              file, err! := os.Open(filePath) // err != nil: default return
              result := make([]string, ...)
              ...
              return result, nil // successful return
            }


  1. svn74
    11.01.2025 11:59

    Сразу не возлюбил Go, когда узнал, что есть запрет на перенос скобок, - теперь для меня все это выглядит как "гомнокод" :(


    1. astec
      11.01.2025 11:59

      Питон бы вы наверное вообще возненавидели?