Условное выражение, регистрирующееся с минимальной цикломатической сложностью

NullPointerException s - исключения, возникающие при попытке использовать ссылку, которая указывает на отсутствие местоположения в памяти (null), как если бы она ссылалась на объект. Вызов метода по нулевой ссылке или попытка получить доступ к полю нулевой ссылки вызовет функцию NullPointerException. Они наиболее распространены, но другие способы перечислены на странице NullPointerException javadoc.

Вероятно, самый быстрый пример кода, который я мог бы придумать для иллюстрации NullPointerException, be:

public class Example {

    public static void main(String[] args) {
        Object obj = null;
        obj.hashCode();
    }

}

В первой строке внутри main я явно устанавливаю ссылку Object obj равной null. Это означает, что у меня есть ссылка, но она не указывает на какой-либо объект. После этого я пытаюсь обработать ссылку так, как если бы она указывала на объект, вызывая метод на нем. Это приводит к NullPointerException, потому что нет кода для выполнения в местоположении, на которое указывает ссылка.

(Это техничность, но я думаю, что она упоминает: ссылка, которая указывает на null, равна 't то же, что и указатель C, указывающий на недопустимую ячейку памяти. Нулевой указатель буквально не указывает на в любом месте , который отличается от указаний на местоположение, которое оказывается недопустимым.)

64
задан 10 revs, 2 users 77% 23 May 2017 в 11:54
поделиться

10 ответов

В Python Вы передаете отформатированные значения как параметры к регистрирующейся функции. Строковое форматирование только применяется, если вход включен. Существуют все еще издержки вызова функции, но это крохотно по сравнению с форматированием.

log.info ("a = %s, b = %s", a, b)

можно сделать что-то вроде этого для любого языка с variadic аргументами (C/C++, C#/Java, и т.д.).

<час>

Это действительно не предназначается для того, когда аргументы трудно получить, но для того, когда форматирование их к строкам является дорогим. Например, если Ваш код уже имеет список чисел в нем, Вы могли бы хотеть зарегистрировать тот список для отладки. Выполнение mylist.toString() не будет требовать времени ни к какому преимуществу, поскольку результат будет выброшен. Таким образом, Вы передаете mylist в качестве параметра регистрирующейся функции и позволяете ей обработать строковое форматирование. Тем путем форматирование будет только выполнено в случае необходимости.

<час>

, Так как вопрос OP конкретно упоминает Java, вот то, как вышеупомянутое может использоваться:

я должен настоять, что проблема не 'форматирует' связанный, но 'оценка аргумента', связанная (оценка, которая может быть очень дорогостоящей, чтобы сделать, прежде, чем назвать метод, который ничего не сделает)

, прием должен иметь объекты, которые не выполнят дорогие вычисления, пока абсолютно не необходимый. Это легко на языках как Smalltalk или Python, которые поддерживают лямбды и закрытия, но является все еще выполнимым в Java с небольшим количеством воображения.

Говорят, что у Вас есть функция get_everything(). Это получит каждый объект от Вашей базы данных в список. Вы не хотите называть это, если результат будет отброшен, очевидно. Таким образом вместо того, чтобы использовать вызов для той функции непосредственно, Вы определяете внутренний класс, названный LazyGetEverything:

public class MainClass {
    private class LazyGetEverything { 
        @Override
        public String toString() { 
            return getEverything().toString(); 
        }
    }

    private Object getEverything() {
        /* returns what you want to .toString() in the inner class */
    }

    public void logEverything() {
        log.info(new LazyGetEverything());
    }
}

В этом коде, вызов к getEverything() обертывается так, чтобы это не было на самом деле выполнено, пока это не будет необходимо. Регистрирующаяся функция выполнится toString() на ее параметрах, только если отладка включена. Тем путем Ваш код перенесет только издержки вызова функции вместо полного getEverything() вызов.

31
ответ дан Dave 24 November 2019 в 15:53
поделиться

С текущими платформами журналирования вопрос спорен

, Текущие платформы журналирования как slf4j или log4j 2 не требуют защитных операторов в большинстве случаев. Они используют параметризованный оператор журнала так, чтобы событие могло быть зарегистрировано безусловно, но сообщение, форматирующее только, происходит, если событие включено. Конструкция сообщения выполняется по мере необходимости регистратором, а не преимущественно приложением.

, Если необходимо пользоваться старинной библиотекой входа, можно продолжать читать для получения большего количества фона и способа модифицировать старую библиотеку с параметризованными сообщениями.

защитные операторы, действительно добавляющие сложность?

Рассматривают, исключая вход защитных операторов от цикломатического вычисления сложности.

можно было утверждать, что, из-за их предсказуемой формы, условные проверки входа действительно не способствуют сложности кода.

Негибкие метрики могут заставить в других отношениях хорошего программиста стать плохим. Будьте осторожны!

Предположение, что Ваши инструменты для вычисления сложности не могут быть адаптированы в соответствии с тем градусом, следующий подход, может предложить обходное решение.

потребность в условном выражении, регистрирующемся

, я предполагаю, что Ваши защитные операторы были представлены, потому что у Вас был код как это:

private static final Logger log = Logger.getLogger(MyClass.class);

Connection connect(Widget w, Dongle d, Dongle alt) 
  throws ConnectionException
{
  log.debug("Attempting connection of dongle " + d + " to widget " + w);
  Connection c;
  try {
    c = w.connect(d);
  } catch(ConnectionException ex) {
    log.warn("Connection failed; attempting alternate dongle " + d, ex);
    c = w.connect(alt);
  }
  log.debug("Connection succeeded: " + c);
  return c;
}

В Java, каждый из операторов журнала создает новое StringBuilder и вызывает toString() метод на каждом объекте, связанном к строке. Эти toString() методы, в свою очередь, вероятно, создадут StringBuilder собственные экземпляры, и вызовут toString() методы их участников, и так далее, через график потенциально большого объекта. (Прежде чем Java 5, это было еще более дорого, с тех пор StringBuffer использовался, и все его операции синхронизируются.)

Это может быть относительно дорогостоящим, особенно если оператор журнала находится в некотором выполняемом в большой степени пути выполнения кода. И, записанный как выше, что дорогое форматирование сообщения происходит, даже если регистратор обязан отбросить результат, потому что уровень журнала слишком высок.

Это приводит к введению защитных операторов формы:

  if (log.isDebugEnabled())
    log.debug("Attempting connection of dongle " + d + " to widget " + w);

С этой защитой, оценкой аргументов d и w и конкатенация строк выполняется только при необходимости.

решение для А для простого, эффективного входа

Однако, если регистратор (или обертка, которую Вы пишете вокруг своего выбранного регистрирующегося пакета) берет средство форматирования и аргументы в пользу средства форматирования, конструкция сообщения может быть отложена, пока точно это не будет использоваться при устранении защитных операторов и их цикломатической сложности.

public final class FormatLogger
{

  private final Logger log;

  public FormatLogger(Logger log)
  {
    this.log = log;
  }

  public void debug(String formatter, Object... args)
  {
    log(Level.DEBUG, formatter, args);
  }

  … &c. for info, warn; also add overloads to log an exception …

  public void log(Level level, String formatter, Object... args)
  {
    if (log.isEnabled(level)) {
      /* 
       * Only now is the message constructed, and each "arg"
       * evaluated by having its toString() method invoked.
       */
      log.log(level, String.format(formatter, args));
    }
  }

}

class MyClass 
{

  private static final FormatLogger log = 
     new FormatLogger(Logger.getLogger(MyClass.class));

  Connection connect(Widget w, Dongle d, Dongle alt) 
    throws ConnectionException
  {
    log.debug("Attempting connection of dongle %s to widget %s.", d, w);
    Connection c;
    try {
      c = w.connect(d);
    } catch(ConnectionException ex) {
      log.warn("Connection failed; attempting alternate dongle %s.", d);
      c = w.connect(alt);
    }
    log.debug("Connection succeeded: %s", c);
    return c;
  }

}

Теперь, ни одно из расположения каскадом toString() вызовы с их распределениями буферов произойдут , если они не будут необходимы! Это эффективно устраняет хит производительности, который привел к защитным операторам. Один маленький штраф, в Java, автоупаковал бы любых аргументов типа примитива, которые Вы передаете регистратору.

код, делающий вход, является возможно даже инструментом для очистки чем когда-либо, так как неопрятной конкатенации строк не стало. Это может быть даже инструмент для очистки, если строки формата воплощены (использование ResourceBundle), который мог бы также помочь в обслуживании или локализации программного обеспечения.

Дальнейшие улучшения

Также примечание, что в Java MessageFormat объект мог использоваться вместо "формата" String, который дает Вам дополнительные возможности, такие как формат выбора для обработки кардинальных чисел более аккуратно. Другая альтернатива должна была бы реализовать Вашу собственную возможность форматирования, которая вызывает некоторый интерфейс, который Вы определяете для "оценки", а не основного toString() метод.

56
ответ дан erickson 24 November 2019 в 15:53
поделиться

На языках, поддерживающих лямбда-выражения или блоки кода как параметры, одно решение для этого состояло бы в том, чтобы дать просто это методу входа. Тот мог оценить конфигурацию и только если необходимый на самом деле называют/выполняют обеспеченный блок лямбды/кода. Еще не попробовал его, все же.

Теоретически это возможно. Я не хотел бы использовать его в производстве из-за проблем производительности, которые я ожидаю с тем интенсивным использованием lamdas/code блоков для входа.

, Но как всегда: если в сомнении, протестируйте его и измерьте влияние на загрузку ЦП и память.

6
ответ дан pointernil 24 November 2019 в 15:53
поделиться

Спасибо за все Ваши ответы! Вы скала парней:)

Теперь моя обратная связь не так проста как Ваша:

Да, для один проект (как в 'одной развернутой программе и работающий самостоятельно на единственной производственной платформе'), я предполагаю, что можно пойти все технические на мне:

  • специализированные 'объекты' Ретривера Журнала, которые могут быть передачей в обертку Регистратора, только звоня toString () необходимы
  • используемый в сочетании с входом функция variadic (или простой объект [] массив!)

и там у Вас есть он, как объяснено @John Миллисемьей и @erickson.

Однако эта проблема вынудила нас думать немного о, 'Почему точно мы регистрировались во-первых?'
Наш проект является на самом деле 30 различными проектами (5 - 10 человек каждый) развернутый на различных производственных платформах с потребностями асинхронной передачи и центральной шинной архитектурой.
простой вход, описанный в вопросе, был хорошо для каждого проекта вначале (5 лет назад), но с тех пор, мы должны повыситься. Войдите KPI.

Вместо того, чтобы просить к регистратору регистрировать что-либо, мы просим к автоматически созданному объекту (названный KPI) регистрировать событие. Это - простой вызов (myKPI.I_am_signaling_myself_to_you ()) и не должно быть условным выражением (который решает 'искусственное увеличение цикломатической сложности' проблема).

, Что объект KPI знает, кто называет его и так как он работает с начала приложения, он в состоянии получить много данных, мы были ранее вычислительными на месте, когда мы регистрировались.
Плюс это объект KPI может контролироваться независимо и вычислять/публиковать по требованию свою информацию о единственной и отдельной шине публикации.
Тот путь, каждый клиент может запросить информацию, которую он на самом деле хочет (как, 'мой процесс начался, и если да, с тех пор когда?'), вместо того, чтобы искать корректный файл журнала и держать для загадочной Строки...

Действительно, вопрос, 'Почему точно мы регистрировались во-первых?' заставили нас понять, что мы не регистрировались только для программиста и его модульных или интеграционных тестов, но для намного более широкого сообщества включая некоторые сами заключительные клиенты. Наш механизм 'создания отчетов' должен был быть централизован, асинхронный, 24/7.

определенное из этого механизм KPI является выходом из объема этого вопроса. Достаточно сказать его надлежащая калибровка безусловно, передает, единственная самая сложная нефункциональная проблема, с которой мы сталкиваемся. Это все еще приносит систему на своем колене время от времени! Правильно калиброванный однако, это - спаситель.

Снова, спасибо за все предложения. Мы рассмотрим их для некоторых частей нашей системы, когда простой вход будет все еще на месте.
, Но другая точка этого вопроса должен был проиллюстрировать Вам определенную проблему в намного большем и более сложном контексте.
Hope Вам понравился он. Я мог бы задать вопрос на KPI (который, верьте или нет, не находится ни в каком вопросе на SOF до сих пор!) позже на следующей неделе.

я оставлю на виду этот ответ для голосования до следующего вторника, тогда я выберу ответ (не этот, очевидно;))

