Программирование
Зачем нужно знать всякие низкоуровневые вещи
Возможно, есть еще что-то, о чем я забыл. Смысл всего вышесказанного в том, что никакие высокоуровневые языки и готовые библиотеки не освобождают вас от знания мат части. Или, по крайней мере, ее основ.
Когда-нибудь я планирую запостить в этот блог несколько заметок, посвященных всяким «низкоуровневым» вещам — таким, как алгоритмы сжатия, ассемблер, написание драйверов, WinAPI и тд. В контексте подобных тем всегда возникает вопрос, мол зачем мне все это знать, когда есть готовые библиотеки, высокоуровневые языки и прочие вещи, избавляющие нас от необходимости много думать. Чтобы в будущем не возвращаться к этому вопросу, я попробую ответить на него сейчас.
В первую очередь, конечно, мне хочется осветить названные темы, потому что когда-то я в них немного разбирался, а сейчас некоторые вещи начинают забываться. Имея под рукой годную шпаргалку, я всегда смогу быстро восстановить свои знания. Тем не менее, остается открытым вопрос, зачем во все это вникать уважаемым читателям.
Вообще-то, совсем не факт, что абсолютно всем программистам нужно знать ассемблер. Уверен, что веб-разработчиком он не нужен. Но, возможно, что некоторым программистам будет полезно знать хотя бы принципы работы алгоритмов сжатия и внутреннего устройства современных ОС.
Чтобы мои аргументы звучали убедительнее, я решил подкрепить их словами двух авторитетных людей в мире программирования. Александр Степанов, создатель STL, в предисловии ко 2-му изданию книги С++ и STL — справочное руководство (не его авторства) пишет следующее:
Для того чтобы быть хорошим программистом, важно понимать, что в действительности происходит «за кулисами» высокоуровневого языка программирования. Следует знать по крайней мере пару разных архитектур.
На самом деле, это написано даже не в предисловии, а в сноске к нему. Но суть дела от этого не меняется. Джоэл Спольски, не нуждающийся в представлении, пишет в своей книге Джоэл о программировании:
Я думаю, что некоторые крупнейшие ошибки — даже на самых верхних уровнях архитектуры — происходят из-за слабого или неверного понимания некоторых простых вещей на самых нижних уровнях.
Думаю, многие другие широко известные (в узких кругах) люди когда-то писали нечто подобное. Я ограничился этими двумя цитатами, только потому что наткнулся на них в двух недавно прочитанных книгах.
А теперь давайте перейдем непосредственно к аргументам. Первым, и наиболее важным аргументом, на мой взгляд, является следующий: вы понимаете, что вообще происходит. Невозможно объяснить рядовому пользователю Windows, что значит «убить процесс», потому что он просто не знает о существовании процессов. Рядовой юзер мыслит в контексте окон и ярлыков на рабочем столе.
Когда лет пять назад я встал на путь юниксоида, моей первой успешно установленной ОС была PC-BSD. Поскольку я совершенно не понимал, что находится у нее «под капотом», то любую проблему я пытался решить, как истинный пользователь Windows. То есть, путем поиска того пункта меню и той галочки, что решат мою проблему. В конце концов до меня дошло, что в мире UNIX это не работает. Вместо PC-BSD я поставил чистую FreeBSD и погрузился в чтение книжек и мануалов. Было непросто, зато намного интереснее. И с юниксами я теперь, что называется, на «ты».
Понимание важно не только в отношении операционных систем. Недавно один мой коллега пытался добиться от меня «обоснованного доказательства», что такая-то база данных «будет быстро работать». Разумеется, про B-деревья он никогда не слышал. (Надеюсь, упомянутый коллега не примет этот пример близко к сердцу.) Также мне совершенно не ясно, как некоторые люди умудряются писать на C++, не понимая, что такое стек и адресное пространство.
Как следствие из предыдущего пункта, понимая, что происходит, вы совершаете меньше ошибок. Невозможно избежать ошибок переполнения буфера, целочисленного переполнения или sql injection, не понимая происходящего. Не зная мат части, невозможно обнаружить ошибку в следующем коде:
void doSomethingUseful(float a, float b) { if(a == b) { // do something ... } // do something else ... }
Знание различных низкоуровневых вещей существенно упрощают отладку приложений. Два программиста отлаживают одну программу. Один из них знаком с ассемблером, потому он загоняет прогу в OllyDbg (кстати, под UNIX есть годный аналог — kgdb), ставит точки останова, смотрит, что собственно происходит и быстро находит ошибку. Второй с ассемблером не знаком, потому лучшее, что он может сделать — это посмотреть значения переменных во время выполнения программы.
Рано или поздно оба программиста понимают, что что-то не так в библиотеке стороннего производителя, предназначенной для работы с протоколом FTP. Один из программистов знает этот протокол и умеет пользоваться tcpdump, а второй нет… В общем, вы меня поняли.
Следующий аргумент — оптимизация программ. Да, переписывание программы с C++ на ассемблер в наши дни, скорее всего, не даст ощутимого прироста производительности, если только речь не идет о каком-нибудь сжатии или шифровании данных. Зато переписывание с «высокоуровневого» Python на «низкоуровневый» C++ может весьма существенно ускорить программу. Не говоря уже о том, что переписанная программа будет кушать на порядок меньше памяти.
Помимо возможности переписать программу, знание «матана» поможет вам выбрать более эффективный алгоритм. Например, эллиптические кривые намного быстрее алгоритма RSA. Вы можете сколько угодно пытаться оптимизировать последний, но он все равно не догонит ЭК в плане производительности. Это как пытаться разогнать пузырьковую сортировку до уровня quicksort.
Изучая мат часть, вы открываете для себя множество новых возможностей, помимо уже названных. Взять к примеру перехват Windows API. Казалось бы, нигде, кроме как в вирусах, это не нужно. И продолжает так казаться до тех пор, пока вам не захочется написать свой отладчик. Или антивирус. Или программу, создающую виртуальный жесткий диск. Или сделать portable версию какого-нибудь приложения не своего производства. Сколь бы высокоуровневой ни была Java, тут она вам не поможет.
Но труднее всего, как мне кажется, переоценить наличие опыта разработки собственного транслятора или интерпретатора. Подумайте только, сколько возможностей тут открывается! Мы можем реализовать язык сценариев для своего приложения, что, скажем, в игровых движках — must have. Можем написать препроцессор для C++, добавляющий в язык синтаксический сахар для работы с юникодом, умными указателями и регулярными выражениями. Можем написать свой транслятор языка Си в хитрый байт-код для виртуальной машины, с целью усложнить реверсинг. Это действительно целый новый мир!
Теперь забудем на секунду о названных плюсах. Вы видели современные вакансии на должность программиста? Чтобы устроиться в серьезную компанию, требуется отличное знание ассемблера, Си, ОС семейства UNIX, WinAPI, сетевого программирования, структур данных и базовых алгоритмов, наличие от пяти лет опыта в отладке, оптимизации и рефакторинге программ, а также в разработке высоконагруженных систем обязательно, и еще столько же пунктов в разделе «желательно». Вы хотите работать в серьезной компании? Тогда учите мат часть, даже если считаете, что она вам не пригодиться (хотя на самом деле это не так).
Ну и напоследок — мне лично всякие низкоуровневые вещи всегда казались очень интересными. Хотя, поговаривают, что я один такой больной, а нормальным людям пиво с друзьями и говорить о политике…
Возможно, есть еще что-то, о чем я забыл. Смысл всего вышесказанного в том, что никакие высокоуровневые языки и готовые библиотеки не освобождают вас от знания мат части. Или, по крайней мере, ее основ. Уже упомянутый Джоэл Спольски называет это «законом дырявых абстракций». Мы можем сколько угодно абстрагироваться от машинного кода, программируя на C#. Или от битиков на жестком диске при работе с файловой системой. Но этот закон все равно тянет нас вниз.