iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 21
0
Software Development

在Kata中尋找Clean Code是否搞錯了什麼系列 第 21

Day 21 集合容易散發的壞味道

我們時常在各種場合使用List,例如交友軟體的黑名單、部落格的文章列表、購物車裡面的商品清單...等等。今天我們用個有趣一點的東西來舉例,假設我們要一個龍珠雷達,支援兩種顯示模式

  1. 顯示某一個可視範圍內的龍珠
  2. 顯示所有可視範圍內的龍珠
public class DragonBallRadar
{
    private readonly List<DragonBall> _dragonBalls;
    private const int MaxVisibleDistance = 100;

    ...
    
    public RadarScreen Visible(int index)
    {
        return new RadarScreen()
        {
            UserPosition = GetUserPosition(),
            DragonBalls = new List<DragonBall>() { _dragonBalls[index] },
            Scale = GetScale(),
            Rotation = GetRotation(),
        };
    }

		public RadarScreen Visible()
    {
        return new RadarScreen()
        {
            UserPosition = GetUserPosition(),
            DragonBalls = GetVisibleDragonBalls(),
            Scale = GetScale(),
						Rotation = GetRotation(),
        };
    }

    private IEnumerable<DragonBall> GetVisibleDragonBalls()
    {
        return _dragonBalls.Where(ball => ball.Position.GetDistance() < MaxVisibleDistance);
    }

		...
}

在DragonBallRadar中,我們可以發現MaxVisibleDistance和GetDragonBallsIn都只跟_dragonBalls有相依,有Feature Envy的壞味道,所以我們把_dragonBalls和其相關的方法、欄位抽取類別,提升DragonBallRadar與DragonBalls的內聚力。

public class DragonBalls
{
    private readonly List<DragonBall> _balls;
    private const int MaxVisibleDistance = 100;

    public DragonBalls(List<DragonBall> balls)
    {
        _balls = balls;
    }

    public IEnumerable<DragonBall> GetVisibleDragonBalls()
    {
        return _balls.Where(ball => ball.Position.GetDistance() < MaxVisibleDistance);
    }

    public DragonBall GetDragonBallAt(int index)
    {
        return _balls[index];
    }
}

public class DragonBallRadar
{
    private readonly DragonBalls _dragonBalls;

    ...
    
    public RadarScreen Visible(int index)
    {
        return new RadarScreen()
        {
            ...
            DragonBalls = new List<DragonBall>() {_dragonBalls.GetDragonBallAt(index)},
            ...
        };
    }
    
    public RadarScreen Visible()
    {
        return new RadarScreen()
        {
            ...
            DragonBalls = _dragonBalls.GetVisibleDragonBalls(),
            ...
        };
    }

    ...
}

再抽的過程中可以發現為了將_dragonBalls[index]也放到DragonBalls,我們不得不為他在建一個方法,讓DragonBallRadar用index存取特定龍珠,這會讓代碼稍微複雜一點。此時我們可以透過覆寫operator,讓DragonBalls可以維持原本的作法而不必多一個方法。

public class DragonBalls
{
		private readonly List<DragonBall> _balls;
    ...

    public DragonBall this[int index] => _balls[index];
}

public class DragonBallRadar
{
		private readonly DragonBalls _dragonBalls;
    ...
    
    public RadarScreen Visible(int index)
    {
        return new RadarScreen()
        {
            ...
            DragonBalls = new List<DragonBall>() { _dragonBalls[index] },
            ...
        };
    }
    
    ...
}

透過覆寫[] operator,讓我們可以即便抽取了List到某個類別之後,依舊可以維持我們熟悉的陣列用法,維持使用端的可讀性。關於覆寫operator不只能用在[ ]操作,還能用在其他像是常用的加法,我們之後會再針對這個多聊一些。

針對抽取把_dragonBalls抽了一個類別,我們可以發現GetVisibleDragonBall()方法名稱上有DragonBall,造成使用的時候變成變數名稱有dragonBall,方法名稱又出現了一次,名稱多餘的重複。

_dragonBalls.GetVisibleDragonBalls()

此時我們應該把方法名稱上多餘的DragonBall移除。

public class DragonBalls
{
    ...

    public IEnumerable<DragonBall> GetVisibleList()
    {
        return _balls.Where(ball => ball.Position.GetDistance() < MaxVisibleDistance);
    }
}

public class DragonBallRadar
{
    private readonly DragonBalls _dragonBalls;

    ...

    public RadarScreen Visible()
    {
        return new RadarScreen()
        {
            ...
            DragonBalls = _dragonBalls.GetVisibleList(),
            ...
        };
    }

    ...
}

當我們看到DragonBalls裡面有個GetVisibleList的方法,正常來說我們會預期他會拿到DragonsBall list,而不是其他類別。不太需要擔心從方法名稱上看不出來是要得到什麼,因為類別名稱很多時候會幫這個方法解釋。

透過代碼審查,持續思考代碼還能如何改善,從類別或方法的職責,或是他們的名稱,都是需要不斷的思考並調整,讓代碼越來越簡潔乾淨。


上一篇
Day 20 更多的壞味道
下一篇
Day 22 覆寫operator
系列文
在Kata中尋找Clean Code是否搞錯了什麼30

尚未有邦友留言

立即登入留言