6
\$\begingroup\$

I'm currently writing a game of chess, and, more specifically, I'm creating the code for the game board. The board is simply a static class that contains a multi-dimensional array representing the board, and methods to interact with it. The method in question for the purposes of this question, is called TryGetEntity:

private static GameEntity[,] _board = new GameEntity[8,8];
...
public static bool TryGetEntity(BoardCoordinate requestedPosition, out GameEntity occupyingEntity) {
    occupyingEntity = null;
    if (_board.GetLength(0) < requestedPosition.X || _board.GetLength(1) < requestedPosition.Y)
        return false;
    
    if (_board[requestedPosition.X, requestedPosition.Y] != null)
        occupyingEntity = _board[requestedPosition.X, requestedPosition.Y];
    else return false;
    return true;
}

My concern with this implementation is the default return value of true. I've been taught over many years to assume failure which loosely translates to returning false by default. Currently, I'm at war with myself on refactoring this to:

occupyingEntity = null;
if (_board.GetLength(0) >= requestedPosition.X || _board.GetLength(1) >= requestedPosition.Y) {
    if (_board[requestedPosition.X, requestedPosition.Y] != null) {
        occupyingEntity = _board[requestedPosition.X, requestedPosition.Y];
        return true;
    }
}

return false;

I personally think that the second version reads better, but I'm wanting a second (or more) opinion on it.


Edit: Per request from the comments, here is the entire GameBoard class, along with an example use-case (this project is still in very early phases, so no actual calls exist yet):

public static class GameBoard {
    private static GameEntity[,] _board = new GameEntity[8,8];
    public static bool TryGetEntity(BoardCoordinate requestedPosition, out GameEntity occupyingEntity) {
        occupyingEntity = null;
        if (_board.GetLength(0) >= requestedPosition.X || _board.GetLength(1) >= requestedPosition.Y) {
            if (_board[requestedPosition.X, requestedPosition.Y] != null) {
                occupyingEntity = _board[requestedPosition.X, requestedPosition.Y];
                return true;
            }
        }

        return false;
    }
}

// Example use-case:
var requestedCoordinate = new BoardCoordinate(3, 3);
if (GameBoard.TryGetEntity(requestedCoordinate, out GameEntity occupyingPiece))
    CapturePiece(occupyingPiece);
else
    MoveToPosition(requestedCoordinate);

Does this implementation of the try pattern, in its originally presented state, seem easy to follow? How about the second version?

Edit: I think, that by posting this question, I sort-of answered it. Given that it made me stop and seek a second opinion in its original state, I suspect the same would be true for others. Additionally, now that I've gotten some sleep, the first version is much harder to read than the second. It's not more complex, just, cluttered and doing too much. I'll keep this question up for others to answer in greater detail, but I'm not going to be surprised if the consensus is "the first method is harder to read".

Note: Let's ignore formatting for this post.

\$\endgroup\$
1
  • 3
    \$\begingroup\$ Any an all critiques of the code should be posted in answers. Posting critiques of the code in comments are subject to removal for being a violation of the commenting privilege - "Comments are not recommended for any of the following: Answering a question or providing an alternate solution to an existing answer; instead, post an actual answer (or edit to expand an existing one);". \$\endgroup\$
    – Peilonrayz
    Commented Aug 16, 2021 at 9:41

6 Answers 6

10
\$\begingroup\$

First version:

  • You shouldn't omit braces, {}, although they might be optional, because that can lead to hidden and therefore hard-to-track bugs. Having braces helps structuring the code as well.
  • Instead of the else you just could return early inside the if block and have a return false; afterwards.

This would result in

public static bool TryGetEntity(BoardCoordinate requestedPosition, out GameEntity occupyingEntity)
{
    occupyingEntity = null;
    if (_board.GetLength(0) < requestedPosition.X || _board.GetLength(1) < requestedPosition.Y)
    {
        return false;
    }

    if (_board[requestedPosition.X, requestedPosition.Y] != null)
    {
        occupyingEntity = _board[requestedPosition.X, requestedPosition.Y];
        return true;
    }
    return false;
}

which is IMO easy to read.

The second version:

