Чи запах кодів init ()?


20

Чи є якась мета для оголошення init()способу для типу?

Я не запитую, чи слід віддавати перевагу init()конструктору чи як уникнути декларуванняinit() .

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


init()Ідіоми досить часто, але я до сих пір не бачу ніякої реальної користі.

Я говорю про типи, які заохочують ініціалізацію методом:

class Demo {
    public void init() {
        //...
    }
}

Коли це коли-небудь стане корисним у виробничому коді?


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

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


1
До "... бачачи, як це звичайно ...": це звичайно? Чи можете ви навести кілька прикладів? Можливо, ви маєте справу з рамкою, яка вимагає розділення ініціалізації та побудови.
ПРИЙДАЄТЬСЯ

Чи знайдений метод базового класу чи похідного класу чи обох? (Або: чи знайдений метод класу, який належить до ієрархії спадкування? Чи базовий клас називає init()похідний клас, чи навпаки?) Якщо так, це приклад дозволу базовому класу виконувати "пост-конструктор "який може бути виконаний лише після закінчення побудови найбільш похідного класу. Це приклад багатофазної ініціалізації.
rwong

Якщо ви не хочете ініціалізувати в точці опису, було б сенс розділити їх.
JᴀʏMᴇᴇ

вам може бути цікаво is-a-start-run-or-Execute-method-a-good-practice
Laiv

Відповіді:


39

Так, це кодовий запах. Запах коду - це не те, що обов'язково завжди потрібно видаляти. Це те, що змушує поглянути на другий погляд.

Тут у вас є об'єкт у двох принципово різних станах: pre-init та post-init. Ці держави несуть різні обов'язки, різні методи, які можна назвати, та різну поведінку. Це фактично два різних класи.

Якщо ви фізично складете їх у два окремі класи, ви статично видалите цілий клас потенційних помилок, ціною, можливо, зробити так, щоб ваша модель не настільки відповідала «моделі реального світу». Ви зазвичай називають перший Configабо Setupабо що - то в цьому роді.

Тож наступного разу спробуйте перефактурувати свої конструктиво-init ідіоми на двокласні моделі та подивіться, як це виходить для вас.


6
Ваша пропозиція спробувати модель двох класів - це добре. Запропонувати конкретний крок для вирішення кодового запаху корисно.
Іван

1
Це найкраща відповідь поки що. Мене одначе клопотить: " Ці штати несуть різні обов'язки, різні методи, які можна називати, та різну поведінку ". - Нерозмежування цих обов'язків порушує СРП. Якби метою цього програмного забезпечення було копіювання реального світового сценарію в кожному аспекті, це мало б сенс. Але на виробництві розробники здебільшого пишуть код, який заохочує легке керування, переглядаючи модель, якщо це потрібно, щоб краще підходити до програмного забезпечення (продовження наступного коментаря)
Vince Emigh,

1
Реальний світ - це інша поведінка. Програми, які намагаються відтворити, здаються специфічними для домену, як це не можна / не слід враховувати в більшості проектів. Багато речей, на які нахмуриться, приймаються, якщо мова йде про конкретні доменні проекти, тому я намагаюся зберегти це якомога загальніше (наприклад, те, як виклик thisу конструкторі має запах коду і може призвести до схильного до помилок коду, і уникати його рекомендується незалежно від того, під який домен підпадає ваш проект).
Вінс Емі

14

Це залежить.

initМетод є код запахом , коли немає необхідності мати ініціалізації об'єкта , відокремлені від конструктора. Іноді трапляються випадки, коли є сенс розділити ці кроки.

Швидкий пошук Google отримав мені цей приклад. Я легко уявляю більше випадків, коли код, виконаний під час виділення об'єкта (конструктор), може бути краще відокремлений від самої ініціалізації. Можливо, у вас вирівняна система, і розподіл / побудова відбувається на рівні X, але ініціалізація лише на рівні Y, оскільки тільки Y може забезпечити необхідні параметри. Можливо, "init" є дорогим і його потрібно запускати лише для підмножини виділених об'єктів, і визначення цього підмножини можна здійснити лише на рівні Y. Або ви хочете замінити (віртуальний) метод "init" у похідному клас, який неможливо виконати з конструктором. Можливо, рівень X надає вам виділені об’єкти із дерева спадкування, але рівень Y не знає конкретного виведення, лише про загальний інтерфейс (деinit можливо визначено).

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


2
Відповідь у цьому посиланні говорить, що це корисно для зменшення обсягу роботи в конструкторі, але це заохочує частково створені об'єкти. Менші конструктори можуть бути achived через розкладання, тому мені здається , що відповіді створює нову проблему (потенціал забути викликати всі необхідні методи ініціалізації, в результаті чого об'єкт схильний до помилок) і підпадає під категорію код запах
Vince Emigh

@VinceEmigh: добре, це був перший приклад, який я міг знайти тут на платформі SE, можливо, не найкращий, але є законні випадки використання для окремого initметоду. Однак кожного разу, коли ви бачите такий метод, сміливо ставите під сумнів його необхідність.
Док Браун