4
ответ дан VonC 24 November 2019 в 15:53
поделиться

Возможно, это слишком просто, но что относительно того, чтобы использовать "метод извлечения", осуществляющий рефакторинг вокруг защитного пункта? Ваш пример кода этого:

public void Example()
{
  if(myLogger.isLoggable(Level.INFO))
      myLogger.info("A String");
  if(myLogger.isLoggable(Level.FINE))
      myLogger.fine("A more complicated String");
  // +1 for each test and log message
}

Становится этим:

public void Example()
{
   _LogInfo();
   _LogFine();
   // +0 for each test and log message
}

private void _LogInfo()
{
   if(!myLogger.isLoggable(Level.INFO))
      return;

   // Do your complex argument calculations/evaluations only when needed.
}

private void _LogFine(){ /* Ditto ... */ }
4
ответ дан flipdoubt 24 November 2019 в 15:53
поделиться

В C или C++ я использовал бы препроцессор вместо если операторы для условного входа.

3
ответ дан Tom Ritter 24 November 2019 в 15:53
поделиться

Передайте уровень журнала регистратору и позвольте ему решить, записать ли оператор журнала:

//if(myLogger.isLoggable(Level.INFO) {myLogger.info("A String");
myLogger.info(Level.INFO,"A String");

ОБНОВЛЕНИЕ: А-ч, я вижу, что Вы хотите условно создать строку журнала без условного оператора. По-видимому, в а не время компиляции во время выполнения.

