将 switch 语句中的逻辑替换为通用代码。

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

Replace logic from switch with generic code

问题

我有一个代码中执行数据库查询的地方。
代码不是我的,我不能对其架构做出决策。

最近,我们得到了一个任务,需要执行一些额外的查询。
我的上级决定将这些额外的查询放在同一段数据库代码中。

现在他们希望我对代码的这部分进行优化。
所有那些ifswitch语句让人眼花缭乱。

我们使用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 ifs and switches "pierce their eyes".<br/>

We use sqlx for database queries.<br/><br/>
I have decided to make 2 maps 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&#39;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] 		= 	&quot;SELECT * FROM progress_bars&quot;
queries[textbox] 		    = 	&quot;SELECT * FROM textboxes&quot;
queries[calendar] 		    = 	&quot;SELECT * FROM calendars&quot;
}
// I am providing non-refactored version for comparison
func OriginalPerformSqlQuery(type int) ([]interface{}, error) {
query := string
switch type {
case progressbar:
query =     &quot;SELECT * FROM progress_bars&quot;
case textbox:
query =     &quot;SELECT * FROM textboxes&quot;
case calendar:
query =     &quot;SELECT * FROM calendars&quot;
default:  
return interface{}, 
fmt.Errorf(&quot;unknown window type&quot;)
}
rows, err := db.Queryx(query)
if err != nil {
return interface{}, 
fmt.Errorf(&quot;db.Queryx() failed: %v&quot;, err)
}
defer func() { _ = rows.Close() }()
result := make([]interface{}, 0)
for rows.Next() {
switch type {
case progressbar:
r     :=     ProgressBar{}
if err := rows.StructScan(&amp;r); err != nil {
return interface{}, 
fmt.Errorf(&quot;rows.StructScan() failed: %v&quot;, err)
}
result = append(result, r)
case textbox:
r     :=     TextBox{}
if err := rows.StructScan(&amp;r); err != nil {
return interface{}, 
fmt.Errorf(&quot;rows.StructScan() failed: %v&quot;, err)
}
result = append(result, r)
case calendar:
r     :=     Calendar{}
if err := rows.StructScan(&amp;r); err != nil {
return interface{}, 
fmt.Errorf(&quot;rows.StructScan() failed: %v&quot;, err)
}
result = append(result, r)
}
}
return result, nil
}
func PerformSqlQuery(type int) ([]interface{}, error) {
query, exists = queries[type]
if !exists {
return interface{}, 
fmt.Errorf(&quot;unknown window type&quot;)
}
rows, err := db.Queryx(query)
if err != nil {
return interface{}, 
fmt.Errorf(&quot;db.Queryx() failed: %v&quot;, err)
}
defer func() { _ = rows.Close() }()
result := make([]interface{}, 0)
for rows.Next() {
// get required destination struct
r := scanStructs[type]()
if err := rows.StructScan(&amp;r); err != nil {
return interface{}, 
fmt.Errorf(&quot;rows.StructScan() failed: %v&quot;, 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:       &quot;SELECT * FROM progress_bars&quot;,
scanStruct:  func() interface{} { return ProgressBar{} },
},
textbox: {
query:       &quot;SELECT * FROM textboxes&quot;,
scanStruct:  func() interface{} { return TextBox{} },
},
calendar: {
query:       &quot;SELECT * FROM calendars&quot;,
scanStruct:  func() interface{} { return Calendars{} },
}
}

huangapple
  • 本文由 发表于 2021年11月22日 20:58:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/70066140.html
匿名

发表评论

匿名网友

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

确定