Назад Зміст Вперед

3.4. Використання аssertions

Перевірка тверджень (assertions) і умовна компіляція

Починаючи з JDK 1.4, в мові Java з'явилася вбудована підтримка перевірки тверджень (assertions). Твердження - це код, використовуваний, як правило, тільки під час розробки, за допомогою якого програма перевіряє правильність свого виконання. Використання тверджень є ефективним і дешевим способом виявлення помилок в логіці програми.

На рівні мови перевірка тверджень реалізована за допомогою введення нового оператора assert, який має форму:
assert [Вираз типу boolean];
або
assert [Вираз типу boolean] : [Вираз будь-якого типу, окрім void];

Під час виконання програми у тому випадку, якщо перевірка тверджень включена, обчислюється значення булевого виразу, і якщо його результат false, то генерується виключення, що має тип java.lang.AssertionError (підклас java.lang.Error). У разі використання другої форми оператора assert вираз після двокрапки задає детальне повідомлення про помилку (обчислення виразу буде перетворено в рядок і передане конструктору AssertionError), те що сталася.

Перевірка тверджень під час виконання програми припускає виконання довільних (і, можливо, тривалих) обчислень, які можуть серйозно вплинути на продуктивність додатка. Таким чином, твердження це простий і зручний механізм для пошуку помилок під час розробки, який не робить ніякого впливу на роботу готового продукту (звичайно, це справедливо тільки у разі коректного використання тверджень).

Опис деталей реалізації можна прочитати в документі ASimple Assertion Facility For the Java ProgrammingLanguage

Приклад 1:
// Constants.java
public class Constants {
public static final boolean DEBUG = true;
}
// TestConstants.java
public class TestConstants {
public static void main (String[] args) {
if (Constants.DEBUG) {
System.out.println ("Обчислення тверджень включене");
} else {
System.out.println ("Обчислення тверджень вимкнене");
}
}
}

Скомпілюємо ці класи і подивимося, як виглядає код методу TestConstants.main:
0 getstatic#16 <java/lang/System.out>
3 ldc#22 <Обчислення тверджень включене>
5 invokevirtual#24 <java/io/PrintStream.println>
8 return


Приклад 2:
import java.util.Scanner;  
  class AssertionExample{  
   public static void main( String args[] ){  
  
  Scanner scanner = new Scanner( System.in );  
  System.out.print("Enter ur age ");  
    
  int value = scanner.nextInt();  
  assert value>=18:" Not valid";  
  
  System.out.println("value is "+value);  
 }   
}  

Якщо використати твердження, то воно не буде працювати тому, що твердження за замовчуванням відключене. Щоб включити твердження, -EA або -enableassertions перемикач Java повинен бути використаний. 
Збирати за: JAVAC AssertionExample.java
Запустіть : Java -ea AssertionExample 

Output: Enter ur age 11
        Exception in thread "main" java.lang.AssertionError: Not valid

Де не використовувати твердження:
Є деякі ситуації, коли слід уникати використання твердження:
  1. Згідно зі специфікацією Sun, твердження не слід використовувати для перевірки аргументів в публічних методів, оскільки це повинно привести відповідне виключення під час виконання, наприклад IllegalArgumentException, NullPointerException і т.д.
  2. Не використовуйте твердження, якщо ви не хочете будь-якої помилки в будь-якій ситуації.

Компілювання коду із твердженнями-попередженнями

Компілятор Java 6 буде за замовчуванням використовувати assert як ключове слово. Якщо не буде вказівок діяти інакше, компілятор згенерує повідомлення про помилку, якщо побачить assert в якості ідентифікатора. Але можна повідомити компілятору про наявність фрагменту коду попередніх версій, і він виконає функції попередніх компіляторів! Припустимо, що потрібно компілювати код версії 1.3, де assert використовується в якості ідентифікатора. В такому випадку у командному рядку необхідно ввести:
javac -source 1.3 OldCode.javaКомпілятор видасть попередження при використанні слова assert в якості ідентифікатора, але код скомпілюється і виконається. При повідомленні компілятору про версію коду Java 1.4 або пізнішої, наприклад:
javac -source 1.4 NotQuiteSoOldCode.javaкомпілятор видасть помилку при використанні assert в якості ідентифікатора.
Для завдання компілятору правил Java 6 необхідно виконати щось одне з трьох: 
  • видалити опцію -source, що використовується за замовчуванням, або 
  • додати одну з двох опцій джерела: -source 1.6 або 
  • source 6.
