Сравните стратегии обработки ошибок: исключения, возвращаемые коды ошибок, типы Result/Either (Rust/Haskell), и подход Go с явным возвращением ошибки; разберите пример на Go, где программист проигнорировал возвращённую ошибку при работе с файлом, и опишите последствия и правильное исправление

6 Окт в 10:39
8 +1
0
Ответы
1

Кратко — четыре подхода и их свойства

1) Исключения exceptions—Java,Python,C++ит.п.exceptions — Java, Python, C++ и т.п.exceptionsJava,Python,C++ит.п.

Механика: при ошибке бросается исключение; если не перехвачено — стек разворачивается.Плюсы: удобны для «выхода» из глубоко вложенного кода; обработчики можно поставить в одном месте; меньше шумного кода по проверке ошибок.Минусы: контроль потока не явный в сигнатуре функции; возможны неожиданные исключения runtimeruntimeruntime; труднее понять, какие ошибки может бросать функция; производительность/стоимость при частых исключениях; нужен аккуратный механизм очистки finally/RAIIfinally/RAIIfinally/RAII.Проверяемые исключения JavaJavaJava дают явность в сигнатуре, но делают API жирным и часто раздражают разработчиков.

2) Возвращаемые коды ошибок C−style:int,errnoC-style: int, errnoCstyle:int,errno

Механика: функция возвращает код 0/не−00/не-00/не0 или записывает errno.Плюсы: минимальные накладные расходы; предсказуемо.Минусы: легко забыть проверить; малая выразительность толькочислатолько числатолькочисла; плохая композиция; глобальные errno — источник ошибок в многопоточной среде, если неправильно использовать.

3) Типы Result/Either Rust,Haskellит.д.Rust, Haskell и т.д.Rust,Haskellит.д.

Механика: функции возвращают тип вроде Result<T, E> или Either e a; компилятор/типовая система вынуждает обработать оба варианта либо явно пропагировать ошибку.Плюсы: безопасность — невозможно «случайно» игнорировать ошибку; очень выразительно; легко комбинировать/цепочить; в Rust — ? оператор для краткой пропагации с явной типовой безопасности.Минусы: немного больше шума в сигнатурах; иногда нужна обёртка/конвертация ошибок; новые пользователи привыкают к шаблонам обработки.

4) Подход Go явноевозвращениеошибкикакотдельногозначенияявное возвращение ошибки как отдельного значенияявноевозвращениеошибкикакотдельногозначения

