Сегодня Джошуа Блох — главный Java-архитектор в Google, до этого работал в Sun Microsystems, где был удостоен звания Заслуженный инженер и руководил созданием и реализацией Java Collections Framework, появившегося в Java 2, а также внес некоторые дополнения в язык для версии Java 5. Получил степень бакалавра в Колумбийском университете, PhD — в Университете Карнеги-Меллона, где работал над Camelot — распределенной системой обработки транзакций, позднее получившей название Encina; она была выпущена компанией Transarc, где Блох являлся старшим системным проектировщиком. Написал книгу «Effective Java», удостоенную Премии Джолта за 2001 год, и еще две в соавторстве — «Java Puzzlers» (Java: головоломки) и «Java Concurrency in Practice» (Java: параллельность на практике).

Как и следовало ожидать оттого, чья работа состоит в пропаганде использования Java в Google, Блох — активный сторонник этого языка. Несмотря на сегодняшний всплеск интереса к таким инструментам параллельной обработки данных, как Software Transactional Memory или механизм обмена сообщениями в языке Erlang, Блох считает, что в Java реализован «наилучший, сравнительно с другими языками», подход к параллельным процессам, и предсказывает возрождение интереса к Java по мере того, как программисты будут переходить на многоядерные процессоры.

Блох также защищает отношение к программированию как к проектированию API. Мы беседовали о том, как это помогает ему самому конструировать программы, о том, что Java становится слишком громоздким языком, и о том, что выбирать язык программирования — почти то же, что выбирать бар.

Сейбел: Как получилось, что вы занялись программированием?

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

Сейбел: Это какой же год?

Блох: Кажется, 1971. Но по-настоящему я загорелся этим лишь через несколько лет — благодаря системе разделения времени. На Лонг-Айленде был компьютер DECsystem-Ю, которым пользовались все школы округа Саффолк. Другой такой же предназначался для округа Нассау. Сколько известных теперь людей начинали на одной из тех двух машин!

Как только появляется интерактивность, человек сразу загорается. Я программировал на Бейсике, как и все в 1973-1976 годах. Вот тогда уже все было серьезно. Любопытно, что у меня от того времени сохранились программы на телетайпной бумаге Teletype — телетайпы все еще живы! — и глядя на них, я понимаю, что мой стиль был отчасти заложен уже тогда.

Сейбел: Можете ли вы вспомнить свою первую по-настоящему интересную программу?

Блох: 4 июля 1977 года я написал свой вариант известной игры 20Q (Двадцать вопросов) и назвал его «Животные». Там было бинарное дерево с вопросами типа «да/нет» на внутренних узлах и животными на листьях. Когда программа встречала незнакомое животное, она «заучивала» его название, задавая пользователю вопросы, предполагающие ответ «да» или «нет». Так она училась отличать новое животное от того, название которого определила неверно. Бинарное дерево хранилось на диске, так что программа со временем становилась «умнее».

Помнится, подумалось: «Черт, да она же учится!» Это было что-то вроде прозрения. Еще помню, как классе в десятом работал на той самой DECsystem-10. Нам не позволялось писать то, что сейчас называют мгновенными сообщениями, — слишком много системных ресурсов они отнимали.

Сейбел: Как и сейчас.

Блох: Давайте не будем об этом. Мгновенные сообщения — моя погибель. Хотя нет, моя погибель — электронная почта, сообщения — пустяки. Так или иначе, я был непослушным мальчиком, поэтому включился в проект для Лонг-Айлендской математической ярмарки, который я называл «программами межпрофессиональной коммуникации».

Сейбел: Вы писали эти программы?

Блох: Да, кроме одной, которую писал мой приятель Томас Де Беллис. Интересно, что программа Тома была написана целиком на Бейсике. Она была строчно-ориентированной и использовала для связи файлы. Она не была особенно быстрой или эффективной — но работала! Я написал две — одну строчно-ориентированную, другую с посимвольной записью — на MACRO-10, языке ассемблера для PDP-10. Для связи там использовалось подобие разделяемой памяти, названное «старшим сегментом» (high segment).

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

Сейбел: Вы сказали, что некоторые элементы вашего стиля проявились уже в первых программах. Какие?

Блох: Стремление сделать программу читаемой. Как говорит Кнут, программа — это прежде всего литературное произведение. Я уже тогда каким-то образом понял, что программа должна быть читаемой. И я сохранил этот подход.

Сейбел: А что изменилось?

Блох: Трудно сделать программу читаемой, когда можешь давать переменным имена длиной только в один символ. Так что сейчас я больше забочусь об именах переменных. Да и вообще, когда берешься за новый язык с новыми свойствами, многое меняется. То, что смутно понимал, укладывается в голове.

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

Сейбел: Если бы вам предложили вернуться в прошлое и начать все сначала, хотели бы вы что-нибудь всерьез изменить? У вас в голове ничего не повредилось от Бейсика? Или от чего-то еще?

Блох: На самом деле, это довольно занятная вещь. Дейкстра, царство ему небесное, по-моему, был здесь в корне неправ. Многие действительно классные программисты начинали с Бейсика, потому что им был доступен только он.