я просто скажу, что способ, которым мы решили это, состоит в том, чтобы поместить код форматирования в класс регистратора так, чтобы форматирование только произошло, если уровень передает. Очень похожий на встроенный sprintf. Например:

myLogger.info(Level.INFO,"A String %d",some_number);   

, Который должен соответствовать Вашим критериям.

3
ответ дан 24 November 2019 в 15:53
поделиться

Так, как я ненавижу макросы в C/C++ на работе, у нас есть #defines для, если часть, который, если ложь игнорирует (не оценивает), следующие выражения, но если истинные возвраты поток, в который материал может быть передан по каналу с помощью '< <'; оператор. Как это:

LOGGER(LEVEL_INFO) << "A String";

я предполагаю, что это устранило бы дополнительную 'сложность', которую Ваш инструмент видит, и также устраняет любое вычисление строки или любые выражения, которые будут зарегистрированы, если уровень не был достигнут.

1
ответ дан quamrana 24 November 2019 в 15:53
поделиться

Вот элегантное решение, использующее троичное выражение

logger.info (logger.isInfoEnabled ()? "Здесь идет оператор журнала ...": null);

1
ответ дан 24 November 2019 в 15:53
поделиться

alt text
(источник: scala-lang.org )

Scala имеет аннотацию @elidable () , которая позволяет вам удалять методы с флагом компилятора.

