英文:
Avoiding reflection - How best can I refactor this code?
问题
我开始尝试使用Go语言,到目前为止感觉非常棒。我决定制作一个小应用,帮助一个朋友在他的(小型)公司中组织与业务相关的信息,而我打算使用Go来实现它。
我并没有遇到问题,只是有一个疑问,什么时候应该考虑使用反射?例如,我有3个相关的类型:Company
、Project
和Staff
。它们都有一些共同的字段(比如id
、name
),所以可以想象,从数据库中加载它们的函数(我正在使用MySQL
)都非常相似。
看看LoadCompany()
、LoadStaff()
和LoadProject()
函数:
// 从数据库中加载具有给定id的公司。
func LoadCompany(id int) (Company, error) {
db := tools.OpenDB()
defer db.Close()
stmt, err := db.Prepare("SELECT * FROM companies WHERE id = ?")
if err != nil {
log.Panic(err)
}
var c Company
err = stmt.QueryRow(id).Scan(&c.id, &c.FullName, &c.Name, &c.History, &c.Overview, &c.Est, &c.Phone, &c.Website, &c.Email)
if err != nil {
return Company{}, err
}
return c, nil
}
// 从数据库中加载具有给定id的员工。
func LoadStaff(id int) (Staff, error) {
db := tools.OpenDB()
defer db.Close()
stmt, err := db.Prepare("SELECT * FROM staff WHERE id = ?")
if err != nil {
log.Panic(err)
}
var s Staff
err = stmt.QueryRow(id).Scan(&s.id, &s.FullName, &s.Name, &s.Email, &s.Joined, &s.Left, &s.History, &s.Phone, &s.Position)
if err != nil {
return Staff{}, err
}
return s, nil
}
// 从数据库中加载具有给定id的项目。
func LoadProject(id int) (Project, error) {
db := tools.OpenDB()
defer db.Close()
stmt, err := db.Prepare("SELECT * FROM projects WHERE id = ?")
if err != nil {
log.Panic(err)
}
var p Project
err = stmt.QueryRow(id).Scan(&p.id, &p.Title, &p.Overview, &p.Value, &p.Started, &p.Finished, &p.Client, &p.Architect, &p.Status)
if err != nil {
return Project{}, err
}
return p, nil
}
当我编写LoadCompany()
时,我对自己感到非常满意(作为一个初学者/中级程序员),因为它看起来很简洁。但是当我编写LoadStaff()
和LoadProject()
时,我只是在复制和修改代码。我相信有更好的方法来解决这个问题,但是在阅读了Pike关于反射的文章之后,我对直接使用反射有些犹豫:
[反射是]一种强大的工具,应该谨慎使用,除非绝对必要。
所以我的问题是,我应该使用反射吗?如果是的话,你能给我一些关于这种情况下最佳技术的指导吗?这只是冰山一角,因为我觉得与这些类型相关的其他函数和方法都非常重复(别提测试了!)。
谢谢!
英文:
I started experimenting with Go, and so far it's been a blast. I decided to make a small app which will help a friend organize information business-related information in his (small) company, and I thought I would use Go to implement it.
I haven't (exactly) run into a problem, it's more of a question, when should I consider using reflection? For example, I have 3 related types: Company
, Project
and Staff
. They all have several fields in common (such as id
,name
) so as you can imagine, the functions that load them from the database (I'm using MySQL
) are all very similar.
Look at LoadCompany()
, LoadStaff()
, and LoadProject()
:
// Loads the company from the database with the given id.
func LoadCompany(id int) (Company, error) {
db := tools.OpenDB()
defer db.Close()
stmt, err := db.Prepare("SELECT * FROM companies WHERE id = ?")
if err != nil {
log.Panic(err)
}
var c Company
err = stmt.QueryRow(id).Scan(&c.id, &c.FullName, &c.Name, &c.History, &c.Overview, &c.Est, &c.Phone, &c.Website, &c.Email)
if err != nil {
return Company{}, err
}
return c, nil
}
// Loads the staff from the database with the given id.
func LoadStaff(id int) (Staff, error) {
db := tools.OpenDB()
defer db.Close()
stmt, err := db.Prepare("SELECT * FROM staff WHERE id = ?")
if err != nil {
log.Panic(err)
}
var s Staff
err = stmt.QueryRow(id).Scan(&s.id, &s.FullName, &s.Name, &s.Email, &s.Joined, &s.Left, &s.History, &s.Phone, &s.Position)
if err != nil {
return Staff{}, err
}
return s, nil
}
// Loads the project from the database with the given id.
func LoadProject(id int) (Project, error) {
db := tools.OpenDB()
defer db.Close()
stmt, err := db.Prepare("SELECT * FROM projects WHERE id = ?")
if err != nil {
log.Panic(err)
}
var p Project
err = stmt.QueryRow(id).Scan(&p.id, &p.Title, &p.Overview, &p.Value, &p.Started, &p.Finished, &p.Client, &p.Architect, &p.Status)
if err != nil {
return Project{}, err
}
return p, nil
}
When I wrote LoadCompany()
, I was feeling pretty good about myself (ahem as a beginner/intermediate programmer) because it seemed minimal and clean. But as I wrote LoadStaff()
and LoadProject()
, all I was doing is copying and tweaking. I'm certain there's a better way to do this, but I'm weary of jumping into reflection, after reading Pike's post on it:
>[Reflection is] a powerful tool that should be used with care and avoided unless strictly necessary.
So my question is, should I use reflection, and if so, can you give me some pointers on the best technique for something like this? This is only the tip of the iceberg, because I feel as though the rest of the functions and methods relating to these types are all similarly repetitive (and don't get me started on the tests!).
Thanks!
答案1
得分: 2
func LoadObject(sql string, id int, dest ...interface{}) error {
db := tools.OpenDB()
defer db.Close()
stmt, err := db.Prepare(sql)
if err != nil {
log.Panic(err)
}
defer stmt.Close()
return stmt.QueryRow(id).Scan(dest)
}
// 使用给定的id从数据库加载公司信息。
func LoadCompany(id int) (c Company, err error) {
err = LoadObject("SELECT * FROM companies WHERE id = ?", &c.id,
&c.FullName, &c.Name, &c.History, &c.Overview, &c.Est, &c.Phone, &c.Website, &c.Email)
return
}
请注意,我还没有编译这段代码,但希望它足够好以给你一个想法。
一些建议:
- 仔细阅读文档:https://golang.org/pkg/database/sql/
- 在程序启动时创建一个
sql.DB
实例 - 在 SQL 语句中明确指定列的顺序(例如
select full_name, history, .... from companies ....
)
英文:
Something like:
func LoadObject(sql string, id int, dest ...interface{}) error {
db := tools.OpenDB()
defer db.Close()
stmt, err := db.Prepare(sql)
if err != nil {
log.Panic(err)
}
defer stmt.Close()
return stmt.QueryRow(id).Scan(dest)
}
// Loads the company from the database with the given id.
func LoadCompany(id int) (c Company, err error) {
err = LoadObject("SELECT * FROM companies WHERE id = ?", &c.id,
&c.FullName, &c.Name, &c.History, &c.Overview, &c.Est, &c.Phone, &c.Website, &c.Email)
return
}
Note that I haven't compiled this code, but hopefully it is good enough to give you an idea.
A few suggestions:
- Read through the docs: https://golang.org/pkg/database/sql/
- create
sql.DB
instance once on program start up - specify column order explicitly in the SQL statements (
select full_name, history, .... from companies ....
)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论