И все же, как я считаю, полезно пользоваться разными языками. В колледже я писал программы сразу на нескольких. На разных занятиях применялись разные языки. На занятиях по математике и естественным наукам — Фортран, на занятиях по программированию — Паскаль, SAIL, Симула или что-то в этом духе. А на занятиях по искусственному интеллекту мы работали на Лиспе.

Может быть, стоило освоить еще больше языков. Интересно, что всерьез заниматься объектно-ориентированным программированием (ООП) я стал довольно поздно. Java был первым объектно-ориентированным языком, на котором я работал по-настоящему, отчасти потому, что я так и не смог заставить себя работать на C++.

Сейбел: Когда это было?

Блох: Это началось в 1996 году, когда я пришел в Sun. Думаю, мне стоило познакомиться с этими концепциями чуть раньше. Однако я не считаю, что все они хороши. ООП — занятная штука. Это прежде всего две вещи. Первое — модульное построение, что очень удобно. Но оно было еще до ООП. Возьмите старую литературу, например работы Парнаса по скрытию данных, и вы увидите, что понятие о том или ином типе класса как об абстракции существовало до ООП. Второе — наследование свойств, но у меня, как и у многих сегодня, отношение к нему смешанное.

Кроме того, мне надо было попробовать себя в разных областях помимо компьютерных наук. Чем больше различных вещей вы узнаете в молодости, тем лучше для вас. Я очень мало занимался графическими интерфейсами пользователя — надо было заставить себя засесть за них. Мне отчего-то интереснее всего было заниматься библиотеками, готовить модули для других. Так что я десятилетиями занимался структурами данных и алгоритмами.

Сейбел: Есть ли книги, которые должен прочесть каждый программист?

Блох: Разумеется, «Design Patterns» (Шаблоны проектирования), хотя я отношусь к ней не совсем однозначно. Она дает общий для всех словарь. И в ней масса хороших идей. С другой стороны, мешанина стилей и языков; кроме того, этот труд частично уже устарел. Но прочесть его стоит, я твердо убежден.

Затем «Elements of Style» (Элементы стиля). Хотя она, собственно, не о программировании, прочесть ее нужно по двум причинам. Во-первых, потому, что разработчик ПО тратит значительную часть времени на написание прозы. Если вы неспособны создавать логичные, стройные, хорошо читаемые спецификации, вашими программами никто не сможет пользоваться. Поэтому все, что способствует улучшению стиля изложения, есть благо. И во-вторых, большинство высказанных там мыслей применимы и к программам.

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

Сейбел: Это же книга про перестановку битов?

Блох: Да. Люблю это дело, и оно напрямую связано с моей работой. Для тех, кто пишет библиотеки, компиляторы, криптомодули, создает низкоуровневую графику, она обязательна к прочтению. Уоррен собрал всю, так сказать, устную традицию и дал ей достойную строгую математическую обработку. Я был сам не свой, когда эта книга вышла.

Еще, конечно, «The Art of Computer Programming» Кнута. На самом деле я не читал ее полностью — даже близко к этому не подходил. Но когда я работаю над тем или иным алгоритмом, то смотрю, что сказал по этому поводу Кнут. И часто нахожу то, что мне нужно, — там есть все.

Но у меня нет ни способностей, ни времени, чтобы прочесть ее от и до, так что на этот счет врать не буду. Очень важна, по-моему, старая книга «The Elements of Programming Style». Все примеры даны на Фортране IV и PL/1, так что она несколько устарела. Но учитывая возраст этой книги, удивительно, что высказанные там идеи все еще в силе.

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

А сегодня непременно нужно читать литературу о параллельном программировании. Поэтому стоит взять в руки 4с Java Concurrency in Practice» (Java: параллельность на практике). Несмотря на заголовок, многое в ней применимо не только к Java.

Сейбел: Вы ее написали вместе с Брайаном Гетцем?

Блох: Мое имя стоит на обложке, но я так спокойно ее рекомендую, потому что на самом деле я тут ни при чем. В основном ее писали Брайан с Тимом Пайрлзом и все прочие — программисты, работающие над JSR-166, спецификацией Java, касающейся параллельных процессов. Правда, эти остальные упомянуты скорее из вежливости — мы поставляли материал, но ничего не писали.

Да, и еще 11-е издание словаря английского языка «Merriam-Webster». Без него никуда. Его не то чтобы нужно читать, но при написании программ вы должны давать переменным правильные имена. И, конечно, стиль должен быть хорошим. Без приличного словаря я просто теряюсь.

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

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

Отчасти смена подхода касается конкретных свойств разных языков. Каждый язык снабжен набором инструментов. Для каждой конкретной работы нужно брать правильный инструмент — но правильный инструмент в одном языке может оказаться неправильным в другом. Банальный пример: если вы пишете на Java 5, использование enum вместо int или булевых выражений сильно упростит вашу программу, сделает ее безопаснее и прочнее.

Сейбел: С учетом этого, что вы можете сказать о приобретении беглости в пользовании новым языком?

Блох: Думаю, это во многом как с обычными языками. Один подход состоит в том, чтобы знать много языков: если вы освоили итальянский и испанский, а теперь желаете заняться португальским, вам будет не слишком трудно. Чем больше вы знаете, тем больше опираетесь на ваши знания.

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

