LINUX.ORG.RU

golang - не хочу возвращать err, хочу паниковать!

 , ,


0

3

Какая-то секта с этими err. Код распухает в несколько раз. Идея с defer выглядит довольно здравой - я в своё время делал такой defer для 1C и для Delphi. Но паника лучше, чем возврат err-ов. Таковой возврат ничего не упрощает. Когда выпадает исключение, сразу виден весь стек. Сгенерированный err не показывает места своего возникновения, т.е. с помощью брекпойнтов нужно много итераций, чтобы локализовать ошибку. А на fatalpanic есть чуть ли не встроенный брекпойнт, во всяком случае, у меня на fatalpanic отладка сама по себе останавливается.

Кроме того, разбор err после каждого вызова офигенно многословен, код распухает буквально в разы.

Я собираюсь попробовать в своих упражнениях максимально использовать панику. Труъ голангисты, разубедите!

★★★★★

Ответ на: комментарий от den73

Дефер есть, его можно приклеить искусственно, только вот в случае с паникой дефер будет всегда, а с err оно не обязательно

Но вот искусственный пример

func hasErr() (res string) {
	defer func() {
		if err := doErr(); err != nil {
			res = "Here's error"
		}
	}()
	return
}

Benchmark_hasErr-4 30000000 53.3 ns/op 0 B/op 0 allocs/op

Все равно существенны быстрее паники

RedPossum ★★★★★ ()
Последнее исправление: RedPossum (всего исправлений: 1)
Ответ на: комментарий от RedPossum

У меня так и не получилось обмануть оптимизатор, возможно кто-то подскажет.

#[macro_use] extern crate bencher;

use bencher::Bencher;


#[derive(Clone, Copy)]
struct MyError();

static NUMBERS: &[i32] = &[10, -20, 30, 0, -100, 50];


fn panics_impl() -> i32 {
    std::panic::set_hook(Box::new(|_info| {}));

    let mut total = 0;
    for n in NUMBERS {
        total += std::panic::catch_unwind(|| panics_1(*n)).unwrap_or(0);
    }

    total
}

#[inline(never)]
fn panics_1(n: i32) -> i32 {
    panics_2(n)
}

#[inline(never)]
fn panics_2(n: i32) -> i32 {
    panics_3(n)
}

#[inline(never)]
fn panics_3(n: i32) -> i32 {
    panics_4(n)
}

#[inline(never)]
fn panics_4(n: i32) -> i32 {
    if n > 0 {
        n
    } else {
        panic!(MyError())
    }
}

fn panics(bencher: &mut Bencher) {
    bencher.iter(|| {
        let _ = panics_impl();
    })
}


fn errors_impl() -> i32 {
    let mut total = 0;
    for n in NUMBERS {
        total += errors_1(*n).unwrap_or(0);
    }

    total
}

#[inline(never)]
fn errors_1(n: i32) -> Result<i32, MyError> {
    let v = errors_2(n)?;
    Ok(v)
}

#[inline(never)]
fn errors_2(n: i32) -> Result<i32, MyError> {
    let v = errors_3(n)?;
    Ok(v)
}

#[inline(never)]
fn errors_3(n: i32) -> Result<i32, MyError> {
    let v = errors_4(n)?;
    Ok(v)
}

#[inline(never)]
fn errors_4(n: i32) -> Result<i32, MyError> {
    if n > 0 {
        Ok(n)
    } else {
        Err(MyError())
    }
}

fn errors(bencher: &mut Bencher) {
    bencher.iter(|| {
        let _ = errors_impl();
    });
}


benchmark_group!(benches, errors, panics);
benchmark_main!(benches);

test errors ... bench:           0 ns/iter (+/- 0)
test panics ... bench:       9,430 ns/iter (+/- 48)

С бектрейсом:

test errors ... bench:           0 ns/iter (+/- 0)
test panics ... bench:   1,133,437 ns/iter (+/- 152,135)
RazrFalcon ★★★★★ ()
Последнее исправление: RazrFalcon (всего исправлений: 2)
Ответ на: комментарий от WitcherGeralt

Если немного подумать, то мы опять же видим здесь сектоводство. Вирт тоже был хороший, злой такой сектовод. Сначала мы принимаем некие решения, потому что у нас срок сдачи проекта. Потом маркетологи строят религию и убеждают общество, что всё правильно. Гугл, фейсбук и прочие компании с очень посещаемыми сайтами обладают административным ресурсом в виде безплатной рекламы, поэтому они могут хоть чукчам снег зимой продать.

Но команда проекта живёт (хочет кушать дальше), продукт развивается (выявляются очевидные проблемы). Дальше нужно выпускать версию 2.0. Естественно, часть принятых в версии 1.0 решений была принята именно под давлением времени.

Решения перерабатываются. Маркетологи перестраивают миф. Паства хавает, т.к. гугл всё ещё остаётся гуглом, плюс феномен клипового сознания, когда чел назавтра уже забыл, что ему впарили вчера.

Это было так и с С++. Он был совершенен. Потом сделали шаблоны. Потом сделали лямбды. Потом сделали ещё что-нибудь. В каждой инкарнации С++ был совершенен и лучше всех.

Это было так и с Обероном. Один только Вирт придумал несколько разных оберонов под разные задачи и с учётом ошибок прошлых версий, но в глазах адептов Оберона все эти обероны одновременно совершенны. Хотя Вирт прямо пишет о своих ошибках (может быть, религиозность присуща самим людям, а не есть только козни маркетологов), это никого не канает: объявлен богом и всё тут. Непогрешим. Терпи и носи нимб.

И с голангом будет точно так же, что мы уже и наблюдаем.

den73 ★★★★★ ()
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: Спасибо тебе, чувак! от dk-

Re: Спасибо тебе, чувак!

Он хоть в лисп и sql может, а ты совсем бессмысленный организм. Ну кому в наше время нужны фотографы, это ж анекдот. Тебя спасает только достаточное количество косоруких дятлов, не умеющих даже мобилу как следует навести и нажать кнопку.

anonymous ()
Ответ на: комментарий от den73

Это было так и с С++. Он был совершенен. Потом сделали шаблоны. Потом сделали лямбды. Потом сделали ещё что-нибудь. В каждой инкарнации С++ был совершенен и лучше всех.

Ага, довелось наблюдать эволюцию крестофанов от «блямбда гыгыгы» до «наша лямбда самая правильная». Фанбойство это худшее что может случиться с программистом.

anonymous ()
Ответ на: комментарий от den73

Можно вынести этот факт за скобки и считать деструкторы defer-ами

А смысл в таком измерении если ты сразу же выкинул 99% тормозов? Тут вопрос в том, что в го швырять эксепшены это моветом и вм может быть плохо оптимизирована под такие кейсы. Я так понял там 2.5 человека его разрабатывают - каждая новая фича на вес золота.

ya-betmen ★★★★★ ()
Последнее исправление: ya-betmen (всего исправлений: 1)
Ответ на: Спасибо тебе, чувак! от dk-

Ну извини, я действительно довольно умный. Правда теперь я ещё и достаточно старый, так что это уже не слишком актуально. Закопал талант в землю. Я не кичусь талантом своим, но и не буду отрицать, что он у меня есть. Кроме того, я всё же достаточно опытный, как никак, больше 20 лет уже общаюсь с компами и застал несколько смен поколений технологий. И никогда ничего не принимал на веру (такой характер). Практика показывает, что в новых технологиях обычно много вранья, и выигрывают не лучшие с инженерной точки зрения решения. Потому что тем, кто впаривает технологии, всегда нужен какой-то крючочек, чтобы с помощью технологии получить какую-то выгоду с тех, кто ей будет пользоваться. Отсюда тормозные винды, сишечка с уязвимостью в каждой строке и совсем уж идиотский JS. И так смотришь и видишь, что какой-нибудь PL/1, о котором все забыли, по всем статьям превосходит всякие эти вот голанги.