При використанні assert в якості ідентифікатора НЕОБХІДНО використати при компіляції опцію - source 1.3. Наступна таблиця підсумовує реакцію Java 6 на assert в якості ідентифікатора або ключового слова.

Командний рядок
аssert -  ідентифікатор
assert – ключове слово
javac -source 1.3 TestAsserts.javaСкомпілюється з попередженнямПомилка компіляції
javac -source 1.4 TestAsserts.javaПомилка компіляціїКод скомпілюється
javac -source 1.5 TestAsserts.javaПомилка компіляціїКод скомпілюється
javac -source 5 TestAsserts.javaПомилка компіляціїКод скомпілюється
javac -source 1.6 TestAsserts.javaПомилка компіляціїКод скомпілюється
javac -source 6 TestAsserts.javaПомилка компіляціїКод скомпілюється
javac TestAsserts.javaПомилка компіляціїКод скомпілюється

Виконання коду із твердженнями

В написаному коді із твердженнями-попередженнями (iіншими словами, в коді, що використовує assert в якості ключового слова для дійсного виконання тверджень під час виконання), можна обирати – дозволити чи заборонити твердження під час виконання! 
!!!Пам'ятайте – твердження за замовчуванням є забороненими.

Ввімкнення твердження під час виконання

Дозвіл тверджень під час виконання здійснюється наступним чином:
java -ea com.geeksanonymous.TestClass
або
java -enableassertions com.geeksanonymous.TestClass
Попередні перемикачі у командному рядку повідомляють JVM про виконання код із дозволеними твердженнями.

Заборона тверджень під час виконання

Необхідно також знати перемикачі командного рядку для заборони тверджень під час виконання:
java -da com.geeksanonymous.TestClass
або
java -disableassertions com.geeksanonymous.TestClass

Через те, що твердження є забороненими за замовчуванням, використання перемикачів заборони може здатися непотрібним. Дійсно, використання перемикачів у попередньому прикладі викликає поведінку програми за замовчуванням (іншими словами, отримано однаковий результат незалежно від використання перемикачів заборони). Проте, можна вибірково дозволяти та забороняти твердження таким чином, що вони будуть дозволеними для деяких класів і/або пакетів, та заборонені для інших під час виконання програми.

Вибіркове ввімкнення і вимикання

Перемикачі командного рядку для тверджень можуть використовуватись кількома способами:
■ Без аргументів (як в попередньому прикладі) Дозволяють або забороняють твердження в усіх класах, крім системних.
■ З ім'ям пакету Дозволяють або забороняють твердження у вибраному пакеті
та в усіх пакетах, нижчих цього пакету в одній ієрархічній директорії.
■ З ім'ям класу Дозволяють або забороняють твердження у вибраному класі.


Можна комбінувати перемикачі, наприклад, для заборони тверджень в одному класу, але залишити їх дозволеними для всіх інших, наприклад:
java -ea -da:com.geeksanonymous.Foo


Попередній командний рядок наказує JVM дозволити твердження в цілому, але заборонити їх для класу com.geeksanonymous.Foo. Те саме можна робити із пакетами:
java -ea -da:com.geeksanonymous...


Попередній командний рядок наказує JVM дозволити твердження в цілому, але заборонити їх для com.geeksanonymous та всіх його субпакетів! (Субпакетом є кожний пакет в субдиректорії названого пакету).

Наступна таблиця містить приклади перемикачів командного рядку для дозволу та заборони тверджень.
Приклад командного рядка
Що він означає
java –ea
java -enableassertions
Дозволяє твердження
java –da
java -disableassertions
Забороняє твердження (поведінка Java 6 за замовчуванням)
java -ea:com.foo.BarДозволяє твердження в класі com.foo.Bar.
java -ea:com.foo...Дозволяє твердження в пакеті com.foo і його субпакетах
java -ea –dsaДозволяє твердження в цілому, але забороняє їх в системних класах
java -ea -da:com.foo...Дозволяє твердження в цілому, але забороняє їх в пакеті com.foo і його субпакетах.

