Шрифт:
Интервал:
Закладка:
Сейбел: Итак, ошибка содержалась не в вашем коде, но вы тем временем написали столь подробные тесты для кода, что ошибку волей-неволей пришлось искать вне его. Как по-вашему, мог ли — или должен ли был — автор мьютексов написать тесты для нахождения этой ошибки, которые избавили бы вас от полутора недель отладки?
Блох: Мне кажется, хорошая автоматическая программа проверки мьютексов спасла бы меня от мучений, но не забудем, что это было в начале 1990-х. Мне и в голову не приходило винить разработчика за то, что он не создал достаточно хороших модульных тестов. Даже сегодня писать модульные тесты для многопоточных программ — подлинное искусство.
Сейбел: Мы говорили о пошаговом прохождении кода. А какими средствами отладки вы пользуетесь сейчас?
Блох: Наверное, я кажусь неандертальцем, но важнейшие инструменты для меня, как и раньше, — мои глаза и мозг. Я распечатываю все необходимые фрагменты кода и очень внимательно их изучаю.
Отладчики — хорошее средство, и порой мне хочется пользоваться оператором print, но вместо этого я прибегаю к точке останова. Время от времени я применяю отладчики, но и без них чувствую себя вполне уверенно. Имея возможность использовать операторы print и внимательно читать код, я вполне могу находить ошибки.
Я уже говорил, что пользуюсь операторами утверждения для проверки сохранности сложных инвариантов. Если инварианты ломаются, я хочу знать, когда это случилось, какие действия привели к этому.
Кстати, я вспомнил еще одну труднонаходимую ошибку. Правда, не могу сказать точно, было это в Transarc или на последнем курсе Университета Карнеги-Меллона, когда я работал над системой распределенных транзакций Camelot. He я нашел эту ошибку, но сам случай меня глубоко поразил.
У нас был трассировочный пакет, позволявший коду выводить отладочную информацию. Каждое отслеженное событие снабжалось меткой с указанием идентификатора потока, где оно произошло. Иногда идентификаторы оказывались неверными, и мы не понимали, почему. Наконец, мы решили, что с этой ошибкой можно еще пожить сколько-то времени, — она казалась безобидной.
Но выяснилось, что ошибка не в трассировочном пакете — все было гораздо серьезнее. Чтобы найти идентификатор потока, трассировочный пакет вызывал код из потоковой библиотеки. А тот делал штуку, очень в то время распространенную: смотрел старшие биты адреса стековой переменной. То есть он брал значение указателя стековой переменной и сдвигал его вправо на фиксированное число позиций, получая таким образом идентификатор потока. Дело в том, что у каждого потока был стек определенного размера, который выражался заранее известной степенью двойки.
Выглядит логично, так? Но, к сожалению, те, кто создавал объекты в стеке, делали их слишком большими по тогдашним меркам. Массив из 100 элементов, по 4 Кбайт каждый, — всего 400 Кбайт в стеке одного потока. Получался перескок через красную зону стека в стек соседнего потока. И мы получали неверный идентификатор потока. Хуже того: когда поток обращался к локальным для потока переменным, он считывал переменные другого потока, поскольку его идентификатор использовался как ключ для доступа к этим переменным.
Итак, то, что мы приняли за безобидный недочет трассировочного пакета, оказалось признаком действительно серьезной ошибки. Событие приписывалось потоку 43 вместо потока 42, так как один поток невольно подменял собой другой, и это могло иметь катастрофические последствия.
Вот почему нам нужны языки с хорошими параметрами безопасности. Лучше обойтись без таких случаев. Недавно у меня был разговор в одном университете: там хотели обучать программистов сначала языкам Си и C++, а потом Java, так как они хотели, чтобы программисты овладели системой «на всю глубину». Меня спросили, что я думаю об этом.
Думаю, посыл здесь правильный, но выводы ошибочные. Да, студентам нужно изучать низкоуровневые языки, и даже язык ассемблера, и даже устройство чипов. Правда, чипы сейчас превратились в невероятно сложных чудовищ и теряют в производительности именно из-за своей сложности. Но знание того, что происходит на низших уровнях системы, сильно облегчает высокоуровневое программирование.
И я считаю, что все это важно изучать. Но это не значит, что надо начинать с такого низкоуровневого языка, как Си! Зачем студентам, только-только приступающим к программированию, сталкиваться с переполнением буфера, ручным выделением памяти и тому подобным?
Мы с Джеймсом Гослингом однажды обсуждали появление Java, и он сказал: «Время от времени нужно нажимать кнопку перезагрузки. Это едва ли не самое прекрасное, что может случиться». Обычно вам приходится поддерживать совместимость со старыми программами, но иногда — нет, и это здорово. Но к сожалению, как это случилось с Java, проходит десятилетие — и ваша система сама становится проблемой для других.
Сейбел: Значит ли это, что язык Java уже немного устарел и что он быстро усложняется, но при этом совершенствуется куда медленнее?
Блох: Очень непростой вопрос. Например, Java 5 вышел намного более сложным, чем мы хотели. Я даже не представлял, насколько обобщенные типы и особенно символы подстановки[56] усложнят язык. Надо отдать должное Грэму Гамильтону — он понял все это в свое время, а я нет.
Интересно, что он годами боролся за невключение обобщенных типов в язык. Но понятие вариативности — которая и лежит в основе символов подстановки — вошло в моду в то время, когда мы старались не снабжать Java обобщенными типами. Если бы они появились раньше и без всякой вариативности, мы бы теперь имели более простой и легкий в работе язык.
При всем том от символов подстановки есть реальная польза. Есть глубокая несовместимость между методом выделения подтипов и обобщенными типами, и символы подстановки позволяют во многом ее нивелировать. Но это достигается ценой переусложнения. Некоторые считают наилучшим решением вариативность на стороне объявления, в противоположность таковой на стороне использования, но я не сильно уверен.
Нельзя твердо судить о чем-то, если это не было использовано многими программистами в реальной рабочей обстановке. Есть языки, хорошо работающие в своей узкой области, и некоторые говорят о них: «Отличный язык, жаль, что им пользуется так мало народа». Иногда, однако, для этого есть веские причины. Надеюсь, какой-нибудь язык, где используется вариативность при объявлении, к примеру Scala или С# 4.0, ответит на этот вопрос раз и навсегда.
Сейбел: Что же дало импульс к появлению обобщенных типов?
Блох: Как часто бывает с идеями, которые на практике оказываются хуже, чем в теории, мы верили собственным заявлениям для прессы. Я представлял себе это так: почти все коллекции у нас однородны — список строк, хеш строк на целые числа и так далее. Но по умолчанию они создаются разнородными — все это коллекции объектов, которые надо приводить к нужным типам при выборке, — абсурд! Не лучше ли указать системе, что вот это, например, хеш строк на целые числа? Пусть она сделает приведение типов за меня, а во время компиляции укажет мне, если я допущу ошибку. Больше ошибок будет отслежено, система будет иметь больше высокоуровневой информации, а это хорошо.
Обобщенные типы, как и многое из того, что мы добавили в Java 5, казались мне средством автоматизации того, что раньше делалось вручную: пусть этим займется язык! Кое-где я попал в точку: цикл f or-each — отличная штука. Он скрывает от вас сложное устройство итератора или индексных переменных. Код становится короче, но площадь концептуальной поверхности при этом не увеличивается. Даже скорее уменьшается: мы ввели ложный полиморфизм массивов и других коллекций, и можно выполнять итерацию над ArrayList или над массивом, совершенно не интересуясь, над чем именно она выполняется.
Но главная причина того, почему эта идея не сработала для обобщенных типов, — они стали крупным прибавлением к системе типизации, и без того сложной. С системами типизации нужно обращаться осторожно, поскольку это может повлечь далеко идущие и непредсказуемые последствия для языка.
- Николай Георгиевич Гавриленко - Лора Сотник - Биографии и Мемуары
- Свидетельство. Воспоминания Дмитрия Шостаковича - Соломон Волков - Биографии и Мемуары
- Первый учитель муай тай в России. Sanit Konak (THA). 1993 г. - Сергей Иванович Заяшников - Биографии и Мемуары / Спорт / Публицистика
- Фридрих Ницше в зеркале его творчества - Лу Андреас-Саломе - Биографии и Мемуары
- На заре космонавтики - Григорий Крамаров - Биографии и Мемуары
- Конец Грегори Корсо (Судьба поэта в Америке) - Мэлор Стуруа - Биографии и Мемуары
- Рассказы - Василий Никифоров–Волгин - Биографии и Мемуары
- Победивший судьбу. Виталий Абалаков и его команда. - Владимир Кизель - Биографии и Мемуары
- Власть в тротиловом эквиваленте. Наследие царя Бориса - Михаил Полторанин - Биографии и Мемуары
- Записки нового репатрианта, или Злоключения бывшего советского врача в Израиле - Товий Баевский - Биографии и Мемуары