Майкл О. Чёрч – известный блоггер-провокатор, автор таких эпичных текстов, как, например, «Не тратьте время зря на эти ерундовые стартапы». Сегодня мы предлагаем вашему вниманию статью на чисто профессиональную тему – про функциональное программирование...
Итак, пока в меня еще не полетели гнилые помидоры, хотелось бы конкретно объяснить, что именно я имею в виду. Я не хочу сказать, будто «функциональное программирование – отстой». Отнюдь. Далеко не все гетто – это бедные, криминогенные и безрадостные кварталы. Еврейские гетто существовали в Европе долгие века, от Ренессанса до Второй мировой войны, и многие из них стали интеллектуальными центрами мирового значения – некоторые из них существовали вполне безбедно. В какое-то время нью-йоркский район Гарлем был местом компактного проживания состоятельного среднего класса черных американцев и прославился так называемым «гарлемским ренессансом». Нечто подобное характерно и для функционального программирования. Это – недооцененный интеллектуальный капитал в мире программирования. Ведь идеи функционального программирования в конце концов просачиваются во все остальные сферы промышленности – но, тем не менее, оно было и остается своеобразным гетто.
Гетто – это городской аналог географического анклава: оно со всех сторон окружено большим городом, но отличается культурной изоляцией и обычно значительно уступает по размерам самому городу. Часто в гетто находят приют те, кому приходилось с трудом выживать за его пределами. И те, кто слишком глубоко пустит корни в гетто, с недоверием относится к внешнему миру. Подобную же настороженность к жителям гетто испытывают и те, кто живет за его пределами. Слово «гетто» обладает негативной коннотацией, как будто жизнь в гетто – это своего рода насильственное заключение, но зачастую это не так. Например, чайна-тауны являются добровольными гетто, и их статус никак не связан с ограничением свободы. Монастыри – это также закрытые общины, формируемые по добровольному принципу. Аналогичным «добровольным гетто» является и функциональное программирование. В сфере разработки программного обеспечения образовалась своеобразная «элитарная ниша», и многие из ее обитателей отказываются работать за ее пределами – но все мы оказались в ней по доброй воле.
Что же такое функциональное программирование? Примечательно, что я собираюсь поговорить не о «функциональном программировании» в его наиболее пуристической трактовке, поскольку большинство «функциональных программистов» не гнушаются и побочными эффектами. Культурные нюансы, связанные с функциональным программированием, обусловлены не каким-то абстрактным неприятием излишних вычислений. Скорее, они происходят из необходимости контролировать сложность создаваемой программы, а также из необходимости работы с инструментами (в частности – с языками), обеспечивающими наиболее адекватную разработку. Common Lisp, Scala и Ocaml не являются чисто функциональными языками, но они обеспечивают поддержку тех абстракций, благодаря которым и становится возможным функциональное программирование.
На самом деле настоящие функциональные программисты используют «мультипарадигмальный» подход – в основном он функционален, но при необходимости в нем применяются императивные методы. Все споры относительно такого подхода сводятся к тому, что здесь является «краеугольным камнем» программы. С точки зрения функционального программиста это референциально-прозрачная функция (то есть всякий раз возвращающая одинаковый вывод при определенном вводе). Таковы, например, математические функции. В императивном программировании референциально-прозрачным элементом является действие с сохранением состояния. В объектно-ориентированном программировании таким первоэлементом является объект – более общая идея, которая может представлять собой и референциально-прозрачную функцию, и действие в составе самостоятельно разработанного предметно-ориентированного языка программирования (DSL), и что-то совершенно иное. Разумеется, большинство явлений, относимых к объектно-ориентированному программированию, превращаются в сборную солянку из многочисленных стилей и специализированных DSL, особенно в случаях, когда над объектно-ориентированным проектом трудится несколько разработчиков. Это долгий разговор, к которому лучше вернуться как-нибудь в другой раз.
В принципе, функциональное программирование – вещь правильная. Да, функциональный подход подходит не для всякой задачи. Но, если говорить о задаваемых по умолчанию абстракциях, применяемых при построении большинства программ высокого уровня, то, несомненно, такими абстракциями должны быть неизменяемые значения и референциально-прозрачные функции – за исключением случаев, когда явно имеется что-то более подходящее. Почему? А потому, что без дополнительной информации вычислительное действие невозможно проверить или интерпретировать, поскольку тестировщик не может контролировать среду, в которой оно существует. Иными словами, чтобы судить о правильности действия, человек должен знать среду, в которой происходит действие (и еще, как правило – контролировать эту среду). Как правило, от тестировщика требуется проверить все частные случаи данного действия, а для этого знать среду недостаточно – ее совершенно необходимо и контролировать. Но в данном случае состояние среды, в которой происходит тестирование, – это имплицитный (неявный) параметр действия. Чтобы сделать его явным, нужно превратить действие в референциально-прозрачную функцию.
Во многих случаях лучше поступить именно так – если это возможно. Но такая возможность существует не всегда. Например, среда (скажем, состояние базы данных кластера компьютеров) может быть слишком большой, сложной или нестабильной, чтобы просто вплести ее в функцию в виде еще одного потока. Как правило, на практике функциональное программирование сводится не к тому, чтобы избавиться от всех состояний, а к управлению такими состояниями, которые по определению присущи системе, являются необходимыми или желательными для работы.
Приведу конкретный пример. Допустим, у меня есть грифельная доска, лежащая лицевой стороной вниз, а на ее лицевой стороне записано число (состояние). Я прошу кого-либо прочитать это число (обозначим число n), стереть его, а потом записать на лицевой стороне доски число, равное n+1. При этом я исхожу из того, что мой ассистент не будет мне лгать и что он физически способен поднять доску. И я собираюсь определить, способен ли он выполнить эту операцию. Если n мне неизвестно, и я лишь вижу число, написанное ассистентом, я никак не могу узнать, правильно ли этот человек выполнил мое указание. Разумеется, я могу проконтролировать тестируемую среду, написать на доске, допустим, число 5, а потом попросить ассистента, чтобы он выполнил вышеописанное действие. Если в результате у него получится 6, то я буду знать, что он верно выполнил мое указание. Но на данном этапе необходимость в доске уже отпадает. Достаточно спросить ассистента: «сколько будет 5+1»? И вот – я только что перешел от императивного тестирования к функциональному. Я проверяю, дает ли верный результат используемая ассистентом модель функции сложения, вместо того чтобы заставлять его совершать практическое действие, которое позволило бы мне проверить состояние после операции.
Функциональная альтернатива такой модели – это модульный тест (unit test). Я не пытаюсь проверить, умеет ли мой ассистент переворачивать доску, считывать с нее число, стирать его и записывать на доске новое число – меня интересует лишь то, умеет ли он складывать. Если я хочу оценить и все остальные вышеупомянутые параметры, то мне потребуется выполнить интеграционный (комплексный) тест. При реальной разработке программ применяются оба вида тестирования, но преимущество модульных тестов заключается в том, что они позволяют с точностью определить, что именно пошло не так – соответственно, обеспечивают быструю отладку.
Тестирование, отладка и техническая поддержка – основные составляющие разработки ПО в реальном мире, и функциональное программирование предоставляет нам средства, позволяющие решать эти проблемы таким способом, который значительно проще отслеживать. Функции должны быть референциально-прозрачными и в, идеале, маленькими (менее 20 строк). Крупные функции следует иерархически разбивать на более мелкие – попутно отмечу, что полученные мелкие компоненты можно переиспользовать и в других системах. Почему это желательно? Потому что модульность облегчает не только многократное использование кода, но и его отладку и рефакторинг (изменение делается всего один раз). В долгосрочной перспективе модульность повышает удобство работы с кодом для тех, кому придется его изменять и поддерживать. Человек просто не в состоянии держать в уме 500-строчный объектный метод – так зачем он нам, если без него можно обойтись?
Для тех, кто считает себя функциональным программистом, совершенно очевидно, что мы не всегда пишем программы без сохранения состояний, но стремимся к референциальной прозрачности или к тому, чтобы в создаваемых интерфейсах возникали очевидные эффекты, обусловленные определенными состояниями. Такой подход очень удобен для других программистов, которым придется работать с нашим кодом (а также для нас самих, если мы вернемся к коду через несколько месяцев). Когда мы пишем программы на C, мы используем императивный подход, поскольку C – императивный язык. Но мы стремимся сделать работу нашей программы настолько предсказуемой и логичной, насколько это возможно.
На практике функциональное программирование не означает категорического отказа от использования состояний. Просто к состояниям нужно относиться рационально. Почему же функциональное программирование, несмотря на свои бесспорные достоинства, напоминает гетто? Дело в том, что мы слишком увлечены качественным проектированием – настолько, что избегаем браться за работу, которая, возможно, заставит нас иметь дело с образцами плохого проектирования. С нашей стороны это не слабость, а осознанная необходимость. Мы не хотим тратить время или рисковать карьерой, пытаясь «спасти» безнадежные системы или компании, с которыми все кончено.
А вообще, мы обычно распознаем даже самые слабые признаки деградации. Нам нравятся языки Clojure и Scala для работы с виртуальной машиной Java, и при необходимости мы можем прибегнуть к Java как к языку. Но мы терпеть не можем «ява-шопы» (то есть Java как мировоззрение), так как точно знаем, что такая политика порождает лишь путаные, неравномерно устаревающие кусочки хаоса, несмотря на все благие намерения. Скажите нам «POJO» или «Шаблон "Посетитель"» – и говорить нам станет не о чем.
Это может показаться высокомерным, но для человека, привыкшего мыслить масштабно, такой подход – необходимость. В каждый момент времени разрабатывается миллион «суперновых вещей», и 995 000 из них являются реинкарнациями «хорошо забытого старого», найденного на свалке истории. Если и можно как-то охарактеризовать образ мыслей функционального программиста, то следует сказать, что мы консервативны. Мы не доверяем методологиям или паттернам проектирования тех универсальных фреймворков, которые претендуют на спасение мира. Мы не верим ни в какие «панацеи», так как знаем, что программирование – изначально сложная наука. А вообще, мы считаем, что самая совершенная интегрированная среда разработки имеет достаточно достоинств, чтобы компенсировать ее же недостатки и ложный комфорт. Например, никакая IDE не поможет, если нужно справиться с критической ситуацией, возникшей на удаленном сервере, который расположен в 3 000 миль от вас. Мы в таком случае воспользуемся Vim или Emacs, либо командной строкой, так как знаем – они работают всегда и везде, и их вполне достаточно для качественного редактирования.
Функциональному программисту легко ошибиться и принять за «гетто» всю остальную индустрию разработки ПО (особенно с учетом негативной коннотации этого слова, от которой я здесь сознательно отмежевываюсь). Наши произведения стабильны и привлекательны, и мы так хорошо умеем подчищать следы своей работы, потому что знаем: чисто там, где не сорят. За стенами нашего городка раскинулись трущобы с шаткими пятнадцатиэтажными общагами, которые уже начинают подкашиваться. Мегаполис, от которого мы отгородились, грязный и заразный, и мы полагаем, что еще лет десять – и его придется попросту сжечь, чтобы вывести всех чумных крыс. Нам не нравится ходить туда, но иногда это бывает целесообразно – хотя бы потому, что он в пятьдесят раз больше нашего гетто. Если мы перестаем задумываться о размерах и масштабах, а также осознавать, что это означает – то можем и забыть о том, что мы-то живем в гетто. Дело не в том, что там жить не стоит – в конце концов, наше гетто зажиточное и интеллектуально богатое, – но, как ни крути, это гетто.
Я полагаю, что большинство функциональных программистов целиком осознает эту ситуацию, лишь когда приходится искать работу. К счастью, большинство из нас принадлежит к 5% наиболее успешных программистов, и такие поиски обычно не затягиваются. Опять же, я уже и не помню, сколько раз за последние пять лет мне попадались очень интересные вакансии, устраивавшие меня во всем, кроме языковых предпочтений. «Опыт программирования на Java от 5 лет, знание методологий и паттернов проектирования». Ни за что. Возможно, это действительно хорошая компания, которая просто не умеет составлять вакансии, но ее технические предпочтения выглядят так же, как и у плохих фирм. Так что кто знает… Возможно, рабочий процесс там быстро вгоняет в уныние. Нет, я отнюдь не считаю языки C++ и Java «грязными», с которыми ни за что не буду работать. Дело в том, что «грязной» обычно оказывается любая работа, требующая писать на этих языках в течение полного рабочего дня. Скорее всего, это отстой, и углубляться в изучение вакансии не стоит.
Случается, что C++ и Java действительно идеально подходят для решения поставленной задачи, но никому не советую выстраивать компанию, заложив в ее основу эти языки. В 2012 году это уже не выход. Java – не тот язык, который люди выбирают добровольно, на котором собираются вести основную разработку. Это язык, работать с которым людей заставляют другие люди. Обычно такое решение принимает менеджер, не расположенный к риску и не имеющий технического образования. Ява-шоп – это именно такая компания, в которой тон работы задают не программисты.
Функциональное программирование – фактически просто название «для своих», которое означает «программирование с хорошим вкусом». Мы предпочитаем наилучшие языки программирования, в частности, Ocaml и Clojure, но, конечно же, не ограничиваемся написанием функциональных программ. Будем ли мы использовать C, если он идеально подходит нам для работы? Без проблем. Можем ли мы реализовать в программе изменяемые состояния, если от этого программа станет проще (да, так бывает)? Не сомневайтесь. С другой стороны, мы доверяем эстетическим и архитектурным решениям, предложенным блестящими, испытанными седобородыми мэтрами – гораздо больше, чем сиюминутным капризам бизнеса. Мы консервативно верим в превосходство красоты и удобства над крайне недолговечными управленческими подходами, которые вдруг становятся мейнстримом. Нас не устраивает мнимая привлекательность всяких панацей и «методологий». Мы отводим взгляд, когда новоиспеченный магистр делового администрирования поучает нас, что, стоит толькоподелить рабочий календарь на двухнедельные «итерации» – и будут решены все мыслимые и немыслимые проблемы. К сожалению, такая твердая приверженность хорошему вкусу (часто – перед лицом крупного менеджера) делает нас белыми воронами. Это позиция «выскочки», она вполне может быть непопулярной и негативно сказываться на карьере. Мы никогда не были многочисленны. Большинство покидает наш лагерь, либо превращаясь в менеджеров (в таком случае станешь работать даже в ява-шопе), либо признает поражение и скатывается в дурновкусие. Жить в гетто непросто.
Сейчас я уже слабо верю в абстрактного среднего программиста, такого, который расстается с последней мыслью о работе в 17:01 и которого не смущает восьмичасовой рабочий день, проведенный за написанием кода на Java, поскольку решать неизбежно возникающие проблемы – удел «парня из техподдержки». Вероятно, такой парень и не должен программировать. С другой стороны, на настоящий момент наша работа составляет два процента софтверной индустрии – по оптимистическим оценкам.
Мы можем чего-то достигнуть. Можем справляться еще лучше. И мы совсем не небожители, в число которых никогда не войти остальным 98% программистов. Нисколько. На свете живет множество работающих с IDE, хакающих Яву, изнывающих от скуки девелоперов, которые не глупее нас – просто они еще блуждают во тьме. Наша задача – показать им свет, и если нам не удастся их убедить, что они могут работать в 2-10 раз продуктивнее, чем могли даже мечтать, и что программирование вновь может стать для них захватывающим делом – то грош нам цена. Мы должны из кожи вон лезть и открыть глаза десяти, двадцати, а может быть, и тридцати процентам программистов. А потом в программировании наступят кардинальные изменения.
Я считаю, что надежность нашей профессии и сохранение ее принципов зависит от того, хватит ли нам умения и желания, чтобы понять, как достичь этих высоких идеалов.
Источник.
Читайте также
10 курсов по SQL для лучшего понимания работы с большими данными (май, 2023)
10 курсов по SQL для лучшего понимания работы с большими данными (май, 2023)
Собрали 10 платных и бесплатных онлайн-курсов для изучения SQL. Программы рассчитаны на слушателей, которые только начинают или продолжают знакомство с языком.
10 способов научиться программировать самостоятельно
10 способов научиться программировать самостоятельно
Хотите научиться кодить и освоить алгоритмы? Собрали десять советов с чего начать изучение программирования для тех, кто только начинает своё путешествие в мир программирования и снабдили все это полезными ссылками на курсы для начинающих программистов.
Топ языков программирования по версии IEEE в 2022 году
Топ языков программирования по версии IEEE в 2022 году
Обсуждение
Релоцировались? Теперь вы можете комментировать без верификации аккаунта.