Чому для додаткової перевірки змінної використовується додаткова преференція?


25

Візьміть два приклади коду:

if(optional.isPresent()) {
    //do your thing
}

if(variable != null) {
    //do your thing
}

Наскільки я можу сказати, найбільш очевидною різницею є те, що необов'язково вимагає створення додаткового об'єкта.

Однак багато людей почали швидко приймати необов’язкові. Яка перевага використання необов’язкових даних проти нульової перевірки?


4
ви майже ніколи не хочете використовувати isPresent, зазвичай ви повинні використовувати ifPresent або map
jk.

6
@jk. Оскільки ifзаяви дуууже останнє десятиліття, і все це з допомогою монади абстракцій і лямбда в даний час.
користувач253751

2
@immibis Я колись мав тривалі дискусії щодо цієї теми з іншим користувачем. Справа в тому, що if(x.isPresent) fails_on_null(x.get)ви виходите з системи типу і дотримуєтесь гарантії того, що код не зламається «в голові» на (правдоподібно коротку) відстань між умовою та викликом функції. У optional.ifPresent(fails_on_null)системі типу ця гарантія надається вам, і вам не потрібно хвилюватися.
ziggystar

1
Основний недолік Java з використанням Optional.ifPresent(та різних інших конструкцій Java) полягає в тому, що ви можете лише ефективно змінювати остаточні змінні та не можете кидати перевірені винятки. Це є достатньою причиною, щоб ifPresent, на жаль, часто уникати .
Майк

Відповіді:


19

Optionalвикористовує систему типів для виконання роботи, яку в іншому випадку вам доведеться робити все в голові: пам'ятати, чи може бути дана посилання null. Це добре. Завжди розумно дозволити компілятору обробляти нудні інші роботи та зарезервувати людську думку для творчої, цікавої роботи.

Без цього Optional, кожне посилання у вашому коді - як бомба, що не підірвана. Доступ до нього може зробити щось корисне, інакше він може припинити вашу програму з виключенням.

За бажанням та без нього nullкожен доступ до нормальної посилання є успішним, і будь-яке посилання на Факультатив є успішним, якщо воно не встановлено, і ви не змогли перевірити це. Це величезна виграш у ремонтопридатності.

На жаль, більшість мов, які зараз пропонують Optional, не скасували null, тому ви можете отримати прибуток від цієї концепції, запровадивши сувору політику "абсолютно nullніколи не буває ". Тому, Optionalнаприклад, Java не є настільки виграшною, як повинна бути в ідеалі.


Оскільки ваша відповідь найкраща, можливо, варто додати використання Lambdas як перевагу - для тих, хто читає це в майбутньому (див. Мою відповідь)
William Dunne

Більшість сучасних мов надають нерегульовані типи: Kotlin, Typescript, AFAIK.
Дзен

5
IMO це краще вирішувати з анотацією @Nullable. Немає підстав використовувати Optionalлише для того, щоб нагадати, що значення може бути нульовим
Hilikus

18

Покращене Optionalвведення операцій, які можуть бути невдалими, як і інші відповіді, але це далеко не найцікавіше або найцінніше , що подається Optionalsдо столу. Набагато кориснішою є можливість затримати або уникати перевірки на помилку, а також легко складати багато операцій, які можуть вийти з ладу.

Подумайте, чи мала ви optionalзмінна зі свого прикладу коду, тоді вам довелося виконати два додаткові кроки, які можуть потенційно вийти з ладу. Якщо якийсь крок на цьому шляху не вдається, ви хочете повернути значення за замовчуванням. Використовуючи Optionalsправильно, ви закінчуєте щось подібне:

return optional.flatMap(x -> x.anotherOptionalStep())
               .flatMap(x -> x.yetAnotherOptionalStep())
               .orElse(defaultValue);

З цим nullмені довелося б перевірити три рази, nullперш ніж продовжувати, що додає коду чимало складностей і головних болів у обслуговуванні. Optionalsувімкнути цю перевірку в функції flatMapта orElseфункції.

