lundi 21 décembre 2015

Avoiding reflection - How best can I refactor this code?

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 full_name AS fullName, short_name AS name, history, overview, est, phone, website, email
                                FROM companies WHERE id = ?`)
    if err != nil {
        log.Panic(err)
    }   
    c := Company{id: id} 
    err = stmt.QueryRow(c.id).Scan(&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 full_name AS fullName, short_name AS name, email, joined, ` + "`left`" + `, history, phone, position
                                FROM staff WHERE id = ?`)
    if err != nil {
        log.Panic(err)
    }
    s := Staff{id: id}
    err = stmt.QueryRow(s.id).Scan(&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)
    }
    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!





Aucun commentaire:

Enregistrer un commentaire