分配分支条件大小过大

时间:2019-03-29 17:03:50

标签: ruby rubocop

我正在制作采用多行字符串(日志)并将新字符串写入数组的方法。

def task_2(str)
  result = []
  str.each_line do |x|
    ip = x[/^.* - -/]
    datetime = x[/[\[].*[\]]/]
    address = x[/T .* H/]
    if !ip.nil? && !datetime.nil? && !address.nil?
      result << datetime[1..-2] + ' FROM: ' + ip[0..-4] + 'TO:' + address[1..-3]
    end
  end
  result
end

,我需要它通过默认配置通过rubocop分析,但是它提供了AbcSize 18.68 / 15 而且我敢肯定,因为有if..end语句,但是我该如何重写它?

日志示例:

10.6.246.103 - - [23/Apr/2018:20:30:39 +0300] "POST /test/2/messages HTTP/1.1" 200 48 0.0498
10.6.246.101 - - [23/Apr/2018:20:30:42 +0300] "POST /test/2/run HTTP/1.1" 200 - 0.2277

5 个答案:

答案 0 :(得分:1)

def task_2(str)
  result = []
  str.each_line do |x|
    ip = x[/^.* - -/]
    datetime = x[/[\[].*[\]]/]
    address = x[/T .* H/]
    if ip && datetime && address
      result << datetime[1..-2] + ' FROM: ' + ip[0..-4] + 'TO:' + address[1..-3]
    end
  end
  result
end

具有!variable.nil吗?是多余的。基本上,您是在这里检查状态,所以#present?方法就足够了,但是任何非nil或false的值都将被视为false,因此,为了更加习惯用法,最好只使用if语句中使用的形式。这样可以解决ABS问题。

答案 1 :(得分:1)

ABC大小是通过执行以下操作来计算的:

√(assignments² + branches² + conditionals²)

首先让我们看一下作业:

result = []
ip = x[/^.* - -/]
datetime = x[/[\[].*[\]]/]
address = x[/T .* H/]

这给我们留下了4个作业。

下一个分支。为此,我不得不提到,大多数运算符是方法(因此计入分支),例如1 + 1也可以写成1.+(1) +是整数上的方法。 string[regex]也是一样,也可以写成string.[](regex) []是一种字符串方法。顺便说一句,让我们计算分支。

str.each_line
x[/^.* - -/]
x[/[\[].*[\]]/]
x[/T .* H/]
!ip.nil? # counts for 2 (! and .nil?)
!datetime.nil? # counts for 2 (! and .nil?)
!address.nil? # counts for 2 (! and .nil?)
result << ...
datetime[1..-2]
ip[0..-4]
address[1..-3]
+ # 4 times in result << ... + ... + ....

这给我们留下了18个分支。

最后要计算的是条件句。由于Ruby使用&&||运算符进行短路,因此它们将计入条件。

if
&& # 2 times

这给我们留下了3个条件。

√(4² + 18² + 3²) ≈ 18.68

现在我们已经知道数字的来源,我们可以尝试减少它。减小ABC大小最简单的方法是减少具有最大数字的东西,因为该数字是平方的。在您的情况下,这些是分支。您已经发现问题所在。

if !ip.nil? && !datetime.nil? && !address.nil?
  result << datetime[1..-2] + ' FROM: ' + ip[0..-4] + 'TO:' + address[1..-3]
end

可以简化为:

if ip && datetime && address
  result << "#{datetime[1..-2]} FROM: #{ip[0..-4]}TO:#{address[1..-3]}"
end

总共有10个分支。 !something.nil?的3倍(因为!.nil?都计入分支,因此为2)和+的4倍。

留下您:

√(4² + 8² + 3²) ≈ 9.43

答案 2 :(得分:0)

我不使用rubocop,但是我使用此数据测试了以下内容:

data = <<FILE
10.6.246.103 - - [23/Apr/2018:20:30:39 +0300] "POST /test/2/messages HTTP/1.1" 200 48 0.0498
10.6.246.101 - - [23/Apr/2018:20:30:42 +0300] "POST /test/2/run HTTP/1.1" 200 - 0.2277
12.55.123.255 - - Hello
FILE

使用String#gsub!Enumerable#select报告AbcSize为3

def task_2(str)
  str.each_line.select do |x|
    # Without named groups 
    # x.gsub!(/\A([\d+\.\d+]+).*(?<=\[)(.*)(?=\]).*(?<=\s)((?:\/\w+)*?)(?=\s).*\z/m,
    # '\2 FROM \1 TO \3')
    x.gsub!(/\A(?<ip>[\d+\.\d+]+).*(?<=\[)(?<date_time>.*)(?=\]).*(?<=\s)(?<address>(?:\/\w+)*?)(?=\s).*\z/m,
      '\k<date_time> FROM \k<ip> TO \k<address>')
  end
end


task_2(data)
# => ["23/Apr/2018:20:30:39 +0300 FROM 10.6.246.103 TO /test/2/messages", 
#      "23/Apr/2018:20:30:42 +0300 FROM 10.6.246.101 TO /test/2/run"]

在这里,我们使用String#gsub!进行模式替换,如果不进行替换,它将返回nil,从而从Enumerable#select拒绝它。