Представьте, что из всех инструментов вы владеете только молотком, и вдруг кто-то дает вам отвертку. И вы говорите: «Ну, молоток так себе, но попробую взять за жало и забивать гвозди ручкой». И вот у вас никудышний молоток, который на самом деле отличная отвертка. То есть нужна непредубежденность и готовность пользоваться всеми своими знаниями. И, конечно, писать код! И еще раз: писать код! Чем больше вы применяете язык на практике, тем скорее его выучите.

Сейбел: Откуда этот фанатизм у приверженцев того или иного компьютерного языка?

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

Сейбел: Java поражает меня в том смысле, что Java-сообществ целых два. Есть реализаторы и системные программисты, которые работают в Javasoft, Weblogic и тому подобных местах. И есть те, кто использует Java, серверы приложений, готовые фреймворки для бизнес-приложений. Это два очень разных бара.

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

Это как закон Меткалфа: полезность сети пропорциональна квадрату численности пользователей этой сети. То же и с языками: люди пользовались каким-то языком, и вдруг появляются Eclipse, FindBugs, Guice. Даже если Java не идеальный язык для вас, пользуясь им, вы имеете такие вот попутные выгоды. Можно создать собственное сообщество по численному программированию на Java, по какому угодно виду программирования.

Сейбел: Программирование приносит вам такое же удовольствие, как в школьные годы?

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

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

Сейбел: А случалось так, что по мене накопления опыта вы понимали: просто заставить программу работать недостаточно, есть и другие соображения? Вас это не обескураживало?

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

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

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

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

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

Блох: Полагаю, так думают те, кто этого всего не изучал. Но знание математики, само собой, помогает в работе. Я работал с парнем по имени Майк Макклоски. Он мыслил математически, не зная теорию чисел. Он переписывал Biglnteger. Раньше она была надстройкой над Си-библиотекой, но Майк переписал ее на Java так, что потери в скорости не было. Недавно он завершил свою работу, в процессе освоив теорию чисел. Без математического склада ума он этого не сделал бы, но выучи он эту теорию до того, ему не пришлось бы осваивать ее в ходе работы.

Сейбел: Но он решал сугубо математическую задачу.

Блох: Да, пожалуй, не слишком удачный пример. Но даже при решении задач, прямо не имеющих отношения к математике, математическое мышление все равно нужно программисту. Например, доказательство по индукции так тесно связано с рекурсивным программированием, что одно без другого не понять. Вы можете не знать такие термины, как «база индукции» и «индукционный переход», но должны понимать их суть для написания хороших рекурсивных программ. Так что даже если задача программиста не связана с математикой, без знания математических понятий ему придется туго.

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

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

К счастью, нам не нужно выбирать. Можно освоить и то и другое. Даже если вы не собираетесь использовать матанализ так активно, как дискретную математику, все равно знать их нужно. Но дискретный подход все равно полезнее непрерывного.

Сейбел: Вы говорили о том, что у программирования много общего с написанием прозы. Обычно с компьютерами и программированием всегда была тесно связана именно математика. Но если говорить о веб-фреймворках или веб-приложениях на их основе, требуют ли они скорее писательских навыков?

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

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

Блох: Несколько лет назад на конференции по объектно-ориентированному программированию я делал доклад «Как создать качественный API и почему это важно». Несколько его вариантов есть в Сети. Там я подробно объясняю свой подход.

Главное — понимать, что именно вы строите, какую проблему решаете. Важность анализа требований трудно переоценить. Некоторые думают, что это просто — идешь к клиенту, тот говорит, что ему нужно, и готово.

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

На этом этапе самое важное — иметь обширный набор сценариев использования. А от них уже можно отталкиваться в своих поисках решения. Нужно тщательно, не жалея времени, обдумать его, потому что если решение неправильное, все ваши дальнейшие усилия пойдут прахом.

Хуже всего — а я сталкивался с такими случаями — это когда вы сажаете в офисе команду смышленых парней, которые через полгода выдают вам 247-страничную спецификацию, толком не понимая, что они разрабатывают. Через полгода это будет программа с подробной спецификацией — программа, которая может оказаться бесполезной. Часто можно услышать: «Мы столько потратили на эту спецификацию, что должны ее реализовать». В итоге получается бесполезная система, которая никому не нужна. Вот что ужасно. Если нет сценариев использования, вы создаете нечто, а потом пытаетесь написать что-то очень простое и тут говорите себе: «Черт, ведь чтобы распечатать XML-документ, нужно много страниц стандартного кода». Это ужасно.

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

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

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

Сейбел: Какие здесь особенности у Java-коллекций, представляющих собой особый вид автономных API?

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

Хорошие программисты стараются делать вещи, работающие автономно, по нескольким причинам. Первая состоит в том, что вы, возможно неосознанно, создаете модули, пригодные для повторного использования. Если строить монолитную систему, а затем, когда она разрастется, разбивать ее на части, то у вас не будет четких границ, и вы получите кучу мусора, который невозможно поддерживать. Поэтому то, о чем я говорю, есть просто самый разумный подход к программированию, и неважно, ощущаете вы себя проектировщиком API или нет.

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

