Что делает @elidable аннотация в Scala, и когда я должен ее использовать?
Я заметил в некотором коде библиотеки scala, особенно Predef
, есть код вроде:
/** Tests an expression, throwing an `AssertionError` if false.
* Calls to this method will not be generated if `-Xelide-below`
* is at least `ASSERTION`.
*
* @see elidable
* @param p the expression to test
*/
@elidable(ASSERTION)
def assert(assertion: Boolean) {
if (!assertion)
throw new java.lang.AssertionError("assertion failed")
}
Эта аннотация позволяет мне во время компиляции исключить код. Когда я компилирую с помощью -Xelide-below MAXIMUM
, делает ли это
- удалить метод и все его вызовы? (Если это так, что произойдет, если другая библиотека ожидает, что этот метод будет там?), Получаем ли мы NoSuchMethodError или что-то еще?
- оставьте метод там, но удалите весь код из метода, оставив пустой метод?
- просто удалите вызовы метода, но оставьте там метод?
Можно ли использовать его для уменьшения скомпилированного размера класса? Поэтому, если бы у меня было:
class Foobar {
// extremely expensive toString method for debugging purposes
@elidable(FINE) def toString(): String = "xxx"
}
и скомпилирован с помощью -Xelide-below WARNING
, будет ли toString в этом классе вообще исчезнуть? Обратите внимание, что в этом примере я хотел бы, чтобы метод был удален из класса, потому что я не хотел бы, чтобы он вызывался.
Вторая часть: я видел он предложил, что это будет использоваться для устранения кода регистрации отладки. Учитывая, что большинство фреймворков (log4j заметно) позволяют установить уровень ведения журнала во время выполнения, я не думаю, что это хороший вариант использования. Лично я хотел бы, чтобы этот код поддерживался. Итак, помимо методов assert() в Predef
, что является хорошим вариантом использования для @elidable
?
Ответы
Ответ 1
Короткий ответ
Оба метода и все обращения к нему просто исчезают. Это может быть хорошей идеей для ведения журнала, поскольку каждая система ведения журнала вводит некоторые накладные расходы при вызове журнала, но данный уровень отключен (вычисление эффективного уровня и подготовка аргументов).
Обратите внимание, что современные рамки ведения журналов стараются максимально уменьшить этот размер (например, Logback оптимизирует вызовы is*Enabled()
и SLF4S передает сообщение по имени, чтобы избежать ненужных конкатенаций строк).
Длинный один
Мой тестовый код:
import scala.annotation.elidable
import scala.annotation.elidable._
class Foobar {
info()
warning()
@elidable(INFO) def info() {println("INFO")}
@elidable(WARNING) def warning() {println("WARNING")}
}
Доказывается, что при -Xelide-below 800
оба оператора печатаются, а при 900
появляется только "WARNING"
. Итак, что происходит под капотом?
$ scalac -Xelide-below 800 Foobar.scala && javap -c Foobar
public class Foobar extends java.lang.Object implements scala.ScalaObject{
public void info();
//...
public void warning();
//...
public Foobar();
Code:
0: aload_0
1: invokespecial #26; //Method java/lang/Object."<init>":()V
4: aload_0
5: invokevirtual #30; //Method info:()V
8: aload_0
9: invokevirtual #32; //Method warning:()V
12: return
}
Как вы можете видеть, это компилируется нормально. Однако, когда эта инструкция используется:
$ scalac -Xelide-below 900 Foobar.scala && javap -c Foobar
вызывает info()
, и сам метод исчезает из байт-кода:
public class Foobar extends java.lang.Object implements scala.ScalaObject{
public void warning();
//...
public Foobar();
Code:
0: aload_0
1: invokespecial #23; //Method java/lang/Object."<init>":()V
4: aload_0
5: invokevirtual #27; //Method warning:()V
8: return
}
Я ожидал бы, что NoSuchMethodError
будет запущен во время выполнения, когда удаленный метод вызывается из кода клиента, скомпилированного с версией Foobar
с нижним порогом elide-below
. Также он пахнет хорошим старым препроцессором C, и, как таковой, я подумал бы дважды, прежде чем использовать @elidable
.
Ответ 2
В качестве дополнения к Томаш Нуркевич ответьте на два комментария.
(1) Стиль С++
Поскольку я пришел из С++, я определил
/** ''Switch'' between '''Debug''' and '''Release''' version. */
object BuildLevel {
type only = annotation.elidable
final val DEBUG = annotation.elidable.INFO
}
и используйте это в старом стиле препроцессора С++, например
import BuildLevel._
@only(DEBUG)
private def checkExpensive(...) {
...
}
override def compare(that: ): Int = {
checkExpensive(...)
...
}
для отметки дорогих проверок (проверка предварительных условий или инвариантов, которые должны всегда выполняться), которые я хочу отключить в сборках релизов.
Конечно, это похоже на пример использования, за исключением разницы рефакторинга дорогого кода в отдельном методе, который должен быть отключен в целом. Но все это стоит только для действительно дорогостоящих проверок. В проекте 10k строк у меня есть только 3 отмеченных чека. Более дешевые тесты я бы не выключил и не оставил в коде, потому что они повышают его надежность.
(2) Подпись блока
Этот подход подходит только для методов с сигнатурой (...) => Unit
. Если вы используете результат такого отключенного метода, например
@only(DEBUG)
def checkExpensive(that: Any): Int = {
4
}
val n = checkExpensive(this)
по крайней мере мой Scala 2.9.1.final компилятор сбой. Однако в такой подписи нет особого смысла. Потому что: Какое значение должно возвращать такой отключенный метод?
Ответ 3
На самом деле выражения не могут просто исчезнуть, потому что они имеют результат. Когда вы отказываетесь от вызова метода типа результата Boolean, вы заканчиваете с false
и так далее.
Был вопрос через несколько месяцев после того, как этот вопрос был опубликован, чтобы решить, что происходит. Ничего не происходит. Результат заключался в том, чтобы достигнуть ???
.