1. 程式人生 > >順序執行模組程式碼處理技巧——error轉panic

順序執行模組程式碼處理技巧——error轉panic

問題描述

完成某項具體工作時, 通常會將工作拆分成多個子模組, 然後將各個子模組進行有效組合即可解決問題. 當各個子模組具有嚴格依賴關係時, 通常對每一個子模組的執行結果進行判斷, 程式碼結構如下: 

func processBigProgram() (da interface{}, err error) {
	data, err := f1()
	if err != nil {
	    return data, err
	}
	data, err = f2()
	if err != nil {
	    return data, err
	}
	data, err = f3()
	if err != nil {
	    return data, err
	}
	data, err = f4()
	if err != nil {
	    return data, err
	}
	return
}

不難看出完成該工作只需要依次執行f1、f2、f3、f4. 但是每個函式(子模組)執行完成之後都對err進行判斷, 使得簡單的主線被掩藏. 程式碼行數被放大了4倍. 當然也可以將判斷與函式執行放到一行, 但是並沒有解決任何問題.

嘗試解決

各個子模組執行完成之後需要判定執行過程中是否發生err, 若err!=nil, 返回相應結果. 程式碼功能完全相同, 因此可以將該部分程式碼提取為函式proxyFunc, 當proxyFunc執行有err時通過傳送panic, 中斷程式繼續向下執行. 最後通過defer接收panic, 解析最終返回結果. 完整實現程式碼如下. 通過proxyFunc處理之後, test()主脈絡更清晰, 程式碼更容易閱讀, 也更整潔. 

package main

import (
	"encoding/json"
	"fmt"
	"github.com/pkg/errors"
	"math/rand"
	"os"
	"time"
)

type result struct {
	data interface{}
	errV error
}

type Music struct {
	name string
	id   int
}

func main() {
	res := test()
	if res == nil {
		fmt.Println("result is nil")
		os.Exit(1)
	}
	data, ok := res.(Music)
	if !ok {
		fmt.Println("result is not music")
		os.Exit(1)
	}
	fmt.Println(data)
}

func test() (data interface{}) {
	defer func() {
		re := recover()
		r, ok := re.(result)
		if ok {
			b, _ := json.Marshal(r)
			fmt.Println("system error,data:", string(b))
			data = r
		} else {
			fmt.Println("other panic process...")
		}
	}()

	var music = Music{name: "origin", id: 0}

	music = proxyFunc(test1, validateTrue, music).(Music)
	music = proxyFunc(test2, validateTrue, music).(Music)
	music = proxyFunc(test3, validateTrue, music).(Music)
	music = proxyFunc(test4, validateTrue, music).(Music)
	fmt.Println("run success!!!!!")

	return music
}

func proxyFunc(f func(param ...interface{}) (data interface{}, err error), validate func(data interface{}, err error), param ...interface{}) (res interface{}) {
	data, err := f(param)
	validate(data, err)
	return data
}

func validateTrue(data interface{}, err error) {
	if err != nil {
		panic(result{data: data, errV: err})
	}
}

func test1(param ...interface{}) (data interface{}, err error) {
	rand.Seed(time.Now().UnixNano())
	var music = Music{name: "test1", id: 1}
	if rand.Int31n(12)%3 == 0 {
		err = errors.New("test1 error")
		fmt.Println("test1 error")
		return data, err
	}
	fmt.Println("test1 success")
	return music, nil
}

func test2(param ...interface{}) (data interface{}, err error) {
	rand.Seed(time.Now().UnixNano())
	var music = Music{name: "test2", id: 2}
	if rand.Int31n(12)%3 == 0 {
		err = errors.New("test3 error")
		fmt.Println("test2 error")
		return data, err
	}
	fmt.Println("test2 success")
	return music, nil
}

func test3(param ...interface{}) (data interface{}, err error) {
	rand.Seed(time.Now().UnixNano())
	var music = Music{name: "test3", id: 3}
	if rand.Int31n(12)%3 == 0 {
		err = errors.New("test3 error")
		fmt.Println("test3 error")
		return data, err
	}
	fmt.Println("test3 success")
	return music, nil
}

func test4(param ...interface{}) (data interface{}, err error) {
	rand.Seed(time.Now().UnixNano())
	var music = Music{name: "test4", id: 4}
	if rand.Int31n(12)%3 == 0 {
		err = errors.New("test4 error")
		fmt.Println("test4 error")
		return data, err
	}
	fmt.Println("test4 success")
	return music, nil
}