Я допитую / оскаржую кожен випадок використання для цього, оскільки я вважаю, що немає ситуації, коли це було б необхідним. Для мене це поганий термін створення об'єкта, і цього слід уникати, оскільки це кандидат для помилок, яких можна уникнути завдяки правильному проектуванню. Якби було правильне використання init()методу, я впевнений, що я отримав би користь, дізнавшись про його мету. Вибачте, моє невігластво, я просто вражений тим, наскільки важко я зараз знаходжу для цього використання, заважаючи мені вважати це чимось, чого слід уникати
Вінс Емі,

1
@VinceEmigh: коли ти не можеш думати про таку ситуацію, потрібно працювати над своєю уявою ;-). Або прочитайте мою відповідь ще раз, не зводьте її лише до "виділення". Або працювати з більшою кількістю рамок від різних постачальників.
Док Браун

1
@DocBrown Використання уяви, а не перевірених практик призводить до певного коду, наприклад, подвійної ініціалізації дужок: розумний, але неефективний і його слід уникати. Я знаю, що виробники використовують його, але це не означає, що використання виправдане. Якщо ви відчуваєте це так, будь ласка, дайте мені знати, чому це я намагаюся з'ясувати. Ви здалися мертвим набором цього з деяким корисним призначенням, але це здається, що вам важко дати приклад, який заохочує гарний дизайн. Я знаю, за яких обставин це можна було б використовувати, але чи слід його використовувати?
Вінс Емі

5

Мій досвід розпадається на дві групи:

  1. Код, де насправді потрібно init (). Це може статися, коли надклас або рамки заважають конструктору вашого класу отримати всі його залежності під час побудови.
  2. Код, де використовується init (), але його можна було уникнути.

В особистому досвіді я побачив лише декілька випадків (1), але ще багато випадків (2). Отже, я зазвичай вважаю, що init () є кодовим запахом, але це не завжди так. Іноді просто не обійтись.

Я виявив, що використання шаблону Builder часто може допомогти усунути потребу / бажання мати init ().


1
Якщо суперклас або рамка не дозволяють типу набути залежностей, які йому потрібні за допомогою конструктора, як би додавання init()методу вирішило це? init()Метод повинен або параметри прийняти залежності, або ви повинні створити екземпляр залежностей в межах від init()методу, який ви могли б також зробити за допомогою конструктора. Чи можете ви навести приклад?
Вінс Емі

1
@VinceEmigh: init () іноді може використовуватися для завантаження конфігураційного файлу із зовнішнього джерела, відкриття підключення до бази даних чи чогось подібного. Метод DoFn.initialize () (від рамки apache Crunch) використовується таким чином. Він також може бути використаний для завантаження несерійних внутрішніх полів (DoFns повинен бути серіалізаційним). Дві проблеми тут полягають у тому, що (1) потрібно щось забезпечити, щоб метод ініціалізації був названий, і (2) об'єкт повинен знати, куди він збирається (або як він збирається) ці ресурси.
Іван

1

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

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


Якщо оновлення конфігурації, і для цього потрібно об'єкт для скидання / змінити її стан в залежності від конфігурації, ви не думаєте , що було б краще мати об'єкт виступати в якості спостерігача до Config?
Вінс Емі

@Вінс Емі Не потрібен. Спостерігач працював би, якщо я знаю точний момент, коли змінюється конфігурація. Однак якщо дані конфігурації зберігаються у файлі, який можна змінити за межами програми, то насправді не існує елегантного підходу. Наприклад, якщо у мене є програма, яка аналізує деякі файли і перетворює дані в якусь внутрішню модель, і окремий файл конфігурації містить значення за замовчуванням для відсутніх даних, якщо я зміню значення за замовчуванням, я прочитаю їх ще раз, коли я наступного запуску синтаксичний розбір Застосування методу Ініта в моїй заяві було б досить зручно в такому випадку.
Володимир Стокич

Якщо конфігураційний файл зовнішньо модифікований під час виконання, немає можливості перезавантажити ці змінні без якогось повідомлення, яке інформує вашу програму про те, що їй потрібно викликати init ( update/ reloadможливо, це буде більш описовим для такої поведінки), щоб фактично зареєструвати ці зміни . У такому випадку це сповіщення призведе до зміни значень конфігурації у вашій програмі внутрішньо, що, на мою думку, могло б спостерігатись за тим, щоб ваш конфігурація була спостережливою, повідомляючи спостерігачів про те, що конфігу буде сказано змінити одне з його значень. Або я неправильно розумію ваш приклад?
Вінс Емі

Додаток бере деякі зовнішні файли (експортовані з якогось ГІС чи іншої системи), аналізує ці файли та перетворює їх у якусь внутрішню модель, яку використовують системи, які додаток. Під час цього процесу можуть бути знайдені деякі прогалини даних, і ці прогалини можуть бути заповнені за замовчуванням значень. Ці значення за замовчуванням можна зберігати у конфігураційному файлі, який можна прочитати на початку процесу перетворення, який викликається, коли є нові файли для перетворення. Саме тут може стати в нагоді метод Init.
Володимир Стокич

