避免反射 – 我应该如何最好地重构这段代码?

huangapple go评论90阅读模式
英文:

Avoiding reflection - How best can I refactor this code?

问题

我开始尝试使用Go语言,到目前为止感觉非常棒。我决定制作一个小应用,帮助一个朋友在他的(小型)公司中组织与业务相关的信息,而我打算使用Go来实现它。

我并没有遇到问题,只是有一个疑问,什么时候应该考虑使用反射?例如,我有3个相关的类型:CompanyProjectStaff。它们都有一些共同的字段(比如idname),所以可以想象,从数据库中加载它们的函数(我正在使用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 ....)

huangapple
  • 本文由 发表于 2015年12月22日 15:49:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/34410869.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定