Примітка: Я жодного isPresentразу не дзвонив , що слід використовувати як запах коду під час використання Optionals. Це не обов'язково означає, що ви ніколи не повинні використовувати isPresent, просто що вам слід ретельно вивчити будь-який код, який є, щоб побачити, чи є кращий спосіб. В іншому випадку ви маєте рацію, ви отримуєте лише граничну перевагу щодо безпеки від використання null.

Також зауважте, що я не так переживаю, як інкапсулювати все це в одну функцію, щоб захистити інші частини мого коду від нульових покажчиків від проміжних результатів. Якщо є сенс мати мою .orElse(defaultValue)іншу функцію, наприклад, у мене набагато менше завдань щодо її розміщення, і набагато простіше складати операції між різними функціями за потребою.


10

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

В ідеалі встановлення змінної на null було б помилкою часу компіляції в будь-якому місці, крім додаткового; усунення винятку з нульовим покажчиком часу виконання. Очевидно, що сумісність ззаду виключає це


4

Дозвольте навести приклад:

class UserRepository {
     User findById(long id);
}

Цілком зрозуміло, для чого призначений цей метод. Але що станеться, якщо репозиторій не містить користувача із заданим ідентифікатором? Це може кинути виняток або, можливо, воно поверне нульове значення?

Другий приклад:

class UserRepository {
     Optional<User> findById(long id);
}

Тепер, що тут відбувається, якщо немає користувача із заданим ідентифікатором? Правильно, ви отримуєте екземпляр Optional.absent () і можете перевірити його за допомогою Optional.isPresent (). Підпис методу з Факультативом більш чітко стосується його договору. Безпосередньо зрозуміло, як повинен поводитися метод.

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

User user = userRepository.findById(userId).get();

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

Необов’язково використовується з умовою, що методи із типом повернення Необов’язково ніколи не повертають нульові, а зазвичай також із (менш сильною) умовою, що метод без необов'язкового типу повернення ніколи не повертає нуль (але краще анотувати його за допомогою @Nonnull, @NotNull або подібного) .


3

Optional<T>Дозволяє мати «провал» або «немає результату» в якості дійсного значення відповіді / повернення для ваших методів (думаю, наприклад, у вигляді пошуку в базі даних). Використання Optionalзамість того, nullщоб вказувати на збій / відсутність результату, має деякі переваги:

  • Це чітко повідомляє, що "провал" - це варіант. Користувач вашого методу не повинен здогадуватися, чи nullможе бути повернутий.
  • Значення null, принаймні, на мою думку, не повинно використовуватися для позначення нічого, оскільки семантика може бути не зовсім зрозумілою. Це слід використовувати для того, щоб повідомити сміттєзбірнику, що раніше може бути зібраний об'єкт.
  • OptionalКлас надає безліч методів приємно умовно роботу зі значеннями (наприклад, ifPresent()) або визначити значення по замовчуванням прозорим чином ( orElse()).

На жаль, це не повністю усуває необхідність nullперевірки коду, оскільки сам Optionalоб'єкт все ще може бути null.


4
Насправді це не обов'язково. Щоб повідомити про помилку, використовуйте виняток. Якщо ви не можете цього зробити, використовуйте тип, який містить або результат, або код помилки .
Джеймс Янгмен

Я категорично не згоден. Необов’язково.empty () - це чудовий спосіб показати невдачу чи неіснування на мою думку.
LegendLength

@LegendLength, я мушу категорично не погодитися у випадку відмови. Якщо функція не спрацьовує, краще дайте мені пояснення, а не лише порожнє: "Я не зміг"
Вінстон Еверт

Вибачте, я згоден, я маю на увазі "очікуваний збій", оскільки під час пошуку не знайшли працівника з прізвищем "x".
LegendLength

3

