How to write a check function gracefully

Posted Jun 16, 20203 min read

Sometimes, in order to check the input parameters, there are many items that need to be checked. If one by one if-else is judged, it will look very low. Let's look at an ugly way of writing:

func checkQeuryParam(c *condition) bool {
    if c.offset> 100000 {
        return false
    }

    if c.limit> 100 {
        return false
    }

    if c.timebegin != "" {
        zone := time.FixedZone("CST", 8*3600)
        t, err := time.ParseInLocation("2006-01-02T15:04:05.000+0800", c.timebegin, zone)
        if err != nil {
            aialog.Error.Printf("Query time%s is invalid.\n", c.timebegin)
            return false
        }
        c.timebegin = t.Format("2006-01-02T15:04:05.000+0800")
    }

    //ip no check
    //come_from no check

    //event
    if c.event != "" {
        if c.event != "added" && c.event != "modified" && c.event != "deleted" {
            return false
        }
    }

    //ruleid
    if c.ruleid != "" {
        id, err := strconv.Atoi(c.ruleid)
        if err != nil {
            return false
        }

        //id range, temporary
        if id <0 || id> 1000000 {
            return false
        }
    }

    if c.rulelevel != "" {
        level, err := strconv.Atoi(c.rulelevel)
        if err != nil {
            return false
        }

        //level range 0-16
        if level <0 || level> 15 {
            return false
        }
    }

    if c.agentid != "" {
        if len(c.agentid)> 64 {
            return false
        }
    }

    //order
    if c.order != "" {
        if c.order != "desc" && c.order != "asc" {
            return false
        }
    }

    return true
}
  1. Poor legibility
  2. Troublesome modification
  3. Not easy to expand
  4. The printing failure information is troublesome, and it needs to be added for each exception

Adjust as follows:

func(c *condition)checkOffset() bool {
    if c.offset> 100000 {
        return false
    }

    return true
}

func(c *condition)checkLimit() bool {
    if c.limit> 100 {
        return false
    }

    return true
}

func(c *condition)checkTime() bool {
    if c.timebegin != "" {
        zone := time.FixedZone("CST", 8*3600)
        t, err := time.ParseInLocation("2006-01-02T15:04:05.000+0800", c.timebegin, zone)
        if err != nil {
            aialog.Error.Printf("Query time%s is invalid.\n", c.timebegin)
            return false
        }
        c.timebegin = t.Format("2006-01-02T15:04:05.000+0800")
    }

    return true
}

func(c *condition)checkEvent() bool {
    if c.event != "" {
        if c.event != "added" && c.event != "modified" && c.event != "deleted" {
            return false
        }
    }

    return true
}


func(c *condition)checkRuleid() bool {
    if c.ruleid != "" {
        id, err := strconv.Atoi(c.ruleid)
        if err != nil {
            return false
        }

        //id range, temporary
        if id <0 || id> 1000000 {
            return false
        }
    }

    return true
}

func(c *condition)checkRulelevel() bool {
    if c.rulelevel != "" {
        level, err := strconv.Atoi(c.rulelevel)
        if err != nil {
            return false
        }

        //level range 0-16
        if level <0 || level> 15 {
            return false
        }
    }

    return true
}

func(c *condition)checkAgentid() bool {
    if c.agentid != "" {
        if len(c.agentid)> 64 {
            return false
        }
    }

    return true
}

func(c *condition)checkOrder() bool {
    if c.order != "" {
        if c.order != "desc" && c.order != "asc" {
            return false
        }
    }

    return true
}

func checkQeuryParam(c *condition) bool {
    checks := []struct{
        name string
        fn func() bool
    }{
        {"check offset", c.checkOffset},
        {"check limit", c.checkLimit},
        {"check time", c.checkTime},
        {"check event", c.checkEvent},
        {"check rule id", c.checkRuleid},
        {"check rule level", c.checkRulelevel},
        {"check agent id", c.checkAgentid},
        {"check order", c.checkOrder},
    }

    for _, check := range checks {
        if !check.fn() {
            aialog.Error.Printf("%s failed.\n", check.name)
            return false
        }
    }

    return true
}
  1. Clear structure and good legibility
  2. When adding judgment, directly add a function
  3. It is more convenient to print the failure information, and the new judgment does not need to be added.

Note:
For the function name as a parameter, if the method is passed, you need to pass in the object together.
The fn above is c.checkXXX when passed in, and c is the actual call object of this method.