被 Java 毒害的脑子想在 Go 中实现一个操作,望打醒

6 天前
 assiadamo

以前接触过的一个 Java 项目,实现了一种在我看来很新的做法:

  1. 代码生成的协议类,里面自带了一个未实现的 process 方法
public class Echo extend Msg {
	String msg;
  	public void decode(){}
  	public void encode(){}
  	public void process() throws Exception {
  		throw new UnsupportedOperationException();
  	}
}
  1. 代码生成的协议处理类,格式是这样的
@MsgProcess
public static boolean process(Echo echo) {
	return true;
}
  1. 框架启动的时候,会反射获取到注解@MsgProcess的 Metchod 和他的参数,然后用 javaassist 的字节码操作,将协议类Echoprocess方法给替换掉!这样框架层调用协议的msg.process()就可以直接执行业务逻辑!

Java 写了 10 年,一说起框架,自然想到的就是各种设计模式抽象继承与反射之类,当写 Go 的时候,也受到影响,我现在想用 Go 实现类似的操作,实践的效果如下

  1. 代码生成了 Echo 协议类
package proto
type Echo struct {
	BaseMsg
	Msg string
}
func (msg *Echo) Decode(src *bytes.Buffer) error {}
func (msg *Echo) Encode(dst *bytes.Buffer) error {}
func (msg *Echo) Process() {
	panic("implement me")
}
  1. 代码生成了业务逻辑类
package logic
import proto
func ProcessEcho(msg *proto.Echo) {}
  1. 使用 ast/parser 将Echoprocess的方法体替换为ProcessEcho
func (msg *Echo) Process() {
	logic.ProcessEcho(msg)
}

但重新生成的 Echo 类,有一些问题,首先生成出来的文件,我将其保存为echo_override.go放在另一个 package ,相关的 import 都可能有问题,然后Processimport 了 logic ,而 logic 自然要 import echo ,非常经典的 import cycle 。

这是第一步遇到的问题,我打算先用 interface 解决看看,为什么不用 func 替换,我觉得好丑啊!各位 Go 大神有没有什么建议?我这种思路,符合 Go 的设计哲学吗?

5929 次点击
所在节点    Go 编程语言
68 条回复
assiadamo
5 天前
@leonshaw 对的,如果有注解就不会太纠结
jeesk
5 天前
@assiadamo 还有一点就是 request 和 response 有规范,java 也有 servlet api ,但是有些根本不鸟 servlet , 导致有各种框架
NessajCN
5 天前
@assiadamo 你要不写段完整点的代码,发 playground 或 gist 都行,我看看你到底要做成啥样
至少从你的文字里我还是没法理解你在框架外面写的逻辑为啥还要传回给框架
soul11201
5 天前
@NessajCN #29 OP 的诉求确实没看明白想干嘛。看原文应该是做关注点分离:看上去想把 rpc 协议生成代码,跟基于生成的代码写的业务逻辑做分离。先前用 thrift 的时候,thrift 解决思路感觉还不错,记得大概思路:
1. 基于协议生成 interface ,并且组合 thrift 基础设施层的 interface
2. 实现 interface 对应的业务逻辑 struct
3. 注册类实例,配合 thrift 留置的中间件能力实现 aop

这套整体解决方案还算可以,即不太复杂,还做到了关注点分离
Z1on
5 天前
看看 kratos 框架用的那套代码生成是不是能解决你的问题
xuanbg
5 天前
@HiShan 人菜确实该怪 Java 。。。早让他写 C 艹写个 10 年,不只需要 3 年,你看他还菜不菜。
soul11201
5 天前
还是针对原帖,没细看楼主的评论内容。

贴中 Java 注解实现的注解非常好,不是什么毒害,Go 里面还不太好用优雅的方式实现类似的能力。

其中一个原因是 Go 是单向依赖,如果依赖图最终 main 到达不了,就会排除编译。
Java springboot 添加完扫描目录就会被编译进去了,且循环依赖也不是什么事情。
assiadamo
5 天前
@NessajCN
// 注册协议处理函数
type MsgProcessorFunc[T Msg] func(msg T) error

var MsgProcessor = map[int32]MsgProcessorFunc[Msg]{}

// 注册协议创建函数
var MsgCreator = map[int32]func() Msg{}

// ========= 生成的协议类 ===========
type Echo struct {
// TypeId 应该隐藏在 MsgBase 中 此处简略
TypeId int32
Msg string
}