Сейбел: Итак, вы стоите за модули, которые сцепляются между собой не слишком тесно. Сегодня на этот счет есть две точки зрения. Сторонники первой призывают, как и вы, проектировать межмодульные API в самом начале работы. Защитники другой заявляют: «Делайте простейшую вещь из всех возможных» и «Беспощадный рефакторинг!».

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

Сейбел: Значит, вы пишете клиентский код, использующий API, потом смотрите на него и спрашиваете себя: «Это в самом деле тот код, который мне нужен?»

Блох: Именно так. Иногда даже не доходит до того, чтобы взглянуть на клиентский код. При попытке начать его писать происходит одно из двух: или ты не можешь его написать, так как чего-то не хватает в API, или можешь его написать, но понимаешь, что ошибся в подходе.

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

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

Сырые программы никому не нужны. «Делайте простейшую вещь из всех возможных» и «Беспощадный рефакторинг!» — эти два призыва совсем не означают, что надо писать сырой код, отказавшись от предварительного проектирования. Я беседовал об этом с Мартином Фаулером. Он твердо стоит за продумывание целей и задач, чтобы придать программе разумные размеры и структуру. «Не пишите 247-страничную спецификацию до того, как начнете писать код», — говорит он, и я согласен.

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

Итак, между двумя лагерями есть разногласия. Но, по-моему, непреодолимой пропасти, как считает кое-кто, нет.

Сейбел: Раз уж вы упомянули Фаулера, написавшего несколько книг по UML, скажите: вы используете UML в качестве средства проектирования?

Блох: Нет. Думаю, это здорово — создавать понятные для других графические схемы. Но, если честно, не могу припомнить, какие там компоненты круглые, а какие квадратные.

Сейбел: Вы занимались всерьез литературным программированием в духе Кнута?

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

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

Сейбел: Одно из отличий программы от литературного произведения — если не говорить об экспериментальной литературе — состоит в том, что не существует одного-единственного порядка чтения программы. Как вы читаете большие чужие программы?

Блох: Хороший вопрос. На самом деле я люблю хорошо написанные программы. Я знаю людей, способных взять большую неважно написанную программу и зарыться в код, пока не станет вырисовываться общая картина. Завидую такой способности — у меня ее никогда не было.

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

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

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

Сейбел: Вы применяли пошаговое исполнение кода, чтобы его понять?

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

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

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

Блох: Нет. Вы, вероятно, знаете, что утверждения (assertions) — первый элемент, помещенный мною в Java, и я сознаю, что они так и не стали частью Java-культуры. Лишь немногие Java-программисты пользуются ими — даже не знаю, почему. Кстати, о математике: инварианты являются в высшей степени математической идеей.

Сейбел: Но для их понимания не нужно знать математику на «отлично».

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

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

Сейбел: Чувствую, вы готовы сказать, что заняться программированием — лучший способ воспитать у себя этот стиль мышления. Вы бы преподавали программирование как науку об инвариантах...

Блох: В какой-то мере я согласен, но так можно зайти слишком далеко. Вернемся к Дейкстре. Уверен, вы читали его книгу «On the Cruelty of Really Teaching Computing Science» (О жестокости реального преподавания компьютерных наук), и полагаю, что в ней он абсолютно неправ. Деикстра говорит, что студентов нужно подпускать к компьютеру лишь после того, как те в течение семестра научатся обращаться с символами и понимать их подлинный смысл. Но это же бред! Ведь это удовольствие — приказать компьютеру сделать что-то и наблюдать, как он это делает. Я не в силах лишить студентов такого удовольствия. Да и не в состоянии — ведь компьютеры повсюду. Десятилетние дети пишут программы.

Сейбел: Как человек, пропагандирующий Java в Google, не находите ли вы, что этот язык мог бы использоваться более широко? Оставим в стороне поступь истории и уже сделанный людьми выбор и представим, что у вас есть волшебная палочка. Если бы могли заменить весь C++ на Java, это бы сработало?

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

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

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

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

Компании очень рано выстраивают свою ДНК. Это может принести им громадный успех, но потом очень трудно отказаться от той архитектуры, когда она перестает отвечать потребностям. Помню, когда я был начинающим специалистом в исследовательском центре IBM, в Йорктаун-Хайтсе. Это было году в 1982-м, и там вовсю применялась пакетная обработка данных. Даже применяя разделение времени, они мыслили в терминах виртуальных считывателей карт и виртуальных перфораторов. Везде записи в 80 колонок! В DEC так и не смогли мыслить иначе как в терминах разделения времени. Ну, а что касается Microsoft, то еще вопрос, смогут ли они держать в уме что-то иное, помимо настольного персонального компьютера.

Сейбел: А через двадцать лет скажут, что компания Google навсегда ушиблена он л айн-рек ламой.

Блох: Конечно. В Google так или иначе господствует стереотип, что Java — язык медленный и ненадежный. И понятно, почему: версия Blackdown Java, созданная для Linux около 1999 года, была медленной и ненадежной. Старые предрассудки очень живучи. Но по правде говоря, Google использует Java в очень важных с деловой точки зрения случаях, к примеру для рекламы.

Так что в определенной степени там не считают этот язык медленным и ненадежным. Но основной поиск в Google, занимающий наибольшее число машинных циклов, основан на C++, и ясно, что это имеет историческое обоснование. Это будет продолжаться еще какое-то время.