Well, I think the nested ifs are hurting readability. I would at least keep the first if separately and would change the ..... no, forget it. I would just take the first version and change it like so

public static bool TryGetEntity(BoardCoordinate requestedPosition, out GameEntity occupyingEntity)
{
    occupyingEntity = null;
    if (_board.GetLength(0) < requestedPosition.X || _board.GetLength(1) < requestedPosition.Y)
    {
        return false;
    }

    occupyingEntity = _board[requestedPosition.X, requestedPosition.Y];
    return occupyingEntity != null;
}
\$\endgroup\$
1
  • 4
    \$\begingroup\$ Just because you can add braces everywhere doesn't mean you have to. When you have lots of if... else if ... else if ... else and they're all one-liners, you double the length by adding the redundant braces. I agree, though, that if at least one of the if's needs the braces, the whole block should get them. \$\endgroup\$
    – Haukinger
    Commented Aug 17, 2021 at 9:59
9
\$\begingroup\$

Yes the code is readable.

I'm curious why you feel you need multiple return statements. The return statement can return a boolean expression.

public static class GameBoard
{
    private static GameEntity[,] _board = new GameEntity[8, 8];
    public static bool TryGetEntity(BoardCoordinate requestedPosition, out GameEntity occupyingEntity)
    {
        occupyingEntity = null;
        if (_board.GetLength(0) >= requestedPosition.X || _board.GetLength(1) >= requestedPosition.Y)
        {
            if (_board[requestedPosition.X, requestedPosition.Y] != null)
            {
                occupyingEntity = _board[requestedPosition.X, requestedPosition.Y];
            }
        }

        return occupyingEntity != null;
    }
}
\$\endgroup\$
3
  • 1
    \$\begingroup\$ I think I wrote it that way due to lack of sleep and all the while trying to be explicit with the intent of the code. By far, I like this approach much more than my initial version. Thanks for taking the time to answer! \$\endgroup\$
    – Taco
    Commented Aug 16, 2021 at 14:24
  • 3
    \$\begingroup\$ IMHO this is a bad solution. _board[requestedPosition.X, requestedPosition.Y] gets used twice, plus there is a second level of if and that second level isn't even necessary since if _board[requestedPosition.X, requestedPosition.Y] is null, there is no danger in assigning that value to occupyingEntity anyway. \$\endgroup\$
    – BCdotWEB
    Commented Aug 16, 2021 at 16:50
  • \$\begingroup\$ @BCdotWEB IMHO, this answer is true to the message it's trying to convey, regardless of the redundant array access. The takeaway here is to return an expression instead of utilizing multiple return statements. As such, I wouldn't say this is a bad solution, in-fact, it's a great solution; it just has an oversight on the redundancy. \$\endgroup\$
    – Taco
    Commented Aug 17, 2021 at 13:53
5
\$\begingroup\$

I don't know C#, but this is what Nullable types are for. From what I can tell, they obsolete the TryX+out parameter combination.

So your method signature should be more like:

public static GameEntity? GetEntity(BoardCoordinate requestedPosition)

Where you either return a valid GameEntity directly, or just return null.

\$\endgroup\$
9
  • 2
    \$\begingroup\$ Not sure if this was the answer you were speaking on earlier, but it's a great point. Since you pointed out that you're not well versed in C#, I'd like to point out that GameEntity is a reference type, and as such it is null by default. However, it would be good practice if I added explicit declaration that null is permitted within my method by using the nullable annotation GameEntity? as you suggest. +1 \$\endgroup\$
    – Taco
    Commented Aug 16, 2021 at 19:56
  • \$\begingroup\$ @Tacoタコス Yep, and then you get a nicer syntax at the callsite :) \$\endgroup\$
    – Alexander
    Commented Aug 16, 2021 at 20:01
  • 2
    \$\begingroup\$ A significant amount of the time, enough to feel like always, when I program (in Python) I use if None is not (value := foo.get(bar)):. I did like C# having tryGet because it'd make all those if statements quite a bit nicer IMO - if foo.tryGet(bar, value):. Ultimately I think providing both a tryGet and get gives the user the ability to pick the nicest option for the situation. \$\endgroup\$
    – Peilonrayz
    Commented Aug 16, 2021 at 22:45
  • \$\begingroup\$ Try to beat if (board.TryGetPiece( position, out var piece )) in terms of nice syntax with Nullable and lots of ?... perhaps in the future, but right now you can't even write piece?.Color = red; \$\endgroup\$
    – Haukinger
    Commented Aug 17, 2021 at 9:51
  • \$\begingroup\$ "but right now you can't even write piece?.Color = red;" Has that syntax not shipped yet? \$\endgroup\$
    – Alexander
    Commented Aug 17, 2021 at 15:19