Відповідне використання тверджень

Не всі коректні використання тверджень вважаються відповідними. Можна свідомо зловживати використанням тверджень, не звертаючи уваги на застереження інженерів Sun

Наприклад, не є передбаченим оброблення відмов тверджень. Тобто, не є передбаченим перехоплення винятку та намагання відновлення зворотнього виклику. Проте, AssertionError є підкласом Throwable, тобто, може бути перехопленим. Але цього дійсно не потрібно робити! Намагання відновлення після відмови тверджень спричинить виняток. Для запобігання намаганням замінити твердження на виняток AssertionError не забезпечує доступ до об'єктів, які ним генеруються. Вони є представленими у String повідомленні.

Не використовуйте тверджень для перевірки аргументів в public методах
Наступний код є невідповідним використанням тверджень:
public void doStuff(int x) {
assert (x > 0); //невідповідно!
// оброблення x
}

Public метод може бути викликаним кодом, що є поза контролем (який ніколи не бачили). Оскільки public методи є частиною інтерфейсу до зовнішнього світу, передбачено гарантії, що будь-які комбінації аргументів будуть враховані методом. Проте, оскільки твердження не гарантують дійсного виконання (в нормальному режимі вони є забороненими), ці врахування не відбудуться, якщо твердження не є дозволеними. Не потрібно робити доступ public до коду, який виконуватиметься умовно, в залежності від дозволу тверджень.

При необхідності перевірки аргументів public методу, можливо використати виклик винятків, наприклад, IllegalArgumentException, якщо значення, що передані до public методу є некоректними.

Використовуйте твердження для перевірки аргументів private методу

При написанні private методу, як правило, пишуть (або контролюють) будь-який код, що його викликає. Припущення щодо коректності логіки коду виклику private методу можна протестувати наступним твердженням:
private void doMore(int x) {
assert (x > 0);
// оброблення x
}


Єдиною різницею між двома попередніми прикладами є модифікатор доступу. Тому, перевіряйте обмеження аргументів private методів, але не перевіряйте обмежень public методів. 

Можна, звичайно, відкомпілювати код із невідповідним використанням тверджень для перевірки public аргументів, проте на екзамені (та в житті) потрібно знати, що цього не можна робити.

Не використовуйте тверджень для перевірки аргументів командного рядка

Це є дійсно специфічним випадком правила "Не використовуйте тверджень для перевірки аргументів public методу". Якщо програма вимагає аргументів командного рядка, можна використати механізм винятків для дотримання правил.

Використовуйте твердження, навіть в public методах, для виділення випадків, які ніколи не повинні відбутись
Це може містити блоки коду, які ніколи не будуть виконаними, включно із виразами default в операторі switch:
switch(x) {
case 1: y = 3; break;
case 2: y = 9; break;
case 3: y = 27; break;
default: assert false; //Сюди неможливо дістатись!
}

Якщо можна припустити, що певний блок коду ніколи не буде виконаним, як у попередньому прикладі, де припущено, що x повинен приймати значення 2, 3 або 4, тоді можна використати assert false для миттєвого виклику AssertionError, якщо до цього коду все ж таки дістатись. Тому у прикладі із switch тест boolean не виконається — вже припустили, що ніколи тут не опинимось, тобто дійсне знаходження в цьому місці є автоматичною відмовою твердження/припущення.

Не використовуйте виразів Assert, що можуть спричинити побічні ефекти!
Наступний код є надзвичайно неправильним:
public void doStuff() {
assert (modifyThings());
// продовження
}
public boolean modifyThings() {
y = x++;
return true;
}
Це є правилом: вираз assert після свого виходу повинен залишити програму у тому ж стані, в якому вона була до цього виразу! Вирази assert не завжди виконуються, тому є небажаною різна поведінка коду в залежності від дозволу тверджень. Твердження не повинні спричиняти жодних побічних ефектів. Якщо твердження є дозволеними, єдиною зміною шляху виконання програми може бути AssertionError, що викличеться у випадку хибності одного з тверджень.
.