Обработка ошибок
Вы, наверняка, знаете, что в императивных языках есть два основных механизма сообщать об ошибке — выбрасывать исключение или возвращать код/значение явно. Можно было бы сказать, что тут даже есть два лагеря — сторонники исключений и сторонники явного возврата, но все несколько хуже. Лагеря на самом деле два, но они иные — те, кто понимает важность обработки ошибок в коде, и те, кто по большей части этот аспект программирования игнорирует. Причем второй лагерь — пока-что безоговорочный лидер.
Логично предположить, что это именно то, что отличает «хороших» программистов от «плохих» программистов, и доля правды тут, несомненно есть. Но есть одно но. Инструментарий — в данном случае это «язык программирования» — тоже решает. Если ваш язык позволяет делать «неправильно» намного проще, чем делать «правильно» — будьте уверены, никакое количество статей и книг «Как не нужно писать на [LANG]» не помогут — люди будут продолжать делать неправильно. Просто потому что это проще.
Вот казалось бы — уже каждый школьник знает, что «глобальные переменные» это зло. Сколько статей на эту тему — вроде бы всем всё понятно. Но тем не менее — даже сейчас, в 2015 году, вы найдете тонны кода, использующего глобальные переменные. Почему?
А потому что создать глобальную переменную — «сделать неправильно» занимает ровно одну строчку почти в любом языке программирования. В то же время, чтобы создать любой из «правильных вариантов», любую минимальную обертку — уже нужно потратить больше времени и сил. Пусть даже на 1 символ больше — но это решает.
Это очень важно осознать — инструментарий решает. Инструментарий формирует наш выбор.
Но вернемся к обработке ошибок и попробуем понять, почему авторы Go сочли исключения — «неправильным путем», решили не реализовывать их в Go, и в чем отличие «возврата нескольких значений» в Go от подобного в других языках.
Возьмем для примера простую вещь — открытие файла.
Вот код на C++
ifstream file; file.open ("test.txt");
Это полностью рабочий код, и «правильно» обрабатывать ошибку было бы либо проверкой failbit флага, либо включив ifstream.exeptions() и завернув все в try{} catch{} блок. Но «неправильно» сделать намного проще — всего одна строчка, а «обработку ошибок можно потом добавить».
Тот же код на Python:
file = open('test.txt', 'r')
Тоже самое — гораздо проще просто вызвать open(), а обработкой ошибок заняться потом. При этом под «обработкой ошибок» чаще всего подразумевается «завернуть в try-catch, чтобы-не-падало».
(Сразу оговорюсь — этот пример не пытается сказать, что в C++ или Python программисты не проверяют ошибку при открытии файла — как раз в этом, примере из учебника, скорее всего как раз проверяют чаще всего. Но в менее «стандартном» коде посыл этого примера становится очевиднее.)
А вот аналогичный пример на Go:
file, err := os.Open("test.txt")
И вот тут становится интересно — мы не можем просто так получить хендлер файла, «забыв» про возможную ошибку. Переменная типа error возвращается явно, и ее нельзя просто так оставить без внимания — неиспользованные переменные это ошибка на этапе компиляции в Go:
./main.go:8: err declared and not used
Ее нужно либо заглушить, заменив на _, либо как-то проверить ошибку и среагировать, например:
if err != nil { log.Fatal("Aborting: ", err) }
«Заглушать» ошибки в Go — считается дурным тоном. Даже когда кажется, что «тут не может быть никакой ошибки» — например, в функциях вроде strconv.Atoi() — все равно остается ощущение дискомфорта — а вдруг таки возникнет ошибка, а я тут беру и сознательно отрезаю возможность об этом узнать — она тут не просто так, в конце концов. Проще все таки эту ошибку как-то обработать.
И эта простота и явная невозможность проигнорировать ошибку создает стимул. А стимул становится привычкой — всегда возвращать и проверять ошибки. Это стало слишком просто делать для того, чтобы игнорировать и избегать этого.
Тестирование
Сейчас вряд ли кому-то нужно доказывать, что код покрытый тестами — это «правильный» путь, а код без тестов — это зло. Даже большее, чем глобальные переменные. «Хорошие» программисты — покрывают ~100% кода тестами, «плохие» — забивают. Но опять же — это не вина программистов, это инструментарий, который делает написание тестов сложной задачей.
Кто совсем не знаком с состоянием дел в Go в плане тестирования — вот краткая история: чтобы написать тест для вашей функции не нужно никаких дополнительных библиотек или фреймворков. Все что нужно — создать файл mycode_test.go и добавить функцию, начинающуся с Test:
import "testing" func TestMycode(t *testing.T) { }
в которой пишете свои условия и проверки. Там практически нет никакого дополнительного синтаксиса — проверки осуществляются стандартными if-условиями. Это банально и примитивно, но это ужасно просто делать и это работает как часы.
Все что вам нужно теперь, это запустить
go test
и вы получаете полноценный прогон тестов. С дополнительными параметрами вроде -cover, у вас появляется возможность посмотреть покрытие тестами кода.
Так вот это game-changer. Программисты не любят писать тесты, не потому что они «плохие программисты», а потому что затраты времени и сил на то, чтобы «написать тесты» всегда высоки. Гораздо больше профита будет, если это же время потратить на написание нового кода. Go тихо и незаметно меняет правила игры.
То, что всегда требовало установки дополнительного фреймворка, плясок с бубном для того, чтобы заставить работать нужную версию на нужной системе, разобраться с зачастую не очень понятной документацией, запомнить все эти десятки строк, паттернов и команд, необходимых просто для того, чтобы мочь написать один простой тест — это сложно. Проще не писать тест, а свалить все тестирование на QA-отдел. Ничто так не отнимает стимула делать «правильно», как простота и соблазнительность «неправильного» пути.
Я. к примеру, далеко не сразу начал осознавать важность тестирования кода, а когда начал — это было сложно и неудобно, и при первой же возможности не тестировать — я и не тестировал. С Go писать тесты стало не то что просто — стало стыдно «не писать». Я даже сам того не осознавая стал использовать TDD — просто потому что это стало чертовски просто и время потраченное на написание тестов стало минимальным.
Вывод
Многие решения дизайна языка основаны именно на этом — стимулировать «правильные» подходы в написании программ, и делать неудобными «неправильные». Go просто таки вынуждает программистов принимать KISS-принцип как аксиому и уменьшает «ненужную сложность» (по Бруксу) насколько это возможно. В конце-концов, Go делает программистов лучше.
И в этом, по моему глубокому убеждению, одно из самых главных преимуществ Go.
Статьи по теме
Why Go gets exceptions right
It’s 2015. Why do we still write insecure software?
ссылка на оригинал статьи http://habrahabr.ru/post/248857/

Добавить комментарий