Размер ветви задания слишком велик - PullRequest
1 голос
/ 29 марта 2019

Я делаю метод, который принимает многострочные строки (логи) и записывает новые строки в массив.

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

и мне нужно пройти анализ рубокопа с конфигурацией по умолчанию, но это дает 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 ]

1 голос
/ 29 марта 2019
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, считается ложным, поэтому для большей идиоматичности лучше просто использовать форму, которую я использовал в операторе if.Это решает проблему с АБС.

1 голос
/ 29 марта 2019

Размер 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 веток. 3 раза !something.nil? (что считается 2, поскольку ! и .nil? оба учитываются в ответвлениях) и 4 раза +.

Оставив вас с:

√(4² + 8² + 3²) ≈ 9.43
0 голосов
/ 02 апреля 2019

Это проблема, когда удобно использовать именованные группы захвата.

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) при написании регулярного выражения в режиме свободного пробела.Это связано с тем, что в свободном пространстве пробелы удаляются перед вычислением выражения.Кроме того, пробелы можно сохранить в режиме свободного пробела, заключив их в классы символов ([ ]).

0 голосов
/ 30 марта 2019

Каждый раз, когда я сталкиваюсь с слишком высокой 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 и т. д. И если бы вы создали класс, вы могли бы избежать беспорядочных (на мой взгляд) многозначных возвратов.

0 голосов
/ 29 марта 2019

Я не использую 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 ( Reports 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 всесразу и выделите соответствующие группы: ( Сообщает AbcSize 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
...