Из книги Дэвида Копека Классические проблемы компьютерных наук в Java

Брайан Гетц — одна из ведущих фигур в мире Java. В качестве архитектора языка Java в Oracle он помогает направлять развитие языка и поддерживающих его библиотек. Он провел язык через несколько важных модернизаций, включая Project Lambda. Брайан имеет долгую карьеру в области разработки программного обеспечения и является автором бестселлера «Java Concurrency in Practice». (Аддисон-Уэсли, 2006 г.)

Получите скидку 40 % на Классические проблемы компьютерных наук на Java, введя fcckopec3 в поле кода скидки при оформлении заказа на сайте manning.com.

Это интервью, взятое из главы 10, было взято 25 августа 2020 года в доме Брайана в Уиллистоне, штат Вермонт. Стенограмма была отредактирована и сокращена для ясности.

Как вы начали заниматься вычислительной техникой?

Я начал заниматься хобби примерно в 1978 году, когда мне было 13 или 14 лет. У меня был доступ к компьютерной системе с разделением времени через местную школу, и мой старший брат, который прошел ту же программу раньше меня, приносил мне книги и прочее для чтения, и я был просто абсолютно зацепил. Я был полностью очарован этой системой, которая управлялась сложным, но понятным набором правил. Итак, я проводил столько времени, сколько мог, после школы в компьютерном классе, в школе, изучая все, что мог. И в то время это было очень многоязычное время для программирования. Не было доминирующего языка, как это было, наверное, последние 25 лет. Все должны были знать несколько языков программирования. Я сам выучил BASIC, Fortran, COBOL, APL и язык ассемблера. Я видел, как каждый из них был разными инструментами для разных проблем. Я был полностью самоучкой, потому что в то время не было никакого формального образования. Моя степень связана не с информатикой, а с математикой, потому что в то время во многих школах даже не было факультетов информатики. И я думаю, что математическая ориентация сослужила мне очень хорошую службу.

Был ли какой-то язык программирования, который оказал на вас очень большое влияние, когда вы только начинали его изучать?

Когда я только учился, их действительно не было. Я просто заменял все это. Доминирующими языками в то время были Fortran, COBOL и BASIC для разных категорий задач. Но позже, когда я был аспирантом, у меня была возможность пройти курс «Структура и интерпретация компьютерных программ» в Массачусетском технологическом институте, где я выучил Scheme, и именно здесь загорелись все лампочки. На тот момент я уже программировал почти 10 лет и уже столкнулся с массой интересных задач. Это был первый раз, когда я увидел, что существует всеобъемлющая теория, которая могла бы связать множество моих наблюдений. Мне очень повезло, что я попал на этот курс в качестве аспиранта, а не первокурсника, потому что первокурсники были просто ошеломлены объемом материала, который им бросали. Наличие большего опыта позволило мне увидеть лежащую в основе красоту и структуру, а также то, чего они на самом деле пытались достичь. Если мне нужно было выбрать момент, когда красота вычислений действительно стала для меня очевидной, то это был этот класс.

Как вы развили свои навыки разработки программного обеспечения и информатики после колледжа?

Я думаю так же, как почти все остальные — в основном, делая. В типичных профессиональных инженерных ситуациях чаще всего вы оказываетесь в глубоком тупике с проблемой, которую нужно решить, и вам приходится решать ее самостоятельно. В вашем распоряжении есть множество инструментов, но не всегда очевидно, какой из них использовать правильно, и есть процесс проб и ошибок, когда вы пробуете что-то. Вы видите, что работает, а что перестает работать, когда проблема достигает определенного порога сложности. Будем надеяться, что наряду с этим происходит некий индуктивный процесс рассуждений, с помощью которого вы можете выяснить, почему что-то сработало, и когда это может снова сработать, а может и нет. В начале своей карьеры у меня было несколько довольно типичных должностей в области разработки программного обеспечения; Я работал в исследовательской лаборатории, я работал в небольшой компании по разработке программного обеспечения, которая делала сетевое программное обеспечение. Я учился, делая и экспериментируя, как и большинство разработчиков сегодня.

Как ваша карьера привела вас к тому, что вы стали архитектором языка Java?

Довольно странным и окольным путем! Первую половину своей карьеры я был в основном обычным программистом. В какой-то момент я сделал этот переход к тому, чтобы быть на полпути между программированием и образованием, проводить презентации и писать статьи, а в конечном итоге книгу. Я всегда старался выбирать темы, которые смущали меня, потому что теоретически они могут сбивать с толку и других, и старался представить их в понятном свете. Я обнаружил, что у меня есть талант преодолевать разрыв между техническими деталями и интуитивными ментальными моделями, и это привело к написанию книги «Параллелизм в Java на практике», которая на тот момент была почти 15 лет назад! И оттуда я перешел на работу в Sun в роли, которая была больше связана с пропагандой технологий, чем с разработкой, объясняя людям: «Как работает JVM?» «Что делает динамический компилятор?» «Почему динамическая компиляция потенциально лучше статической компиляции?» Я пытался демистифицировать технологию и развенчать окружающие ее мифы. И как только я оказался там, я увидел, как работает инженерный процесс в команде Java, и в какой-то момент появилась возможность внести более существенный вклад. Если бы мне нужно было дать людям дорожную карту, как туда добраться, я не знаю, как бы я ее нарисовал. Это определенно была не прямая линия.

Что значит быть архитектором языка Java?

Проще говоря, я должен решить, в каком направлении движется модель программирования Java. А тут, конечно, много-много вариантов — и много людей, которые с радостью дадут мне совет! Я уверен, что у каждого из девяти миллионов разработчиков Java есть пара идей для языковой функции. И, конечно же, мы не можем делать их все или даже многие из них. Итак, мы должны выбирать очень, очень тщательно. Моя работа заключается в том, чтобы сбалансировать потребность двигаться вперед, чтобы Java оставалась актуальной, с необходимостью того, чтобы Java оставалась «Java». А актуальность имеет много измерений: она связана с проблемами, которые мы хотим решить, связана с аппаратным обеспечением, на котором мы работаем, связана с интересами, ориентациями и даже модой программистов. Мы должны развиваться, но мы также не можем двигаться так быстро, чтобы терять людей. Если бы мы в одночасье внесли радикальные изменения, люди сказали бы: «Это не та Java, которую я знаю», и занялись бы чем-то другим. Итак, мы должны выбрать как направление, так и скорость движения вперед, чтобы мы могли оставаться актуальными для проблем, которые люди хотят решить, не вызывая у них дискомфорта.

Для меня это означает встать на место Java-разработчиков и понять, в чем их болевые точки. Затем мы пытаемся изменить язык таким образом, чтобы он работал с ними и устранял болевые точки, которые они испытывают, но не обязательно так, как они себе представляли. Есть старая поговорка, приписываемая Генри Форду: «Если бы я спросил своих клиентов, чего они хотят, они бы ответили мне: «Лошади побыстрее». это предложение и понимание той боли, в которой они находятся, заставляют их думать, что это было бы правильным решением. Сравнивая это с тем, что мы слышали от других разработчиков, возможно, мы сможем увидеть, чего действительно не хватает, что на самом деле поможет решить проблемы людей, сделать их более продуктивными, сделать программы более безопасными и эффективными.

Как происходит процесс развития языка Java? Как принимается решение о добавлении в язык новых функций?

На самом деле довольно редко функция изобретается из цельного куска ткани. Реальность такова, что почти каждая «новая» идея десятилетиями витала в мире программирования. Всегда, когда кто-то приходит ко мне с идеей, я вижу связь с чем-то, что было сделано на каком-то другом языке давным-давно. Большая часть процесса заключается в том, чтобы дождаться подходящего момента, чтобы раскрыть новую концепцию и встроить ее таким образом, чтобы это соответствовало остальной части языка. В идеях для функций нет недостатка, и в каждом языке вы найдете множество функций, которые нравятся людям в этих сообществах. И настоящая игра заключается в том, чтобы заглянуть под них и спросить: «Что дает вам эта функция, чего вы не можете сделать иначе? Как это делает ваши программы более безопасными? Как это позволит улучшить проверку типов? Как это делает ваши программы менее подверженными ошибкам, более выразительными и т. д.?»

Это довольно субъективный процесс; если мы хотим облегчить боль людей, мы должны делать субъективные выводы о том, какие виды боли сейчас важнее облегчить. Если вы посмотрите на большие функции, которые были добавлены в Java в прошлом: в середине 2000-х мы видели дженерики, и это был очевидный пробел. В то время язык требовал параметрического полиморфизма. Они хотели это сделать в 1995 году, но не знали, как это сделать так, чтобы это имело смысл для языка. И они не хотели прививать шаблоны C++ к Java, что было бы ужасно. Итак, потребовалось еще почти 10 лет, чтобы выяснить, как можно естественным образом добавить параметрический полиморфизм и абстракцию данных в Java таким образом, чтобы он казался естественным. И я думаю, что они проделали фантастическую работу. Мы сделали то же самое с поведенческой абстракцией, когда совсем недавно делали лямбда-выражения. Опять же, вся тяжелая работа там была не в теории. Теория лямбда-выражений была хорошо изучена с тридцатых годов. Трудная часть заключалась в том, как сделать так, чтобы он вписался в Java, чтобы он не выглядел пригвожденным сбоку? Высшим показателем успеха является то, что когда вы, наконец, делаете что-то через три, пять или семь лет, люди говорят: «Почему ты так долго? Это так очевидно». Ну, версия, которая была у нас в первый год, не выглядела бы такой простой и очевидной. И мы не хотим навязывать это людям, поэтому нам нужно не торопиться.

Как понять, рассматривая функцию, что это не просто мода, а что-то действительно важное, что действительно нужно разработчикам?

Это действительно хороший вопрос, потому что было несколько настоящих промахов. В начале 2000-х прозвучал громкий призыв добавить XML-литералы в язык Java, и я считаю, что это пуля, от которой уклонились. Не все языки избежали этой пули.

Я не могу дать вам алгоритм; часто вам просто нужно сесть и подумать об этом в течение длительного времени и посмотреть, как выглядят связи с остальной частью языка. Мы все видели языки, которые прикрепляли функцию сбоку для решения конкретной проблемы, но эта проблема не может быть проблемой на все времена. Если вы готовы сесть и набраться терпения и думать об этом снова и снова, прежде чем принять решение, очень часто вы можете почувствовать, когда что-то является просто привкусом недели.

Какими дополнениями к Java вы больше всего гордитесь за время работы архитектором языка Java?

Я руководил спецификациями по добавлению лямбда-выражений в Java. И это было не только огромным изменением, но и означало изменение того, как язык будет развиваться в будущем. В каком-то смысле это было решающим фактором, потому что в период, предшествовавший этому, Sun была довольно занята медленным уходом из бизнеса, и мы не могли развивать платформу с той скоростью, с которой нам хотелось бы. . В то время было совершенно ясно, что Java отстает, и это был наш большой шанс доказать миру, что Java может оставаться актуальной и интересной для программирования; что мы могли бы продолжать учить эту старую собаку новым причудливым трюкам.

Основная проблема добавления лямбда-выражений в Java заключалась в том, чтобы сделать так, чтобы они не выглядели так, как будто они приклеены сбоку, а интегрированы в целое, как если бы они всегда были там. И не было недостатка в предложениях, как это сделать — почти все из них были «делай так, как это делает этот другой язык». Все это было бы неудовлетворительно. Мы могли бы добраться туда на год или два быстрее, но мы бы не получили результат, который также работал, и мы застряли бы с ним надолго. Чем я действительно горжусь, так это тем, как нам удалось выяснить, как интегрировать его в язык на нескольких уровнях, чтобы он выглядел так, как будто он принадлежит ему. Он очень четко работает с общей системой типов. Нам пришлось пересмотреть ряд других аспектов языка, чтобы заставить его работать — нам пришлось пересмотреть вывод типов и его взаимодействие с выбором перегрузки. Если вы спросите людей, какие функции были добавлены в Java 8, они никогда не будут ни в чьем списке. Но это была огромная часть работы — она была ниже ватерлинии, но это была основа, необходимая для того, чтобы вы могли писать код, который хотели написать, и это просто естественно работало.

Мы также должны были начать решать проблему эволюции совместимого API. Один из огромных рисков, с которым мы столкнулись в Java 8, заключался в том, что способ написания библиотеки с лямбда-выражениями сильно отличается от того, как вы пишете на языке без лямбда-выражений. И мы не хотели сделать язык лучше, а потом оказаться в ситуации, когда сейчас вдруг все наши библиотеки выглядят на 20 лет старше. Нам пришлось решить вопрос о том, как мы собираемся развивать библиотеки совместимым образом, чтобы они могли использовать преимущества этих новых идиом дизайна библиотек. Это привело к возможности совместимого добавления методов к интерфейсам, что было важной частью сохранения актуальных библиотек, чтобы в первый же день язык и библиотеки были готовы к новому стилю программирования.

Многие студенты задают мне вопрос: насколько часто им следует использовать лямбда-выражения? Могут ли они злоупотреблять ими в своем коде?

Я подхожу к этому, возможно, с другой точки зрения, чем многие студенты, потому что большая часть кода, который я пишу, представляет собой библиотеки, предназначенные для использования большим количеством людей. Планка для написания такой библиотеки, как API потоков, очень высока, потому что вы должны сделать все правильно с первого раза. Планка совместимости для его изменения очень высока. Я склонен думать об абстрагировании от поведения, когда вы пересекаете границу между кодом пользователя и кодом библиотеки, главное, что лямбда-выражения позволяют вам сделать, — это разрабатывать API, которые можно параметризовать не только данными, но и поведением. , потому что лямбда-выражения позволяют нам рассматривать поведение как данные. Итак, я сосредоточен на взаимодействии между клиентом и этой библиотекой и естественном потоке управления. Когда клиенту имеет смысл дергать за все нити, а когда клиенту имеет смысл передать некоторое поведение библиотеке, которую он вызовет в соответствующее время? Я не уверен, что мой опыт напрямую переносится на опыт ваших учеников. Но одна вещь, которая, безусловно, будет ключом к успеху, — это способность распознавать, где в вашем коде проходят границы и где лежит разделение ответственности. Независимо от того, строго ли они разграничены в отдельно скомпилированных модулях или тщательно задокументированных API, или это просто соглашения о том, как вы организуете свой код, мы хотим быть в курсе.

Мы используем эти границы в нашем коде не просто так — чтобы мы могли управлять сложностью по принципу «разделяй и властвуй». Каждый раз, когда вы разрабатываете одну из этих границ, вы разрабатываете небольшое протокольное взаимодействие, и вы должны думать о ролях участников с каждой стороны, какой информацией они обмениваются и как выглядит этот обмен.

Вы говорили ранее о периоде застоя для Java. Когда это было и почему?

Я бы сказал, что временные рамки Java 6–7 были темными веками для Java. Не случайно это было время, когда Scala начала набирать обороты, отчасти потому, что я думаю, что экосистема говорила: «Что ж, нам, возможно, придется найти другую лошадь для поддержки, если Java не встанет и не заработает». К счастью, он встал и заработал, и с тех пор работает.

И сейчас мы наблюдаем довольно быструю эволюцию языка. Как изменилась философия?

В целом он не сильно изменился, но в деталях он сильно изменился. Начиная с Java 9, мы перешли к шестимесячному графику выпуска обновлений, а не к многолетнему циклу выпуска новых функций. И для этого было множество веских причин, но одна из них заключалась в том, что было много хороших небольших идей, которые всегда вытеснялись, когда вы планировали многолетние релизы с большими драйверами релизов. Более короткая частота выпуска релизов позволила нам лучше сочетать большие и маленькие функции. В этих шестимесячных выпусках многие из них имеют меньшие языковые функции, такие как вывод типа локальной переменной. На их создание не обязательно ушло всего шесть месяцев; возможно, им все еще потребовался год или два, но теперь у нас больше возможностей доставить что-то, когда оно будет готово. В дополнение к более мелким функциям вы также увидите более крупные дуги функций, такие как сопоставление с образцом, которые могут воспроизводиться постепенно в течение многолетнего периода. Более ранние части могут дать вам представление о том, в каком направлении движется язык.

Существуют также группы связанных функций, которые могут предоставляться по отдельности. Например, сопоставление с образцом, записи и запечатанные типы работают вместе, чтобы поддерживать модель программирования, более ориентированную на данные. И это не случайность. Это основано на наблюдении за тем, с какими трудностями сталкиваются люди, использующие статическую систему типов Java для моделирования данных, с которыми они работают. И как изменились программы за последние 10 лет? Их стало меньше. Люди пишут более мелкие единицы функциональности и развертывают их как, скажем, микросервисы. Таким образом, большая часть кода находится ближе к границе, где он будет получать данные от какого-либо партнера, будь то JSON, XML или YAML, через сокетное соединение, которое затем будет преобразовано в некоторую модель данных Java, работать с ней, а затем сделать обратное. Мы хотели упростить моделирование данных как данных, поскольку люди делают это чаще. Таким образом, этот набор функций предназначен для совместной работы таким образом. И вы можете увидеть похожие кластеры функций во многих других языках, только с другими названиями. В ML вы бы назвали их алгебраическими типами данных, потому что записи — это типы продуктов, а запечатанные классы — это типы сумм, и вы выполняете полиморфизм над алгебраическими типами данных с сопоставлением с образцом. Итак, это отдельные функции, которые, возможно, Java-разработчики не видели раньше, потому что они не программировали на Scala, ML или Haskell. Они могут быть новыми для Java, но это не новые концепции, и было доказано, что они работают вместе, чтобы обеспечить стиль программирования, соответствующий проблемам, которые люди решают сегодня.

Мне интересно, есть ли какая-то новая функция в Java, которая вас больше всего волнует.

Я действительно взволнован более широкой картиной сопоставления с образцом, потому что, когда я работал над этим, я понял, что это была недостающая часть объектной модели Java, а мы просто не заметили. Java предлагает хорошие инструменты для инкапсуляции, но он работает только в одном направлении: вы вызываете конструктор с некоторыми данными, и это дает вам объект, который затем очень осторожно отказывается от своего состояния. И способы, которыми он отказывается от своего состояния, как правило, через какой-то специальный API, о котором трудно рассуждать программно. Но есть большая категория классов, которые просто моделируют простые старые данные. Понятие шаблона деконструкции на самом деле просто двойственное понятие, которое у нас было с самого первого дня, то есть конструктор. Конструктор принимает состояние и превращает его в объект. Что обратного этому? Как вы деконструируете объект в состояние, с которого вы начали (или с которого могли бы вернуться)? И это именно то, что позволяет вам сделать сопоставление с образцом. Оказывается, есть очень много проблем, решение которых намного проще, элегантнее и, что наиболее важно, компонуемо, чем решение ad hoc.

И я говорю об этом, потому что, несмотря на все, что мы узнали о достижениях в теории языков программирования за последние 50 лет, мое краткое изложение истории языков программирования в одном предложении звучит так: «У нас есть один хороший трюк, который работает». И этот трюк — композиция. Это единственное, что помогает справиться со сложностью. Итак, как разработчик языка, вы должны искать методы, которые позволяют разработчикам работать с композицией, а не против нее.

Почему важно знать методы решения проблем из области информатики?

Стоять на плечах гигантов! Есть так много проблем, которые уже были решены кем-то другим, часто с большими усилиями и затратами и со многими фальстартами. И если вы не знаете, как распознать, что вы смотрите на проблему, которая, вероятно, была решена кем-то раньше, у вас возникнет соблазн заново изобрести их решение — и вы, вероятно, не будете делать это так, как раньше. Что ж.

На днях я видел забавный комикс о том, как работает математика. Когда открывается что-то новое, поначалу никто не верит даже в то, что это правда, и уходят годы на то, чтобы разобраться в деталях. Могут пройти годы, прежде чем остальная часть математического сообщества согласится с тем, что это действительно имеет смысл. А потом, на другом конце, вы тратите на это 45 минут на лекции, и когда студент этого не понимает, профессор спрашивает: «Мы вчера потратили на это весь урок, как вы могли этого не понять?» Многие концепции, которые мы рассматриваем как единицы понимания размером с лекцию в классе, являются результатом многих лет того, как кто-то бьется головой о проблему. Проблемы, которые мы решаем, настолько сложны, что нам нужна любая помощь, которую мы можем получить. Если мы сможем разложить проблему так, чтобы какую-то ее часть можно было решить с помощью существующей техники, это дает огромную свободу. Это означает, что вам не нужно заново изобретать решение, и особенно неплохое решение. Вам не нужно заново открывать все способы, в которых очевидное решение не совсем верно. Вы можете просто положиться на существующее решение и сосредоточиться на той части вашей проблемы, которая уникальна.

Иногда учащимся сложно представить себе, как проблемы со структурой данных и алгоритмами, которые они изучают, на самом деле возникнут при разработке программного обеспечения в реальном мире. Можете ли вы рассказать нам, как часто проблемы информатики возникают в разработке программного обеспечения?

Это напоминает мне разговор, который у меня был, когда я вернулся к своему научному руководителю через 10–15 лет после выпуска. Он задал мне два вопроса. Первый был: «Вы используете математику, которую выучили, в своей работе здесь?» И я сказал: «Ну, если честно, не очень часто». Второй: «Но используете ли вы навыки мышления и анализа, которые вы приобрели, изучая математику?» И я сказал: «Абсолютно, каждый день». И он улыбнулся с гордостью за хорошо выполненную работу.

Например, взять красно-черные деревья. Как они работают? Большую часть времени меня это не должно волновать. Если вам это нужно, у каждого языка есть отличная предварительно написанная, хорошо протестированная высокопроизводительная библиотека, которую вы можете просто использовать. Важным навыком является не способность воссоздать эту библиотеку, а знание того, как определить, когда вы можете использовать ее с пользой для решения более крупной проблемы, является ли это правильным инструментом, как это впишется во временную или пространственную сложность вашего общего решения. и т. д. Это навыки, которые вы используете постоянно. Когда вы находитесь посреди класса структур данных, может быть трудно увидеть лес за деревьями. Вы можете потратить много времени в классе, работая над механикой красно-черного дерева, и это может быть важно, но вам, скорее всего, больше никогда не придется этим заниматься. И, надеюсь, их не попросят сделать это на собеседовании, потому что я думаю, что это ужасный вопрос для интервью! Но вы должны знать, какой может быть временная сложность поиска по дереву, какими должны быть условия распределения ключей для достижения этой сложности и т. д. Именно к такому мышлению призваны прибегать разработчики из реального мира. применять каждый день.

Можете ли вы привести пример случая, когда вы или другой инженер смогли использовать знания из компьютерных наук для более эффективного решения инженерной проблемы?

В моей собственной работе это довольно забавно, потому что теория является очень важной основой для многих вещей, которые мы делаем. Но эта теория также не способна решить проблему за вас при проектировании языка реального мира. Например, Java — не чистый язык, поэтому теоретически у монад нечему учиться. Но, конечно, у монад можно многому научиться. Итак, когда я смотрю на возможную функцию, есть много теории, на которую я могу опереться. Это дает мне интуицию, но мне придется пройти последнюю милю самому. То же самое и с системами типов. Большая часть теории типов не имеет дело с такими эффектами, как генерация исключений. Ну, у Java есть исключения. Это не означает, что теория типов бесполезна. Существует множество теорий типов, на которые я могу опираться при разработке языка Java. Но я должен признать, что теория поможет мне продвинуться далеко вперед, и мне нужно будет проложить последнюю милю самостоятельно.

Найти этот баланс сложно. Но это важно, потому что слишком легко сказать: «О, теория мне не поможет», а потом заново изобретать велосипеды.

Какие области информатики важны для развития языка?

Теория типов очевидна. В большинстве языков есть система типов, а в некоторых их несколько. Java, например, имеет одну систему типов во время статической компиляции и другую систему типов во время выполнения. Существует даже система третьего типа для проверки времени. Конечно, эти системы типов должны быть согласованными, но они имеют разную степень точности и детализации. Итак, теория типов, конечно, важна. Существует много формальной работы по семантике программ, о которой полезно знать, но не обязательно то, что применяется в повседневном проектировании языков. Но я не думаю, что любой разумный проект обходится без открытия книг по теории типов и прочтения десятков статей.

Если есть кто-то, кто заинтересован в том, чтобы в конечном итоге заняться языковым дизайном, есть ли что-то, что вы порекомендуете ему изучить или что он должен сделать в своей карьере, чтобы когда-нибудь оказаться в такой же ситуации, как ваша? >

Очевидно, что для того, чтобы участвовать в разработке языка, вы должны понимать инструменты, которые используют разработчики языка. Вы должны понимать компиляторы и системы типов, а также все детали вычислительной теории: конечные автоматы, контекстно-свободные грамматики и т. д. Это необходимое условие для понимания всего этого материала. Также очень важно программировать на нескольких разных языках и особенно на разных видах языков, чтобы увидеть разные способы решения проблем, разные предположения, которые они делают, разные инструменты, которые они используют. резерв для языка по сравнению с тем, что они дают в руки пользователю, и т. д. Я думаю, что вы должны иметь довольно широкий взгляд на программирование, чтобы иметь возможность преуспеть в разработке языка. Вы также должны иметь точку зрения «системного мышления». Когда вы добавляете функцию к языку, это меняет то, как люди будут программировать на этом языке, и меняет набор направлений, которыми вы можете двигаться в будущем. Вы должны быть в состоянии увидеть не только то, как функция будет использоваться, но и как ею злоупотреблять, и действительно ли новое равновесие лучше старого или оно просто переносит проблему в другое место.

На самом деле, я бы дал некоторые из этих советов — в частности, пойти и изучить разные языки программирования — всем, независимо от того, интересуются они языками программирования или нет. Изучение более чем одной парадигмы программирования сделает их лучшими программистами; когда они подходят к проблеме, им легче увидеть несколько способов ее решения. Я бы особенно рекомендовал изучать функциональный язык, потому что он даст вам другой и полезный взгляд на то, как создавать программы, и разомнет ваш мозг (в хорошем смысле).

Какие ошибки, по вашему мнению, часто совершают Java-программисты, которых они, возможно, могли бы избежать, лучше используя возможности языка?

Я думаю, что самая большая проблема заключается в том, чтобы не понять, как работают дженерики. В дженериках есть несколько неочевидных концепций, но их не так много, и они не так уж сложны, как только вы настроитесь на них. А дженерики лежат в основе других функций, таких как лямбда-выражения, а также являются ключом к пониманию многих библиотек. Но многие разработчики рассматривают это как упражнение в том, «что мне нужно сделать, чтобы красные волнистые линии исчезли», а не как рычаг.

Что, по вашему мнению, является одним из самых больших изменений, которые произойдут в ближайшие 5-10 лет для работающих программистов?

Я подозреваю, что это будет интеграция традиционного решения вычислительных задач с машинным обучением. Сейчас программирование и машинное обучение — совершенно разные области. Нынешние методы машинного обучения бездействовали в течение 40 лет. Вся работа над нейронными сетями была проделана в шестидесятых и семидесятых годах. Но до сих пор у нас не было вычислительной мощности или данных для их обучения. У нас есть эти вещи сейчас, и вдруг это стало актуальным. Вы видите, как машинное обучение применяется к таким вещам, как распознавание рукописного ввода, распознавание речи, обнаружение мошенничества и ко всем тем вещам, которые мы раньше пытались решить (не очень хорошо) с помощью систем, основанных на правилах, или эвристик. Но проблема в том, что инструменты, которые мы используем для машинного обучения, и стиль мышления, который мы применяем для машинного обучения, полностью отличаются от того, как мы пишем традиционные программы. И я думаю, что это станет большим вызовом для программистов в ближайшие 20 лет. Как они собираются соединить эти два разных типа мышления в этих двух разных наборах инструментов, чтобы решать проблемы, для решения которых все чаще требуются оба набора навыков?

Как вы думаете, какими будут самые большие эволюционные изменения в языках программирования в следующем десятилетии?

Я думаю, что сейчас мы наблюдаем общую тенденцию, которая представляет собой конвергенцию между объектно-ориентированными и функциональными языками. Двадцать лет назад языки были строго разделены на функциональные языки, процедурные языки и объектно-ориентированные языки, и у каждого из них была своя собственная философия моделирования мира. Но каждая из этих моделей была в чем-то несовершенна, потому что моделировала лишь часть мира. То, что мы наблюдали за последнее десятилетие или около того, начиная с таких языков, как Scala и F#, а теперь и с такими языками, как C# и Java, заключается в том, что многие из концепций, изначально укоренившихся в функциональном программировании, находят свое применение в языках более широкого спектра. , и я думаю, что эта тенденция будет только продолжаться. Некоторые любят шутить, что все языки сливаются в $MY_FAVORITE_LANGUAGE. В этой шутке есть доля правды в том, что функциональные языки приобретают все больше инструментов для инкапсуляции данных, а объектно-ориентированные языки получают больше инструментов для функциональной композиции. И тому есть очевидная причина: это оба полезных набора инструментов. Каждый преуспевает в решении той или иной проблемы, и мы призваны решать проблемы, которые имеют оба аспекта. Итак, я думаю, что в следующие 10 лет мы увидим усиление конвергенции концепций, которые традиционно считались объектно-ориентированными, и концепций, которые традиционно считались функциональными.

