DEV Community

Julian-Chu
Julian-Chu

Posted on

[Go] use custom type to improve your code

#go

I will not discuss struct type here, what I would like to focus is custom type for basic type. What is a custom type for basic type? You could think it likes a alias name of basic type, for example:

type Username string
type ErrorCode int

Readability

Why we should use custom type instead of basic type?
One of my opinion is readability, like the above example, if you only give an int for error code, it's not so easy to understand.

compare the following 2 functions, assign specific type to arguments make the function easier to understand

func Print(s string) {
    fmt.Println(s)
}

func Print(u Username)  {
    fmt.Println(u)
}

Cohesion

Another benefit is, it can have superpower to use method like struct.
Then the code has high cohesion, and because of explicit/specific type, we could have more intellisense/auto-complete support from editor/IDE.

func Print(s string) {
    fmt.Println(s)
}

func Print(u Username)  {
    fmt.Println(u)
}

func (u Username) Print() {
    fmt.Println(u)
}

Let's write another example:
scenario: we would like to extract origin, productId, prodcution year, batch number from a product serial number,

first version: only use basic type

func main() {
    p := "XX-0010-2020-0015"
    fmt.Println(GetOrigin(p))
    fmt.Println(GetProductId(p))
    fmt.Println(GetProductionYear(p))
    fmt.Println(GetBatchNumber(p))
}

const (
    origin = iota
    productId
    productionYear
    batchNumber
)

func GetOrigin(s string) string {
    return getInformation(s, origin)
}

func GetProductId(s string) string {
    return getInformation(s, productId)
}
func GetProductionYear(s string) string {
    return getInformation(s, productionYear)
}

func GetBatchNumber(s string) string {
    return getInformation(s, batchNumber)
}

func getInformation(s string, field int) string {
    return strings.Split(s, "-")[field]
}

second version: use custom type of string

func main() {
    p:= ProductSerialNumber("XX-0010-2020-0015")
    fmt.Println(p.GetOrigin())
    fmt.Println(p.GetProductId())
    fmt.Println(p.GetProductionYear())
    fmt.Println(p.GetBatchNumber())
}
type ProductSerialNumber string

const (
    origin = iota
    productId
    productionYear
    batchNumber
)

func (p ProductSerialNumber) GetOrigin() string {
    return  p.getInformation(origin)
}

func (p ProductSerialNumber) GetProductId() string {
    return p.getInformation(productId)
}

func (p ProductSerialNumber) GetProductionYear() string {
    return p.getInformation(productionYear)
}

func (p ProductSerialNumber) GetBatchNumber() string {
    return p.getInformation(batchNumber)
}

func (p ProductSerialNumber) getInformation(field int) string {
    return strings.Split(string(p), "-")[field]
}

Which one you prefer, if you may change or reorganize code 1 year later.

Example in standard library

Let's look one example in encoding/json.
json.Number is essentially a string which decode a number from json into string format, but with custom type, we can easily convert to string/float/int
as we need.

json.Number

// A Number represents a JSON number literal.
type Number string

// String returns the literal text of the number.
func (n Number) String() string { return string(n) }

// Float64 returns the number as a float64.
func (n Number) Float64() (float64, error) {
    return strconv.ParseFloat(string(n), 64)
}

// Int64 returns the number as an int64.
func (n Number) Int64() (int64, error) {
    return strconv.ParseInt(string(n), 10, 64)
}

Conclusion

Yes, using custom type may need to write more code(but not so much), but it provides good readability and cohesion. From my point of view, it's worthwhile.

Top comments (0)