Сейбел: Какими инструментами вы сегодня пользуетесь для программирования?

Блох: Ждал этого вопроса. Да, я старпер, и гордиться тут нечем. Команды Emacs навсегда врезались в мой мозг. Я стараюсь писать небольшие программы, библиотеки и так далее. Как видите, я в основном обхожусь без современных инструментов, хотя и знаю, что с ними работа идет быстрее.

Для больших программ я использую IntelliJ, так как вся моя группа работает с ней, но у меня выходит неважно. Да, она производит впечатление — мне нравится то, как эти утилиты делают за вас статический анализ. Кое-кого из поклонников таких инструментов, как IntelliJ, Eclipse, NetBeans и FindBugs, я привлекал для выверки текста книги «Java Puzzlers». Многие ляпы были найдены автоматически с помощью этих программ. Просто здорово.

Сейбел: Стали бы вы работать продуктивнее, если бы потратили месяц для подробного изучения IntelliJ?

Блох: Да. Современные интегрированные среды разработки отлично справляются с масштабным рефакторингом. Брайан Гетц заметил, что сегодня программисты пишут более качественный код, потому что раньше не могли делать такой рефакторинг, как сейчас. Они полагаются на эти инструменты, чтобы вносить сквозные изменения, не затрагивающие работу кода.

Сейбел: Как насчет прочих инструментов?

Блох: С утилитами для программирования у меня не очень хорошо — а жаль. Инструменты сборки и системы управления версиями изменяются сильнее, чем хотелось бы, и мне трудно следить за ними. Поэтому, сталкиваясь с новой средой, я всегда пристаю к коллегам, более привычным к таким инструментам. Я вечно спрашиваю: «А как сегодня это делается?» Коллеги закатывают глаза и помогают мне, а я пользуюсь средой, пока она окончательно не откажет.

Гордиться тут нечем. Разработчики программ могут быть искусны в одном и малоискусны в другом. Кое-кто утверждает, что это не так, что разработчики взаимозаменяемы, что каждый может и должен уметь все. Но на практике это не так. И если заставлять каждого разработчика делать все, результат будет никудышным.

Я говорю прежде всего о тех, кто, по выражению Кевина Бурильона, «лишен гена эмпатии». Нельзя создавать хорошие API или языки, не представив себя в шкуре рядового программиста, который пользуется ими. Однако есть люди, создающие хорошие API и языки. И есть знатоки технической стороны проектирования языка, которые говорят: «Это сделает все несовместимым с LALR(l), надо сделать по-другому». Это крайне полезное знание. Но оно не заменяет гена эмпатии — такой знаток может создать кошмарный язык, не пригодный для использования.

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

Сейбел: Поговорим об отладке. Можете ли вы назвать худшую ошибку из тех, что вам встречались?

Блох: Мне сразу приходит в голову один кошмарный и в то же время любопытный случай. Это было в начале 1990-х, я тогда работал в питт-сбургской компании Transarc. Мне пришлось заниматься реализацией транзакционной разделяемой памяти при очень плотном графике. Проектирование и реализацию я закончил в срок и даже успел написать несколько библиотечных компонентов. Но я нервничал из-за того, что произвел много нового кода в спешке.

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

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

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

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

В своей превосходной статье «Engineering a Sort Function» (Разработка функции Sort) Бентли и Макилрой цитируют чудесное высказывание Кнута насчет приведения себя в самое поганое настроение, на которое только вы способны. Как раз это я и сделал для той серии тестов. Но это сделало ошибку крайне трудно обнаружимой. Прежде всего, из-за многопоточности каждый случай оказывался почти невоспроизводимым. Далее, оказались ложными мои представления не о чем-нибудь, а о ядре системы. Обычно начинающие программисты легко приходят к выводу, что язык или система не в порядке. Но тут базовая конструкция, на которую я опирался, — мьютекс — действительно оказалась сломанной.

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

Блох: Мне кажется, хорошая автоматическая программа проверки мьютексов спасла бы меня от мучений, но не забудем, что это было в начале 1990-х. Мне и в голову не приходило винить разработчика за то, что он не создал достаточно хороших модульных тестов. Даже сегодня писать модульные тесты для многопоточных программ — подлинное искусство.

Сейбел: Мы говорили о пошаговом прохождении кода. А какими средствами отладки вы пользуетесь сейчас?

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

Отладчики — хорошее средство, и порой мне хочется пользоваться оператором print, но вместо этого я прибегаю к точке останова. Время от времени я применяю отладчики, но и без них чувствую себя вполне уверенно. Имея возможность использовать операторы print и внимательно читать код, я вполне могу находить ошибки.

Я уже говорил, что пользуюсь операторами утверждения для проверки сохранности сложных инвариантов. Если инварианты ломаются, я хочу знать, когда это случилось, какие действия привели к этому.

Кстати, я вспомнил еще одну труднонаходимую ошибку. Правда, не могу сказать точно, было это в Transarc или на последнем курсе Университета Карнеги-Меллона, когда я работал над системой распределенных транзакций Camelot. He я нашел эту ошибку, но сам случай меня глубоко поразил.

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