func NewEcho() *Echo {
return &Echo{
TypeId: 1,
}
}

func (echo *Echo) Process() error {
return MsgProcessor[echo.TypeId](echo)
}

// ========= 业务层 ===========
func ProcessEcho(echo *Echo) error {
fmt.Println(echo.Msg)
return nil
}

// 调用例子
func main() {
// 想干掉的手动注册 如果不行只能用代码生成
MsgCreator[1] = func() Msg { return NewEcho() }
MsgProcessor[1] = func(msg Msg) error { return ProcessEcho(msg.(*Echo)) }

// 模拟协议发送
msg := &Echo{TypeId: 1, Msg: "test"}

// 省掉了编解码和 socket 操作

msg.Process()
}

这是可执行的代码示例
做了很多工作就是为了干掉那两句注册
mcfog
5 天前
大型 XY 问题现场
soul11201
5 天前
@assiadamo 从最后一个评论,我想起来了 Java 确实有一个很深的毒害就是过度 DRY ,Go 里面是鼓励 copy 一部分代码减少依赖,或者说减少整体复杂度的。
assiadamo
5 天前
@NessajCN
少贴了 这是全部
// ========= 通信框架层 ===========
type Msg interface {
Process() error
}

// 注册协议处理函数
type MsgProcessorFunc[T Msg] func(msg T) error

var MsgProcessor = map[int32]MsgProcessorFunc[Msg]{}

// 注册协议创建函数
var MsgCreator = map[int32]func() Msg{}

// ========= 生成的协议类 ===========
type Echo struct {
// TypeId 应该隐藏在 MsgBase 中 此处简略
TypeId int32
Msg string
}

func NewEcho() *Echo {
return &Echo{
TypeId: 1,
}
}

func (echo *Echo) Process() error {
return MsgProcessor[echo.TypeId](echo)
}

// ========= 业务层 ===========
func ProcessEcho(echo *Echo) error {
fmt.Println(echo.Msg)
return nil
}

// 调用例子
func main() {
// 想干掉的手动注册 如果不行只能用代码生成
MsgCreator[1] = func() Msg { return NewEcho() }
MsgProcessor[1] = func(msg Msg) error { return ProcessEcho(msg.(*Echo)) }

// 模拟协议发送
msg := &Echo{TypeId: 1, Msg: "test"}

// 省掉了编解码和 socket 操作

msg.Process()
}
xuanbg
5 天前
Java 生态确实有一种很不好的思想,就是什么都要搞个框架出来,然后把什么业务都往这个框子里面装。明明不好装也要硬装,譬如 Spring Security ,简直就是业界毒瘤。
soul11201
5 天前
@mcfog 也不算是 XY 作者应该就是想把,框架层,协议层,业务层彻底解耦和,不想写一点冗余代码。Go 里面确实有点难搞。其中一个很重要的原因是 Go 对依赖的管理跟 Java 完全不是路子。
assiadamo
5 天前
@xuanbg 互联网行业叫框架,我更喜欢把我做的这套叫引擎...
我想了想图啥呀这么折腾,很多服务器也是静态代码做引擎,业务逻辑和协议处理用脚本语言,比如 go+lua ,cpp+python ,语法糖多还不折腾还好热更,go 服务器热更要用的 plugin 也是很耐人寻味
povsister
5 天前
搞那么多就是为了省掉注册两行?你早说呗

开发脚手架 new 个消息桩子出来,然后生成一下 init ,导入包路径即注册,这不是很简单的
mcfog
5 天前
@soul11201 可是 golang 的 duck typing 风格的 interface ,就单纯按官方风格正确使用,就已经是非常(超过其他主流语言)的解耦了

在我看来,这里描述不清楚问题也讨论不明白,是因为出发点已经错了,又希望按正常的思路解决一个错误 or 不存在的问题。所以我说是 XY 问题
Danswerme
5 天前
作为一个只会前端的人看你们讨论这个和看魔法一样,请教下想了解这些是不是从 Java 开始学比较合适?
assiadamo
5 天前
@Danswerme 我看前端也是看魔法的,比如 vue 为啥改了 data 里的数据,显示也跟着变了
NessajCN
5 天前
@assiadamo
我精简了一下你的代码,你看看哪里不符合你的需求? 我就直接用了你的业务逻辑
https://go.dev/play/p/zCDaPEkuPL3
daju233
5 天前
@Danswerme 同问,简直像看天书

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

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

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

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

© 2021 V2EX