Касабельно Яра - я всего лишь пытался сделать лисп с синтаксисом как у бейсика. Там были некие относительно небольшие фишки. Но лисп сам по себе велик, придумал его не я. Я всего лишь рассчитывал, что это будет понято. Но это не было понято. Большинство программистов неспособны увидеть преимущество, даже если им подробно расписывать с примерами и прочая. Это скорее меня печалит, чем тешит моё эго. Потому что науку сектоводства я не осилил.

Но раз уж это так, то значит нужно хотя бы извлечь из этого профит, а не просто грустить.

den73 ★★★★★ ()
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от RazrFalcon

Нет.

Оно и заметно.

Но предполагаю, что в Java, checked exceptions сделали неудобными

Их сделали так, как хотели. В надежде избавиться от проблем, которые вас пугают. Но получилось как всегда. Особенно когда столкнулись с обобщенным программированием.

В том же rust

В Rust нет исключений. Поэтому по информативности Rust-овкий прототип fn get() -> Result<ValueType, ErrorType> ничем не лучше, чем C++ный ValueType get().

eao197 ★★★★★ ()
Ответ на: комментарий от Leron

«эта функция бросает checked exeptions такие, какие бросают функции вызываемые в ней».

Да? И какой в этом толк? Как узнать, что она вызывает внутри и что может вылететь наружу?

Такое ощущение, что нужный вам throws * уже есть в виде throws Exception.

eao197 ★★★★★ ()
Ответ на: комментарий от quantum-troll

В Go 2 будет тебе обработка ошибок:

Теперь к defer еще и handle будет безумный. Почему им религия запрещает такой синтаксис:

func doit() {
  ...
} catch SomeErr err {
  ...
} catch AnotherErr err {
  ...
} finally {
  ...
}
anonymous ()
Ответ на: комментарий от mord0d

Вообще, когда ты только пишешь, ты не тестируешь это на прод-сервере, а гоняешь на тестовом, где всё всплывает и ты уже делаешь нормально. Хуже когда на тестовом вроде всё нормально, а потом у тебя половина данных пережёваны в кашу, и ты замечаешь это уже когда код пошёл в прод.

Это все хорошо в теории. На деле получается так, что на тестинге проверяются только те проблемные места, о которых ты знаешь. Например, для C/C++ эту проблему можно решить фазингом, а для питона я не видел ничего подобного.

xpahos ★★★★★ ()
Ответ на: комментарий от anonymous

Почему им религия запрещает такой синтаксис

И что писать в finally, если функция открывает несколько ресурсов? Ту же хрень, что в Java v<7:

} finally {
    if (rs != null) {
        rs.close();
    }
    if (stmt != null) {
        stmt.close();
    }
    if (connection != null) {
        connection.close();
    }
}

?

Потом вспоминаем, что close тоже может бросить исключение, начинаем обмазываться вложенными try-catch-finally или какие-нибудь костыли изобретать.

korvin_ ★★★★★ ()
Ответ на: комментарий от anonymous

И вот еще интересно, что теперь делать буратинам, у которых полна коробочка имен handle и check в коде? Они же не были зарезервированы, так? В общем меня терзают смутные сомнения насчет адекватности разработчиков Go.

Буратинам продолжать буратинить. Остальные начнут искать/писать тулзу для миграции исходников. А скорее всего, разработчики Go её сами и выпустят с релизом Go2.

Сохранения совместимости между Go1 и Go2 никто не обещал, насколько я знаю.

korvin_ ★★★★★ ()
Ответ на: комментарий от dem

И это прекрасно.

Не вижу ничего прекрасного в падении рантайма из-за опечатки. Можешь пояснить в чем прелесть?

