避免反射 - 我怎样才能最好地重构这段代码?

Avoiding reflection - How best can I refactor this code?

我开始尝试使用 Go,到目前为止效果非常好。我决定做一个小应用程序来帮助朋友整理他(小)公司中与业务相关的信息,我想我会用 Go 来实现它。

我还没有(确切地)运行遇到问题,这更像是一个问题,我什么时候应该考虑使用反射?例如,我有 3 个相关类型:CompanyProjectStaff。它们都有几个共同的字段(例如idname)所以你可以想象,从数据库加载它们的函数(我使用的是MySQL)都非常相似。

查看 LoadCompany()LoadStaff()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
}

当我写 LoadCompany() 时,我对自己感觉很好(ahem 作为一个 beginner/intermediate 程序员)因为它看起来很简洁。但是当我写 LoadStaff()LoadProject() 时,我所做的只是复制和调整。我确信有更好的方法来做到这一点,但我厌倦了在阅读 Pike's post on it:

后跳入反思

[Reflection is] a powerful tool that should be used with care and avoided unless strictly necessary.

所以我的问题是,我应该使用反射吗?如果是的话,你能给我一些关于这种事情的最佳技术的建议吗?这只是冰山一角,因为我觉得与这些类型相关的其余函数和方法都类似地重复(并且不要让我开始测试!)。

谢谢!

类似于:

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
}

请注意,我还没有编译这段代码,但希望它足以给你一个想法。

几点建议:

  • 通读文档:https://golang.org/pkg/database/sql/
  • 在程序启动时创建 sql.DB 实例一次
  • 在 SQL 语句中明确指定列顺序 (select full_name, history, .... from companies ....)