以if子句的前面语句为条件

I'm having trouble with an if statement that includes a conditional on the preceding statement part of the if clause.

In this simplified example, I want to extend a lookup by id to also lookup by name, with an optional prefix to be preferred.

The psuedo code is:

IF I have a prefix AND I can find a record using the prefix and name
THEN override the record ID
ELSE IF I can find a record using name
THEN override the record ID
ELSE do something else to override the record ID

In bash it would be simple:

if test -n "$PREFIX" && id_=$(GetByName "$PREFIX:$id")
then id="$id_"
elif id_=$(GetByName "$id")
then id="$id_"
elif id_=$(legacy_attempt "$id")
then id="$id_"
fi

But how to express this in go?

This is wrong:

if PREFIX != "" && n, err := GetByName(PREFIX + ":" + id) && err == nil {
    id = n.ID
} else if n, err := GetByName(id) && err == nil {
    id = n.ID
} else if n, err := legacy_attempt(id) && err == nil {
    id = n
}

I'm guessing because the middle portion of the && sequence is a statement, not an expression (unlike C).

I've tried with = instead of := and having err and g declared out of scope but it doesn't help.

I tried such horrors as:

if if PREFIX != "" { n, err := GetByName(PREFIX + ":" + id) } ; err == nil {

but the errors were more than the scoping errors of n and err, go claimed that it was expecting an expression after the if although I was trying to give it a preceding statement consisting of an if

Because of the leading conditional on PREFIX being non-empty I can't easily convert this if condition to use a preceding statement. I want the second half to execute if the first half doesn't execute or if the first half fails.

Are there any useful go idioms to help here?

I can factor the code out to be as ugly as sin*, with repeated blocks, but I'm looking for a nice idiomatic answer.

*And I mean ugly because of the two else if clauses.

You can't do this with a single if statement, so use 2 of them. And you may use a "state" variable to track if the first lookup succeeds:

found := false
if PREFIX != "" {
    if n, err := GetByName(PREFIX + ":" + id); err == nil {
        id, found  = n.ID, true
    }
}
if !found {
    if n, err := GetByName(id); err == nil {
        id = n.ID
    }
}

If you create a helper function, you can make your original task more compact. Image this utility function:

func lookup(name string, id *string) (ok bool) {
    if n, err := GetByName(name); err == nil {
        *id = n.ID
        return true
    }
    return false
}

With this, your original task will be:

_ = PREFIX != "" && lookup(PREFIX+":"+id, &id) || lookup(id, &id)

Correctness of the above line builds on the short-circuit evaluation of the bool logic. That is, if PREFIX is non-empty and the first lookup succeeds, the second lookup will not be called. If PREFIX is empty or the first lookup "fails", the second lookup will be called.

Even though this 2nd version is very compact, I'd still go with the first, more verbose but more clean solution. Ultimately you're goal is not to write the most compact code, but the most readable, most maintainable code.

The cleaner solution would be to define a helper function:

func getId(prefix string, name string) (int, error) {
    if (prefix != "") {
        if n, err := GetByName(prefix + ":" + name); err == nil {
            return n.ID, nil
        }
    }
    return GetByName(name)
}

and call the function as that:

if newId, err := getId(prefix, name) {
   id = newId
}