golang項目代碼重構(二)

使用interface重構代碼,面向接口,減小重複代碼

項目背景

  • 須要提供節目,節目集數據的增刪改查,數據庫使用ES(elasticsearch)

重構前 →_→

  • 本文着重強調用接口重構的思路,因此只選取讀取功能做爲例子
  • 數據結構
type Video struct{
    VideoID string `json:"video_id"`
    //包含不少節目的屬性,如節目類型,上下線狀態等
}

type Show struct{
    ShowID string `json:"show_id"`
    //包含不少節目集的屬性,如評分,演員等
}
  • ES中對應兩個index,video,show 分別對應上邊的兩個結構。重構前讀取功能實現
package es

import (
    "context"
    "log"
    "encoding/json"
    "github.com/olivere/elastic"
)

func(video *Video) ReadVideo(videoID string){
    client,_ := elastic.NewClient(elastic.SetURL("http://127.0.0.1:19200"))
    defer client.Stop()
    if !isVideoExists(videoID){
        return
    }
    esResponse,err := client.Get().Index("video").Type("video").Id(videoID).Do(context.Background())
    if err != nil {
        log.Println("Failed to read ES data of ID:! ",videoID)
        return
    }
    json.Unmarshal(*esResponse.Source,&video)
}

func isVideoExists(videoID string)bool{
    client,_ := elastic.NewClient(elastic.SetURL("http://127.0.0.1:19200"))
    defer client.Stop()
    exist,_ := client.Exists().Index("video").Type("video").Id(videoID).Do(context.Background())
    if !exist{
        log.Println("video ID may be incorrect! ",videoID)
        return false
    }
    return true
}

func(show *Show) ReadShow(showID string){
    client,_ := elastic.NewClient(elastic.SetURL("http://127.0.0.1:19200"))
    defer client.Stop()
    if !isShowExists(showID){
        return
    }
    esResponse,err := client.Get().Index("show").Type("show").Id(showID).Do(context.Background())
    if err != nil {
        log.Println("Failed to read ES data of ID:! ",showID)
        return
    }
    json.Unmarshal(*esResponse.Source,&show)
}

func isShowExists(showID string)bool{
    client,_ := elastic.NewClient(elastic.SetURL("http://127.0.0.1:19200"))
    defer client.Stop()
    exist,_ := client.Exists().Index("show").Type("show").Id(showID).Do(context.Background())
    if !exist{
        log.Println("show ID may be incorrect! ",showID)
        return false
    }
    return true
}

重構中——分析代碼結構

  • 優勢:處理流程比較清晰(沒有複雜的調用邏輯,想不清晰都難......)
  • 缺點:
    • 面向過程!致使每個過程都須要定義新的函數來處理,讀取節目須要 ReadVideo(),讀取節目集須要ReadShow()。不管這個新加的功能是否是已有相似的實現可複用
    • 代碼冗餘!上邊的代碼有很是嚴重的冗餘,一個是判斷ID是否在ES庫中,一個是讀取功能的實現,除了ES的路徑不一樣,video是 video->video->VIDEO_ID;show 則是 show->show->SHOW_ID;其他的代碼基本一致

重構中——看似可行的方案

  • 加一個interface類型參數,再加一個指示類型的參數,只用一個Read函數,一個isExists函數就行。
func Read(media interface{}, mediaType, id string){
    client,_ := elastic.NewClient(elastic.SetURL("http://127.0.0.1:19200"))
    defer client.Stop()
    if !isExists(id){
        return
    }
    esResponse,err := client.Get().Index(mediaType).Type(mediaType).Id(id).Do(context.Background())
    if err != nil {
        log.Println("Failed to read ES data of ID:! ",id)
        return
    }
    json.Unmarshal(*esResponse.Source,&media)
}

func isExists(mediaType, id string)bool{
    client,_ := elastic.NewClient(elastic.SetURL("http://127.0.0.1:19200"))
    defer client.Stop()
    exist,_ := client.Exists().Index(mediaType).Type(mediaType).Id(id).Do(context.Background())
    if !exist{
        log.Println("ID may be incorrect! ",id)
        return false
    }
    return true
}
  • 緣由 爲何說這是一個看似可行的方案呢?
  1. 增長了參數個數,Read增長了兩個,isExists增長了一個。一個函數的參數天然是越少越好。增長參數,差評+1
  2. 程序的健壯性下降了!一旦mediaType沒有準確地對應ID,程序就不能正常工做,好比傳了 "video" 和show 的ID ,要在ES的video數據庫裏邊找show數據庫的ID,天然就會失敗。另外,使用interface類型參數,一樣也會形成健壯性的下降
  3. 使用了interface類型參數。爲了可以傳入自定義的Show類型和Video類型數據,使用了interface。那麼,在調用這個函數的時候,這個參數傳int,string,bool都是合法的,可是,從實際須要的功能來講,這些參數顯然是不合法的。
  • 總結 雖然這個實現能夠減小重複代碼,可是反而增長了函數調用的風險,一旦你離職,別人接手你的代碼,這就會成爲別人的災難。程序的健壯性下降了,顯然,這個交換並不划算!那麼,有沒有什麼辦法既能減小冗餘代碼,又能不帶來其餘的負面影響,諸如下降代碼健壯性?

重構後 ヽ( ̄▽ ̄)ノ

  • 不少人會問golang是否是一個面向對象的語言,論壇上的答案很玄妙:是也不是。說不是,是由於go裏邊沒有明確的語法聲明類,說是,是由於go也能夠經過struct和interface實現面向對象的特性。How?請看重構以後的代碼!
package es

import (
    "context"
    "log"
    "encoding/json"
    "github.com/olivere/elastic"
)

type Video struct{
    VideoID string `json:"video_id"`
    //包含不少節目的其餘屬性,如節目類型,上下線狀態等,此處省略
}

type Show struct{
    ShowID string `json:"show_id"`
    //包含不少節目集的其餘屬性,如評分,演員等,此處省略
}

func(video *Video) read(videoID string){}

func(show *Show) read(showID string){}

type reader interface {
    read()
}

type esPath struct{
    ESIndex string
    ESType string
    ESID string
}

func Read(reader reader,esPath *esPath){
    client ,_:= ESClient()
    defer client.Stop()
    if !isExists(esPath){
        return
    }
    esResponse,err := client.
        Get().Index(esPath.ESIndex).Type(esPath.ESType).Id(esPath.ESID).Do(context.Background())
    if err != nil {
        logger.LogPrintln("Failed to read ES data of ID:! ",esPath.ESID)
        return
    }
    json.Unmarshal(*esResponse.Source,&reader)
}

func isExists(esPath *esPath)bool{
    client,_ := ESClient()
    defer client.Stop()
    exist,_ := client.Exists().Index(esPath.ESIndex).Type(esPath.ESType).Id(esPath.ESID).Do(context.Background())
    if !exist{
        logger.LogPrintln("ShowID may be incorrect! ",esPath.ESID)
        return false
    }
    return true
}
  • 簡單說明一下爲什麼定義了一個新struct esPath,完整的程序還有修改,新增,刪除等功能,能夠複用這個結構。
  • video和show都有read函數,因此實現了reader接口。在調用Read函數的時候只能使用show或者video類型,不然會報類型錯誤。避免了interface類型什麼數都能瞎傳的問題。在某種程度上,能夠說video和show是reader類的對象,均可以用Read()函數讀出reader類的對象。使用接口可以大大減小代碼冗餘!
相關文章
相關標籤/搜索