Но выяснилось, что ошибка не в трассировочном пакете — все было гораздо серьезнее. Чтобы найти идентификатор потока, трассировочный пакет вызывал код из потоковой библиотеки. А тот делал штуку, очень в то время распространенную: смотрел старшие биты адреса стековой переменной. То есть он брал значение указателя стековой переменной и сдвигал его вправо на фиксированное число позиций, получая таким образом идентификатор потока. Дело в том, что у каждого потока был стек определенного размера, который выражался заранее известной степенью двойки.

Выглядит логично, так? Но, к сожалению, те, кто создавал объекты в стеке, делали их слишком большими по тогдашним меркам. Массив из 100 элементов, по 4 Кбайт каждый, — всего 400 Кбайт в стеке одного потока. Получался перескок через красную зону стека в стек соседнего потока. И мы получали неверный идентификатор потока. Хуже того: когда поток обращался к локальным для потока переменным, он считывал переменные другого потока, поскольку его идентификатор использовался как ключ для доступа к этим переменным.

Итак, то, что мы приняли за безобидный недочет трассировочного пакета, оказалось признаком действительно серьезной ошибки. Событие приписывалось потоку 43 вместо потока 42, так как один поток невольно подменял собой другой, и это могло иметь катастрофические последствия.

Вот почему нам нужны языки с хорошими параметрами безопасности. Лучше обойтись без таких случаев. Недавно у меня был разговор в одном университете: там хотели обучать программистов сначала языкам Си и C++, а потом Java, так как они хотели, чтобы программисты овладели системой «на всю глубину». Меня спросили, что я думаю об этом.

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

И я считаю, что все это важно изучать. Но это не значит, что надо начинать с такого низкоуровневого языка, как Си! Зачем студентам, только-только приступающим к программированию, сталкиваться с переполнением буфера, ручным выделением памяти и тому подобным?

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

Сейбел: Значит ли это, что язык Java уже немного устарел и что он быстро усложняется, но при этом совершенствуется куда медленнее?

Блох: Очень непростой вопрос. Например, Java 5 вышел намного более сложным, чем мы хотели. Я даже не представлял, насколько обобщенные типы и особенно символы подстановки усложнят язык. Надо отдать должное Грэму Гамильтону — он понял все это в свое время, а я нет.

Интересно, что он годами боролся за невключение обобщенных типов в язык. Но понятие вариативности — которая и лежит в основе символов подстановки — вошло в моду в то время, когда мы старались не снабжать Java обобщенными типами. Если бы они появились раньше и без всякой вариативности, мы бы теперь имели более простой и легкий в работе язык.

При всем том от символов подстановки есть реальная польза. Есть глубокая несовместимость между методом выделения подтипов и обобщенными типами, и символы подстановки позволяют во многом ее нивелировать. Но это достигается ценой переусложнения. Некоторые считают наилучшим решением вариативность на стороне объявления, в противоположность таковой на стороне использования, но я не сильно уверен.

Нельзя твердо судить о чем-то, если это не было использовано многими программистами в реальной рабочей обстановке. Есть языки, хорошо работающие в своей узкой области, и некоторые говорят о них: «Отличный язык, жаль, что им пользуется так мало народа». Иногда, однако, для этого есть веские причины. Надеюсь, какой-нибудь язык, где используется вариативность при объявлении, к примеру Scala или С# 4.0, ответит на этот вопрос раз и навсегда.

Сейбел: Что же дало импульс к появлению обобщенных типов?

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

Обобщенные типы, как и многое из того, что мы добавили в Java 5, казались мне средством автоматизации того, что раньше делалось вручную: пусть этим займется язык! Кое-где я попал в точку: цикл f or-each — отличная штука. Он скрывает от вас сложное устройство итератора или индексных переменных. Код становится короче, но площадь концептуальной поверхности при этом не увеличивается. Даже скорее уменьшается: мы ввели ложный полиморфизм массивов и других коллекций, и можно выполнять итерацию над ArrayList или над массивом, совершенно не интересуясь, над чем именно она выполняется.

Но главная причина того, почему эта идея не сработала для обобщенных типов, — они стали крупным прибавлением к системе типизации, и без того сложной. С системами типизации нужно обращаться осторожно, поскольку это может повлечь далеко идущие и непредсказуемые последствия для языка.

А урок таков: если вы совершенствуете зрелый язык, нужно больше чем когда-либо задумываться над балансом возможностей и сложности. Сложность во многих разделах языка растет квадратично: прибавив всего одно свойство, вы получаете куда более сложную структуру. Если язык близок к тому, чтобы превысить уровень понимания программистов, усложнять дальше просто нельзя — все пойдет прахом.

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

Сейбел: Не кажется ли вам, что Java без обобщенных типов был бы сегодня лучше?

Блох: Не знаю. Обобщенные типы по-прежнему мне нравятся — они находят за меня ошибки в моем коде. Эти средства помогают найти мне вещи, которые обычно включаются в комментарии, и перенести их в код, где компилятор может обеспечить их корректность. С другой стороны, когда я вижу сообщения об ошибке, связанные с параметризованными типами, а потом нахожу сделанные для этих типов обобщенные объявления, вроде моего Enum — class Enum>», то понимаю, что обобщенные типы не были достаточно хорошо проработаны, чтобы их включить.

