Мова Google Go не має винятків як вибір дизайну, і Лінус з Linux називає винятки глупостями. Чому?
Мова Google Go не має винятків як вибір дизайну, і Лінус з Linux називає винятки глупостями. Чому?
Відповіді:
Винятки полегшують написання коду, коли виняток, який викидається, порушить інваріанти і залишить об'єкти в несумісному стані. Вони, по суті, змушують вас пам’ятати, що більшість кожного вашого висловлювання може потенційно кинути і правильно це обробити. Це може бути хитро і протиінтуїтивно.
Розглянемо щось подібне як простий приклад:
class Frobber
{
int m_NumberOfFrobs;
FrobManager m_FrobManager;
public:
void Frob()
{
m_NumberOfFrobs++;
m_FrobManager.HandleFrob(new FrobObject());
}
};
Якщо припустити , що FrobManager
воля delete
до FrobObject
, це виглядає нормально, НЕ так? А може, ні ... Уявіть тоді, якщо будь-який FrobManager::HandleFrob()
або operator new
видасть виняток. У цьому прикладі приріст m_NumberOfFrobs
не відкочується. Таким чином, у кожного, хто використовує цей екземпляр Frobber
, буде можливий пошкоджений об’єкт.
Цей приклад може здатися дурним (гаразд, мені довелося трохи розтягнутися, щоб побудувати його :-)), але висновок полягає в тому, що якщо програміст постійно не думає про винятки і переконується, що кожна перестановка стану виконується назад, коли є кидки, ви потрапляєте в неприємності таким чином.
Як приклад, ви можете думати про це, як про мьютекси. Усередині критичного розділу ви покладаєтесь на кілька тверджень, щоб переконатися, що структури даних не пошкоджені, а інші потоки не бачать ваших проміжних значень. Якщо якесь із цих тверджень просто випадковим чином не пробігає, ви потрапляєте у світ болю. Тепер забирайте замки і паралелізм і продумуйте кожен метод таким чином. Подумайте про кожен метод як про трансакцію перестановок стану об’єкта, якщо хочете. На початку виклику методу об'єкт повинен мати чистий стан, а в кінці також повинен бути чистий стан. Між ними змінна foo
може суперечитиbar
, але ваш код врешті-решт це виправить. Винятки означають те, що будь-яке з ваших тверджень може перервати вас у будь-який час. У кожному окремому методі ви покладаєтеся на те, щоб це правильно зробити і відкотитись, коли це трапиться, або замовити ваші операції, щоб кидки не впливали на стан об’єкта. Якщо ви помиляєтесь (і зробити такий тип помилки легко), то абонент бачить ваші проміжні значення.
Такі методи, як RAII, які програмісти на C ++ люблять згадувати як остаточне рішення цієї проблеми, проходять довгий шлях захисту від цього. Але вони не є срібною кулею. Це гарантує, що ви звільняєте ресурси під час кидка, але не звільняє вас від необхідності думати про пошкодження стану об'єкта та тих, хто викликає, бачачи проміжні значення. Отже, для багатьох людей це легше сказати за стилем кодування, без винятків . Якщо ви обмежуєте тип коду, який ви пишете, важче представити ці помилки. Якщо цього не зробити, помилитися досить просто.
Про кодування в C ++ написано цілі книги. Багато експертів помиляються. Якщо це насправді настільки складно і має стільки нюансів, можливо, це хороший знак, що потрібно ігнорувати цю функцію. :-)
Причина того, що Go не має винятків, пояснюється у поширених запитаннях щодо дизайну мови Go:
Винятки - подібна історія. Запропоновано ряд конструкцій для винятків, але кожен додає значну складність мові та часу роботи. За своєю природою винятки охоплюють функції і, можливо, навіть програми; вони мають широкі наслідки. Існує також занепокоєння щодо впливу, який вони мали б на бібліотеки. Вони, за визначенням, є винятковим, але досвід роботи з іншими мовами, які їх підтримують, показує, що вони мають глибокий вплив на специфікацію бібліотеки та інтерфейсу. Було б непогано знайти дизайн, що дозволяє їм бути справді винятковими, не заохочуючи поширені помилки перетворюватися на особливий потік управління, який вимагає від кожного програміста компенсації.
Як і загальні препарати, винятки залишаються відкритим питанням.
Іншими словами, вони ще не з’ясували, як підтримувати винятки в Go таким чином, який, на їхню думку, є задовільним. Вони не говорять, що винятки самі по собі погані ;
ОНОВЛЕННЯ - травень 2012 р
Зараз дизайнери Go зійшли з огорожі. Їхні поширені запитання тепер говорять так:
Ми вважаємо, що поєднання винятків із структурою управління, як в ідіомі try-catch-нарешті, призводить до згорнутих кодів. Це також заохочує програмістів позначати занадто багато звичайних помилок, таких як неможливість відкрити файл, як виняткові.
Go застосовує інший підхід. Для простої обробки помилок багатозначні повернення Go дозволяють легко повідомити про помилку, не перевантажуючи повернене значення. Канонічний тип помилок у поєднанні з іншими функціями Go робить обробку помилок приємною, але зовсім відмінною від такої в інших мовах.
Go також має кілька вбудованих функцій для сигналізації та відновлення від справді виняткових умов. Механізм відновлення виконується лише як частина стану функції, яка руйнується після помилки, що є достатнім для усунення катастрофи, але не вимагає додаткових структур управління і, при доброму використанні, може призвести до чистого коду обробки помилок.
Детальніше див. У статті Відкласти, панікувати та відновити.
Тож коротка відповідь полягає в тому, що вони можуть робити це по-різному, використовуючи багатозначну віддачу. (І вони в будь-якому випадку мають форму обробки винятків.)
... і Лінус з Linux називав винятки глупостями.
Якщо ви хочете знати, чому Лінус вважає, що винятки - це лайно, найкраще шукати його праці на цю тему. Єдине, що я відстежував дотепер, - це цитата, яка вбудована в пару електронних листів на C ++ :
"Весь процес обробки винятків на C ++ принципово зламаний. Особливо він порушений для ядер."
Ви помітите, що він говорить зокрема про винятки на C ++, а не про винятки загалом. (І винятки на C ++ , мабуть, мають деякі проблеми, через які їх важко використовувати правильно.)
Я прийшов до висновку, що Лінус взагалі не називав винятки (загалом) "лайною"!
Винятки непогані самі по собі, але якщо ви знаєте, що вони трапляться багато, вони можуть бути дорогими з точки зору продуктивності.
Основне правило полягає в тому, що винятки повинні позначати виняткові умови, і що ви не повинні використовувати їх для контролю потоку програм.
Я не погоджуюся з "виключенням лише у виняткових ситуаціях". Хоча загалом це правда, це оманливе. Виняток становлять умови помилок (помилки виконання).
Незалежно від мови, якою ви користуєтесь, візьміть копію Рекомендацій щодо розробки Framework : Конвенції, ідіоми та шаблони для багаторазових бібліотек .NET (2-е видання). Розділ про метання винятків - без однолітків. Деякі цитати з першого видання (2-го в моїй роботі):
Є сторінки приміток щодо переваг винятків (послідовність API, вибір місця розташування коду обробки помилок, покращена надійність тощо). Є розділ про продуктивність, який включає кілька шаблонів (Tester-Doer, Try-Parse).
Винятки та обробка винятків є НЕ погано. Як і будь-яка інша функція, ними можна зловживати.
З точки зору golang, я вважаю, що відсутність обробки винятків робить процес компіляції простим і безпечним.
З точки зору Linus, я розумію, що код ядра - це ВСЕ про кутові випадки. Тож має сенс відмовитись від винятків.
Винятки мають сенс у коді, якщо нормально кинути поточне завдання на поверх, і коли загальний код справи має більше значення, ніж обробка помилок. Але вони вимагають генерації коду від компілятора.
Наприклад, вони чудові в більшості високорівневих кодів, орієнтованих на користувача, таких як веб-код та код настільних програм.
Самі по собі винятки не є "поганими", це те, що іноді обробляються винятки, як правило, є поганими. Існує кілька вказівок, які можна застосувати під час обробки винятків, щоб полегшити деякі з цих проблем. Деякі з них включають (але, безумовно, не обмежуються):
Option<T>
замість null
сьогодні. Наприклад, щойно був представлений в Java 8, беручи підказку від Гуави (та інших).
Типовими аргументами є те, що неможливо визначити, які винятки вийдуть з певної частини коду (залежно від мови), і що вони занадто схожі на goto
s, що ускладнює розумове відстеження виконання.
http://www.joelonsoftware.com/items/2003/10/13.html
З цього питання однозначно немає єдиної думки. Я б сказав, що з точки зору такого твердого програміста на Сі, як Лінус, винятки, безумовно, є поганою ідеєю. Типовий програміст Java, однак, знаходиться в зовсім іншій ситуації.
setjmp
/ longjmp
stuff, що досить погано.
Винятки непогані. Вони добре поєднуються з моделлю RAII на C ++, що є найелегантнішим у C ++. Якщо у вас вже є купа коду, який не є виключенням безпечним, тоді вони погані в цьому контексті. Якщо ви пишете дуже низький рівень програмного забезпечення, наприклад ОС Linux, то вони погані. Якщо вам подобається засмічувати свій код безліччю перевірок повернення помилок, тоді вони не корисні. Якщо у вас немає плану управління ресурсами, коли виникає виняток (який надають деструктори C ++), вони погані.
Таким чином, чудовим варіантом використання винятків є ....
Скажімо, ви працюєте над проектом, і кожен контролер (близько 20 різних основних) розширює один контролер суперкласу методом дії. Тоді кожен контролер робить купу речей, що відрізняються один від одного, викликаючи об'єкти B, C, D в одному випадку та F, G, D в іншому випадку. Тут на допомогу приходять винятки у багатьох випадках, коли код повернення був безліч, і КОЖНИЙ контролер обробляв це по-різному. Я зламав весь цей код, викинув належний виняток з "D", схопив його в методі дії контролера суперкласу, і тепер усі наші контролери є послідовними. Раніше D повертав null для БАГАТО різних випадків помилок, про які ми хочемо повідомити кінцевому користувачеві, але не змогли, і я не зробив
так, нам слід турбуватися про кожен рівень та будь-яке очищення / витоки ресурсів, але загалом жоден з наших контролерів не мав ресурсів для очищення після.
слава богу, у нас були винятки, інакше я б зайнявся величезним рефактором і витратив би занадто багато часу на щось, що мало б бути простою проблемою програмування.
Теоретично вони насправді погані. У ідеальному математичному світі ви не можете отримати ситуації винятків. Подивіться на функціональні мови, вони не мають побічних ефектів, тому вони практично не мають джерела для надзвичайних ситуацій.
Але, реальність - це вже інша історія. У нас завжди бувають ситуації, які є "несподіваними". Ось чому нам потрібні винятки.
Я думаю, ми можемо розглядати винятки як синтаксис цукру для ExceptionSituationObserver. Ви просто отримуєте повідомлення про винятки. Нічого більше.
З Go, я думаю, вони запровадять щось, що буде мати справу з "несподіваними" ситуаціями. Я можу здогадуватися, що вони намагатимуться зробити так, щоб це звучало менш згубно як винятки, а більше як логіка програми. Але це лише моє припущення.
Парадигма обробки винятків C ++, яка формує часткову основу для Java, і, в свою чергу, .net, вводить деякі хороші концепції, але також має деякі серйозні обмеження. Одним із ключових дизайнерських намірів обробки винятків є надання методам гарантії, що вони або задовольнятимуть свої пост-умови, або викидати виняток, а також гарантувати, що відбудеться будь-яке очищення, яке має відбутися до того, як метод може вийти. На жаль, усі парадигми обробки винятків C ++, Java та .net не забезпечують жодних хороших засобів для обробки ситуації, коли несподівані фактори заважають виконувати очікуване очищення. Це, у свою чергу, означає, що потрібно або ризикувати, що все зупиниться, якщо щось несподіване трапиться (підхід C ++ до обробки винятків виникає під час розмотування стека),
Навіть якщо обробка винятків, як правило, була б хорошою, нерозумно вважати неприйнятною парадигму обробки винятків, яка не забезпечує хорошого засобу для вирішення проблем, які виникають при прибиранні після інших проблем. Це не означає, що фреймворк не міг бути розроблений за допомогою парадигми обробки винятків, яка могла б забезпечити розумну поведінку навіть у сценаріях множинних відмов, але жодна з найкращих мов або фреймворків поки не може цього зробити.
Я не читав усіх інших відповідей, тому про це вже згадувалося, але одна критика полягає в тому, що вони змушують програми розриватися довгими ланцюгами, ускладнюючи відстеження помилок під час налагодження коду. Наприклад, якщо Foo () викликає Bar (), який викликає Wah (), який викликає ToString (), то випадково виштовхування неправильних даних у ToString () закінчується виглядом помилки у Foo (), майже повністю не пов'язаної функції.
Гаразд, нудна відповідь тут. Думаю, це залежить від мови насправді. Якщо виняток може залишити виділені ресурси позаду, їх слід уникати. У мовах сценаріїв вони просто дезертирують або перевершують частини потоку програм. Це само по собі неприємно, проте уникнення майже фатальних помилок за винятками є прийнятною ідеєю.
Що стосується сигналізації про помилки, я зазвичай віддаю перевагу сигналам про помилки. Все залежить від API, варіанту використання та серйозності, чи достатньо журналювання. Також я намагаюся перевизначити поведінку і throw Phonebooks()
замість цього. Ідея полягає в тому, що "Винятки" часто є тупиковими, але "Телефонна книга" містить корисну інформацію про відновлення помилок або альтернативні шляхи виконання. (Ще не знайдено хорошого варіанту використання, але продовжуйте намагатися.)
Для мене питання дуже просте. Багато програмістів використовують обробник винятків неналежним чином. Чим більше мовних ресурсів, тим краще. Бути здатним обробляти винятки - це добре. Одним з прикладів поганого використання є значення, яке повинно бути цілим числом, не перевірятись, або інший вхід, який може ділитися і не перевірятися на ділення нуля ... обробка винятків може бути простим способом уникнути більшої праці та наполегливості, програміст може захотіти зробити брудний ярлик і застосувати обробку винятків ... Твердження: "професійний код НІКОЛИ не підводить" може бути ілюзорним, якщо деякі проблеми, що обробляються алгоритмом, є невизначеними за своєю природою. Можливо, в невідомих ситуаціях від природи добре вступати в справу обробника винятків. Належна практика програмування є предметом дискусій.