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类的对象。使用接口可以大大减小代码冗余!
相关文章
相关标签/搜索