Программист должен или быть оптимистом, или застрелиться. И мы говорим: «Конечно, мы это умеем. Мы знаем все об обобщенных типах еще с тех пор, как познакомились с языком CLU. Это технология 25-летней давности. То же самое сегодня можно слышать про замыкания, правда, о них говорят, что им уже 50 лет. «Это легко и не усложняет язык».

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

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

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

Блох: Ну, что касается разработчиков, ответ, увы, отрицательный. Пожалуй, виноват здесь я — эта штука казалась мне красивой, и я думал, что стою на правильном пути.

Но при разработке программ мы часто чуем какие-то вещи нутром. Кто-нибудь просил меня о fоreach? Опять же нет. Но я знал, что стою на правильном пути, и это оказалось так — многие пользуются этим. Но большой грех для разработчика — создавать программы, которые просто отлично смотрятся, хорошо сделаны и так далее. Если вы не решаете реальные проблемы реальных пользователей — в нашем случае Java-программистов, — то не надо ничего добавлять.

Есть чудесное выступление Гослинга «The Feel of Java» (Почувствовать Java); в нем он говорит, что нужно трижды ощутить необходимость чего-то, прежде чем внедрять это. Нельзя добавлять программу только из-за ее красоты.

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

Сейбел: Я читал книги «Java Puzzlers» и «Java Concurrency in Practice». Меня удивило, что в языке, который изначально был очень простым, столько секретов.

Блох: Секреты есть, но это неизбежно, они есть во всех языках. Можно было бы написать книгу «Си: головоломки».

Сейбел: Ну, этот язык — сплошная трудность.

Блох: Да, тут понадобилась бы целая книжная полка. В Java такие случаи особенно нужно отмечать, ведь его считают простым языком. В каждом языке свои проблемы, в Java их не так много, и они по большей части довольно забавны и интересны.

Сейбел: Говоря о программировании, есть ли что-то, чему научила вас работа над Java и обдумывание его структуры?

Блох: Очень многому. Об одном я упоминал в своем посте «Nearly All Binary Searches and Mergesorts Are Broken» (Почти все двоичные поиски и сортировки слиянием сломаны): невероятно трудно правильно написать даже небольшую программу. Мы обманываем сами себя, считая, что наши программы более-менее свободны от ошибок. Это не так. Большей частью наши программы не содержат ошибок лишь настолько, чтобы справляться с возложенной на них задачей.

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

Я укрепился в своем мнении насчет того, что нужна качественная документация API. Javadoc во многом способствовал успеху платформы, хотя не все это замечают. Качественная документация API всегда была частью Java-культуры, как я считаю, потому что Javadoc присутствовал с самого начала.

Я также утвердился в своем мнении, что чем проще — тем лучше. Я наблюдаю все больше сложных добавлений, которые оказываются только вредными в долгосрочном плане, а иногда и в краткосрочном. Создавая программу, я включаю свой собственный «измеритель сложности»: если стрелка уходит в красную зону, пора переписывать.

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

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

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

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

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

Что будет дальше, я не знаю. Но если действительно потребуется, я все еще могу сменить язык. Я хочу быть открытым для разных возможностей, возиться с другими языками. У меня сейчас для этого нет времени, но я хотел бы его иметь.

Сейбел: Назовите языки, с которыми вам хочется повозиться больше всего.

Блох: Например, Scala, хотя у меня есть сомнения насчет его будущей популярности. Я очень уважаю Мартина Одерски — он реализовал в своем языке немало красивых идей. Но, возможно, этот язык сложноват и слишком академичен, чтобы иметь широкий успех. Честно говоря, я еще не изучил его толком, так что могу быть неправ.

Затем Python. Из старых — Scheme. Будет неплохо несколько месяцев поизучать «Structure and Interpretation of Computer Programs» вместе с сыном. Говорят, это отличная книга. В качестве первого шага я купил ее. Но для освоения нужно время.

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

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

Сейбел: Что это за конкуренты?

Блох: Как я считаю, C++ и С#.

Сейбел: А как насчет Erlang или транзакционной памяти?

Блох: Насколько я знаю, транзакционной памяти в рабочем виде пока еще нет ни в одном из крупных языков. Если окажется, что это того стоит, то, наверное, она появится и в Java не позже, чем в прочих языках.

У Erlang свой подход к параллелизму — акторы: если они окажутся успешной находкой, то могут быть реализованы во многих языках. Как вы знаете, Одерски с компанией уже реализовали их в Scala. Пока я не уверен, что акторы — лучшее из придуманного для многоядерного параллелизма, но если все же это так, то и в Java вы скоро увидите их.

Сейбел: Итак, Java, по вашим словам, имеет блоки, позволяющие получить портируемый доступ к параллельным потокам, предоставляемым операционной системой, а также конструкции более высокого уровня в рамках API Java.util.concurrent. Но все равно, это ведь средства довольно низкого уровня в сравнении с Erlang или транзакционной памятью?

Блох: Не уверен. Некоторые конструктивные блоки в Java действительно низкоуровневые, например Atomiclnteger; есть среднеуровневые, например CyclicBarrier, и наконец высокоуровневые — ConcurrentHash-Мар и ThreadPoolExecutor. Уверен, что транзакционная память и акторы найдут свое место в наших конструктивных блоках для многопоточных задач, как только народ убедится, что эти новинки работают хорошо. Если, конечно, они будут работать хорошо.

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

