Чи є гарною практикою реалізація двох методів за замовчуванням Java 8 з точки зору один одного?


14

Я розробляю інтерфейс із двома спорідненими методами, подібними до цього:

public interface ThingComputer {
    default Thing computeFirstThing() {
        return computeAllThings().get(0);
    }

    default List<Thing> computeAllThings() {
        return ImmutableList.of(computeFirstThing());
    }
}

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

Чи є цей прецедент у широко використовуваному коді Java 8? Я знаю, що Haskell робить подібні речі в деяких класах ( Eqнаприклад).

Переважно, я повинен написати значно менше коду, ніж якби у мене було два абстрактні класи ( SingleThingComputerі MultipleThingComputer).

Мінус полягає в тому, що порожня реалізація компілюється, але під час виконання програми підіймається StackOverflowError. Можна виявити взаємну рекурсію за допомогою a ThreadLocalі дати приємнішу помилку, але це додасть накладних витрат на невідповідний код.


3
Чому б вам коли-небудь навмисне писати код, гарантований нескінченною рекурсією? Нічого хорошого з цього не може вийти.

1
Ну це не "гарантовано"; правильна реалізація змінила б один із цих методів.
Тавіан Барнс

6
Ти цього не знаєш. Клас або інтерфейс повинен стояти самостійно і не припускати, що підклас буде робити певний спосіб, щоб уникнути великих логічних помилок. В іншому випадку він крихкий і не може дотриматись своїх гарантій. Зауважте, що я не кажу, що ваш інтерфейс може або повинен примушувати виконувати клас виконання throw new Error();або щось дурне, лише те, що сам інтерфейс не повинен мати крихкий контракт defaultметодами.

Я згоден з @Snowman, це міна. Я думаю, що це підручник "злий код" у тому сенсі, що означає Джон Скіт - зауважте, що зло не те саме, що погано , воно зле / розумне / спокусливе, але все-таки неправильне :) Я рекомендую youtu.be/lGbQiguuUGc?t=15m26s ( і справді, вся розмова для більш широкого контексту - це дуже цікаво).
Конрад Моравський

1
Тільки тому, що кожну функцію можна визначити за допомогою іншої, це не означає, що вони можуть бути визначені в термінах одна від одної. Це не летить жодною мовою. Вам потрібно інтерфейс з двома абстрактними спадкоємцями, кожен реалізує одного з точки зору іншого, але абстрагує іншого, щоб виконавці вибирали, яку сторону вони хочуть реалізувати, успадкувавши від правильного абстрактного класу. Одна сторона повинна бути визначена незалежно від іншої, інакше поп переходить стек . У вас є божевільні налаштування за замовчуванням, якщо припущення, що виконавці вирішать проблему для вас, abstractіснує, щоб змусити їх вирішити.
Джиммі Хоффа

Відповіді:


16

TL; DR: не роби цього.

Що ви тут показуєте, це крихкий код.

Інтерфейс - це договір. У ньому написано "незалежно від того, який предмет ви отримаєте, він може робити X і Y". Як написано, ваш інтерфейс не робить ні X, ні Y, оскільки це гарантовано може викликати переповнення стека.

Викидання помилки або підкласу вказує на серйозну помилку, яку не слід сприймати:

Помилка - це підклас Throwable, який вказує на серйозні проблеми, які розумна програма не повинна намагатися наздогнати.

Крім того, VirtualMachineError , батьківський клас StackOverflowError , говорить про це:

Викинуто, щоб вказати, що віртуальна машина Java зламана або не вистачає ресурсів, необхідних для її подальшої роботи.

Ваша програма не повинна стосуватися ресурсів JVM . Така робота СП. Зробити програму, яка викликає помилку JVM як частину звичайної роботи, погано. Це або гарантує, що ваша програма вийде з ладу, або змушує користувачів цього інтерфейсу відловлювати помилки, з якими вона не повинна стосуватися.


Ви можете знати Еріка Ліпперта з таких зусиль, як emeritus "член комітету з розробки мови C #". Він розповідає про мовні особливості, що підштовхують людей до успіху чи невдачі: хоча це не мовна особливість або частина мовного дизайну, його точка настільки ж справедлива, що стосується впровадження інтерфейсів або використання об'єктів.

Ви пам’ятаєте в «Принцесі нареченій», коли Вестлі прокидається і опиняється замкненим у «Ямі Відчаю» із хрипким альбіносом та зловісною шестигранною людиною, графом Ругеном? Принципова ідея ями відчаю двояка. По-перше, що це яма, і тому легко потрапити в цю, але важку роботу з якої вилізти. І друге, що це викликає відчай. Звідси і назва.

Джерело: C ++ та "Пітне розпач"

Якщо викинути інтерфейс StackOverflowErrorза замовчуванням, штовхає розробників у яму розпачу, і це погана ідея . Натомість підштовхуйте розробників до « Ями Успіху» . Зробіть простий у використанні інтерфейс правильно та без збоїв JVM.

Делегація між методами тут добре. Однак залежність повинна йти в один бік. Мені подобається думати про делегування методу, як спрямований графік . Кожен метод - це вузол на графіку. Кожен раз, коли метод викликає інший метод, намалюйте край від методу виклику до методу, який називається.

Якщо ви намалюєте графік і помітите, що він циклічний, це кодовий запах. Це потенціал для підштовхування розробників до Ями розпачу. Зауважте, що категорично заборонено це не слід, лише потрібно обережно . Рекурсивні алгоритми конкретно матимуть цикли в графіку викликів: це нормально. Задокументуйте це та попередити розробників. Якщо це не рекурсивно, спробуйте перервати цей цикл. Якщо ви не можете, з’ясуйте, які вхідні дані можуть спричинити переповнення стека, або пом'якшуйте їх, або документуйте це як останній випадок, якщо більше нічого не вийде.


Чудова відповідь. Мені цікаво, чому це така звична практика в Хаскеллі тоді. Думаю, різні культури розробників.
Тавіан Барнс

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

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

1
Більшість функціональних мов програмування мають оптимізацію хвостових викликів. При такій оптимізації хвостова рекурсія не підірве стек, тому такі практики мають сенс.
Джейсон Йео
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.