Как разбить длинную строку кода на Голанге?

Исходя из Python, я не привык видеть строки кода длиннее 80 столбцов. Поэтому, когда я сталкиваюсь с этим:

err := database.QueryRow("select * from users where user_id=?", id).Scan(&ReadUser.ID, &ReadUser.Name, &ReadUser.First, &ReadUser.Last, &ReadUser.Email)

Я пытался сломать это

err := database.QueryRow("select * from users where user_id=?", id) \
    .Scan(&ReadUser.ID, &ReadUser.Name, &ReadUser.First, &ReadUser.Last, &ReadUser.Email)

Но я получаю

 syntax error: unexpected \

Я также попытался просто разорвать строку нажатием Enter и поставить точку с запятой в конце:

err := database.QueryRow("select * from users where user_id=?", id) 
.Scan(&ReadUser.ID, &ReadUser.Name, &ReadUser.First, &ReadUser.Last, &ReadUser.Email);

Но то я снова получаю:

syntax error: unexpected .

Так что мне интересно, как это можно сделать?

7 ответов

Решение

Сначала немного предыстории. Формальная грамматика Go использует точки с запятой ";" как терминаторы во многих продуктах, но программы Go могут опускать большинство из них (и они должны иметь более четкий, легко читаемый источник; gofmt также удаляет ненужные точки с запятой).

В спецификации указаны точные правила. Spec: точка с запятой:

Формальная грамматика использует точки с запятой ";" в качестве терминаторов в ряде производств. Программы Go могут пропустить большинство этих точек с запятой, используя следующие два правила:

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

  2. Чтобы разрешить сложным операторам занимать одну строку, точка с запятой может быть опущена перед закрывающими ")" или "}".

