Главное преимущество Go

Если спросить среднестатического Go-девелопера, какие есть преимущества у Go — скорее всего вы услышите уже знакомый перечень плюшек. О них уже написано немало, но очень часто обходится стороной другая вещь, гораздо более интересная — долгосрочный эффект тех или иных решений дизайна языка. Я хочу раскрыть эту тему чуть шире, которая на самом деле актуальна не только для Go. Но в данной статье я возьму для примера два аспекта — способ обработки ошибок в Go и систему тестирования и постараюсь показать, как дизайн языка вынуждает людей более писать более качественный код.424dd5fa03204d61af9dc18b9161a33f.jpg

Обработка ошибокВы, наверняка, знаете, что в императивных языках есть два основных механизма сообщать об ошибке — выбрасывать исключение или возвращать код/значение явно. Можно было бы сказать, что тут даже есть два лагеря — сторонники исключений и сторонники явного возврата, но все несколько хуже. Лагеря на самом деле два, но они иные — те, кто понимает важность обработки ошибок в коде, и те, кто по большей части этот аспект программирования игнорирует. Причем второй лагерь — пока-что безоговорочный лидер.Логично предположить, что это именно то, что отличает «хороших» программистов от «плохих» программистов, и доля правды тут, несомненно есть. Но есть одно но. Инструментарий — в данном случае это «язык программирования» — тоже решает. Если ваш язык позволяет делать «неправильно» намного проще, чем делать «правильно» — будьте уверены, никакое количество статей и книг «Как не нужно писать на [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 rightIt’s 2015. Why do we still write insecure software?

© Habrahabr.ru