Pull to refresh

Comments 12

Потрясающая статья: человек в течении многих лет старательно убеждает себя в том, что выбор был правильным и полностью игнорирует проблемы.

Да, подход C++ и Java (довольно старые языки), вызывает проблемы, но и подход Go тоже с сюрпризами. Почему-то, в этих статьях от "больших" специалистов полностью игнорируются такие вещи как Option типы и связанные с этим макросы, которые распространены в других языках типа Haskell, Rust, Kotlin.

Причина такой "избирательности" в банальной старости авторов языка: они не знают ничего за пределами C/C++ похоже.

Точно. И их там таких легион. Им пишут, что они таким решением наоборот провоцируют игнорировать ошибки, т.к. в реальном коде все задолбались писать после каждой строчки ещё три if err != nil и так раз за разом, всего лишь для того, что бы эту ошибку передать на уровень выше, а они в ответ: а исключения зато все игнорируют.

В тех же Java, Kotlin, C# нужно так же чуть ли ни каждую строчку оборачивать в try-catch

Вот пример для Kotlin:

import java.io.IOException
import java.nio.file.Files
import java.nio.file.Paths
 
fun main() {
    val fileName = "/home/data/file.txt"
    try {
        val lines = Files.readAllLines(Paths.get(fileName))
        println(lines)
    } catch (e: IOException) {
        e.printStackTrace()
    }
}

А то, что не обернуто в try-catch, нужно проверить на null. Пример:

companydb = getCustomer(name, inn);
if (companydb == null) {}

Получается в Go много "if err != nil", а в Java/C# много if ( p != null ).

В той же 1с очень редко используется перехват исключений, вся обработка ошибок построена на том, что функция возвращает "неопределенно" (null) в случае ошибки, а вызывающая сторона проверяет, что вернулось.

И проверки на null после каждого вызова функций никого не смущает.

Мне тоже не нравится, что в Go код "замусоривается" проверками ошибок, но прочитав статью, понял, что и механизм исключений не лучше. Получается лучший вариант, это вариант описанный в разделе "Устранение обработки ошибок за счёт устранения ошибок"

Не-не, вы не совсем правильно это поняли. Я не знаю Яву, но предполагаю, что если в примере не обернуть вызов в try, то выполнение функции будет прекращено и исключение пойдет на уровень выше. Мне очень часто именно это и нужно. Т.е. мне не нужно обрабатывать ошибку в этой функции. Мне нужно только сообщить об ошибке туда, откуда эту функцию вызвали, а там тоже нужно только сообщить в вызывающую функцию и так 10 уровней вверх. И только на самом верхнем уровне может быть будет какая-то обработка этой ошибки. А может и не будет. Может просто вернёт какую-нибудь 500 ошибку, да в лог запишет. Но в Go так не получится сделать. В результате, просто что бы прекратить выполнение текущей функции и вернуть управление выше, нужны бесконечные if err != nil.

При чем тут Option? Он не про работу с ошибками, это лишь индикатор того, что функция может вернуть или не вернуть значение. Ладно, предположим имелся в виду паттерн Either. 

Наивно считать, что авторы языка «деды» и ничего не знают о ФП подходах. Подход к обработке ошибок в го - это и есть слегка изменённый вариант реализации Either. 

Вы спросите: почему бы по умолчанию не использовать знакомый многим Either и не шатать мозги? Монады по-настоящему раскрываются только в случае если используются в связке с другими монадами. Го не может себе этого позволить, так как он не настолько функциональный. Иначе получится уродец, в которого насильно впихнули одну ФП фичу. Как итог авторы выбрали компромисс, на мой взгляд удачный. Отказаться от exception’ов и при этом не скатываться в трушную функциональщину.

Наивно считать, что авторы языка «деды» и ничего не знают о ФП подходах.

Да нет тут никакой наивности. Вспомните как они сделали go get и сравните это с rust, ruby или php. Ну видно же, что люди по-прежнему на autotools сидят. Или реализация Generic, которую выпрашивали миллион лет и которая осталась слегка недоделаной.

Вы спросите: почему бы по умолчанию не использовать знакомый многим Either и не шатать мозги? Монады по-настоящему раскрываются только в случае если используются в связке с другими монадами.

Не думаю, что надо весь ФП тянуть. Например Rust и без этого справляется. Точно нужны полноценные макросы и нормальная система типов. Не как в Haskell, конечно, но постараться можно было.

Вот только натыкивание if err != nil { return err } - это по сути и есть размотка стека, как при исключениях, только вручную. То что раньше делал компилятор, теперь стало модно делать копипастным кодом.

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

То, что ошибки в го явные – это очень хорошо. Всё остальное плохо, и в этом проблема.

Пример с `errWriter` типичный паттерн в го, чтобы спасти основную логику от исчезновения в тумане из `if err != nil { return err }`. Но, мало того, что он делает обработку ошибок неявной, очень ограниченно применим, и часто всё равно малоэффективен, он ещё и приносит проблему, которая на первый взгляд не очень заметна. В процессе эволюции программы, подготовка данных для errWriter.Write можеть стать дороже, и тогда мы хотим выходить по первой же ошибке, а не впустую жрать процессор. Надо не забыть это сделать и отказаться от errWriter.

Не знаю, кто вам минус поставил в комментарий, т.к. пишите вы правильно. Нейтрализовал своим плюсом. Пример с errWriter - это даже не паттерн. Это костыль. И я к такому же костылю в конце концов пришел, ну потому, что а как ещё. Функции невозможно замусориваются этим дублированием обработки ошибок. Но я отказываюсь называть это паттерном. Хак это, грязный хак, от бедности.

Почему хак? Красота! Язык фактически принуждает тебя к этому решению.

filepath.Walk(".", visit) применяет такое же решение:

func visit(p string, info os.FileInfo, err error) error {

if err != nil { return err }

fmt.Println(" ", p, info.IsDir())

return nil

}

Исключения нельзя игнорировать без того, что бы программа не упала.

panic нельзя игнорировать без того, что бы программа не упала.

Sign up to leave a comment.

Articles