1

Залежить від того, як ви їх використовуєте.

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

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

Взагалі, це кодовий запах.


Невже reset()метод не буде більш описовим для вашого першого твердження? Що стосується другого (багато конструкторів), то мати багато конструкторів - це кодовий запах. Він передбачає, що об'єкт має багато цілей / відповідальності, що говорить про порушення SRP. Об'єкт повинен нести одну відповідальність, а конструктор повинен визначити необхідні залежності для цієї однієї відповідальності. Якщо у вас є кілька конструкторів через необов'язкові значення, вони повинні телескопом (що також є кодовим запахом, замість цього слід використовувати конструктор).
Вінс Емі

@VinceEmigh ви можете використовувати init або скинути, зрештою, це лише ім'я. Ініт має більше сенсу в контексті, який я, як правило, використовую, оскільки не має сенсу скидати об'єкт, який ніколи не був встановлений вперше, коли я його використовую. Що стосується проблеми конструктора, я намагаюся уникати багато конструкторів, але іноді це корисно. Подивіться stringсписок конструкторів будь-якої мови , тонни варіантів. Для мене зазвичай це може бути максимум 3 конструктори, але загальний підмножина інструкцій як ініціалізація має сенс, коли вони поділяють будь-який код, але різняться будь-яким чином.
Коді

Імена надзвичайно важливі. Вони описують поведінку, яка виконується, не змушуючи клієнта читати договір. Неправильне називання може призвести до помилкових припущень. Що стосується декількох конструкторів String, це може бути вирішено шляхом роз'єднання створення рядків. Врешті-решт, a String- це String, і його конструктор повинен приймати лише те, що потрібно для його виконання. Більшість цих конструкторів піддаються конверсійним цілям, а це неправильне використання конструкторів. Конструктори не повинні виконувати логіку, інакше вони ризикують невдалою ініціалізацією, залишаючи вас марним об'єктом.
Вінс Емі

JDK наповнений жахливими конструкціями, я міг перелічити близько 10 з вершини голови. Дизайн програмного забезпечення розвивався з того часу, коли основні аспекти багатьох мов були відкрито відкриті, і вони затримуються через можливість зламати код, якби його переробили для сучасності.
Вінс Емі

1

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

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

Крім таких випадків, я думаю, що все повинно перейти в конструктор.


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

Не обов'язково. Ви отримуєте об'єкт, який ви тимчасово не можете використовувати для всіх цілей. У таких випадках об'єкт може просто виконувати роль черги чи проксі, поки ресурс не стане доступним. Повністю засуджуючи initметоди є занадто обмежуючим, ІМХО.
до

0

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

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


0

Немає запаху коду, якщо метод init () - семантично вбудований у життєвий цикл об'єкта.

Якщо вам потрібно зателефонувати init (), щоб перевести об'єкт у стійкий стан, це запах коду.

Існує кілька технічних причин існування такої структури:

  1. каркас гачка
  2. повернення об'єкта до початкового стану (уникайте надмірності)
  3. можливість перекриття під час тестів

1
Але навіщо інженеру-програмісту вбудувати його у життєвий цикл? Чи є якась виправдана мета (вважаючи, що вона заохочує частково побудовані об'єкти) і не могла бути знята більш ефективною альтернативою? Я вважаю, що вбудовувати його в життєвий цикл було б кодовим запахом, і його слід замінити кращими термінами створення об'єкта (Навіщо виділяти пам'ять для об'єкта, якщо ви не плануєте його використовувати в той час? Навіщо створювати об'єкт, який частково побудований, коли ви можете просто почекати, поки вам справді потрібен об’єкт, перш ніж його створити?)
Vince Emigh

Справа в тому, що об’єкт повинен бути корисним перед тим, як ви викликаєте метод init. Можливо, інакше, ніж після того, як його називали. Дивіться схему стану. У сенсі часткової побудови об'єкта - це кодовий запах.
oopexpert

-4

Ім'я init іноді може бути непрозорим. Візьмемо машину і двигун. Щоб запустити машину (тільки включити живлення, слухати радіо), ви хочете переконатися, що всі системи готові до роботи.

Таким чином, ви створюєте двигун, двері, колесо і т. Д. На екрані відображається двигун = вимкнено.

Не потрібно починати стежити за двигуном і т. Д., Оскільки все це дорого. Потім, коли ви повертаєте ключ, щоб запалити, ви дзвоните двигуну-> запустити. Він починає запускати всі дорогі процеси.

Тепер ви бачите двигун = увімкнено. І починається процес займання.

Автомобіль не буде живитись без двигуна.

Ви можете замінити двигун своїм складним розрахунком. Як клітина Excel. Не всі комірки мають постійно працювати з усіма обробниками подій. Коли ви зосередитесь на комірці, ви можете її запустити. Підвищення продуктивності таким чином.

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