英文:
Replace logic from switch with generic code
问题
我有一个代码中执行数据库查询的地方。
代码不是我的,我不能对其架构做出决策。
最近,我们得到了一个任务,需要执行一些额外的查询。
我的上级决定将这些额外的查询放在同一段数据库代码中。
现在他们希望我对代码的这部分进行优化。
所有那些if
和switch
语句让人眼花缭乱。
我们使用sqlx进行数据库查询。
我决定使用两个map
,以查询类型作为key
,具体的struct
/SQL
作为数据。
这样可以显著减少视觉噪音,但我想问问是否还有其他可以做的?
以下是示例代码,我相信它很容易理解,如果需要澄清,请随时提问:
/*
我将以UI窗口作为示例。
关键是所有窗口类型都有一些共同的属性,
以及每个窗口类型特有的属性。
*/
type ProgressBar struct {
X int
Y int
Width int
Height int
LowestValue int
Highestvalue int
CurrentPosition int
}
type TextBox struct {
X int
Y int
Width int
Height int
TextLength int
TextContent string
}
type Calendar struct {
X int
Y int
Width int
Height int
SelectedDate time.Time
}
var (
// 保存每个窗口类型的特定SQL查询
queries map[int]string
// 每个窗口类型都有自己的函数,返回用于存储查询结果的结构体
scanStructs map[int]func() interface{}
// 由于Go语言没有枚举类型,我们必须像下面这样模拟它们
progressbar = 1
textbox = 2
calendar = 3
)
func init(){
queries = make(map[int]string)
scanStructs = make(map[int]func() interface{})
scanStructs[progressbar] = func() interface{} { return ProgressBar{} }
scanStructs[textbox] = func() interface{} { return TextBox{} }
scanStructs[calendar] = func() interface{} { return Calendar{} }
queries[progressbar] = "SELECT * FROM progress_bars"
queries[textbox] = "SELECT * FROM textboxes"
queries[calendar] = "SELECT * FROM calendars"
}
// 我提供了未重构版本以供比较
func OriginalPerformSqlQuery(type int) ([]interface{}, error) {
query := string
switch type {
case progressbar:
query = "SELECT * FROM progress_bars"
case textbox:
query = "SELECT * FROM textboxes"
case calendar:
query = "SELECT * FROM calendars"
default:
return interface{},
fmt.Errorf("unknown window type")
}
rows, err := db.Queryx(query)
if err != nil {
return interface{},
fmt.Errorf("db.Queryx() failed: %v", err)
}
defer func() { _ = rows.Close() }()
result := make([]interface{}, 0)
for rows.Next() {
switch type {
case progressbar:
r := ProgressBar{}
if err := rows.StructScan(&r); err != nil {
return interface{},
fmt.Errorf("rows.StructScan() failed: %v", err)
}
result = append(result, r)
case textbox:
r := TextBox{}
if err := rows.StructScan(&r); err != nil {
return interface{},
fmt.Errorf("rows.StructScan() failed: %v", err)
}
result = append(result, r)
case calendar:
r := Calendar{}
if err := rows.StructScan(&r); err != nil {
return interface{},
fmt.Errorf("rows.StructScan() failed: %v", err)
}
result = append(result, r)
}
}
return result, nil
}
func PerformSqlQuery(type int) ([]interface{}, error) {
query, exists = queries[type]
if !exists {
return interface{},
fmt.Errorf("unknown window type")
}
rows, err := db.Queryx(query)
if err != nil {
return interface{},
fmt.Errorf("db.Queryx() failed: %v", err)
}
defer func() { _ = rows.Close() }()
result := make([]interface{}, 0)
for rows.Next() {
// 获取所需的目标结构体
r := scanStructs[type]()
if err := rows.StructScan(&r); err != nil {
return interface{},
fmt.Errorf("rows.StructScan() failed: %v", err)
}
result = append(result, r)
}
return result, nil
}
英文:
I have a place in code where database query is performed.<br/>
Code is not mine and I may not make decisions regarding it's architecture.<br/><br/>
Recently, we got a task to perform couple of additional queries.<br/>
My superiors decided to put the additional queries in the same piece of database code.<br/><br/>
Now they want me to optimize somehow that part of the code.<br/>
All those if
s and switch
es "pierce their eyes".<br/>
We use sqlx for database queries.<br/><br/>
I have decided to make 2 map
s with query type as key
, and concrete struct
/SQL
, as data.<br/>
This reduced visual noise significantly, but I came here to ask if there is something else I could do?<br/><br/>
Below is example code, I believe it is self-explanatory, but do ask for clarifications if needed:<br/>
/*
I will use UI windows as an example.
The point is that all window types have some common properties,
and some properties specific to each window type.
*/
type ProgressBar struct {
X int
Y int
Width int
Height int
LowestValue int
Highestvalue int
CurrentPosition int
}
type TextBox struct {
X int
Y int
Width int
Height int
TextLength int
TextContent string
}
type Calendar struct {
X int
Y int
Width int
Height int
SelectedDate time.Time
}
var (
// holds specific SQL query for each window type
queries map[int] string
// each window type has it's own function that returns struct for storing query results
scanStructs map[int]func() interface{}
// since Go has no enum type, we have to simulate them like below
progressbar = 1
textbox = 2
calendar = 3
)
func init(){
queries = make(map[int]string)
scanStructs = make(map[int]func() interface{})
scanStructs[progressbar] = func() interface{} { return ProgressBar{} }
scanStructs[textbox] = func() interface{} { return TextBox{} }
scanStructs[calendar] = func() interface{} { return Calendar{} }
queries[progressbar] = "SELECT * FROM progress_bars"
queries[textbox] = "SELECT * FROM textboxes"
queries[calendar] = "SELECT * FROM calendars"
}
// I am providing non-refactored version for comparison
func OriginalPerformSqlQuery(type int) ([]interface{}, error) {
query := string
switch type {
case progressbar:
query = "SELECT * FROM progress_bars"
case textbox:
query = "SELECT * FROM textboxes"
case calendar:
query = "SELECT * FROM calendars"
default:
return interface{},
fmt.Errorf("unknown window type")
}
rows, err := db.Queryx(query)
if err != nil {
return interface{},
fmt.Errorf("db.Queryx() failed: %v", err)
}
defer func() { _ = rows.Close() }()
result := make([]interface{}, 0)
for rows.Next() {
switch type {
case progressbar:
r := ProgressBar{}
if err := rows.StructScan(&r); err != nil {
return interface{},
fmt.Errorf("rows.StructScan() failed: %v", err)
}
result = append(result, r)
case textbox:
r := TextBox{}
if err := rows.StructScan(&r); err != nil {
return interface{},
fmt.Errorf("rows.StructScan() failed: %v", err)
}
result = append(result, r)
case calendar:
r := Calendar{}
if err := rows.StructScan(&r); err != nil {
return interface{},
fmt.Errorf("rows.StructScan() failed: %v", err)
}
result = append(result, r)
}
}
return result, nil
}
func PerformSqlQuery(type int) ([]interface{}, error) {
query, exists = queries[type]
if !exists {
return interface{},
fmt.Errorf("unknown window type")
}
rows, err := db.Queryx(query)
if err != nil {
return interface{},
fmt.Errorf("db.Queryx() failed: %v", err)
}
defer func() { _ = rows.Close() }()
result := make([]interface{}, 0)
for rows.Next() {
// get required destination struct
r := scanStructs[type]()
if err := rows.StructScan(&r); err != nil {
return interface{},
fmt.Errorf("rows.StructScan() failed: %v", err)
}
result = append(result, r)
}
return result, nil
}
答案1
得分: 4
- 你可以将窗口类型更改为
const
并进行类型定义:
type windowType int
const (
progressbar windowType = iota + 1
textbox
calendar
)
- 你可以将地图初始化更改为复合字面量:
type helperData struct {
query string
scanStruct func() interface{}
}
var helperMap = map[windowType]helperData{
progressbar: {
query: "SELECT * FROM progress_bars",
scanStruct: func() interface{} { return ProgressBar{} },
},
textbox: {
query: "SELECT * FROM textboxes",
scanStruct: func() interface{} { return TextBox{} },
},
calendar: {
query: "SELECT * FROM calendars",
scanStruct: func() interface{} { return Calendars{} },
}
}
英文:
- You can change the window types to
const
and type it:
type windowType int
const (
progressbar windowType = iota + 1
textbox
calendar
)
- You can change the map init to composite literal
type helperData struct {
query string
scanStruct func() interface{}
}
var helperMap = map[windowType]helperData{
progressbar: {
query: "SELECT * FROM progress_bars",
scanStruct: func() interface{} { return ProgressBar{} },
},
textbox: {
query: "SELECT * FROM textboxes",
scanStruct: func() interface{} { return TextBox{} },
},
calendar: {
query: "SELECT * FROM calendars",
scanStruct: func() interface{} { return Calendars{} },
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论