Еще один недостаток Го - в нем нет библиотек.

Есть linkshared, но в целом опыт показывает, что использование статической сборки не создает проблем с несовместимостью с текущим окружением, а в случае использования контейнеров позволяет не таскать с собой кучу зависимостей. Можно конечно вспомнить, что динамически слинкованные приложения запускаются дольше, но в целом конечно ld.so не дает каких-то очень серьезных просадок на общем фоне питона.

Юниттесты ортогональны компилятору

Да ты шо, а я-то не знал. Ну спасибо что объяснил.

xpahos ★★★★★ ()
Ответ на: комментарий от korvin_

Ну проверишь вызовы внутри finally как сейчас вы делаете, в чем проблема? Я же не исключения предлагаю, а другой синтаксис для предложенного драфта. И не вижу ничего зазорного в приведенном примере. Вся логика финализации в одном блоке, а не разбросана по телу функции.

anonymous ()
Ответ на: комментарий от anonymous

Ну проверишь вызовы внутри finally как сейчас вы делаете, в чем проблема?

Сейчас мы не делаем, потому что есть try-with-resources:

try (
    Connection conn = ...;
    Statement stmt = ...;
    ResultSet rs = ...;
) {
    // do something
}

Я же не исключения предлагаю, а другой синтаксис для предложенного драфта.
И не вижу ничего зазорного в приведенном примере. Вся логика финализации в одном блоке, а не разбросана по телу функции.

Видимо, ты не сталкивался с проблемами такого подхода. Почитай про RAII в C++, try-with-resources в Java или using в C#.

Если уж тебя беспокоит наличие новых ключевых слов (check/handle), то можно было бы сделать как-то так, например:

func itemById(id uint64) (*Item, error) {
    conn, err := sql.Open(...)
    // return nil, err if err != nil
    // defer conn.Close() otherwise
    defer(err) conn.Close()
    
    stmt, err := conn.Stmt(...)
    stmt = defer(err) stmt.Close()
    
    stmt.SetUint64(1, id)
    
    rs, err := stmt.Exec()
    defer(err) {
        // additional error handling if needed
        log.Error(err)
    } rs.Close()

    name, err := rs.String(2)
    // just return nil, err if err != nil
    defer(err)
    
    // no need to specify nil error
    return &Item{id, name}
}
korvin_ ★★★★★ ()
Последнее исправление: korvin_ (всего исправлений: 1)
Ответ на: комментарий от eao197

Поэтому по информативности Rust-овкий прототип fn get() -> Result<ValueType, ErrorType> ничем не лучше, чем C++ный ValueType get().

Функция, записывающая в типе, какие ошибки она может возвращать vs функция, в которой эта информация отсутствует. Ну да, ну да, никак не лучше </sarcasm>.

~~@~~

anonymous ()
Ответ на: комментарий от eao197

Поэтому по информативности Rust-овкий прототип fn get() -> Result<ValueType, ErrorType> ничем не лучше, чем C++ный ValueType get().

Только в Rust я вижу, что функция возвращает и ошибку, и знаю какую. В C++ - нет.

RazrFalcon ★★★★★ ()
Ответ на: комментарий от RazrFalcon

В C++ - нет

Отсутствие noexcept говорит о том, что исключения могут вылетать. И в нормальной кодовой базе это будут наследники std::exception.

Так что все достаточно очевидно.

eao197 ★★★★★ ()
Ответ на: комментарий от korvin_

Сейчас мы не делаем, потому что есть try-with-resources

Погоди, гошники должны весь путь страданий пройти. От сишки-переростка через раннюю жабу и так далее. Сейчас они уже начали подозревать, что возврат ошибок это как-то грустно, и без дженериков кисло.

anonymous ()
Ответ на: комментарий от RazrFalcon

Но неизвестно какие.

Читаем внимательно: «в нормальной кодовой базе это будут наследники std::exception»

