Java — это универсальный язык программирования, имеющий много альтернативных решений для ваших определённых задач. Тем не менее, существуют хорошие подходы, которым следует следовать, и также существуют некоторые неудачные подходы, которые мы до сих пор в большинстве своём используем.
Один из наиболее распространённых неудачных подходов — это использование исключений для контроля потока выполнения. Этого следует избегать по двум причинам:
- Это снижает производительность и быстродействие вашего кода
- Это делает ваш код менее читаемым
Давайте начнём с рассмотрения примера. Здесь исключение используется для управления потоком выполнения:
public static int findAge(String name) {
try {
String ageAsString = findUser(name);
return ageAsString.length();
} catch (NameNotFoundException e) {
return 0;
}
}
private static String findUser(String name) {
if(name==null) {
throw new NameNotFoundException();
}
return name;
}
Если пользователь предоставит не нулевое значение имени, то метод findAge вернёт длину этого имени, но если имя пользователя будет null, то тогда метод findUser выбросит исключение NameNotFoundException и в этом случае метод findAge вернёт 0.
Как мы можем преобразовать этот код без использования исключений? Вообще, существует множество вариантов, рассмотрим один из них:
public static int findAgeNoEx(String name) {
String ageAsString = findUserNoEx(name);
return ageAsString.length();
}
private static String findUserNoEx(String name) {
if(name==null) {
return "";
}
return name;
}
Чтобы выяснить влияние исключения на производительность, я подготовил следующий код, который вызывает 10 миллионов раз оба варианта методов: с исключением и без.
public class ControlFlowWithExceptionOrNot {
public static class NameNotFoundException extends RuntimeException {
private static final long serialVersionUID = 3L;
}
private static final int TRIAL = 10000000;
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
for (int i = 0; i < TRIAL; i++) {
findAgeNoEx(null);
}
System.out.println("Duration :" + (System.currentTimeMillis() - start));
long start2 = System.currentTimeMillis();
for (int i = 0; i < TRIAL; i++) {
findAge(null);
}
System.out.println("Duration :" + (System.currentTimeMillis() - start2));
};
public static int findAge(String name) {
try {
String ageAsString = findUser(name);
return ageAsString.length();
} catch (NameNotFoundException e) {
return 0;
}
}
private static String findUser(String name) {
if (name == null) {
throw new NameNotFoundException();
}
return name;
}
public static int findAgeNoEx(String name) {
String ageAsString = findUserNoEx(name);
return ageAsString.length();
}
private static String findUserNoEx(String name) {
if (name == null) {
return "";
}
return name;
}
}
Вывод:
Duration :16
Duration :6212
Как видно, использование исключения обошлось нам в тысячи миллисекунд на моём Intel Core i7-3630QM.
Если мы сравним два наших findAge метода с точки зрения читабельности, то метод без исключения абсолютно понятен: во-первых мы может быть абсолютно уверены, что метод findUser возвращает строку; а во-вторых независимо от того, какая именно строка будет возвращена, мы получим её длину. В то же время, метод с исключением несколько запутанный: не до конца ясно что именно возвращает метод findUser. Он может вернуть строку, а может и выбросить исключение и из сигнатуры метода этого не видно. По этой причине парадигма функционального программирования не приветствует использование исключений.
В конце концов, будет лучше, если вы будете использовать исключения там, где действительно возникает исключительная ситуация и это необходимо. Если же вы будете использовать исключения для управления потоком выполнения, это вызовет падение производительности программы и сделает ваш код менее читабельным.
Надеюсь, данная статья была вам интересна, а возможно и полезна.
Автор: Алексей Ильинчик