Too many 'if' statements?

前端 未结 26 1074
天涯浪人
天涯浪人 2020-11-30 16:24

The following code does work how I need it to, but it\'s ugly, excessive or a number of other things. I\'ve looked at formulas and attempted to write a few solutions, but I

26条回答
  •  醉梦人生
    2020-11-30 16:45

    Other people have already suggested my initial idea, the matrix method, but in addition to consolidating the if statements you can avoid some of what you have by making sure the arguments supplied are in the expected range and by using in-place returns (some coding standards I've seen enforce one-point-of-exit for functions, but I've found that multiple returns are very useful for avoiding arrow coding and with the prevalence of exceptions in Java there's not much point in strictly enforcing such a rule anyway as any uncaught exception thrown inside the method is a possible point of exit anyway). Nesting switch statements is a possibility, but for the small range of values you're checking here I find if statements to be more compact and not likely to result in much of a performance difference, especially if your program is turn-based rather than real-time.

    public int fightMath(int one, int two) {
        if (one > 3 || one < 0 || two > 3 || two < 0) {
            throw new IllegalArgumentException("Result is undefined for arguments outside the range [0, 3]");
        }
    
        if (one <= 1) {
            if (two <= 1) return 0;
            if (two - one == 2) return 1;
            return 2; // two can only be 3 here, no need for an explicit conditional
        }
    
        // one >= 2
        if (two >= 2) return 3;
        if (two == 1) return 1;
        return 2; // two can only be 0 here
    }
    

    This does end up being less readable than it might otherwise be due to the irregularity of parts of the input->result mapping. I favor the matrix style instead due to its simplicity and how you can set up the matrix to make sense visually (though that is in part influenced by my memories of Karnaugh maps):

    int[][] results = {{0, 0, 1, 2},
                       {0, 0, 2, 1},
                       {2, 1, 3, 3},
                       {2, 1, 3, 3}};
    

    Update: Given your mention of blocking/hitting, here's a more radical change to the function that utilizes propertied/attribute-holding enumerated types for inputs and the result and also modifies the result a little to account for blocking, which should result in a more readable function.

    enum MoveType {
        ATTACK,
        BLOCK;
    }
    
    enum MoveHeight {
        HIGH,
        LOW;
    }
    
    enum Move {
        // Enum members can have properties/attributes/data members of their own
        ATTACK_HIGH(MoveType.ATTACK, MoveHeight.HIGH),
        ATTACK_LOW(MoveType.ATTACK, MoveHeight.LOW),
        BLOCK_HIGH(MoveType.BLOCK, MoveHeight.HIGH),
        BLOCK_LOW(MoveType.BLOCK, MoveHeight.LOW);
    
        public final MoveType type;
        public final MoveHeight height;
    
        private Move(MoveType type, MoveHeight height) {
            this.type = type;
            this.height = height;
        }
    
        /** Makes the attack checks later on simpler. */
        public boolean isAttack() {
            return this.type == MoveType.ATTACK;
        }
    }
    
    enum LandedHit {
        NEITHER,
        PLAYER_ONE,
        PLAYER_TWO,
        BOTH;
    }
    
    LandedHit fightMath(Move one, Move two) {
        // One is an attack, the other is a block
        if (one.type != two.type) {
            // attack at some height gets blocked by block at same height
            if (one.height == two.height) return LandedHit.NEITHER;
    
            // Either player 1 attacked or player 2 attacked; whoever did
            // lands a hit
            if (one.isAttack()) return LandedHit.PLAYER_ONE;
            return LandedHit.PLAYER_TWO;
        }
    
        // both attack
        if (one.isAttack()) return LandedHit.BOTH;
    
        // both block
        return LandedHit.NEITHER;
    }
    

    You don't even have to change the function itself if you want to add blocks/attacks of more heights, just the enums; adding additional types of moves will probably require modification of the function, though. Also, EnumSets might be more extensible than using extra enums as properties of the main enum, e.g. EnumSet attacks = EnumSet.of(Move.ATTACK_HIGH, Move.ATTACK_LOW, ...); and then attacks.contains(move) rather than move.type == MoveType.ATTACK, though using EnumSets will probably be slightly slower than direct equals checks.


    For the case where a successful block results in a counter, you can replace if (one.height == two.height) return LandedHit.NEITHER; with

    if (one.height == two.height) {
        // Successful block results in a counter against the attacker
        if (one.isAttack()) return LandedHit.PLAYER_TWO;
        return LandedHit.PLAYER_ONE;
    }
    

    Also, replacing some of the if statements with usage of the ternary operator (boolean_expression ? result_if_true : result_if_false) could make the code more compact (for example, the code in the preceding block would become return one.isAttack() ? LandedHit.PLAYER_TWO : LandedHit.PLAYER_ONE;), but that can lead to harder-to-read oneliners so I wouldn't recommend it for more complex branching.

提交回复
热议问题