Я думаю, что есть много примеров влияния мира функционального программирования на Java. Можете ли вы дать нам несколько из них?

Самый очевидный из них — лямбда-выражения. И знаете, не совсем справедливо называть их концепцией функционального программирования, потому что лямбда-исчисление старше компьютеров на несколько десятилетий. Это естественная модель для описания и построения поведения. В таком языке, как Java или C#, это имеет такое же значение, как и в Haskell или ML. Итак, это явно одно. Другим похожим является сопоставление с образцом, которое опять же у большинства людей ассоциируется с функциональными языками, потому что они, вероятно, впервые увидели его, но на самом деле сопоставление с образцом восходит к таким языкам, как SNOBOL из семидесятых, который был языком обработки текста. Сопоставление с образцом на самом деле очень точно вписывается в объектную модель. Это не чисто функциональная концепция. Просто случилось так, что функциональные языки заметили, что это полезно немного раньше нас. Многие из этих концепций, которые мы связываем с функциональными языками, имеют смысл и в объектно-ориентированных языках.

Java по многим параметрам является одним из самых популярных языков программирования в мире. Как вы думаете, что привело к такому успеху, и почему вы думаете, что он будет продолжать быть успешным в будущем?

Как и в случае с любым успехом, здесь есть доля удачи, и я думаю, вы всегда должны признавать ту роль, которую сыграла удача в вашем успехе, потому что поступать иначе — нечестно. Я думаю, что во многих отношениях Java появилась как раз в нужное время. В то время мир был на пороге принятия решения о переходе от C к C++. C был доминирующим языком в то время, к лучшему или к худшему, и C++ предлагал, с одной стороны, лучшую абстрактную мощь, чем C, а с другой стороны, нечестивую сложность. Итак, вы можете представить, что мир балансирует на скале, говоря: «Мы действительно хотим совершить этот прыжок?» И появился Java и сказал: «Я могу дать вам большую часть того, что обещает вам C++, без особых сложностей». И все говорили: «Да, пожалуйста, мы этого хотим!» Это было правильно в нужное время. Он подхватил ряд старых идей, которые годами витали в компьютерном мире, включая сборку мусора и встраивание параллелизма в модель программирования, которые раньше не использовались в серьезных коммерческих языках. И все это имело отношение к проблемам, которые люди решали в девяностых.

Так, у Джеймса Гослинга есть цитата, где он описывает Яву как «волка в овечьей шкуре». Людям нужна была сборка мусора, им нужна была интегрированная модель параллелизма, которая была бы лучше, чем pthreads, но им не нужны были языки, с которыми традиционно поставлялись эти штуки, — потому что они поставлялись со всевозможными другими вещами, которые их чертовски пугали. Java, с другой стороны, выглядела как C. На самом деле, они изо всех сил старались сделать синтаксис похожим на C. Он был знаком, а затем они могли привнести вместе с ним кое-что интересное, что вы заметили гораздо позже. Одна из вещей, которую сделала Java, заключалась в том, что вся среда выполнения языка была разработана с ожиданием того, что компиляция «точно в срок» появится, но ее еще не было. Первая версия Java в 1995 году была строго интерпретирована. Это было медленно, но каждое дизайнерское решение о языке, формате файла класса и структуре среды выполнения было принято с ноу-хау, позволяющим сделать это быстро. В конце концов он стал достаточно быстрым, а в некоторых случаях даже быстрее, чем C (хотя некоторые до сих пор не верят, что это возможно). Технология развивалась, и то, в чем люди действительно нуждались, привело к развитию Java. Но именно это и произошло в начале — чтобы сохранить Java № 1, когда конкуренты жаждали съесть обед Java, нам нужно было что-то большее. И я думаю, что то, что поддерживало нас даже в темные времена, которые мы обсуждали, — это неустанная приверженность совместимости.

Внесение несовместимых изменений нарушает ваши обещания. Это делает недействительными инвестиции, которые ваши клиенты вложили в свой код. Всякий раз, когда вы ломаете чей-то код, вы почти даете ему возможность переписать его на каком-то другом языке, а Java никогда этого не делала. Код, который вы написали на Java 5, 10, 15, 20, 25 лет назад, все еще работает. Это означает, что мы эволюционируем немного медленнее. Но это означает, что инвестиции, которые вы сделали не только в код, но и в ваше понимание того, как работает язык, сохраняются. Мы не нарушаем свои обещания и не причиняем вреда нашим пользователям. Задача состоит в том, как сбалансировать продвижение вперед с таким стремлением к совместимости. И я думаю, что это наше секретное оружие. Мы выяснили, как это сделать за последние 25 лет, и у нас это неплохо получается. Это то, что позволяет нам добавлять дженерики, лямбда-выражения, модули, сопоставление с образцом и другие вещи, которые могут показаться чуждыми для Java, но при этом они не выглядят прикрученными — потому что мы выяснили, как это сделать.

Go пользуется большой популярностью за свою интегрированную модель параллелизма, но Java уже имела примитивы синхронизации, ключевые слова и модель многопоточности, встроенные в язык еще в 1995 году. Как вы думаете, почему это не заслуживает большего признания?

Я думаю, что часть этого заключается в том, что большая часть настоящего ума находится под ватерлинией, где люди этого не видят. Когда что-то просто работает, оно часто не получает признания. Так что, это может быть частью этого. Я не большой поклонник Go по нескольким причинам. Все думают, что модель параллелизма — это секретное оружие Go, но я думаю, что их модель параллелизма на самом деле довольно подвержена ошибкам. То есть у вас есть сопрограммы с очень простым механизмом передачи сообщений (каналы). Но почти во всех случаях объекты по одну или по другую сторону канала будут иметь какое-то общее изменяемое состояние, защищенное блокировками. И это означает, что у вас есть объединение ошибок, которые вы можете сделать при передаче сообщений, и ошибок, которые вы можете сделать с параллелизмом с разделяемым состоянием, в сочетании с тем фактом, что примитивы параллелизма Go для параллелизма с разделяемым состоянием значительно слабее, чем в Java. (Например, их блокировки не являются реентерабельными, а это означает, что вы не можете составить какое-либо поведение, использующее блокировки. Это означает, что вам часто приходится писать две версии одной и той же вещи, одну для вызова с удерживаемой блокировкой, а другую — без блокировки. будет вызываться с удерживаемой блокировкой.) Я думаю, что люди обнаружат, что модель параллелизма Go, мало чем отличающаяся от Reactive, будет переходной технологией, которая какое-то время выглядела привлекательной, но скоро появится что-то лучшее, и я думаю, что люди покинут его довольно быстро. (Конечно, это может быть просто проявлением моей предвзятости.)

Как выглядит ваша повседневная работа архитектора языка Java?

Это на самом деле по всей карте. В любой день я мог бы заняться чистым исследованием эволюции языка и того, как отдаленные функции будут связаны. Я мог бы прототипировать реализацию чего-то, чтобы увидеть, как движущиеся части подходят друг к другу. Я мог бы написать заявление о направлении для команды: «Вот где я думаю, что мы находимся в процессе решения этой проблемы, вот что, я думаю, мы выяснили, вот проблемы, которые остались». Я мог бы выступать на конференциях, разговаривать с пользователями, пытаясь понять, в чем их болевые точки, и в какой-то степени продавать сообщение о том, куда мы идем в будущем. Любой день может быть любой из этих вещей. Некоторые из этих вещей очень актуальны в данный момент, некоторые ориентированы на будущее, некоторые — на прошлое, некоторые обращены к сообществу, некоторые обращены внутрь себя. Каждый день разный!

Прямо сейчас один из проектов, в котором я участвую, над которым мы работаем уже несколько лет, — это обновление системы обобщенных типов для поддержки примитивов и примитивоподобных агрегатов. Это то, что касается языка, компилятора, стратегии перевода, формата файла класса и JVM. Чтобы можно было достоверно сказать, что у нас есть история, все эти части должны выстроиться в линию. Так что в любой день я мог бы работать на пересечении двух из этих вещей, чтобы увидеть, правильно ли выстраивается история или нет. Это процесс, который может занять годы!

Какой совет вы можете дать программистам-самоучкам, пытающимся улучшить свои навыки, студентам или опытным разработчикам, возвращающимся к материалам и просматривающим материалы, чтобы улучшить свои навыки в области компьютерных наук?

Один из самых ценных способов понять технологию — поместить ее в исторический контекст. Спросите: «Как эта технология соотносится с тем, что было до нее для решения той же проблемы?» Потому что большинство разработчиков не всегда могут выбрать, какую технологию они используют для решения проблемы. Итак, если бы вы были разработчиком в 2000 году и устроились на работу, вам бы сказали: «Мы используем эту базу данных, мы используем этот контейнер приложений, мы используем эту IDE, мы используем этот язык. Теперь приступайте к программе». Итак, все эти варианты уже были сделаны за вас, и вы можете быть ошеломлены сложностью того, как все они сочетаются друг с другом. Но каждая из тех частей, с которыми вы работаете, существует в историческом контексте, и это продукт чьей-то лучшей идеи для решения проблемы, которую мы вчера решили по-другому. Очень часто вы можете лучше понять, как работает данная технология, если поймете, что не работало в предыдущей итерации технологии, что заставило кого-то сказать: «Давайте не будем так делать. Давайте сделаем это так». Поскольку история вычислений настолько сжата, большая часть этого материала все еще доступна, и вы можете вернуться и прочитать то, о чем было написано в выпуске версии 1.0. Конструкторы расскажут, зачем они это придумали и какие проблемы их разочаровали, что они не могли решить с помощью вчерашней техники. Этот метод чрезвычайно полезен для понимания как того, для чего он нужен, так и ограничений, с которыми вы столкнетесь.

Подписаться на Брайана можно в Твиттере @BrianGoetz

Если вы хотите узнать больше о книге, вы можете ознакомиться с ней на нашей браузерной платформе liveBook здесь.