iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 8
1
Software Development

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

Day 8 消除常見迴圈

幾乎的所有的代碼中都會包含有迴圈,但是都沒正式聊到好好可以如何改善,今天就來說說迴圈可以怎麼調整,主要會以LINQ來改寫一些常見的迴圈形式。

  1. 把每一個元素轉換成另外一個元素,例如:

    • 把string集合轉換成int集合
    var numbers = new List<int>();
    for (var stringNumber in stringNumbers)
    {
    		numbers.Add(int.Parse(stringNumber));
    }
    

    ⇒ 可以使用Select取代

    var numbers = stringNumbers.Select(stringNumber => int.Parse(stringNumber));
    
  2. 搜尋符合條件元素,例如:

    • 在int集合中找尋偶數
    var evenNumbers = new List<int>();
    for (var number in numbers)
    {
    		if (number % 2 == 0)
    		{
    				evenNumbers.Add(number);
    		}
    }
    

    ⇒ 可以使用Where取代

    var evenNumbers = numbers.Where(numbers => numbers % 2 == 0);
    
  3. 計算每一個元素的得出另一個狀態或數值,例如:

    • 計算集合的總和
    int sum = 0;
    for (var number in numbers)
    {
    		sum += number;
    }
    

    ⇒ 可以使用Sum取代

    int sum = numbers.Sum();
    
    • 判斷集合中是否每個元素都符合條件
    int allLargeThanZero = true;
    for (var number in numbers)
    {
    		if (number <= 0)
    		{
    				allLargeThanZero = false;
    		}
    }
    

    ⇒ 可以使用All取代

    int allLargeThanZero = numbers.All(number => number <= 0);
    
    • 找出數字集中最大/最小的數字
    var max = 0;
    var min = 0;
    for (var number in numbers)
    {
    		if (max < number)
    		{
    				max = number;
    		}
    
    		if (min > number)
    		{
    				min = number;
    		}
    }
    

    ⇒ 可以使用Max/Min取代

    var max = numbers.Max(number => number);
    var min = numbers.Min(number => number);
    
  4. 使用自定義規則去綜合每一個元素的狀態得出另一個狀態,例如:

    • 用string集合中的每一個string開頭字元組出另一個string
    var newString = "";
    for (var str in strings)
    {
    		newString += str[0];
    }
    

    ⇒ 可以使用更泛用Aggregate取代

    var newString = strings.Aggregate("", (newString, str) => newString + str);
    
  5. 把每一次的迭代當作下一次迭代的輸入,得出另一個狀態,例如:

    public int Play(int count, int step)
    {
        var candidates = Enumerable.Range(1, count).ToList();
    
        int start = 0;
        while (!HasSurvivor(candidates))
        {
            var victim = FindVictim(step, start, candidates);
            candidates = NextCandidates(candidates, victim);
            start = victim;
        }
    
        return candidates.First();
    }
    
    private List<int> NextCandidates(List<int> candidates, int victim)
    {
        return candidates.Where((_, index) => index != victim).ToList();
    }
    
    private int FindVictim(int step, int start, List<int> candidates)
    {
        return (start + step - 1) % candidates.Count();
    }
    
    private bool HasSurvivor(List<int> candidates)
    {
        return candidates.Count() == 1;
    }
    

    在這種情況中可以使用遞迴的方式來消除迴圈,讓start的狀態保存在每個方法的迭代之中,而不需要自己維護。也能讓暫存變數變成參數,避免閱讀代碼時過度關注暫存變數。

    public int Play(int count, int step)
    {
        var candidates = Enumerable.Range(1, count).ToList();
    
        return FindSurvivor(candidates, 0, step);
    }
    
    private int FindSurvivor(List<int> candidates, int start, int step)
    {
        if (HasSurvivor(candidates))
        {
            return candidates.First();
        }
    
        var victim = FindVictim(start, step, candidates.Count());
        return FindSurvivor(NextCandidates(candidates, victim), victim, step);
    }
    
    private List<int> NextCandidates(List<int> candidates, int victim)
    {
        return candidates.Where((candidate, index) => index != victim).ToList();
    }
    
    private int FindVictim(int start, int step, int count)
    {
        return (start + step - 1) % count;
    }
    
    private bool HasSurvivor(List<int> candidates)
    {
        return candidates.Count() == 1;
    }
    

    使用遞迴的方式雖然可以消除迴圈,但對於不熟悉遞迴的人來說可能會大大增加閱讀的困難。所以使用時還是得考慮實際場景,如果寫代碼的速度比較快,甚至可以把兩種版本都寫出來比較差異。

其他語言例如Javascript、Dart也都有許多方便的集合的Util方法,例如:map, reduce。寫迴圈的時候,或許可以思考一下迴圈的目的,使用更直觀的寫法。以上是自己過去經驗中比較常遇到的情境,不知道大家在寫代碼時都是什麼情況呢?


上一篇
Day 7 if判斷式的使用
下一篇
Day 9 抽象你的代碼
系列文
在Kata中尋找Clean Code是否搞錯了什麼30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言