Как сделать ваш код лаконичным и послушным одновременно?
Вам когда-нибудь встречалось приложение, которое вело себя очевидно странно? Ну, вы знаете, вы нажимаете на кнопку и ничего не происходит. Или экран вдруг чернеет. Или приложение впадает в «странное состояние» и вам приходится перезагружать его, чтобы все снова заработало.
Если у вас был подобный опыт, то вы вероятно стали жертвой определенной формы защитного программирования (defensive programming), которую я бы хотел назвать «параноидальное программирование». Защитник осторожный и рассудительный. Параноик испытывает страх и действует странно. В этой статье я предложу альтернативный подход: Offensive programming .
Бдительный читатель
Как может выглядеть параноидальное программирование? Вот типичный пример на Java
public String badlyImplementedGetData(String urlAsString) {
// Convert the string URL into a real URL
URL url = null;
try {
url = new URL(urlAsString);
} catch (MalformedURLException e) {
logger.error("Malformed URL", e);
}
// Open the connection to the server
HttpURLConnection connection = null;
try {
connection = (HttpURLConnection) url.openConnection();
} catch (IOException e) {
logger.error("Could not connect to " + url, e);
}
// Read the data from the connection
StringBuilder builder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
builder.append(line);
}
} catch (Exception e) {
logger.error("Failed to read data from " + url, e);
}
return builder.toString();
}
Этот код просто читает содержимое URL как строку. Неожиданное количество кода для выполнения очень простого задания, но такова Java.
Что не так с этим кодом? Код кажется справляется со всеми вероятными ошибками, которые могут возникнуть, но он делает это ужасным способом: просто игнорируя их и продолжая работу. Такая практика безоговорочно поддерживается Java’s checked exceptions ( абсолютно плохое изобретение), но и в других языках видно похожее поведение.
Что происходит если возникает ошибка:
- Если переданный URL невалидный (например “http//..” вместо “http://…”), то следующая строчка выдаст NullPointerException: connection = (HttpURLConnection) url.openConnection(); В данный момент несчастный разработчик, который получит сообщение об ошибке потерял весь контекст настоящей ошибки и мы даже не знаем какой URL вызвал проблему.
- Если рассматриваемый веб сайт не существует, то ситуация становится намного, намного хуже: метод вернет пустую строку. Почему? Результат StringBuilder builder = new StringBuilder(); по-прежнему будет возвращен из метода.
Некоторые разработчики утверждают, что подобный код хорош, так как наше приложение не упадет. Я бы поспорил, что существуют вещи похуже, которые могут случиться с нашим приложением помимо падения. В данном случае, ошибка просто вызовет неправильное поведение без какого-либо объяснения. Например экран может оставаться пустым, но приложение не выдает ошибки.
Давайте посмотрим на код, написанный по беззащитному методу.
public String getData(String url) throws IOException {
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
// Read the data from the connection
StringBuilder builder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
builder.append(line);
}
}
return builder.toString();
}
Оператор throws IOException (необходимый в Java, но не в других известных мне языках) показывает, что данный метод может не отработать и вызывающий метод должен быть готов разобраться с этим.
Этот код более лаконичный и если возникает ошибка, то пользователь и логгер (предположительно) получат правильное сообщение об ошибке.
Урок 1: Не обрабатывайте исключения локально.
Оградительный поток
Тогда как же должны обрабатываться ошибки подобного рода? Чтобы хорошо сделать обработку ошибок, нам необходимо учитывать всю архитектуру нашего приложения. Давайте предположим, что у нас есть приложение, которое периодически обновляет UI с содержимым какого-либо URL.
public static void startTimer() {
Timer timer = new Timer();
timer.scheduleAtFixedRate(timerTask(SERVER_URL), 0, 1000);
}
private static TimerTask timerTask(final String url) {
return new TimerTask() {
@Override
public void run() {
try {
String data = getData(url);
updateUi(data);
} catch (Exception e) {
logger.error("Failed to execute task", e);
}
}
};
}
Это именно тот тип мышления который мы хотим! Из большинства непредвиденных ошибок нет возможности восстановиться, но мы не хотим же чтобы наш таймер от этого остановился, не так ли?
А что случится если мы этого хотим? Во-первых, существует известная практика оборачивания Java’s checked exceptions в RuntimeExceptions
public static String getData(String urlAsString) {
try {
URL url = new URL(urlAsString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// Read the data from the connection
StringBuilder builder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
builder.append(line);
}
}
return builder.toString();
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
На самом деле, библиотеки были написаны с немного большим смыслом, чем сокрытие этой уродливой фичи языка Java.
Теперь мы можем упростить наш таймер:
public static void startTimer() {
Timer timer = new Timer();
timer.scheduleAtFixedRate(timerTask(SERVER_URL), 0, 1000);
}
private static TimerTask timerTask(final String url) {
return new TimerTask() {
@Override
public void run() {
updateUi(getData(url));
}
};
}
Если мы запустим этот код с ошибочным URL ( или сервер не доступен), все станет достаточно плохо: Мы получим сообщение об ошибке в стандартный поток вывода ошибок и наш таймер умрет.
В этот момент времени одна вещь должна быть очевидна: Этот код повторяет действия вне зависимости от того баг ли это, который вызывает NullPointerException, или же просто сервер сейчас не доступен.
В то время как вторая ситуация нас устраивает, первая может быть не очень: баг, который заставляет наш код падать каждый раз теперь будет мусорить в наш лог ошибок. Может нам будет лучше просто убить таймер?
public static void startTimer() // ...
public static String getData(String urlAsString) // ...
private static TimerTask timerTask(final String url) {
return new TimerTask() {
@Override
public void run() {
try {
String data = getData(url);
updateUi(data);
} catch (IOException e) {
logger.error("Failed to execute task", e);
}
}
};
}
Урок 2: Восстановление не всегда хорошо. Вы должны принимать во внимание ошибки, вызванные средой, например проблемами с сетью, и ошибки, вызванные багами, которые не исчезнут до тех пор, пока кто-то не исправит код.
Вы на самом деле там?
Давайте предположим, что у нас есть класс
WorkOrders
с задачами. Каждая задача реализуется кем-то. Мы хотим собрать людей, которые вовлечены в WorkOder
. Вы наверняка встречались с подобным кодом:public static Set findWorkers(WorkOrder workOrder) {
Set people = new HashSet();
Jobs jobs = workOrder.getJobs();
if (jobs != null) {
List jobList = jobs.getJobs();
if (jobList != null) {
for (Job job : jobList) {
Contact contact = job.getContact();
if (contact != null) {
Email email = contact.getEmail();
if (email != null) {
people.add(email.getText());
}
}
}
}
}
return people;
}
В этом коде мы не особо доверяем тому что происходит, не так ли? Давайте предположим, что нам скормили плохие данные. В этом случае, код радостно проглотит данные и вернет пустой набор. Мы на самом деле не заметим, что данные не соответствуют нашим ожиданиям.
Давайте вычистим код:
public static Set findWorkers(WorkOrder workOrder) {
Set people = new HashSet();
for (Job job : workOrder.getJobs().getJobs()) {
people.add(job.getContact().getEmail().getText());
}
return people;
}
Эй! Куда исчез весь код? Вдруг снова легко обсуждать и понимать код. И если есть проблема со структурой обрабатываемой задачи, наш код радостно сломается и сообщит нам об этом.
Проверка на null является одним из самых коварных источников параноидального программирования и их количество быстро увеличивается. Представьте что вы получили багрепорт с продакшна — код просто свалился с NullPointerException в этом коде.
public String getCustomerName() {
return customer.getName();
}
Люди в стрессе! И что делать? Конечно, вы добавляется еще одну проверку на null.
public String getCustomerName() {
if (customer == null) return null;
return customer.getName();
}
Вы компилируете код и выгружаете его на сервер. Немного погодя, вы получаете другой отчет: null pointer exception в следующем коде.
public String getOrderDescription() {
return getOrderDate() + " " + getCustomerName().substring(0,10) + "...";
}
И так это и начинается — распространение проверки на null во всем коде. Просто пресеките проблему в самом начале: не принимайте nulls.
И кстати, если вы интересуетесь можем ли мы заставить код парсера принимать nulls и оставаться простым, то да-мы можем. Предположим, что в примере с задачами мы получаем данные из XML файла. В этом случае, мой любимый способ решения этой задачи будет таким:
public static Set findWorkers(XmlElement workOrder) {
Set people = new HashSet();
for (XmlElement email : workOrder.findRequiredChildren("jobs", "job", "contact", "email")) {
people.add(email.text());
}
return people;
}
Конечно, это требует более достойной библиотеки чем та что есть в Java сейчас.
Урок 3: Проверки на null прячут ошибки и порождают больше проверок на null.
Заключение
Пытаясь писать защитный код, программисты часто в конечном итоге становятся параноиками — отчаянно накидываясь на все проблемы, которые они видят, вместо того чтобы разобраться с первопричиной. Беззащитная стратегия разрешения коду упасть и исправления источника проблемы сделает ваш код чище и менее подверженным ошибкам.
Сокрытие ошибок ведет к размножению багов. Взрыв приложения, прямо перед вашим носом заставляет вас решать реальную проблему.
За перевод спасибо Ольге Чернопицкой и компании Edison, которая разрабатывает внутреннюю систему тестирования персонала и приложение для учета рабочего времени сотрудников.
Комментарии (34)
webkumo
09.11.2016 20:46+5Простите, для кого эта статья? Для новичков? Тогда может по рукам пора дать?
«Урок 1». Вам не нравятся checked exception? Это нормально — примерно половине разработчиков на java они не нравятся. Другой половине — нравятся. И вот что интересно — если их уметь готовить, то они очень даже удобны (речь не идёт о работе со стримами в java8, это единственная область, где они действительно неудобны почти всем). А вот ваш способ решения — давно проверенный антипаттерн. Конкретно эту ошибку нужно обработать здесь и сейчас. А вот если она ломает логику вашего метода — наверх выкинуть надо СВОЁ исключение. И нет, расплываться мыслью по древу не надо — верхний пример прекрасно схлопывается в один try-catch блок
«Урок 2», спасибо КЭП!.. Только что же примеры такие слабые?
«Урок 3», чувак, всё хорошо на своём месте. null — очень полезная фича. В некоторых языках их легко синтаксисом обернуть в Optional, в Java это приходится делать самому. Ну и проверки на null должны быть адекватны — обязательное поле оказалось null? ПАДАЕМ и логируем — это дело надо исправлять. Необязательное поле? — так и хрен же с ним, а вот защититься от падения здесь надо.
PS в общем переработав статью можно свести к одной фразе «при программировании необходимо включать мозги».Lure_of_Chaos
10.11.2016 00:30+1Урок 1. Не всякое исключение обязательно ловить и пробрасывать собственное — в некоторых случаях «системное» исключение будет не менее информативно, а кол-во кода и классов собственных исключений будет меньше. И еще, нет ничего отвратительнее catch(Exception ex)
Урок 2. Да, исключения бывают штатными и неожиданными, но тут разница только в том, что отсутствие обработки первых — позорно, а вот вторые нужно «почти» не обрабатывать (в гуёвом приложении можно выкинуть окошко «Извините, но что-то плохое случилось», в headless — как минимум, расписать подробно в лог, что мы пытались сделать)
Урок 3. Все сводится к холивару «fail-fast vs defaults», и где лучше исключение, а где — пустые данные — вопрос вечно открытый. А на самом деле, в Java явно недостает конструкции?.. чтобы вместо if(a!=null&&a.getB()!=null&&a.getB().getC()!=null) писать if(a?.getB()?.getC()!=null)qw1
10.11.2016 01:29+1И еще, нет ничего отвратительнее catch(Exception ex)
С этим можно поспорить. Досталось в наследство консольное приложение (c#), которое забирало данные с сервера и рассылало на почту. Запускалось через Task Scheduler ежедневно. И там все ситуации, типа ошибка 404 или ошибка 500, были перехвачены отдельно.
Но в определённый момент оно стало просто виснуть. В настройках расписания было указано, что если вчерашний запуск не закончился, новые экземпляры не запускаются. Оказалось, что появилось новое, не предвиденное автором исключение — ошибка ресолва хоста. Исключение не перехватывалось и программа висело со стандартным .net окном: «я упало, выберите Дебажить/Закрыть».
Просто заменил кучу всяких catch-ей на одинcatch (Exception ex)
, который пишет ошибку в лог, пишет письмо админу и завершает программу и проблема решена.drakmail
10.11.2016 01:56+1Имхо, это тоже самое (концептуально), что и работа без catch вообще.
webkumo
10.11.2016 02:03Если не идёт речи о работе в каком-либо контейнере (который, кстати, ловит Exception), то концептуально это может быть полезным кейсом (в большинстве случаев — аналогиным кейсам этих самых контейнеров). Вот Error ловить — совсем нельзя…
MacIn
10.11.2016 03:53+1Без catch вообще исключение может никем быть не поймано вовсе, а если и поймано, то неизвестно как обработано — может быть, тем же выводом окна «я упало».
redmanmale
10.11.2016 12:49+1А можно было просто изменить настройки винды, чтобы такое окно (я упало, выберите Дебажить/Закрыть) не появлялось. Это один флаг в реестре.
А ещё лучше дописать немного кода, преобразовав приложение в сервис, и в его настройках выбрать требуемое поведение при остановке, например, повторный запуск.
qw1
10.11.2016 18:37А можно было просто изменить настройки винды
И повлиять на все другие приложения. Нет, так не надо.
ещё лучше дописать немного кода, преобразовав приложение в сервис
И круглосуточно занимать память и крутить цикл ожидания (подписываться на таймеры), чтобы сделать что-то 5 минут в день.
Lure_of_Chaos
10.11.2016 13:53+1Для таких случаев можно использовать catch (Exception ex) на самом верхнем уровне, чтобы пользователю не показывалось 404 и 500, но это очень специфичное исключение. Например, эта же конструкция на нижних уровнях — уродлива.
qw1
10.11.2016 18:39Да, я и сделал на верхнем уровне. Я хотел сказать, на верхнем уровне (в Main) отлавливать исключения по списку бывает ненадёжно.
Например, эта же конструкция на нижних уровнях — уродлива.
А если так
catch (Exception ex) { throw new MySpecificException(LocalOperationDetails, ex); }
Igor_Sib
10.11.2016 06:15// И еще, нет ничего отвратительнее catch(Exception ex)
Почему? Поясните, пожалуйста, я вот иногда так делаю.Lure_of_Chaos
10.11.2016 13:59+2Потому что рискуем зажевать неожиданное исключение и создать ложное впечатление правильности работы юнита (класса, слоя) и долго потом искать реальную причину неправильного поведения системы. Более правильный способ — ловить наиболее конкретизированные исключения, с индивидуальной обработкой (и индивидуальным логгированием), за, возможно, исключением случая, когда неожиданное исключение долетает до пользователя.
svboobnov
09.11.2016 20:56-5А почему не предлагается подход, распространённый в 1С? Примерно так:
public String getCustomerName() { if ( ! isCustomerCorrect(customer) ) { logger.log("in getCustomerName() we have" + customer.toString() + " incorrect value."); /* если метод не гуёвый, то выбросим здесь exception и дело с концом */ GUI.alert("The value of '\customer\' field is incorrect! \n" + customer.toString()); return null; }; return customer.getName(); }
т.е. здесь идея в том, что код сам по себе не падает, а переходит в ветку «МнеДалиПлохиеДанные-Бибикаю»Lure_of_Chaos
10.11.2016 00:03+4Во-первых, не переходит в ветку «МнеДалиПлохиеДанные-Бибикаю», а падает выше с кодом
public String getOrderDescription() { return getOrderDate() + " " + getCustomerName().substring(0,10) + "..."; }
Во-вторых, приложение получается «бибикающим», вместо работающего (помните программы-шутки из 90х годов, которые заставляли нажимать ОК 100 раз?)
И в-третьих, GUI.alert по-хорошему не должен быть доступен отовсюду: изолированный код — хороший код (см. закон Деметры)
Sing
10.11.2016 12:54+1Вашей функции дали плохие данные. Вы сообщаете об этом факте — это хорошо.
Но после сообщения вы возвращаете, опять-таки, плохие данные. Ваша функция — её контракт, если хотите — вернуть имя пользователя. null — это не имя пользователя ни в коей мере, вы проталкиваете плохие данные дальше, рискуя:
1. Упасть где-то ещё — проблемы с нахождением первопричины ошибки
2. Испортить пользовательские данные
3. Где-то ещё плодить кучу костылей, героически справляясь со своим кривым дизайном
MacIn
09.11.2016 21:29Урок 3: Проверки на null прячут ошибки и порождают больше проверок на null.
Проверки на null не прячут ошибки. Обертки вида
if (abc != null) {
}
прячут. Тут поможет assert вместо проверки.
Давайте вычистим код:
for (Job job : workOrder.getJobs().getJobs()) {
Сразу возникает вопрос — сфигаль мы, получив какие-то джобс из них получаем какие-то другие джобс?
В длинном коде:
Jobs jobs = workOrder.getJobs(); if (jobs != null) { List jobList = jobs.getJobs();
хотя бы понятно, что мы из какого-то супер-объекта джобс получаем список. А в короткой версии это вообще неочевидно — wtf/min увеличивается.Lure_of_Chaos
10.11.2016 00:15assert вместо проверки — и есть защитный подход.
по второму пункту: да, «короткий» код скрывает типы объектов, но по сути, тут просто неверный выбор имен. Код
куда понятнее.workOrder.getJobGroup().getJobList()
kk86
09.11.2016 23:33> Как может выглядеть параноидальное программирование? Вот типичный пример на Java
И дальше идет пример параноидального программирования. Это совсем не то же самое, что и защитное программирование.
«защитное программирование» это не есть «стелить солому везде, где только можно»
Lure_of_Chaos
10.11.2016 00:09+1наш код радостно сломается и сообщит нам об этом.
что он упал с NPE на 4ой строке. А почему? да хз, то ли наgetContact(), то ли .getEmail(), а может и на getText(). В страшном коде с кучей проверок мы хотя бы сможем узнать, что у нас null.
EngineerSpock
10.11.2016 10:23Урок 1: Не обрабатывайте исключения локально.
Нет. Как раз урок должен быть: старайтесь обрабатывать ошибки локально. Чем ближе код к месту возникновения ошибки, тем больше он знает про контекст и тем вероятнее может компенсировать ошибку (прописать обработку). Иногда это невозможно, тогда уровень выше обязан сделать это. Через уровень — 99% никто ничто уже не обработает. Улавливаете?
Урок 2: Восстановление не всегда хорошо. Вы должны принимать во внимание ошибки, вызванные средой, например проблемами с сетью
Если вылетает SocketException в результате того, что я дёрнул проводок — ваше приложение падает? Серьёзно?
Урок 3: Проверки на null прячут ошибки и порождают больше проверок на null.
Это не урок. Это НИ О ЧЁМ. Про это целую статью накатать можно. Как можно давать такие размашистые советы, вообще не понимаю. Детский сад, ну, ей богу.Lure_of_Chaos
10.11.2016 14:13тем вероятнее может компенсировать ошибку
опять же вопрос — как именно он это делает. Есть опасность вернуть неверные данные. И опять же, появляется необходимость пробрасывания и обработки исключений выше — иногда это излишне.
Если вылетает SocketException в результате того, что я дёрнул проводок — ваше приложение падает?
Не намного хуже того, если приложение не падает, а назойливо жалуется, ничего не делает или вообще виснет, пока не вдёрнете проводок на место — вот что имелось ввиду, по-моему.
Про это целую статью накатать можно
Так вся статья о том, насколько и в каких случаях приложение должно быть параноидальным — городить ли проверки на каждую функцию, или «пусть падает» до более общей проверки?EngineerSpock
10.11.2016 15:07опять же вопрос — как именно он это делает. Есть опасность вернуть неверные данные. И опять же, появляется необходимость пробрасывания и обработки исключений выше — иногда это излишне.
Кому какой вопрос? Речь о том, что изначальный посыл — неверен. Абсолютно.
Не намного хуже того, если приложение не падает, а назойливо жалуется, ничего не делает или вообще виснет, пока не вдёрнете проводок на место — вот что имелось ввиду, по-моему.
Плачу кровавыми слезами. Приложение, падающее при выдёргивании провода — ДЕБИЛИЗМ. Просто вдумайтесь в это, неужели непонятно? Во-первых, зачастую приложения не ограничены в своих возможностях наличием связи. Может быть независимый функционал. Второе — возможно кэширование. Третье — на крайний случай — надо сказать, что данных нет, так как нет связи — не надо тупо падать и не надо делать назойливо, надо просто уведомить и всё. Откройте официальный клиент ВК без связи — он падает? Вот и подумайте ещё раз. Запомните: SocketException, IOException и иже с ними НИКОГДА не должны валить приложение.
Так вся статья о том, насколько и в каких случаях приложение должно быть параноидальным — городить ли проверки на каждую функцию, или «пусть падает» до более общей проверки?
В контексте использования null — утверждение в статье это пшик. Просто ни о чём. Хорошее приложение должно быть устойчивым, а средств достижения много. Об этом в статье ноль.grossws
10.11.2016 19:47Запомните: SocketException, IOException и иже с ними НИКОГДА не должны валить приложение.
It depends. Если приложение, например, не смогло прочитать необходимый для запуска конфиг, т. к. его нет на диске и нет каких-нибудь sensible defaults с которыми можно запуститься — вполне логично упасть с информированием пользователя об отсутствии этого конфига (но не с голым IOE, который ничего не говорит пользователю, ессно).
EngineerSpock
10.11.2016 23:22Да, согласен. Если не способно запуститься приложение, то да, согласен. Интересно не единственный ли это кейс.
Akon32
10.11.2016 14:18Если некто написал такой код
Скрытый текстURL url = null; try { url = new URL(urlAsString); } catch (MalformedURLException e) { logger.error("Malformed URL", e); } // Open the connection to the server HttpURLConnection connection = null; try { connection = (HttpURLConnection) url.openConnection(); } catch (IOException e) { logger.error("Could not connect to " + url, e); }
webkumo
10.11.2016 16:22-1По проблеме второго цитированного куска кода — бить по рукам не за null нужно, а за избыточный функционал (и за использование коллекций-не-генериков) и хреновую архитектуру… хотя да, за null вместо коллекций можно и по кривым рукам хлопнуть.
Вот что такое класс Jobs? Очевидно workOrder должен содержать коллекцию <job>. (бьём по рукам за кривую архитектуру)
Почему jobs.getJobs может вернуть null вместо пустой коллекции? (бьём по рукам за некорректное использование null)
Какой на***н getContact в Job?! (бьём по рукам за кривую архитектуру)
Почему Email это не подкласс Contact (вот это может быть и обоснованными… но архитектура-то получается кривой)
Какого чёрта мы в Set<непонятно_чего> запихиваем email.getText()?!
А как должно быть? А должно быть разделение обязанностей:
Есть метод findWorkers? Пусть возвращает РАБОТНИКОВ! Хотим извлечь текстовые(!) значения их емейл адресов? Обработаем полученную коллекцию работников в другом месте!
И код свёлся бы к
private Set<Worker> findWorkers(WorkOrder workOrder) {
Set<Worker> workers = new HashSet<>();
for(Job job: workOrder.getJobs()) {
workers.add(job.getWorker());
}
return workers;
}
private Set<String> getWorkerContacts(Set<Worker> workers) {
return workers.stream()
.map(Worker::getEmail)
.filter(email -> email != null)
.map(Email::getText)
.collect(Collectors.toSet());
}
public Set<String> getWorkerContacts(WorkOrder workOrder) {
Set<Worker> workers = getWorkers(workOrder);
return getWorkersContacts(workers);
}
PS сори, парсер съел отступы, а на использование корректной разметки нехватает кармы.
grossws
10.11.2016 19:39+1Это не deffencive programming, это говнокод обыкновенный. При вызове, например
badlyImplementedGetData(null)
будет простой тупой NPE изconnection = (HttpURLConnection) url.openConnection();
.
public String badlyImplementedGetData(String urlAsString) { // Convert the string URL into a real URL URL url = null; try { url = new URL(urlAsString); } catch (MalformedURLException e) { logger.error("Malformed URL", e); } // Open the connection to the server HttpURLConnection connection = null; try { connection = (HttpURLConnection) url.openConnection(); } catch (IOException e) { logger.error("Could not connect to " + url, e); } }
Deffencive programming предполагает проверку всех веток. Например, проверка
urlAsString
на null, отсечение веткиurl == null
(ранним возвратом, использование исключения или монадическими операциями наj.u.Optional<T>
— не суть важно).burjui
10.11.2016 21:09+2Жаль, монады вообще и Optional в частности мало кто знает и использует. В народе бытует мнение, что функциональное программирование — игрушка для теоретиков, и несовместимо с императивщиной. Хотя в данном примере монада Either не только не помешает, но и сильно улучшит код: обработку ошибок нельзя будет пропустить, и программе не будет грозить падение из-за необработанного исключения.
Akon32
11.11.2016 10:35К сожалению, на scala Option (думаю, и на java тоже) может давать значительную дополнительную нагрузку на gc, если нужно иметь в памяти десятки или сотни миллионов optional значений.
grossws
11.11.2016 15:35Если это значение короткоживущее, то оно будет в TLAB, выделение в котором очень дешевое. Т. е. для короткоживущих
Option
/Either
оверхед для gc будет небольшой. HotSpot, кажется, не имеет оптимизации по размещению таких объектов на стеке, к сожалению, а то могло бы быть ещё приятнее.
Есть ещё пример rust'а, где
Option
для ссылок (которые всегда неnull
) иBox
(объект в куче) не имеет оверхеда, т. к. представляет в памяти просто нулевой указатель дляNone
и ненулевой дляSome(...)
. Во многих других случаях он имеет оверхед размером в 4 байта на тэг как и остальные такие enum'ы.
DrBAXA
Что значит
Всё зависит от контекста, вот например
Обязан каждый контакт иметь имейл? А если только телефон. Здесь
null
вполне правильное значение, никак не исключительное соответственно не должно никак вызывать исключений.Поетому проверки на
null
нужны там где они нужны!В Java 8 появились Optional которые позволяют делать эти проверки более красивыми и читаемыми.
kk86
NullObject может решить проблему в ряде случаев.