А наличие noexcept говорит о том, что будет вызван std::terminate.

Да, поэтому об исключениях можно не заботиться. Но ведь вас, насколько помню, интересовал случай, когда ошибка выходит наружу.

eao197 ★★★★★ ()
Ответ на: комментарий от RazrFalcon

и если мы не обмазываемся catch(...) - то всё, приехали.

А ты обмазывайся. И вот мне интересно, это ты из опыта взял «завтра васян добавит», или просто теоретизируешь мол может случиться дерьмо. Ну может, и чо? Тесты для чего придумали?

anonymous ()
Ответ на: комментарий от anonymous

возврат ошибок это как-то грустно

Это не грустно, это во многих случайх удобно. Грустно только отсутствие удобного синтакиса, а ля (монады + do-нотация) или чего-то подобного.

korvin_ ★★★★★ ()
Ответ на: комментарий от KillTheCat

В реальности как-то так

func CopyFile(src, dst string) (err error) {

    var r, w *os.File

    defer func() {
        if err != nil {
            err = fmt.Errorf("copy %s %s: %v", src, dst, err)
            if w != nil {
                os.Remove(dst)
            }
        }
    }()

    if r, err = os.Open(src); err != nil {
        return
    }
    defer r.Close()

    if w, err = os.Create(dst); err != nil {
        return
    }
    defer w.Close()

    _, err = io.Copy(w, r)
    return 
}
kostyarin_ ()

Когда выпадает исключение, сразу виден весь стек

Да. С больим количеством горутин, проматывать до начала этого стека можно вплоть до китайской пасхи.

kostyarin_ ()
Ответ на: комментарий от den73

Полемизируя с маркетологами и сектантами, ты сам незаметно превратился в сектанта-маркетолога :D

Что мешает ориентироваться на нормальных? Всегда есть люди, которые понимают недостатки и ограничения своих технологий. Плюются даже, бывает, но используют, и могут объяснить почему.

anonymous ()
Ответ на: комментарий от den73

Однако странно слышать обвинения других в религиозности от человека, который пилил собственный ЯП, потому что другие были либо не популярны, либо не соответствовали его... гхм, религии) От человека, который взял го, но ему мучительно пользоваться обработкой ошибок, которая заложена в этот язык и он колхозит свою. Почему ты просто не можешь делать вещи, а тебе нужно делать их _правильно_? Ведь как правильно и есть один из вопросов религии. Если что, не считаю религию как что-то плохое.

goingUp ★★★★★ ()

Оснавная причина, по которй используется проверка ошибок - это скорость. Горазо быстрее проверить ошибку, или, например, число перед делением, чем обрабатывать исключение. Паника, как и try/catch каки все прочие исключения - это чертовски долго. А теперь, представьте, что у вас высоконагруженный веб-сервис, который принимает некореетный запрос. Думаю как быстро этот сервис станет нафиг никому не нужен объяснять не нужно. Да любой дудосер обрадуется подобным новшевствам, вот только в ральности - это не нужно. Ошибки должны обрабатываться быстро. И по возможности обрабатываться и corner cases. Вот и вся суть программирования.

func div(a, b int) (quo int, err error) {
   if b == 0 {
        return 0, ErrZeroDivision
   }
   return a / b, nil
}

или так

func div(a, b int) (quo int, ok bool) {
   if b == 0 {
        return 0, false
   }
   return a / b, true
}
То же самое и для переполнения и так далее и тому подобное. Размеется в разумных пределах.

kostyarin_ ()
Ответ на: комментарий от eao197

Читаем внимательно: «в нормальной кодовой базе это будут наследники std::exception»

В нормальной кодовой базе и утечек нет. В нормальном мире вообще всё хорошо. Только это не наш случай.

Да, поэтому об исключениях можно не заботиться.

С чего бы это? Я в самом начале написал, что «С-стайл abort - это моветон».

RazrFalcon ★★★★★ ()