Материал взят из журнала DotNetCurry посвященному технологиям основанным на платформе .NET.
Дорогие читатели, мы очень рады видеть Эрика Липперта в этом номере журнала DNC. Эрик не нуждается в представлении людям знакомым с C#, но для остальных Эрик известен своей работой в команде разработчиков компилятора языка С#. Он посвятил значительную часть своей карьеры компании Microsoft, работая на различных должностях. До того как придти в Microsoft, Эрик работал в компании Watcom. Наши «старички» помнят Watcom как компанию, которая создала очень хорошие компиляторы для языков C++ и Fortran. В настоящее время Эрик работает в компании Coverity, помогая создавать продукты статического анализа кода.
DNC: Здравствуй Эрик, мы очень рады видеть тебя здесь, вместе с нами.
EL: Спасибо. Я рад быть здесь.
DNC: Ты работал в Microsoft долгих 16 лет. Опиши свое путешествие (если это можно так назвать) от стажировки до работы над VBScript, JScript, VSTO (Visual Studio Tools for Office) и становление главным разработчиком команды компилятора языка C#.
EL: Я вырос в Ватерлоо, интересовался наукой и математикой с раннего возраcта, так что для меня было естественным пойти в университет Ватерлоо. Кроме того, у меня были родственники в штате, и я уже знал ряд профессоров, и как вы сказали, будучи студентом, я работал в компании UW, которая являлась дочерней компании Watcom. UW имела отличную программу обучения, одну из самых больших во всем мире, через которую мне удалось получить три стажировки в Microsoft, в команду разработчиков языка VisualBasic. Они с радостью продлили мне предложение о работе, когда я закончил стажировку, и я остался в подразделении разработки инструментов на протяжении всей своей карьеры в компании Microsoft.
DNC: До того как ты начал стажировку в Microsoft, справедливо предположить, что в ранние годы ты получил много хороших советов от старших инженеров. Какой из этих советов явился лучшим советом по программированию, который ты когда-либо получал?
EL: Я получал много хороших советов от старших инженеров на протяжении всей своей карьеры, не только в её начале; Microsoft поощряет формальное и неформальное наставничество. Я где-то недавно говорил о лучшем совете в карьере, что получил его в Microsoft: в принципе, как стал специалистом в предметной области, я отвечаю на столько вопросов пользователей, насколько могу. Но сказать какой из советов по программированию был лучшим не так просто. Я так много узнал в Microsoft — мирового эксперта по дизайну языков программирования, анализу производительности и многих других вещей, что мне тяжело назвать что-то одно.
Одна вещь запомнилась мне еще до того как я попал в Microsoft. В один день, много лет назад, Брайн Керниган выступал с докладом о программировании в UW. На одном слайде был показан код, с которым было что-то не так. Он был некорректен, поскольку комментарий к коду и сам код не соответствовали друг другу. Керниган задал вопрос: что на самом деле работает — код или комментарии к нему? Я все еще задаю себе этот риторический вопрос, когда пытаюсь понять код, содержащий ошибку; часто бывает, что комментарии вводят в заблуждение, так как они устарели или просто изначально были плохо написаны, в тоже время часто бывает, что комментарии верны, и вам даже не надо вникать в код, содержащий ошибку. Доклад Кернигана полностью изменил моё отношение к комментированию кода. С этого времени я пытаюсь писать комментарии, которые объясняют цель некоторой части кода, до того как пытаюсь объяснить, как он работает.
DNC: Когда твоя команда начала разрабатывать C#, какие основные цели ты преследовал? Рад ли ты тому, каким языком C# стал?
EL: Если быть честным я начал работу над C#, когда основные концепции C# 3.0 были всерьез выработаны и развиты. Я следил за C#-ом с момента его появления, более 10 лет назад, но я не был частью команды C# 1.0 или C# 2.0.
Когда мы говорим о целях, я стараюсь различать «бизнес» цели от «технических»; они тесно связаны, но являются различными. С точки зрения бизнеса главной целью C# было и есть создание богатого языка, который позволял бы получить все преимущества платформы .NET и более того улучшить современное представление экосистемы Windows в целом. Лучшие инструменты влекут за собой более продуктивных разработчиков, более продуктивные разработчики создают лучшие приложения для своих пользователей, лучшие приложения делают платформу более привлекательнее и все выигрывают.
С точки зрения дизайна языка, есть ряд основных принципов, к которым разработчики возвращаются снова и снова. Язык должен быть современным, практичным, языком общего назначения, который используют профессиональные программисты, разрабатывающие программное обеспечение.
C# 1.0 начинал как достаточно простой, современный язык программирования. Очевидно, он ощутил на себе влияние языков C и C++; команда занимающаяся дизайном языка стремилась сгладить некоторые недостатки языков C/C++, в то же время обеспечивая возможность доступа к небезопасному коду. Но спустя 10 лет язык вырос, добавив такие возможности как: обобщенные типы, генераторы последовательностей, функциональные замыкания, выражения запросов, возможность взаимодействия с динамическими языками, и позднее, значительное улучшение поддержки написания асинхронного кода. Я взволнован тем, как язык развился за последние 12 лет, и это честь для меня быть частью некоторых из наиболее захватывающих изменений. Я с нетерпением жду продолжения работы над экосистемой C#.
DNC: Как участник команды разработчиков языка C#, какие переговоры ты вел с командой Windows OS? На каком уровне находится операционная система при разработке тех или иных возможностей языка? Ты в значительной мере работаешь один или же у вас больше совместных усилий?
EL: Каждый раз по-разному. В далеком прошлом это было, как правило первое; при разработке C# 5.0 команда Windows была сильно вовлечена. Я лично, мало общался с Windows командой на протяжении работы над C# 5.0, но команда управления проектом языка C# была почти постоянно рядом со своими коллегами из команды Windows на протяжении всей работы над Windows RT. Было несколько технических вопросов, которые требовали особой осторожности, чтобы обеспечить как можно меньше не соответствия между C# разработчиками и программной моделью Windows RT. В частности, было важно, что «async/await» отвечает потребностям разработчиков Windows RT использующих C#.
Однако, это справедливо относительно недавно. C# исторически не имел прямого взаимодействия с командой Windows, поскольку он основан на управляемой среде CLR, а так же использует библиотеку классов BCL для обеспечения доступа к функционалу операционной системы. С тех пор, как команда C# поняла, что команды CLR И BCL будут выступать в роли «посредника» между сервисами операционной системы, они смогли больше сконцентрироваться над дизайном языка, который использовал бы всю мощность CLR и BCL, и пусть эти команды занимаются взаимодействием с операционной системой.
DNC: Мы слышали о твоем подкасте с командой StackExchange в котором ты упомянул вещи которые находятся на вершине твоего списка — «если бы у меня был Джин, чтобы исправить в C#...». Речь шла о небезопасной ковариации массивов. Мог бы ты рассказать об этом нашим читателям?
EL: Конечно. Для начала давайте определим, что означает термин «ковариация». Надлежащее определение потребует затронуть курс по теории категории, но нам не надо идти так далеко, чтобы понять смысл этого термина. Идея ковариации, как следует из названия, это когда одно утверждение приводится к другому истинность которого сохраняется при совершении некоторой трансформации над исходным утверждением.
В языке C# есть следующее правило: если T и U — ссылочные типы и T приводим к U по ссылочному преобразованию, то T[] приводим к U[] так же по ссылочному преобразованию. Это правило, как говорят, ковариантно, поскольку утверждение «T приводимо к U» вы можете привести к утверждению «T[] приводимо к U[]» при этом истинность утверждения сохраняется. Это верно не для всего; к примеру, вы не можете заключить, что List<T> приводим к List<U>, только потому, что T приводим к U.
К сожалению, ковариация массивов ослабляет безопасность типов в языке. Язык является типобезопасным, когда компилятор отлавливает такие ошибки как, например, присвоение целого числа переменной имеющий строковый тип, то есть программа не будет скомпилирована, пока не будут исправлены все ошибки несоответствия типов. Ковариация массивов, пример такой ситуации, когда несоответствие типов не может быть отловлено на этапе компиляции, а может быть проверено только во время выполнения.
static void M(Animal[] animals)
{
animals[0] = new Turtle();
}
static void N(Giraffe[] giraffes)
{
M(giraffes);
}
Поскольку преобразование массивов является ковариантным, массив жирафов может быть преобразован к массиву животных. А поскольку черепаха является животным, мы можем поместить её в массив животных. Но этот массив на самом деле содержит жирафов. Это несоответствие типов выльется в выбрасывание исключения во время выполнения.
У ковариации массивов есть 2 негативных последствия. Во-первых, присвоение значения переменной должно всегда проверяется на этапе компиляции, но в этом случае это невозможно. И во-вторых, это означает, что каждый раз когда вы присваиваете элементу массива (тип, которого является незапечатанным ссылочным типом) значение не пустой ссылки, среда проверяет фактический тип элементов массива с типом, присваиваемой ссылки. Эта проверка требует времени! Для того, чтобы ковариация массивов работала, корректной программе придется работать медленнее при каждом обращении к элементам массива.
// Если бы у меня был Джин, который мог бы исправить любой код, я бы удалил небезопасную ковариацию массивов полностью.
Команда разработчиков языка C# добавила типобезопасную ковариацию в C# 4.0. Если вам интересно как это сделано, я написал длинную серию статей посвященных реализации этого функционала; вы можете прочитать их тут blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/
Если бы у меня был Джин, который мог бы исправить любой код, я бы полностью удалил небезопасную ковариацию массивов, и исправил бы весь код, используя типобезопасную ковариацию, добавленную в C# 4.0.
DNC: Прежде чем мы перешли к твоей текущей работе, расскажи нам немного о том что такое статический анализ кода.
EL: Под статическим анализом понимается анализ программы на основании только её исходного кода. Он отличается от динамического анализа, который анализирует программу во время выполнения. Компиляторы производят статический анализ, в то время как профайлеры — динамический анализ. Компиляторы используют статический анализ для 3 вещей: во-первых, чтобы определить является ли программа корректной программой, и если нет вывести соответствующие сообщения об ошибках. Во-вторых, чтобы перевести корректную программу в какой-либо другой язык, обычно в байт код, либо машинный язык, однако это может быть и любой другой высокоуровневый язык. И, в-третьих, определить конструкции, являющиеся верными, но использование, которых является сомнительным, и вывести соответствующие предупреждения.
В Coverity мы, как правило, занимаемся третьим видом статического анализа, мы предполагаем, что код является синтаксически корректным; это будет проверено компилятором, а мы проводим гораздо более глубокий анализ по выявлению сомнительных конструкций и предоставляем их вашему вниманию. Чем раньше вы обнаружите ошибку, тем дешевле её исправить.
// Я потратил около 15 тысяч часов тщательно изучая дизайн и реализацию компилятора языка C#.
Есть и другие вещи, которые вы можете сделать с помощью статического анализа, например, Coverity также делает продукт, который использует статический анализ, чтобы найти изменения в коде, которые не имеют соответствующих юнит тестов.
DNC: Каким образом твои глубокие знания по C# приносят пользу компании Coverity?
EL: В основном двумя способами. Во-первых, C# — это огромный язык, его спецификация занимает около 800 страниц. Обычные разработчики конечно не должны знать в подробностях весь язык, чтобы эффективно его использовать, но создатели компиляторов конечно же должны. Я потратил около 15 тысяч часов тщательно изучая дизайн и реализацию компилятора языка C#, а так же учился у проектировщиков языка Андерса Хейлсберга, Нила Гафтера и Эрика Мейера, так что у меня есть довольно твёрдое понимание того, что делает хороший статический анализатор языка C#. Во-вторых, я видел тысячи строк кода на языке C# содержащие ошибки. Я знаю, какого рода ошибки совершают C# разработчики, что помогает нам выявлять места, где надо провести особые усилия в статическом анализе.
DNC: С тех пор как ты работаешь в компании Coverity сталкивался ли ты с ситуацией, в которой ты подумал — «ммм, это поможет сделать язык C# более статически типизированным(доказуемым)»?
EL: Некоторый функционал языка, усложняет статический анализ, но в то же время делает язык более мощным. К примеру, виртуальные методы, усложняют статический анализ, потому что смысл виртуальных методов заключается в том, что реальный метод будет выбираться на основании настоящего типа во время выполнения.
Как я сказал ранее, C# был разработан с учетом недостатков языков C/C++. Разработчики C# 1.0 проделали хорошую работу; ведь достаточно трудно организовать переполнение буфера или создать утечку памяти или использовать переменную до её инициализации или случайно использовать одно и тоже имя, для совершенно разных вещей. Но то, что действительно было поучительным для меня после перехода в компанию Coverity, так это то, что большинство ошибочных конструкций, которые Coverity проверяет на C/C++ одинаково хорошо применимы в современных языках, таких как Java и C#.
DNC: Мы слышали ты разработал одну из первых фан страниц романа «Властелин колец». Расскажи нам подробнее, как это произошло, а так же о своих интересах в книгах и кино.
EL: Мой отец прочитал мне Хоббита, когда я был очень молод; с этих пор я стал увлекаться писателем Толкиеным; я собирал его биографию и это было моим хобби, когда я был подростком. Когда я изучал математику в UW в ранних 1990-ых, всемирная паутина (WWW) была чем-то новым; в один день я занимался «серфингом по интернету» и я нашел фан страницу для оригинального сериала Звездный путь. Я подумал, что это будет отличной идеей создать что-то аналогичное для Толкиена, я обыскал весь интернет, что не заняло у меня много времени в 1993 году и нашел все FTP-сайты, новостные группы и еще много чего о Толкиене, и создал простую веб-страницу, которая была просто набором ссылок и положил её на сервер клуба компьютерных наук GOPHER. С ростом веба, все больше и больше людей связанных с ним присылали мне адреса своих страниц. Я продолжал добавлять все больше и больше ссылок, пока их не стало слишком много. Я перестал поддерживать эту страницу и, в конце концов, мое членство подошло к концу. Я думаю, вы все еще можете найти её в архиве Интернета.
Дело в том, что в то время такие компании как Open Text и Google начали индексировать интернет и их алгоритмы поиска учитывали такие вещи как: как долго страница существовала, как часто она изменялась с течением времени, и как много внешних ссылок на неё было. Даже после того как я перестал её активно поддерживать показатели у страницы были на высоком уровне. В результате, в течение многих лет моё имя было первым, когда делали запрос по «Толкиену». Когда был представлен фильм, многие люди именно это и делали. В результате я закончил тем, что дал интервью нескольким газетам, получил e-mail от одного из внуков Толкиена, Jeopardy (американское название программы «Своя игра») для проверки фактов однажды мне позвонили, спросив «Кто такие Энты?». Мне было очень весело.
Тем не менее, в те дни я читал очень мало фантастики и фэнтази. Большая часть моего чтения в свободное время посвящена научно-популярным книгам.
Я люблю смотреть фильмы и приглашать друзей для их просмотра, наши ночные выборы фильмов бывают очень разными. Один месяц мы смотрим фильмы, номинированные на Оскар, другой месяц фильмы ужасов.
// Один из способов определить, что язык действительно становится большим, это когда пользователи отправляют запрос на новый функционал, а он уже у вас есть.
Вот три маленькие функции, о которых знают не так много людей.
- Вы можете добавить приставку «global:» к имени пространства имен, чтобы заставить алгоритм разрешения имен начать поиск с пространства имен, помеченного словом global. Это полезно в ситуации, когда у вас конфликт между глобальным пространством и локальным пространством либо типом. К примеру, если ваш код находится в плохо названном пространстве имен «Foo.Bar.System», тогда обращение к «String» в «Foo.Bar.System» приведет к ошибке. Если добавить префикс «global:System.String» то String будет искаться в глобальном пространстве имен System.
- C# запрещает «проваливаться» из одной секции оператора switch в другую. Что люди не знают так это то, что вам необязательно использовать слово break в каждой секции. Вы можете заставить оператор провалиться из одной секции в другую, используя метки. Вы можете также завершить секцию switch с помощью goto, return, throw, yield break, continue или даже бесконечным циклом.
- И последняя умная функция, которую мало кто знает: вы можете комбинировать использование объединяющего null операторов, чтобы получить значение первого не пустого элемента в последовательности выражений. Если у вас есть переменные x, y, и z типа int? тогда результатом выражения x??y??z ??-1 будет первое из x, y или z не null число или -1 если они все равны null.
Я часто говорю о необычных особенностях языка в своем блоге, поэтому навестите его, если хотите больше примеров.
Автор: timyrik20