С помощью scala REPL:

C:> scala

Добро пожаловать в Scala версии 2.8.0.final (64-разрядная серверная виртуальная машина Java HotSpot ™, Java 1. 6.0_16). Введите выражения, чтобы они оценивались. Введите: help для получения дополнительной информации.

scala> импортировать scala.annotation.elidable импорт scala.annotation.elidable

scala> импорт scala.annotation.elidable._ import scala.annotation.elidable ._

scala> @elidable (FINE) def logDebug (arg: String) = println (arg)

logDebug: (arg: String) Unit

scala> logDebug ("тестирование ")

scala>

С elide-beloset

C:> scala -Xelide-below 0

Добро пожаловать в Scala версии 2.8.0.final (Java HotSpot (TM) 64-разрядная серверная виртуальная машина, Java 1. 6.0_16). Введите выражения, чтобы они оценивались. Введите: help для получения дополнительной информации.

scala> импортировать scala.annotation.elidable импорт scala.annotation.elidable

scala> импорт scala.annotation.elidable._ import scala.annotation.elidable ._

scala> @elidable (FINE) def logDebug (arg: String) = println (arg)

logDebug: (arg: String) Unit

scala> logDebug ("testing ")

тестирование

scala>

См. Также Определение утверждения Scala

2
ответ дан 24 November 2019 в 15:53
поделиться
Другие вопросы по тегам:

Похожие вопросы: