我正在制作采用多行字符串(日志)并将新字符串写入数组的方法。
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
答案 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#match
,Enumerable#map
和Array#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
)。这是因为在自由空间模式下,在对表达式求值之前会去除空格。另外,可以通过将空格括在字符类([ ]
)中来以自由间距模式保留空格。