Окрім інших відповідей, інша основна перевага Факультативів щодо написання чистого коду полягає в тому, що ви можете використовувати вираз Lambda у випадку наявності значення, як показано нижче:

opTr.ifPresent(tr -> transactions.put(tr.getTxid(), tr));

2

Розглянемо наступний метод:

void dance(Person partner)

Тепер давайте розглянемо якийсь код виклику:

dance(null);

Це спричиняє затруднення відстежувати аварію десь в іншому місці, оскільки danceне очікував нуля.

dance(optionalPerson)

Це спричиняє помилку компіляції, тому що танець очікував " Personне"Optional<Person>

dance(optionalPerson.get())

Якщо у мене не було особи, це спричиняє виняток із цієї лінії, в той момент, коли я незаконно намагався перетворити цю Optional<Person>особу на Особу. Ключовим є те, що на відміну від передачі нуля, за винятком лише слідів тут, а не якогось іншого місця в програмі.

Щоб узагальнити це: неправильне використання необов'язкових призводить до легшого відстеження проблем, неправильне використання нуля спричиняє труднощі для відстеження проблем.


1
Якщо ви викидаєте винятки, коли дзвонить Optional.get()ваш код погано написаний - ви майже ніколи не повинні дзвонити на Optional.get, не перевіряючи Optional.isPresent()спочатку (як зазначено в ОП), або ви могли написати optionalPersion.ifPresent(myObj::dance).
Кріс Купер

@QmunkE, так, ви повинні зателефонувати Optional.get (), лише якщо ви вже знаєте, що необов'язковий не порожній (або тому, що ви викликали isPresent, або тому, що ваш алгоритм це гарантує). Однак я переживаю за те, щоб люди сліпо перевіряли isPresent, а потім ігнорували відсутні значення, створюючи безліч тихих відмов, які будуть болісно простежити.
Вінстон Еверт

1
Мені подобаються додаткові. Але я скажу, що звичайні старі нульові вказівники дуже рідко є проблемою в реальному коді. Вони майже завжди провалюються поблизу порушника рядка коду. І я думаю, що люди перекриваються, коли розмовляють на цю тему.
LegendLength

@LegendLength, мій досвід полягає в тому, що 95% випадків справді легко відстежуються, щоб визначити, звідки походить NULL. Решта 5%, однак, надзвичайно складно відстежити.
Вінстон Еверт

-1

У Objective-C всі посилання на об'єкти необов’язкові. Через це такий код, як ви розмістили, нерозумно.

if (variable != nil) {
    [variable doThing];
}

Кодекс, як зазначено вище, є нечуваним у Objective-C. Натомість програміст просто:

[variable doThing];

Якщо variableмістить значення, то doThingвін буде викликаний на ньому. Якщо ні, не шкода. Програма розцінить це як неоперативний та продовжує працювати.

Це реальна користь необов'язкових. Вам не доведеться засмічувати свій код if (obj != nil).


Хіба це не просто форма мовчки невдачі? У деяких випадках ви можете піклуватися про те, що значення є нульовим, і хочете щось зробити з цим.
LegendLength

-3

якщо (необов’язково.isPresent ()) {

Очевидна проблема тут полягає в тому , що якщо « по бажанню» на насправді є відсутній (тобто дорівнює нулю) , то ваш код буде підривати з NullReferenceException (або аналогічний) намагається викликати будь-який метод на об'єктну посилання нульовий!

Ви можете написати статичний метод Helper-класу, який визначає нульовість будь-якого даного типу об'єкта, приблизно так:

function isNull( object o ) 
{
   return ( null == o ); 
}

if ( isNull( optional ) ) ... 

або, можливо, корисніше:

function isNotNull( object o ) 
{
   return ( null != o ); 
}

if ( isNotNull( optional ) ) ... 

Але чи справді будь-який з них є більш читабельним / зрозумілим / ремонтом, ніж оригінал?

if ( null == optional ) ... 
if ( null != optional ) ... 
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.