使用String#matchEnumerable#mapArray#compact的类似解决方案(尽管效率可能较低)(报告的AbcSize为7.14

def task_2(str)
  str.each_line.map do |x|
    match = x.match(/\A(?<ip>[\d+\.\d+]+).*(?<=\[)(?<date_time>.*)(?=\]).*(?<=\s)(?<address>(?:\/\w+)*?)(?=\s)/)
    "#{match['date_time']} FROM #{match['ip']} TO #{match['address']}" if match
  end.compact
end

在这里,我们使用String#match提取匹配数据,然后确认匹配并在存在匹配的情况下输出所需的格式。不匹配的字符串将输出nil,因此我们compact Array删除nil值。

另一种选择可能是一次scan全部String并分解匹配的组:( Reports AbcSize of 5

def task_2(str)
  str.scan(/^([\d+\.\d+]+).*(?<=\[)(.*)(?=\]).*(?<=\s)((?:\/\w+)*?)(?=\s).*$/)
    .map {|a| "#{a[1]} FROM #{a[0]} TO #{a[2]}"}
end

可以通过

将最后一个低至 2.24
 def task_2(str)
  r = []
  str.scan(/^([\d+\.\d+]+).*(?<=\[)(.*)(?=\]).*(?<=\s)((?:\/\w+)*?)(?=\s).*$/) do |ip, date_time, address | 
    r << "#{date_time} FROM #{ip} TO #{address}"
  end
  r
end

答案 3 :(得分:0)

每当我遇到ABC太高(或类似的复杂性/长度警告)时,我都会很快将方法切碎。您的可读性,可测试性和可维护性几乎总是会提高。

最快的方法是将循环的主体或条件转换为新的方法。根据需要重复操作,直到您可以一口气阅读每种方法。

类似地,如果您有大型复杂的条件/循环构造,请将其也引入新方法中。

将这两种策略足够多地组合在一起,可以将任何方法大致分为两个方法调用。在某些情况下,这可能有点太过热情了……但永远不会太过分。

这是将策略应用于代码的一种方法:

def task_2(str)
  result = []

  str.each_line do |x|
    ip, datetime, address = parse_line(x)

    if [ip, datetime, address].all?
      result << "#{datetime[1..-2]} FROM: #{ip[0..-4]} TO: #{address[1..-3]}"
    end
  end

  result
end

def parse_line(x)
  ip = x[/^.* - -/]
  datetime = x[/[\[].*[\]]/]
  address = x[/T .* H/]
  return [ip, datetime, address]
end

s =<<EOF
123.123.123.999 - - [2009-12-31 13:13:13] T www.google.com H"
456.456.456.999 - - [2009-12-31 13:13:13] 404"
678.678.678.999 - - [2009-12-31 13:13:13] T www.amazon.com H"
EOF

puts task_2(s)

产生输出:

2009-12-31 13:13:13 FROM: 123.123.123.999  TO:  www.google.com
2009-12-31 13:13:13 FROM: 678.678.678.999  TO:  www.amazon.com

如果您想走得更远,可以将each_line的正文拉到新方法process_line等上。如果创建了一个类,则可以避免混乱(我的眼睛)多值回报。

答案 4 :(得分:0)

这是一个方便使用命名捕获组的问题。

R = /
    (?=                       # begin a positive lookahead
      (?<ip>.*\s-\s-)         # match the string in a capture group named 'ip' 
    )                         # end positive lookahead
    (?=                       # begin a positive lookahead
      .*                      # match any number of characters
      (?<datetime>[\[].*[\]]) # match the string in a capture group named 'datetime'
    )                         # end positive lookahead
    (?=                       # begin a positive lookahead
      .*                      # match any number of characters
      (?<address>T\s.*\sH)    # match the string in a capture group named 'address' 
    )                         # end positive lookahead
    /x                        # free-spacing regex definition mode

def task_2(str)
  str.each_line.with_object([]) do |s, result|
    m = str.match(R)
    result << m[:datetime][1..-2] + ' FROM: ' + m[:ip][0..-4] +
              'TO:' + m[:address][1..-3] unless m.nil?      
  end
end

str =<<_
123.123.123.999 - - [2009-12-31 13:13:13] T www.google.com H"
456.456.456.999 - - [2009-12-31 13:13:13] 404"
678.678.678.999 - - [2009-12-31 13:13:13] T www.amazon.com
_
task_2 str
  #=> ["2009-12-31 13:13:13 FROM: 123.123.123.999 TO: www.google.com",
  #    "2009-12-31 13:13:13 FROM: 123.123.123.999 TO: www.google.com",
  #    "2009-12-31 13:13:13 FROM: 123.123.123.999 TO: www.google.com"] 

常规表达通常如下。

R = /(?=(?<ip>\A.* - -))(?=.*(?<datetime>[\[].*[\]]))(?=.*(?<address>T .* H))/

请注意,在自由间距模式下编写正则表达式时,我在此处有空格的地方有空格字符(\s)。这是因为在自由空间模式下,在对表达式求值之前会去除空格。另外,可以通过将空格括在字符类([ ])中来以自由间距模式保留空格。