如何重构 Ruby 方法以通过 Rubocop 测试同时也是 DRY

How to refactor Ruby methods to pass Rubocop tests while also being DRY

作为 Launch School 课程的一部分,我正在研究二十一游戏(将游戏视为 Blackjack 的轻量级版本)。

我需要适当地重构我自己的代码以使其保持干燥,同时还通过所有 Rubocop 测试。我目前使用的是 Rubocop 0.85.0 版本(课程需要这个版本)。

在我的游戏中,我将所有数据存储在一个名为 Keep Score 的散列中:

keep_score = { 'player_cards' => [], \
               'dealer_cards' => [], \
               'player_card_values' => [], \
               'dealer_card_values' => [], \
               'player_points' => 0, \
               'dealer_points' => 0, \
               'player_move' => '', \
               'dealer_move' => '', \
               'end_game' => false }

到目前为止我的代码可以正常工作,到目前为止我已经做了很多重构,但是我需要进一步重构这两个方法以使代码更干,同时仍然通过 Rubocop 测试。

我有两种方法,一种 convert_face_cards 转换 K、Q 和 J(A 单独处理)。然后有一个单独的方法,add_integer_points 来处理卡片上的整数值:

def convert_face_cards(keep_score)
  keep_score['player_points'] = 0
  keep_score['dealer_points'] = 0

  keep_score['player_card_values'].each do |card|
    if card == "jack" || card == "queen" || card == "king"
      keep_score['player_points'] += 10
    end
  end
  keep_score['dealer_card_values'].each do |card|
    if card == "jack" || card == "queen" || card == "king"
      keep_score['dealer_points'] += 10
    end
  end
end

def add_integer_points(keep_score)
  keep_score['player_card_values'].each do |card|
    if card.is_a? Integer
      keep_score['player_points'] += card
    end
  end
  keep_score['dealer_card_values'].each do |card|
    if card.is_a? Integer
      keep_score['dealer_points'] += card
    end
  end
end

虽然这段代码从技术角度来看是可行的,但我没有通过 Rubocop 测试。 convert_face_cards 方法返回错误:Cyclomatic complexity for convert_face_cards is too high. [7/6].

我的代码显然不够干。例如,在 convert_face_cards 方法中,我本质上是 运行 each 方法,在 player_card_values 哈希键和 dealer_card_values 上都采用相同的方式哈希键。

但我不确定如何进一步压缩这些方法。感谢任何帮助或指导!

def convert_face_cards(keep_score)
  players = keep_score['player_card_values'].select { |card| card == "jack" || card == "queen" || card == "king" }
  dealers = keep_score['dealer_card_values'].select { |card| card == "jack" || card == "queen" || card == "king" }

  keep_score['player_points'] = players.length * 10
  keep_score['dealer_points'] = dealers.length * 10
end

由于无论是闲家还是庄家,算人牌的逻辑都是一样的,你可以把它提取到自己的方法中,稍微干点。

def convert_face_cards(keep_score)
  keep_score['player_points'] = count_faces(keep_score['player_card_values'])
  keep_score['dealer_points'] = count_faces(keep_score['dealer_card_values'])
end

def sum_faces(cards)
  faces = cards.select { |card| face?(card) }
  faces.length * 10
end

def face?(card)
  %w[jack queen king].include?(card)
end

您不应该使用散列来存储此数据并编写循环遍历散列的代码。您应该使用包含这些知识的数据结构作为方法。

例如:

Card = Struct.new(:value) do
  def score
    case value
    when :king, :queen, :jack
      10
    when :ace
      1
    else
      value
    end
  end
end

CARDS = [
  :ace,
  :king,
  :queen,
  :jack,
  10,
  ... # other cards here
  2
].map { |card| Card.new(card) }

player_cards = []
dealer_cards = []

def score(cards)
  cards.sum(&:score)
end