Как проследить происхождение значения в Java? - PullRequest
5 голосов
/ 25 октября 2009

У меня есть переменная, которая очень редко получает неправильное значение. Поскольку система довольно сложна, у меня возникают проблемы с отслеживанием всех путей кода, через которые проходит значение - в нем задействовано несколько потоков, его можно сохранить, а затем загрузить из БД и т. Д. Я попытаюсь использовать генератор кодовых графиков, чтобы увидеть, смогу ли я определить проблему, посмотрев на способы вызова сеттера, возможно, есть какой-то другой метод. Может быть, обернуть значение классом, который отслеживает места и изменяет его? Я не уверен, что вопрос достаточно ясен, но я был бы признателен тем, кто столкнулся с такой ситуацией.

[Edit] Проблема не легко воспроизводима, и я не могу поймать ее в отладчике. Я ищу метод статического анализа или регистрации, чтобы помочь отследить проблему.

[Редактировать 2] Просто, чтобы прояснить ситуацию, значение, о котором я говорю, - это временная метка, представленная в виде количества миллисекунд от эпохи Unix (01.01.1970) в переменной длиной 64 бита. В некоторой неизвестной точке старшие 32 бита значения обрезаются, генерируя совершенно неправильные (и невосстановимые) временные метки.

[Правка 3] Хорошо, благодаря некоторым вашим предложениям и пару часов пролистывания кода я нашел виновника. Метка времени, основанная на миллисекундах, была преобразована в метку времени на основе секунды, разделив ее на 1000 и сохранив в переменной int. На более позднем этапе кода метка времени, основанная на секундах (int), была умножена на 1000 и сохранена в новую переменную long. Поскольку как 1000, так и временные метки, основанные на секундах, имели значения int, результат умножения был усечен перед преобразованием в long. Это было тонкое, спасибо всем, кто помог.

Ответы [ 10 ]

6 голосов
/ 25 октября 2009

Если вы используете сеттер и только сеттер для установки значения, вы можете добавить эти строки для отслеживания потока и трассировки стека:

public void setTimestamp(long value) {
  if(log.idDebugEnabled) {
    log.debug("Setting the value to " + value + ". Old value is " + this.timestamp);
    log.debug("Thread is " + Thread.currentThread().getName());
    log.debug("Stacktrace is", new Throwable()); // we could also iterate on Thread.currentThread().getStackTrace()
  }
  // check for bad value
  if(value & 0xffffffff00000000L == 0L) {
    log.warn("Danger Will Robinson", new IlegalValueException());
  }
  this.timestamp = value;
}

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

Редактировать

Возможно FindBugs может помочь с точки зрения статического анализа, я постараюсь найти точное правило позже.

4 голосов
/ 25 октября 2009

Тот факт, что изменяется 32 бита длинного, а не целого значения, убедительно свидетельствует о том, что это проблема многопоточности (два потока обновляют переменную одновременно). Поскольку java не гарантирует атомарный доступ к длинному значению, если два потока обновляют его одновременно, это может привести к тому, что половина битов будет установлена ​​в одну сторону, а половина - в другую. Это означает, что лучший способ решения этой проблемы - это многопоточность. Скорее всего, ничто не задает переменную таким образом, что инструмент статического анализа покажет вам, что это неверное значение, но вместо этого необходимо проверить стратегию синхронизации и блокировки вокруг этой переменной на наличие потенциальных дыр.

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

2 голосов
/ 25 октября 2009

Многопоточное программирование очень сложно, но есть инструменты IDE, которые могут помочь. Если у вас есть intellij IDEA, вы можете использовать функцию анализ потока данных , чтобы определить, где что-то меняется. Если не покажет вам живой поток (это инструмент статического анализа), но он может дать вам хорошее начало.

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

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

2 голосов
/ 25 октября 2009

Я согласен - если значение изменяется только через установщик (независимо от того, что такое orgin) - и это лучше, - тогда лучший способ - изменить установщик, чтобы он отслеживал вас (трассировка стека печати при каждой настройке возможно только в том случае, если установленное значение является конкретным, если это сокращает шум)

1 голос
/ 25 октября 2009

Две вещи:

Во-первых, для меня это пахнет, как будто какой-то вызывающий абонент обрабатывает свою временную метку в целочисленном контексте, теряя свои старшие 32 бита. Это может быть, как и предполагал Ишай , связанный с многопоточностью, но я бы сначала посмотрел на выполняемые операции. Однако, естественно, вам нужно убедиться, что ваше значение обновляется «атомарно» - либо с помощью AtomicLong, как он предложил, либо с помощью какого-то другого механизма.

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

1 голос
/ 25 октября 2009

1) Предположим, что foo - это имя вашей переменной, вы можете добавить что-то вроде этого в метод установки:

try {
  throw new Exception();
}
catch (Exception e) {
  System.out.println("foo == " + foo.toString());
  e.printStackTrace();
}

Насколько хорошо это будет работать, зависит от того, как часто вызывается сеттер. Если он вызывается тысячи раз за время выполнения вашей программы, у вас могут возникнуть проблемы с поиском неверного значения во всех трассировках стека. (Я использовал это раньше, чтобы решить проблему, подобную вашей. Это сработало для меня.)

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

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

0 голосов
/ 26 октября 2009

IMO лучший способ отладить проблему такого типа - использовать точку останова модификации поля. (Особенно если вы широко используете отражение)

Я не уверен, как это сделать в Eclipse, но в Intellij вы можете просто щелкнуть правой кнопкой мыши на поле и сделать «добавить точку останова».

0 голосов
/ 25 октября 2009

Я скорее буду держать точку останова внутри установщика. Eclipse позволяет вам сделать это.

There are some IDE which allows you to halt ( wait for execution of next instruction ) the program, if the value of variable is changed.

0 голосов
/ 25 октября 2009

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

Я знаю, что в Eclipse вы можете установить условные контрольные точки, например, в методе setter. Вы можете указать приостановку только тогда, когда значение будет установлено на определенное значение, и вы также можете фильтровать по потоку, если вы хотите сосредоточиться на конкретном потоке.

0 голосов
/ 25 октября 2009

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

Является ли рассматриваемое значение примитивным? Это поле большого, сложного объекта, который разделяется между потоками? Если это поле какого-либо объекта, является ли этот объект DTO или он реализует поведение, специфичное для домена?

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...