英文:
How to break a long line of code in Golang?
问题
从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)
但是我得到了错误:
语法错误:意外的 \
我还尝试了在换行处加上分号:
err := database.QueryRow("select * from users where user_id=?", id)
.Scan(&ReadUser.ID, &ReadUser.Name, &ReadUser.First, &ReadUser.Last, &ReadUser.Email);
但是我再次得到了错误:
语法错误:意外的 .
所以我想知道在Go语言中应该如何处理这种情况?
英文:
Coming from Python, I'm not used to see code lines longer than 80 columns.
So when I encounter this:
err := database.QueryRow("select * from users where user_id=?", id).Scan(&ReadUser.ID, &ReadUser.Name, &ReadUser.First, &ReadUser.Last, &ReadUser.Email)
I tried to break it to
err := database.QueryRow("select * from users where user_id=?", id) \
.Scan(&ReadUser.ID, &ReadUser.Name, &ReadUser.First, &ReadUser.Last, &ReadUser.Email)
But I get
syntax error: unexpected \
I also tried just breaking the line with hitting enter and put a semicolon at the end:
err := database.QueryRow("select * from users where user_id=?", id)
.Scan(&ReadUser.ID, &ReadUser.Name, &ReadUser.First, &ReadUser.Last, &ReadUser.Email);
But the I again get:
syntax error: unexpected .
So I'm wondering what's the golangic way to do so?
答案1
得分: 164
首先一些背景知识。Go语言的正式语法在许多产生式中使用分号 ;
作为终止符,但是Go程序可以省略大部分分号(为了使源代码更清晰、易读;gofmt
也会删除不必要的分号)。
规范列出了确切的规则。规范:分号
> 正式语法在许多产生式中使用分号 ;
作为终止符。Go程序可以使用以下两个规则省略大部分分号:
> 1. 当输入被分解为标记时,如果该行的最后一个标记是
> - 一个标识符
> - 一个整数、浮点数、虚数、字符或字符串字面量
> - 关键字 break
、continue
、fallthrough
或return
之一
> - 运算符和分隔符 ++、--、)、]或}
> 2. 为了允许复杂语句占据一行,可以在闭合的 )
或 }
之前省略分号。
因此,如果在括号 )
后插入换行符,分号 ;
将自动插入,因此下一行将不被视为前一行的延续。这就是在你的情况下发生的情况,因此下一行以 .Scan(&ReadUser.ID,...
开头将给你一个编译时错误,因为这个行本身(不包括前一行)是一个编译时错误:syntax error: unexpected .
因此,你可以在不与上述第 1.
点中列出的规则冲突的任何地方断开你的行。
通常情况下,你可以在逗号 ,
后、在 开放 括号之后(例如 (
、[
、{
)以及点 .
之后(可能引用某个值的字段或方法)断开你的行。你还可以在二元运算符(需要两个操作数的运算符)之后断开你的行,例如:
i := 1 +
2
fmt.Println(i) // 输出 3
这里值得注意的一点是,如果你有一个结构体、切片或映射的字面量列出了初始值,并且你想在列出最后一个值后换行,你必须放置一个必需的逗号 ,
,即使这是最后一个值,后面不会再有值,例如:
s := []int {
1, 2, 3,
4, 5, 6, // 注意它以逗号结尾
}
这是为了符合分号规则,也可以让你重新排列和添加新行而无需考虑添加/删除最后的逗号;例如,你可以简单地交换这两行而无需删除和添加新的逗号:
s := []int {
4, 5, 6,
1, 2, 3,
}
当列出函数调用的参数时,同样适用:
fmt.Println("first",
"second",
"third", // 注意它以逗号结尾
)
英文:
First some background. The formal grammar of Go uses semicolons ";"
as terminators in many productions, but Go programs may omit most of them (and they should to have a clearer, easily readable source; gofmt
also removes unnecessary semicolons).
The specification lists the exact rules. Spec: Semicolons:
> The formal grammar uses semicolons ";" as terminators in a number of productions. Go programs may omit most of these semicolons using the following two rules:
> 1. When the input is broken into tokens, a semicolon is automatically inserted into the token stream immediately after a line's final token if that token is
> - an identifier
> - an integer, floating-point, imaginary, rune, or string literal
> - one of the keywords break
, continue
, fallthrough
, or return
> - one of the operators and delimiters ++, --, ), ], or }
> 2. To allow complex statements to occupy a single line, a semicolon may be omitted before a closing ")" or "}".
So as you can see if you insert a newline character after the parenthesis )
, a semicolon ;
will be inserted automatically and so the next line will not be treated as the continuation of the previous line. This is what happened in your case, and so the next line starting with .Scan(&ReadUser.ID,...
will give you a compile-time error as this standing by itself (without the previous line) is a compile-time error: syntax error: unexpected .
So you may break your line at any point which does not conflict with the rules listed under point 1.
above.
Typically you can break your lines after comma ,
, after opening parenthesis e.g. (
, [
, {
, and after a dot .
which may be referencing a field or method of some value. You can also break your line after binary operators (those that require 2 operands), e.g.:
i := 1 +
2
fmt.Println(i) // Prints 3
One thing worth noting here is that if you have a struct or slice or map literal listing the initial values, and you want to break line after listing the last value, you have to put a mandatory comma ,
even though this is the last value and no more will follow, e.g.:
s := []int {
1, 2, 3,
4, 5, 6, // Note it ends with a comma
}
This is to conform with the semicolon rules, and also so that you can rearrange and add new lines without having to take care of adding / removing the final comma; e.g. you can simply swap the 2 lines without having to remove and to add a new comma:
s := []int {
4, 5, 6,
1, 2, 3,
}
The same applies when listing arguments to a function call:
fmt.Println("first",
"second",
"third", // Note it ends with a comma
)
答案2
得分: 37
最简单的方法是在第一行中保留运算符(.
)。
在许多 Python 风格指南中,也不鼓励使用 \
进行换行。如果你在 Go 和 Python 之间来回切换,你可以将整个表达式用括号括起来,因为这种技巧在两种语言中都适用。
英文:
The simplest way is to simply leave the operator (.
) on the first line.
\
line continuations are also discouraged in many python style guides, you could wrap the whole expression in parens if you are moving back and forth between go and python as this technique works in both languages.
答案3
得分: 23
如上所述,这是一个风格偏好的问题。我理解Go的创建者建议了一种基于他们的经验的风格,我从中学习,但也保留了一些我自己的风格。
以下是我会如何格式化这段代码:
err := database.
QueryRow("select * from users where user_id=?", id).
Scan(
&ReadUser.ID,
&ReadUser.Name,
&ReadUser.First,
&ReadUser.Last,
&ReadUser.Email,
)
英文:
As mentioned, this is a matter of style preference. I understand that the creators of Go have suggested a style based on their experience of which I learn from but also keep some of my own style from my experience.
Below is how I would format this:
err := database.
QueryRow("select * from users where user_id=?", id).
Scan(
&ReadUser.ID,
&ReadUser.Name,
&ReadUser.First,
&ReadUser.Last,
&ReadUser.Email,
)
答案4
得分: 20
这是一个风格问题,但我喜欢的写法是:
err := database.QueryRow(
"select * from users where user_id=?", id,
).Scan(
&ReadUser.ID, &ReadUser.Name, &ReadUser.First, &ReadUser.Last, &ReadUser.Email,
)
英文:
It's a matter of style, but I like:
err := database.QueryRow(
"select * from users where user_id=?", id,
).Scan(
&ReadUser.ID, &ReadUser.Name, &ReadUser.First, &ReadUser.Last, &ReadUser.Email,
)
答案5
得分: 1
什么是在Go语言中处理这个问题的方式?
自动化解决方案。不幸的是,gofmt
不涵盖这种情况,所以你可以使用 https://github.com/segmentio/golines。
通过以下命令安装它:
go install github.com/segmentio/golines@latest
然后运行
golines -w -m 80 .
-w
表示直接在原文件中进行更改(默认情况下会打印到标准输出)
-m
是最大列长度。
英文:
> what's the golangic way to do so?
An automated solution. Unfortunately, gofmt
doesn't cover this case so you could use
https://github.com/segmentio/golines
Install it via
go install github.com/segmentio/golines@latest
Then run
golines -w -m 80 .
-w
means make the changes in-place (default prints to stdout)
-m
is max column length
答案6
得分: 0
你可以在逗号或大括号等地方断行,就像其他答案建议的那样。但是Go社区对于代码行长度有这样的观点:
> Go源代码没有固定的行长度。如果一行感觉太长,应该进行重构而不是断行。
在样式指南中有几条指南。我列举了一些值得注意的部分(摘录):
-
注释
> 确保注释在源代码中即使在窄屏上也能读得清楚。
> ...
> 尽可能地使注释在80列宽的终端上阅读良好,但这不是一个硬性的限制;Go中的注释没有固定的行长度限制。 -
缩进混淆
> 避免在对齐缩进的代码块后引入换行。如果无法避免,留一个空格将代码块中的代码与换行的代码分开。// 不好的写法:
if longCondition1 && longCondition2 &&
// 条件3和条件4与if语句内部的代码具有相同的缩进。
longCondition3 && longCondition4 {
log.Info("all conditions met")
} -
函数格式化
> 函数或方法声明的签名应保持在一行上,以避免缩进混淆。
> 函数参数列表可能是Go源文件中最长的行之一。然而,它们在缩进之前出现,因此很难以一种不会使后续行看起来像函数体的方式断行:// 不好的写法:
func (r *SomeType) SomeLongFunctionName(foo1, foo2, foo3 string,
foo4, foo5, foo6 int) {
foo7 := bar(foo1)
// ...
}// 好的写法:
good := foo.Call(long, CallOptions{
Names: list,
Of: of,
The: parameters,
Func: all,
Args: on,
Now: separate,
Visible: lines,
})// 不好的写法:
bad := foo.Call(
long,
list,
of,
parameters,
all,
on,
separate,
lines,
)
> 通过提取局部变量,可以缩短行的长度。// 好的写法:
local := helper(some, parameters, here)
good := foo.Call(list, of, parameters, local)
> 同样,函数和方法调用不应仅基于行长度进行分隔。
// 好的写法:
good := foo.Call(long, list, of, parameters, all, on, one, line)
// 不好的写法:
bad := foo.Call(long, list, of, parameters,
with, arbitrary, line, breaks)
-
条件和循环
> if语句不应该断行;多行if子句可能导致缩进混淆。// 不好的写法:
// 第二个if语句与if块内的代码对齐,导致缩进混淆。
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)
}
> 如果不需要短路行为,可以直接提取布尔操作数:
// 好的写法:
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)
}
> 如果条件已经重复,还可以提取其他局部变量:
// 好的写法:
uid := user.GetUniqueUserID()
if db.UserIsAdmin(uid) || db.UserHasPermission(uid, perms.ViewServerConfig) || db.UserHasPermission(uid, perms.CreateGroup) {
// ...
}
// 不好的写法:
if db.UserIsAdmin(user.GetUniqueUserID()) || db.UserHasPermission(user.GetUniqueUserID(), perms.ViewServerConfig) || db.UserHasPermission(user.GetUniqueUserID(), perms.CreateGroup) {
// ...
}
> switch和case语句也应保持在一行上。
// 好的写法:
switch good := db.TransactionStatus(); good {
case db.TransactionStarting, db.TransactionActive, db.TransactionWaiting:
// ...
case db.TransactionCommitted, db.NoTransaction:
// ...
default:
// ...
}
// 不好的写法:
switch bad := db.TransactionStatus(); bad {
case db.TransactionStarting,
db.TransactionActive,
db.TransactionWaiting:
// ...
case db.TransactionCommitted,
db.NoTransaction:
// ...
default:
// ...
}
> 如果行过长,缩进所有case并用空行分隔它们,以避免缩进混淆:
// 好的写法:
switch db.TransactionStatus() {
case
db.TransactionStarting,
db.TransactionActive,
db.TransactionWaiting,
db.TransactionCommitted:
// ...
case db.NoTransaction:
// ...
default:
// ...
}
- 不要将长URL断成多行。
我只列举了样式指南中的一些例子。请阅读指南以获取更多信息。
英文:
You can break the line at several places like commas or braces as suggested by other answers. But Go community has this opinion on line length:
> There is no fixed line length for Go source code. If a line feels too long, it should be refactored instead of broken.
There are several guidelines there in the styling guide. I am adding some of the notable ones (clipped):
-
Commentary
> Ensure that commentary is readable from source even on narrow screens.
> ...
> When possible, aim for comments that will read well on an 80-column wide terminal, however this is not a hard cut-off; there is no fixed line length limit for comments in Go.
> Avoid introducing a line break if it would align the rest of the line with an indented code block. If this is unavoidable, leave a space to separate the code in the block from the wrapped line.
// 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")
}
-
Function formatting
> The signature of a function or method declaration should remain on a single line to avoid indentation confusion.
> Function argument lists can make some of the longest lines in a Go source file. However, they precede a change in indentation, and therefore it is difficult to break the line in a way that does not make subsequent lines look like part of the function body in a confusing way:// 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,
)
> Lines can often be shortened by factoring out local variables.// Good:
local := helper(some, parameters, here)
good := foo.Call(list, of, parameters, local)
> Similarly, function and method calls should not be separated based solely on line length.
// Good:
good := foo.Call(long, list, of, parameters, all, on, one, line)
// Bad:
bad := foo.Call(long, list, of, parameters,
with, arbitrary, line, breaks)
-
Conditionals and loops
> An if statement should not be line broken; multi-line if clauses can lead to indentation confusion.// 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)
}
> If the short-circuit behavior is not required, the boolean operands can be extracted directly:
// 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)
}
> There may also be other locals that can be extracted, especially if the conditional is already repetitive:
// 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 and case statements should also remain on a single line.
// 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:
// ...
}
> If the line is excessively long, indent all cases and separate them with a blank line to avoid indentation confusion:
// Good:
switch db.TransactionStatus() {
case
db.TransactionStarting,
db.TransactionActive,
db.TransactionWaiting,
db.TransactionCommitted:
// ...
case db.NoTransaction:
// ...
default:
// ...
}
- Never brake long URLs into multiple lines.
I have only added some of the few examples there are in the styling guide. Please read the guide to get more information.
答案7
得分: 0
你可以在数据库中执行以下操作:
database.
QueryRow("select * from users where user_id=?", id).
Scan(&ReadUser.ID, &ReadUser.Name, &ReadUser.First, &ReadUser.Last, &ReadUser.Email)
英文:
You can do this
database.
QueryRow("select * from users where user_id=?", id).
Scan(&ReadUser.ID, &ReadUser.Name, &ReadUser.First, &ReadUser.Last, &ReadUser.Email)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论