4
\$\begingroup\$

Your first version of TryGetEntity essentially follows this structure:

public static bool TryGetEntity(BoardCoordinate x, out GameEntity ret) {
    ret = null;
    if (condition1)
        return false;
    
    if (! condition2)
        ret = value;
    else return false;
    return true;
}

It’s otherwise clean, but this structure is just … weirdly inconsistent. Why not write the conditions as consistent guard clauses with early exits, and put the successful code path at the end (saving one else in the process)?

public static bool TryGetEntity(BoardCoordinate x, out GameEntity ret) {
    ret = null;

    if (condition1)
        return false;
    if (condition2)
        return false;
    
    ret = value;
    return true;
}

This makes the logic much more obvious.

\$\endgroup\$
3
\$\begingroup\$

To me the code presented by @pacmaninbw is close to what I would write, but I would simplify it to avoid the second if-statement giving:

public static bool TryGetEntity(BoardCoordinate requestedPosition, out GameEntity occupyingEntity) {
     occupyingEntity = null;

     if (_board.GetLength(0) >= requestedPosition.X || _board.GetLength(1) >= requestedPosition.Y) {
          occupyingEntity = _board[requestedPosition.X, requestedPosition.Y];
     }

     return occupyingEntity != null;
}

I also find that this style of {...} is less impactful making it easier to consistently add it everywhere.

\$\endgroup\$
-1
\$\begingroup\$

I don't know C# but this is how I would write the code

public static class GameBoard {
    private static GameEntity[,] _board = new GameEntity[8,8];
    public static GameEntity TryGetEntity(x, y) {
        if (_board.GetLength(0) < x && _board.GetLength(1) > y)
            return null;
        
        return _board[x, y];
    }
}

// Example use-case:
GameEntity occupyingPiece = GameBoard.TryGetEntity(3,3);
if (occupyingPiece != null)
    CapturePiece(occupyingPiece);
else
    MoveToPosition(3,3);

Syntax might not be 100% correct, as I said I don't know much C#, but the theme should be the same:

  1. Do your checks and return null rather than true/false
  2. Check for null on return, better on a variable than on a function
  3. Coordinates class seemed redundant

Without seeing your entire code something tells me that your choice of statics is a bit exaggerated.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ Thanks for taking the time to answer, and welcome to CodeReview! The try pattern is designed to return a boolean value indicating if the attempt was successful or not. It’s great to explicitly check for null after getting the value back out, though if the method returns false, we already know the output is invalid and as such, within the scope of the use-case, we don’t use the it. \$\endgroup\$
    – Taco
    Commented Aug 17, 2021 at 8:16
  • \$\begingroup\$ @Tacoタコスreturning a boolean result and a value makes redundant code/logic and makes the program prone to errors. If the function returns false then the returned value must be null, and if the function returns true then the returned value must not be null. Any other scenario (which can happen with the original code) is wrong and would require development time tracing the problem. I will admit that I am not familiar with a 'try pattern', is it a design pattern? Even if it is, design patterns are guidelines or skeleton structures and can/should be adapted where it makes sense. \$\endgroup\$
    – slepax
    Commented Aug 17, 2021 at 9:30
  • 2
    \$\begingroup\$ @slepax It’s an established pattern in C#. Yes, there are better modern solutions but the API pattern is from a time before C# supported those. To stay consistent with those APIs, the pattern persists, and it’s best practice to follow it. At any rate the pattern really isn’t error-prone at all, just unnecessarily verbose. \$\endgroup\$ Commented Aug 17, 2021 at 9:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.