java 代码 重构 golang_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

}

原因 为什么说这是一个看似可行的方案呢?

增加了参数个数,Read增加了两个,isExists增加了一个。一个函数的参数自然是越少越好。增加参数,差评+1

程序的健壮性降低了!一旦mediaType没有准确地对应ID,程序就不能正常工作,比如传了 "video" 和show 的ID ,要在ES的video数据库里边找show数据库的ID,自然就会失败。另外,使用interface类型参数,同样也会造成健壮性的降低

使用了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类的对象。使用接口能够大大减少代码冗余!

有疑问加站长微信联系(非本文作者)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值