如何破解Golang中的一长串代码?

h22fl7wq  于 2023-01-10  发布在  Go
关注(0)|答案(6)|浏览(223)

来自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 \

我也尝试过按回车键来打断这行,并在末尾加上分号:

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

但我又得到:

syntax error: unexpected .

所以我想知道戈兰格的方法是什么?

fcwjkofz

fcwjkofz1#

首先介绍一些背景知识,Go语言的形式语法在很多产品中都使用分号";"作为终止符,但是Go语言程序可能会省略其中的大部分(它们应该有一个更清晰、易读的源代码; gofmt还移除不必要的分号)。
规范列出了确切的规则。规范:分号:
形式语法使用分号“;“作为终止符。Go语言程序可以根据以下两条规则省略大多数分号:

  • 当输入被分解为标记时,分号会自动插入到标记流中,紧跟在行的最后一个标记之后,如果该标记是
  • 标识符
  • 整型、浮点型、虚型、符文型或字符串型
  • 关键字breakcontinuefallthroughreturn之一
  • 运算符和分隔符++、--、)、]或}之一
  • 为了使复杂的语句占据一行,可以省略结尾“)”或“}"前的分号。

所以你可以看到,如果你在括号)后面插入一个换行符,分号;会自动插入,所以下一行不会被当作前一行的延续,这就是你的例子中发生的情况。所以下一行以.Scan(&ReadUser.ID,...开头的代码会给予你一个编译时错误,因为这个代码本身(没有前一行)就是一个编译时错误:syntax error: unexpected .
因此,您可以在不与上面1.点下列出的规则冲突的任何点处中断您的线。

通常情况下,您可以在逗号,之后、 左 * 圆括号(例如([{)之后以及可能引用某个值的字段或方法的点.之后换行。您也可以在二元运算符(需要2个操作数的运算符)之后换行,例如:*

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

有一点值得注意的是,如果你有一个列出初始值的结构体、切片或Map文字,并且你想在列出最后一个值后换行,你必须放置一个强制性的逗号,,即使这是最后一个值,后面不会有更多的值,例如:

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

这是为了符合分号规则,也是为了让你可以重新排列和添加新行,而不必考虑添加/删除最后的逗号;例如,您可以简单地交换这两行,而不必删除和添加新逗号:

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

这同样适用于列出函数调用的参数:

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

ljo96ir52#

最简单的方法是将运算符(.)保留在第一行。
\行连续符在许多python风格指南中也是不被鼓励的,如果你在go和python之间来回切换,你可以把整个表达式用括号括起来,因为这种技术在两种语言中都有效。

ojsjcaue

ojsjcaue3#

如前所述,这是一个风格偏好的问题。我理解围棋的创作者根据他们的经验提出了一种风格,我从中学习,但也从我的经验中保留了一些我自己的风格。
下面是我的格式:

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

wlp8pajw4#

这是风格的问题,但我喜欢:

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

n6lpvg4x5#

你可以像其他人建议的那样,在逗号或大括号等几个地方换行,但是Go语言社区对行长有这样的看法:
Go语言的源代码没有固定的行长,如果感觉一行太长,应该重构而不是打断它。
styling guide中有几个指导原则。我添加了一些值得注意的指导原则(已剪辑):
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分成多行。
我只添加了样式指南中为数不多的几个例子中的一些。请阅读指南以获得更多信息。

yqlxgs2m

yqlxgs2m6#

那么戈兰的方式是什么呢?
不幸的是,gofmt没有涵盖这种情况,因此您可以使用https://github.com/segmentio/golines
通过以下方式安装

go install github.com/segmentio/golines@latest

那就跑

golines -w -m 80 .

-w表示就地进行更改(默认打印到stdout)
-m是最大列长度

相关问题