Так что, как вы можете видеть, вставляете ли вы символ новой строки после скобок ) точка с запятой ; будет вставлен автоматически, поэтому следующая строка не будет считаться продолжением предыдущей строки. Это то, что произошло в вашем случае, и поэтому следующая строка начинается с .Scan(&ReadUser.ID,... выдаст вам ошибку во время компиляции, так как это само по себе (без предыдущей строки) является ошибкой во время компиляции: syntax error: unexpected .

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

Как правило, вы можете разбить ваши строки после запятой , после открывающей скобки, например ( , [ , { и после точки . который может ссылаться на поле или метод некоторого значения. Вы также можете разбить вашу строку после бинарных операторов (те, которые требуют 2 операнда), например:

i := 1 +
        2
fmt.Println(i) // Prints 3

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

s := []int {
    1, 2, 3,
    4, 5, 6,  // Note it ends with a comma
}

Это должно соответствовать правилам точки с запятой, а также так, чтобы вы могли переставлять и добавлять новые строки, не заботясь о добавлении / удалении последней запятой; Например, вы можете просто поменять 2 строки без необходимости удалять и добавлять новую запятую:

s := []int {
    4, 5, 6,
    1, 2, 3,
}

То же самое применяется при перечислении аргументов для вызова функции:

fmt.Println("first",
    "second",
    "third",       // Note it ends with a comma
)

Самый простой способ - просто оставить оператора (.) на первой строке.

\ Продолжение строк также не рекомендуется во многих руководствах по стилям Python, вы можете заключить все выражение в скобки, если вы перемещаетесь назад и вперед между go и python, так как эта техника работает на обоих языках.

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

Ниже я хотел бы отформатировать это: err := database. QueryRow("select * from users where user_id=?", id). Scan( &ReadUser.ID, &ReadUser.Name, &ReadUser.First, &ReadUser.Last, &ReadUser.Email, )

Это вопрос стиля, но мне нравится:

err := database.QueryRow(
    "select * from users where user_id=?", id,
).Scan(
    &ReadUser.ID, &ReadUser.Name, &ReadUser.First, &ReadUser.Last, &ReadUser.Email,
)

какой голанговый способ сделать это?

Автоматизированное решение. К сожалению, gofmtне распространяется на этот случай, поэтому вы можете использовать https://github.com/segmentio/golines

Установите его через

      go install github.com/segmentio/golines@latest

Затем запустите

      golines -w -m 80 .

-wозначает внесение изменений на месте (по умолчанию печатает на стандартный вывод)

-mмаксимальная длина столбца

Вы можете сделать это

      database.
QueryRow("select * from users where user_id=?", id).
Scan(&ReadUser.ID, &ReadUser.Name, &ReadUser.First, &ReadUser.Last, &ReadUser.Email)

Вы можете разбить строку в нескольких местах, таких как запятые или фигурные скобки, как это предлагается другими ответами. Но у сообщества Go есть такое мнение о длине строки:

Для исходного кода Go нет фиксированной длины строки. Если строка кажется слишком длинной, ее следует реорганизовать, а не ломать.

В руководстве по стилю есть несколько рекомендаций . Я добавляю некоторые из примечательных (обрезано):

  1. Комментарий

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

  1. Путаница с отступами

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

       // Bad:
 if longCondition1 && longCondition2 &&
     // Conditions 3 and 4 have the same indentation as the code within the if.
     longCondition3 && longCondition4 {
     log.Info("all conditions met")
 }
  1. Форматирование функции

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

       // Bad:
 func (r *SomeType) SomeLongFunctionName(foo1, foo2, foo3 string,
     foo4, foo5, foo6 int) {
     foo7 := bar(foo1)
     // ...
 }

 // Good:
 good := foo.Call(long, CallOptions{
     Names:   list,
     Of:      of,
     The:     parameters,
     Func:    all,
     Args:    on,
     Now:     separate,
     Visible: lines,
 })
 
 // Bad:
 bad := foo.Call(
     long,
     list,
     of,
     parameters,
     all,
     on,
     separate,
     lines,
 )

Строки часто можно сократить, выделив локальные переменные.

       // Good:
 local := helper(some, parameters, here)
 good := foo.Call(list, of, parameters, local)

Точно так же вызовы функций и методов не должны разделяться только на основе длины строки.

       // Good:
 good := foo.Call(long, list, of, parameters, all, on, one, line)
 
 // Bad:
 bad := foo.Call(long, list, of, parameters,
     with, arbitrary, line, breaks)
  1. Условные операторы и циклы

Оператор if не должен быть разбит на строку; многострочные операторы if могут привести к путанице с отступами.

       // Bad:
 // The second if statement is aligned with the code within the if block, causing
 // indentation confusion.
 if db.CurrentStatusIs(db.InTransaction) &&
     db.ValuesEqual(db.TransactionKey(), row.Key()) {
     return db.Errorf(db.TransactionError, "query failed: row (%v): key does not match transaction key", row)
 }

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

       // Good:
 inTransaction := db.CurrentStatusIs(db.InTransaction)
 keysMatch := db.ValuesEqual(db.TransactionKey(), row.Key())
 if inTransaction && keysMatch {
     return db.Error(db.TransactionError, "query failed: row (%v): key does not match transaction key", row)
 }

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

       // Good:
 uid := user.GetUniqueUserID()
 if db.UserIsAdmin(uid) || db.UserHasPermission(uid, perms.ViewServerConfig) || db.UserHasPermission(uid, perms.CreateGroup) {
     // ...
 }
 
 // Bad:
 if db.UserIsAdmin(user.GetUniqueUserID()) || db.UserHasPermission(user.GetUniqueUserID(), perms.ViewServerConfig) || db.UserHasPermission(user.GetUniqueUserID(), perms.CreateGroup) {
     // ...
 }

Операторы switch и case также должны оставаться на одной строке.

       // Good:
 switch good := db.TransactionStatus(); good {
 case db.TransactionStarting, db.TransactionActive, db.TransactionWaiting:
     // ...
 case db.TransactionCommitted, db.NoTransaction:
     // ...
 default:
     // ...
 }
 
 // Bad:
 switch bad := db.TransactionStatus(); bad {
 case db.TransactionStarting,
     db.TransactionActive,
     db.TransactionWaiting:
     // ...
 case db.TransactionCommitted,
     db.NoTransaction:
     // ...
 default:
     // ...
 }

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

       // Good:
 switch db.TransactionStatus() {
 case
     db.TransactionStarting,
     db.TransactionActive,
     db.TransactionWaiting,
     db.TransactionCommitted:
 
     // ...
 case db.NoTransaction:
     // ...
 default:
     // ...
 }
  1. Никогда не разбивайте длинные URL-адреса на несколько строк.

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

Другие вопросы по тегам