Как разбить длинную строку кода на Голанге?
Исходя из 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 могут пропустить большинство этих точек с запятой, используя следующие два правила:
Когда входные данные разбиты на токены, точка с запятой автоматически вставляется в поток токенов сразу после последнего токена строки, если этот токен
- идентификатор
- целочисленный, с плавающей точкой, мнимый, рунический или строковый литерал
- одно из ключевых слов
break
,continue
,fallthrough
, или жеreturn
- один из операторов и разделителей ++, -,), ] или}
Чтобы разрешить сложным операторам занимать одну строку, точка с запятой может быть опущена перед закрывающими ")" или "}".
Так что, как вы можете видеть, вставляете ли вы символ новой строки после скобок )
точка с запятой ;
будет вставлен автоматически, поэтому следующая строка не будет считаться продолжением предыдущей строки. Это то, что произошло в вашем случае, и поэтому следующая строка начинается с .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 нет фиксированной длины строки. Если строка кажется слишком длинной, ее следует реорганизовать, а не ломать.
В руководстве по стилю есть несколько рекомендаций . Я добавляю некоторые из примечательных (обрезано):
Убедитесь, что комментарии читаются из источника даже на узких экранах.
...
По возможности стремитесь к комментариям, которые будут хорошо читаться на терминале шириной 80 столбцов, однако это не является жестким ограничением; в Go нет фиксированного ограничения длины строки для комментариев.
Избегайте разрыва строки, если оставшаяся часть строки будет совмещена с блоком кода с отступом. Если это неизбежно, оставьте пробел, чтобы отделить код в блоке от переносимой строки.
// 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")
}
Сигнатура объявления функции или метода должна оставаться на одной строке, чтобы избежать путаницы с отступом.
Списки аргументов функций могут составлять одни из самых длинных строк в исходном файле 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)
Оператор 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:
// ...
}
- Никогда не разбивайте длинные URL-адреса на несколько строк.
Я добавил только некоторые из немногих примеров, которые есть в руководстве по стилю. Пожалуйста, прочитайте руководство, чтобы получить больше информации.