Тому есть несколько причин. Первая из них состоит в том, что если вы пытаетесь делать автоматическую блокировку или оптимистичное управление параллелизмом, основываясь только на чтении и записи на уровне байтов, то между потоками происходит «мнимый конфликт»: физические конфликты не соответствуют логическим. Если вам нужны блокировки, то убедитесь, что захвачены лишь те, которые помогают решить логические конфликты.

Так, например, если у вас есть два потока и оба прибавляют значения для счетчика, они должны выполняться параллельно. Они могут обращаться к одному и тому же участку памяти, но при этом не конфликтуют в логическом плане. Если один поток считывает значение счетчика, а другой увеличивает его, то есть конфликт. Но ведь у вас может быть множество потоков, которые считывают значения, и других, увеличивающих их. Я пока не видел систем, которые могли бы справляться с такими вещами самостоятельно. Может быть, мой пример несколько искусственный, но часто физические ограничения намного суровее логических.

Вторая проблема с транзакционной памятью в том, что внутри нее осуществляются не все операции, например операция ввода/вывода. А вот третья проблема: некоторые виды транзакционной памяти позволяют «обреченным транзакциям» видеть память в неустойчивых состояниях — потенциально это крайне опасно. С этими проблемами мы уже сражались, когда строили транзакционные системы общего назначения. Решения есть, но все они усложняют систему или снижают скорость работы.

Так или иначе, насколько мне известно, транзакционная память еще не вышла из стадии исследований. Прекрасно, что этим занимаются. Но по-моему, она не решит разом все проблемы параллельности — по крайней мере, в обозримом будущем.

Сейбел: Сменим тему. Каков ваш стиль работы в команде?

Блох: Я довольно уживчив, предпочитаю «приятельское программирование» — когда работаешь вместе с кем-то, но не за одной клавиатурой. Вы пишете разные части программы, обмениваетесь кодом. Можно вообще пребывать в разных полушариях. Мы с Дугом Ли таким образом плотно работали несколько лет. Один писал интерфейс, другой говорил: «Все отлично, но я поправил там кое-что, вот погляди».

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

Мне нравится сидеть с кем-нибудь за одним терминалом и работать над кодом, но таким образом я сделал не много программ с нуля. Обычно это случается, когда я делаю ревизию кода; если вижу, что код надо сильно править, то предлагаю человеку посидеть вместе. Это полезно во многих отношениях, например как средство обучения, способ передать знания старшего поколения младшему.

А работать совсем в одиночку мне не нравится. Когда я пишу программу и обдумываю какое-нибудь сложное проектное решение, мне нужно с кем-то советоваться. Везде, где я работал, рядом был кто-нибудь, с кем я мог поделиться. Для меня это очень важно — обратная связь.

Сейбел: Так что же важнее — обратная связь или просто шанс проговорить задачу вместе?

Блох: И то и другое. Мы делаем очень хитрые вещи — часто есть не одно правильное решение или одно, но которого никто до тебя не нашел. Надо полагаться на свой инстинкт, но иногда полезно выслушать того, кто смотрит на вещи по-другому.

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

Сейбел: Еще один вечный вопрос — кажется, его еще в 1970-е поднимал Вайнберг в «The Psychology of Computer Programming», — дискутируется и сейчас: должен ли кодом владеть один человек, и только он и должен с ним работать, — или им должны владеть все, кто работал над проектом, и всем должно быть разрешено вмешиваться в него?

Блох: По-моему, владение кодом отрицать нельзя. Это похоже на рождение ребенка: вы даете жизнь коду, и он ваш, особенно если он большой, или сложный, или оригинальный. Прежде чем начать работать с чужим кодом, спросите разрешения, особенно если считаете, что нашли ошибки, — ведь вы можете быть неправы. Портить чужой код некрасиво.

Конечно, для компании плохо, если кодом владеет один человек. А если он покинет компанию? Поэтому важно, чтобы сразу несколько программистов знали каждый фрагмент кода и могли с ним работать. Но мечтать о том, чтобы все владели всем кодом, по-моему, не стоит.

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

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

Блох: Согласен. Те, кто способен делать сложные вещи и лишен нужной эмпатии в отношении других, часто становятся жертвой такого подхода. «Я понимаю это и могу этим пользоваться, значит, это годится», — вот их логика.

Сейбел: А есть ли в программировании нечто, привлекающее людей именно с таким внутренним складом?

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

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

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

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

Блох: Полностью согласен. Да, в относительных цифрах процент креативных программистов уменьшается. Когда-то вы покупали машину, для которой не было даже операционной системы, не говоря о языке программирования или готовых приложениях. Каждому приходилось что-то выдумывать.

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

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

Тут есть одна проблема: часто такие люди умнее всех прочих в компании и поэтому считают, что это они должны принимать все решения. Но только то, что они самые умные, не означает, что им можно доверять принятие решений. Ум — качество не скалярное, а векторное. Без эмпа-тии или чувственного разума вы не создадите ни API, ни графический интерфейс, ни новый язык.

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