对于 go 来说,如何设计一个优雅的链式操作?

2023-07-10 16:17:05 +08:00
 dyingfire

假设以最简单的加减乘除举例

type Money struct {
	amount int64
    currency string
}

想要实现

money.new(123.12, "USD").Add(456).Mul(123).toStr()

这里面,先初始化成 money ,然后做运算,最后 toStr 成科学计数法的格式。当然,每一步都可以拆开,比如先初始化,然后 add ,得到的 money 最后通过参数传给其他方法运行,在另外的方法里面可以继续计算或者格式化输出。

这里,new 可能报错,比如溢出、格式不正确,add 、mul 都有可能溢出。

如果是 java 来实现,可以很好的 try catch 包起来,但是放到 go 里面,因为只能通过 return value 来判断,感觉很难设计出都能兼容上面方式的代码。

要么就是在 tostr 最后计算的时候判断前面所有报错

if m, err := money.new(123.12, "USD"); err != nil {
	m := m.Add(456).Mul(123)
    if val, err := m.toStr(); err != nil {
    	...
    }
}

要么就是每一步都判断,错误存入 money 对象中,需要的时候判断

m := money.new(123.12, "USD")
if Errors.isError(m) {
	... 
}

m := Add(456).Mul(123);
if Errors.isError(val) {
	... 
}

val := m.toStr()
if Errors.isError(val) {
	... 
}

or 

val := money.new(123.12, "USD").Add(456).Mul(123).toStr()
if Errors.isError(val) {
	... 
}

刚学习 go ,希望能得到大家的一些中肯建议,感谢!

2596 次点击
所在节点    Go 编程语言
18 条回复
phpfpm
2023-07-10 16:23:36 +08:00
你最后加一个
```go
money.new(123.12, "USD").Add(456).Mul(123).Done().toStr()
```
有问题直接在 Done 那里 panic
pota
2023-07-10 16:24:25 +08:00
if _, err := money.new(123.12, "USD").Add(456).Mul(123).toStr(); err != nil {
// 处理错误
}
每个方法返回结构体指针和 error 就行了啊。
picone
2023-07-10 16:26:53 +08:00
首先,Go 其实是希望每个错误都能处理。不过如果非要这么做,也不是不行,但是有点丑

```go
import "fmt"

type Money struct {
Amount int64
Currency string
LastError error
}

func (m Money)Add(val Money) Money {
if m.LastError != nil || val.LastError != nil {
return m
}
if m.Currency != val.Currency {
m.LastError = fmt.Errorf("currency mismatch")
return m
}
return Money{Amount: m.Amount + val.Amount, Currency: m.Currency}
}

func main() {
a := Money{Amount: 100, Currency: "USD"}
b := Money{Amount: 200, Currency: "USD"}
c := Money{Amount: 200, Currency: "HKD"}

fmt.Println(a.Add(b))
fmt.Println(a.Add(c))
}
```
lisxour
2023-07-10 16:30:36 +08:00
val, err := money.new(123.12, "USD").Add(456).Mul(123).toStr()

每个方法都进行单独的 error 捕捉,如果发生错误了存一个 lastError ,后续的方法判断之前是否有 lastError ,有就不做任何计算返回,最后的 toStr 就判断有没有 lastError ,有的话返回 nil, lastError ,否则返回 result, nil
aapeli
2023-07-10 16:32:32 +08:00
推荐在最后一个函数返回错误, 如果中间状态时发现出错,则暂存错误.

```
val,err := money.new(123.12, "USD").Add(456).Mul(123).Done()
if err != nil {
}
```


参考 gorm 的错误处理 https://gorm.io/zh_CN/docs/error_handling.html

@dyingfire
realpg
2023-07-10 16:47:01 +08:00
如果是 java 来实现,可以很好的 try catch 包起来,但是放到 go 里面,因为只能通过 return value 来判断,感觉很难设计出都能兼容上面方式的代码。

两个字:指针
yazinnnn
2023-07-10 16:58:16 +08:00
ryalu
2023-07-10 17:04:20 +08:00
go 1.20 加上了 errors.Join() 函数了 https://pkg.go.dev/errors#Join ,直接最后对错误做判断处理。1.19 之前可以使用 multierr.Combine(),import "go.uber.org/multierr"
SimbaPeng
2023-07-10 17:15:36 +08:00
推荐将前面加减乘除的链式操作设计为只是 build 表达式,最后的 toStr 才是真正执行计算.

可以参考 ent https://entgo.io/zh/docs/crud 的实现
xiangyuecn
2023-07-10 17:22:46 +08:00
惊呆了,这种语言上的设计,和 python 的缩进语法有的一拼,估计作者自己都吐了😂
FreeEx
2023-07-10 18:00:05 +08:00
你可以这样写

```
package main

import "log"

type OP string

const (
Add OP = "add"
Mul OP = "mul"
)

type step struct {
op OP
value int64
}

func NewMoney(amount int64) *Money {
return &Money{
amount: amount,
}
}

type Money struct {
amount int64
steps []step
}

func (r *Money) Add(v int64) *Money {
r.steps = append(r.steps, step{
op: Add,
value: v,
})
return r
}

func (r *Money) Mul(v int64) *Money {
r.steps = append(r.steps, step{
op: Mul,
value: v,
})
return r
}

func (r *Money) Run() (int64, error) {
for _, step := range r.steps {
switch step.op {
case Add:
r.amount += step.value
case Mul:
r.amount -= step.value
}
}
return r.amount, nil
}

func main() {
val, err := NewMoney(123).Add(456).Mul(123).Run()
if err != nil {
log.Fatal(err)
}

log.Println(val)
}

```
Kumo31
2023-07-10 18:21:44 +08:00
我会使用第一种,「错误是值」,在连续操作中错误不应该打断控制流,而是记录错误,最后再返回。典型例子是 Scanner 的设计:
```go
scanner := bufio.NewScanner(input)
for scanner.Scan() {
token := scanner.Text()
// process token
}
if err := scanner.Err(); err != nil {
// process the error
}
```

官方 blog 有篇文章讲过这种设计模式: https://go.dev/blog/errors-are-values
cnoder
2023-07-10 18:23:13 +08:00
不需要每个方法都执行,前面的可以只构建参数,只在某几个最后调用 exec 的方法里处理 类似 9 楼的方案
lc5900
2023-07-10 18:57:06 +08:00
可以参考下 gorm 的链式实现方式
lesismal
2023-07-10 22:13:14 +08:00
链式不直观清晰,不容易理解,链式本身就很丑。

总结:OP 什么破品味!少制造这种辣鸡封装!
trzzzz
2023-07-10 23:52:34 +08:00
orm 和 httputil 中链式还是挺方便的
xuanbg
2023-07-11 08:24:04 +08:00
链式调用实在是丑得一逼!实现链式调用非常简单,每个方法都返回 this 就行。
proxytoworld
2023-07-11 10:25:15 +08:00
@xuanbg 那啥比较好看

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/955556

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX