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

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

Replace logic from switch with generic code

问题

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

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

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

我们使用sqlx进行数据库查询。

我决定使用两个map,以查询类型作为key,具体的struct/SQL作为数据。
这样可以显著减少视觉噪音,但我想问问是否还有其他可以做的?

以下是示例代码,我相信它很容易理解,如果需要澄清,请随时提问:

  1. /*
  2. 我将以UI窗口作为示例。
  3. 关键是所有窗口类型都有一些共同的属性,
  4. 以及每个窗口类型特有的属性。
  5. */
  6. type ProgressBar struct {
  7. X int
  8. Y int
  9. Width int
  10. Height int
  11. LowestValue int
  12. Highestvalue int
  13. CurrentPosition int
  14. }
  15. type TextBox struct {
  16. X int
  17. Y int
  18. Width int
  19. Height int
  20. TextLength int
  21. TextContent string
  22. }
  23. type Calendar struct {
  24. X int
  25. Y int
  26. Width int
  27. Height int
  28. SelectedDate time.Time
  29. }
  30. var (
  31. // 保存每个窗口类型的特定SQL查询
  32. queries map[int]string
  33. // 每个窗口类型都有自己的函数,返回用于存储查询结果的结构体
  34. scanStructs map[int]func() interface{}
  35. // 由于Go语言没有枚举类型,我们必须像下面这样模拟它们
  36. progressbar = 1
  37. textbox = 2
  38. calendar = 3
  39. )
  40. func init(){
  41. queries = make(map[int]string)
  42. scanStructs = make(map[int]func() interface{})
  43. scanStructs[progressbar] = func() interface{} { return ProgressBar{} }
  44. scanStructs[textbox] = func() interface{} { return TextBox{} }
  45. scanStructs[calendar] = func() interface{} { return Calendar{} }
  46. queries[progressbar] = "SELECT * FROM progress_bars"
  47. queries[textbox] = "SELECT * FROM textboxes"
  48. queries[calendar] = "SELECT * FROM calendars"
  49. }
  50. // 我提供了未重构版本以供比较
  51. func OriginalPerformSqlQuery(type int) ([]interface{}, error) {
  52. query := string
  53. switch type {
  54. case progressbar:
  55. query = "SELECT * FROM progress_bars"
  56. case textbox:
  57. query = "SELECT * FROM textboxes"
  58. case calendar:
  59. query = "SELECT * FROM calendars"
  60. default:
  61. return interface{},
  62. fmt.Errorf("unknown window type")
  63. }
  64. rows, err := db.Queryx(query)
  65. if err != nil {
  66. return interface{},
  67. fmt.Errorf("db.Queryx() failed: %v", err)
  68. }
  69. defer func() { _ = rows.Close() }()
  70. result := make([]interface{}, 0)
  71. for rows.Next() {
  72. switch type {
  73. case progressbar:
  74. r := ProgressBar{}
  75. if err := rows.StructScan(&r); err != nil {
  76. return interface{},
  77. fmt.Errorf("rows.StructScan() failed: %v", err)
  78. }
  79. result = append(result, r)
  80. case textbox:
  81. r := TextBox{}
  82. if err := rows.StructScan(&r); err != nil {
  83. return interface{},
  84. fmt.Errorf("rows.StructScan() failed: %v", err)
  85. }
  86. result = append(result, r)
  87. case calendar:
  88. r := Calendar{}
  89. if err := rows.StructScan(&r); err != nil {
  90. return interface{},
  91. fmt.Errorf("rows.StructScan() failed: %v", err)
  92. }
  93. result = append(result, r)
  94. }
  95. }
  96. return result, nil
  97. }
  98. func PerformSqlQuery(type int) ([]interface{}, error) {
  99. query, exists = queries[type]
  100. if !exists {
  101. return interface{},
  102. fmt.Errorf("unknown window type")
  103. }
  104. rows, err := db.Queryx(query)
  105. if err != nil {
  106. return interface{},
  107. fmt.Errorf("db.Queryx() failed: %v", err)
  108. }
  109. defer func() { _ = rows.Close() }()
  110. result := make([]interface{}, 0)
  111. for rows.Next() {
  112. // 获取所需的目标结构体
  113. r := scanStructs[type]()
  114. if err := rows.StructScan(&r); err != nil {
  115. return interface{},
  116. fmt.Errorf("rows.StructScan() failed: %v", err)
  117. }
  118. result = append(result, r)
  119. }
  120. return result, nil
  121. }
英文:

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/>

  1. /*
  2. I will use UI windows as an example.
  3. The point is that all window types have some common properties,
  4. and some properties specific to each window type.
  5. */
  6. type ProgressBar struct {
  7. X int
  8. Y int
  9. Width int
  10. Height int
  11. LowestValue int
  12. Highestvalue int
  13. CurrentPosition int
  14. }
  15. type TextBox struct {
  16. X int
  17. Y int
  18. Width int
  19. Height int
  20. TextLength int
  21. TextContent string
  22. }
  23. type Calendar struct {
  24. X int
  25. Y int
  26. Width int
  27. Height int
  28. SelectedDate time.Time
  29. }
  30. var (
  31. // holds specific SQL query for each window type
  32. queries map[int] string
  33. // each window type has it&#39;s own function that returns struct for storing query results
  34. scanStructs map[int]func() interface{}
  35. // since Go has no enum type, we have to simulate them like below
  36. progressbar = 1
  37. textbox = 2
  38. calendar = 3
  39. )
  40. func init(){
  41. queries = make(map[int]string)
  42. scanStructs = make(map[int]func() interface{})
  43. scanStructs[progressbar] = func() interface{} { return ProgressBar{} }
  44. scanStructs[textbox] = func() interface{} { return TextBox{} }
  45. scanStructs[calendar] = func() interface{} { return Calendar{} }
  46. queries[progressbar] = &quot;SELECT * FROM progress_bars&quot;
  47. queries[textbox] = &quot;SELECT * FROM textboxes&quot;
  48. queries[calendar] = &quot;SELECT * FROM calendars&quot;
  49. }
  50. // I am providing non-refactored version for comparison
  51. func OriginalPerformSqlQuery(type int) ([]interface{}, error) {
  52. query := string
  53. switch type {
  54. case progressbar:
  55. query = &quot;SELECT * FROM progress_bars&quot;
  56. case textbox:
  57. query = &quot;SELECT * FROM textboxes&quot;
  58. case calendar:
  59. query = &quot;SELECT * FROM calendars&quot;
  60. default:
  61. return interface{},
  62. fmt.Errorf(&quot;unknown window type&quot;)
  63. }
  64. rows, err := db.Queryx(query)
  65. if err != nil {
  66. return interface{},
  67. fmt.Errorf(&quot;db.Queryx() failed: %v&quot;, err)
  68. }
  69. defer func() { _ = rows.Close() }()
  70. result := make([]interface{}, 0)
  71. for rows.Next() {
  72. switch type {
  73. case progressbar:
  74. r := ProgressBar{}
  75. if err := rows.StructScan(&amp;r); err != nil {
  76. return interface{},
  77. fmt.Errorf(&quot;rows.StructScan() failed: %v&quot;, err)
  78. }
  79. result = append(result, r)
  80. case textbox:
  81. r := TextBox{}
  82. if err := rows.StructScan(&amp;r); err != nil {
  83. return interface{},
  84. fmt.Errorf(&quot;rows.StructScan() failed: %v&quot;, err)
  85. }
  86. result = append(result, r)
  87. case calendar:
  88. r := Calendar{}
  89. if err := rows.StructScan(&amp;r); err != nil {
  90. return interface{},
  91. fmt.Errorf(&quot;rows.StructScan() failed: %v&quot;, err)
  92. }
  93. result = append(result, r)
  94. }
  95. }
  96. return result, nil
  97. }
  98. func PerformSqlQuery(type int) ([]interface{}, error) {
  99. query, exists = queries[type]
  100. if !exists {
  101. return interface{},
  102. fmt.Errorf(&quot;unknown window type&quot;)
  103. }
  104. rows, err := db.Queryx(query)
  105. if err != nil {
  106. return interface{},
  107. fmt.Errorf(&quot;db.Queryx() failed: %v&quot;, err)
  108. }
  109. defer func() { _ = rows.Close() }()
  110. result := make([]interface{}, 0)
  111. for rows.Next() {
  112. // get required destination struct
  113. r := scanStructs[type]()
  114. if err := rows.StructScan(&amp;r); err != nil {
  115. return interface{},
  116. fmt.Errorf(&quot;rows.StructScan() failed: %v&quot;, err)
  117. }
  118. result = append(result, r)
  119. }
  120. return result, nil
  121. }

答案1

得分: 4

  • 你可以将窗口类型更改为const并进行类型定义:
  1. type windowType int
  2. const (
  3. progressbar windowType = iota + 1
  4. textbox
  5. calendar
  6. )
  • 你可以将地图初始化更改为复合字面量:
  1. type helperData struct {
  2. query string
  3. scanStruct func() interface{}
  4. }
  5. var helperMap = map[windowType]helperData{
  6. progressbar: {
  7. query: "SELECT * FROM progress_bars",
  8. scanStruct: func() interface{} { return ProgressBar{} },
  9. },
  10. textbox: {
  11. query: "SELECT * FROM textboxes",
  12. scanStruct: func() interface{} { return TextBox{} },
  13. },
  14. calendar: {
  15. query: "SELECT * FROM calendars",
  16. scanStruct: func() interface{} { return Calendars{} },
  17. }
  18. }
英文:
  • You can change the window types to const and type it:
  1. type windowType int
  2. const (
  3. progressbar windowType = iota + 1
  4. textbox
  5. calendar
  6. )
  • You can change the map init to composite literal
  1. type helperData struct {
  2. query string
  3. scanStruct func() interface{}
  4. }
  5. var helperMap = map[windowType]helperData{
  6. progressbar: {
  7. query: &quot;SELECT * FROM progress_bars&quot;,
  8. scanStruct: func() interface{} { return ProgressBar{} },
  9. },
  10. textbox: {
  11. query: &quot;SELECT * FROM textboxes&quot;,
  12. scanStruct: func() interface{} { return TextBox{} },
  13. },
  14. calendar: {
  15. query: &quot;SELECT * FROM calendars&quot;,
  16. scanStruct: func() interface{} { return Calendars{} },
  17. }
  18. }

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:

确定