Механика: func Foo......... T,errorT, errorT,error. Код явно возвращает err; вызов должен проверять err iferr!=nil...if err != nil { ... }iferr!=nil....Плюсы: явность, простота, читабельность; нет скрытого контроля потока; легко понимать код по месту вызова; хорош для простых случаев и concurrence.Минусы: компилятор не заставляет проверять возвращённую ошибку — её можно проигнорировать; шаблон «if err != nil { return err }» многословен; нужна дисциплина и инструменты staticcheck,errcheck,govetstaticcheck, errcheck, go vetstaticcheck,errcheck,govet для поиска игнорируемых ошибок. Для композиции используются явные проверки и обёртки ошибок fmt.Errorf(...fmt.Errorf(... %w ...), errors.Is/Asfmt.Errorf(....

Типичная дилемма: Result/Either дают статическое требование к обработке ошибок; exceptions и Go дают больше гибкости иошибокввидезабывчивостии ошибок в виде забывчивостииошибокввидезабывчивости. Выбор зависит от требований: безопасность + композиция → Result; удобство и меньше boilerplate для «эпичных» ошибок → exceptions; простота и явность API → Go-style.

Разбор конкретного примера на Go: проигнорирована ошибка при работе с файлом

Плохой код примерпримерпример:
func savedata[]bytedata []bytedata[]byte error {
f, _ := os.Create("out.txt") // ошибка проигнорирована
defer f.Close // если f == nil — panic
f.Writedatadatadata // возвращаемая ошибка от Write проигнорирована
return nil
}

Последствия такого кода:

Если os.Create вернёт ошибку например,нетправ,нехватаетместа,путьнедопустимнапример, нет прав, не хватает места, путь недопустимнапример,нетправ,нехватаетместа,путьнедопустим, f будет nil, следующая defer f.Close приведёт к panic, или попытка записи — к panic. Программа крашнется вместо аккуратной обработки ошибки.Даже если Create прошёл, запись WriteWriteWrite может провалиться дискполон,сбойI/Oдиск полон, сбой I/Oдискполон,сбойI/O. Поскольку ошибка Write проигнорирована, программа «думает», что всё успешно, данные могут быть потеряны.Ошибка при Close котораяможетсигнализироватьобошибкахзаписиприбуферизированнойзаписикоторая может сигнализировать об ошибках записи при буферизированной записикотораяможетсигнализироватьобошибкахзаписиприбуферизированнойзаписи тоже может быть потеряна, если Close не проверяется.Это приводит к скрытым потерям данных, непредсказуемым крахам и усложнению отладки.

Правильное исправление — явно проверять ошибки и корректно обрабатывать ошибку от Close. Пример хорошего кода идиоматичноидиоматичноидиоматично:

import (
"fmt"
"os"
)

func savedata[]bytedata []bytedata[]byte errerrorerr errorerrerror {
f, err := os.Create("out.txt")
if err != nil {
return fmt.Errorf("create out.txt: %w", err)
}

// гарантируем проверку ошибки от Close; используем именованный return err
defer func {
if cerr := f.Close; cerr != nil && err == nil {
// если до этого не было ошибки — возвращаем ошибку от Close
err = fmt.Errorf"closeout.txt:"close out.txt: %w", cerr"closeout.txt: } else if cerr != nil {
// если уже была ошибка — можно логировать или обернуть обе
// log.Printf"closeerror:"close error: %v (previous error: %v)", cerr, err"closeerror: }
}
if _, err = f.Writedatadatadata; err != nil {
return fmt.Errorf"writeout.txt:"write out.txt: %w", err"writeout.txt: }
// для уверенности можно сделать Sync и проверить её ошибку
if err = f.Sync; err != nil {
return fmt.Errorf"syncout.txt:"sync out.txt: %w", err"syncout.txt: }
return nil

}

Пояснения:

Мы проверяем ошибку при создании файла и прекращаем операцию, если Create упал.Проверяем результат Write и возвращаем подробную обёрнутую ошибку используемиспользуем %w для последующей обработки через errors.Is/Asиспользуем.Особая осторожность с Close: многие ошибки записи проявляются во время Close особенноприбуферизированныхзаписяхособенно при буферизированных записяхособенноприбуферизированныхзаписях. Поэтому лучше проверять cerr и корректно передавать её наружу. Использование именованного возвращаемого значения err и отложенной функции — общепринятый паттерн для этого.Для поиска забытых проверок ошибок используйте инструменты: staticcheck, errcheck, go vet.

Рекомендации

В Go: всегда проверяйте возвращаемую ошибку, особенно от операций I/O и Close; оборачивайте ошибки контекстом; используйте errors.Is/As для сопоставления.В командах включите статический анализ staticcheck,errcheckstaticcheck, errcheckstaticcheck,errcheck в CI, чтобы ловить игнорируемые ошибки.Выбирайте стратегию обработки ошибок, исходя из требований проекта: нужны ли строгие гарантийные проверки на этапе компиляции тогдаResult−likeвалидацияболеебезопаснатогда Result-like валидация более безопаснатогдаResultlikeвалидацияболеебезопасна, или удобство и простота API exceptions/Go−styleexceptions/Go-styleexceptions/Gostyle с дополнительными инструментами контроля.

Если хотите, могу:

показать ещё несколько типичных антипаттернов с ошибками в Go;показать аналогичный пример на Rust с Result и ? для сравнения.
6 Окт в 11:18
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир