Нужны ли программисту математика, английский язык, теория алгоритмов и паттерны проектирования?

0
1060
views

Вы думаете, что достаточно знать парочку языков программирования и сопутствующие библиотеки? Увы, для успешного программиста этого мало. Илья Гинсбург в статье на proglib.io рассказал, какие еще дисциплины придется освоить новичку.

Разумеется, ответить на такие вопросы может только программист с большим опытом работы. Поэтому писать эту статью доверили именно мне – разработчику со стажем в 28 лет (C++, Delphi, C#), чьи первые продаваемые программы работали еще под MS-DOS.

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

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

Иногда возникают нюансы. Вам приходится не только писать код, но и формализовать задачу, поставленную заказчиком на манер знаменитого «копать от забора и до обеда».

Или ваш код, написанный «на коленке» за пару минут, работает слишком медленно.

Или ваша программа выдает исключение, давно описанное на  stackoverflow.com, но вы не умеете читать по-английски.

Или возникает необходимость сделать рефакторинг кода, а для вас это звучит как «харакири».

Как правило, начальству наплевать на то, как вы справитесь с этими проблемами, но справиться вы обязаны – «вы же программист»! За что же вам платят эдакие деньжищи?

Математика

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

Саша Привалов и математика. Иллюстрация к книге "Понедельник начинается в субботу"

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

– Это мы опубликуем. Это никому не стыдно опубликовать.
Аркадий и Борис Стругацкие, «Понедельник начинается в субботу»

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

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

В моем дипломе, полученном на факультете прикладной математики, гордо значится специальность – математик. Хотя все понимали, что нас учат на программистов.

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

Если говорить о нужности для программиста, всю высшую математику можно разделить на три части:

  1. То, что вы будете использовать каждый день: дискретная математика. О ней мы сейчас поговорим подробнее.
  2. То, что вам нужно для решения возникающих математических задач: дифференциальное и интегральное исчисление, линейная алгебра, статистика. Эти разделы математики используются не каждый день, а у многих программистов – даже не каждый год, но знать их все равно очень полезно, а во многих компаниях – даже необходимо. Почему? Если вам хоть изредка приходится ставить математическую постановку задачи – вы просто не сможете этого сделать без соответствующей математической базы. Имея базу, вы сможете найти информацию о своей задаче в Интернете и использовать ее, а без базы вы эту информацию просто не поймете!
  3. Специфические знания. Нужны только для решения задач из определенной прикладной области. Если вы пишете программы именно в этой области – знание необходимых разделов математики будет огромным плюсом, а если не пишете – эти разделы вам не нужны.

Дискретная математика

Этот раздел математики изучает конечные структуры и содержит множество различных подразделов.

Самый важный подраздел для программистов – это математическая логика, в которую входит обработка привычных каждому программисту логических операций вроде andor, и not.

Например, следующий оператор срабатывает, если верно a, но не b, или, наоборот, верно b, но не a:

if ((a && !b) || (!a && b))
{
   // Сделаем что-нибудь
}

Если вы изучали дискретную математику, то сразу поймете, что то же самое можно написать гораздо проще, используя операцию «исключающее ИЛИ», причем код будет не только намного короче, но и станет работать быстрее:

if (a ^ b)
{
   // Сделаем что-нибудь
}

Математическая логика также помогает «инвертировать» логические выражения или упрощать их, что может потребоваться программисту буквально каждый день:

// Сложное логическое выражение
if ((a || b) && (c || !b))
{
}
// Инвертированное логическое выражение
if ((!a && !b) || (!c && b))
{
}

Еще один важнейший раздел дискретной математики для программистов – это теория графов.

Если слово «граф» для вас ассоциируется с графом Монте-Кристо, а не со структурой данных, состоящей из вершин и соединяющих их ребер, вы рискуете не только провалиться на любом собеседовании, но еще и опозориться:

С чем должно ассоциироваться слово «граф» для программиста

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

Очень популярным подвидом графа является дерево – это связный граф без циклов. Полное незнание теории графов не позволит вам найти нужные алгоритмы и тем более понять их.

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

Для освоения всей дискретной математики вам будет достаточно всего одной книги: «Дискретной математики для программистов» Рона Хаггарти. Было бы очень здорово, если бы все пробелы в нашем образовании можно было закрыть, изучив всего одну книгу, правда? 

Английский язык

Ду ю спик инглиш? Говорите, «академиев не кончали»? Не страшно! Из четырех основных навыков, проверяемых на серьезных экзаменах по языку (чтение, сочинение, разговор и аудирование) программисту достаточно только первого – чтения, но при этом читать придется не Агату Кристи, а техническую литературу по специальности, что намного труднее.

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

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

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

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

Иногда вам придется и написать абзац-другой – например, задать вопрос на форуме или в отдел технической поддержки. Поскольку писать сочинения вроде «London is the capital of Great Britain» вам едва ли понадобится, это не должно вызвать особых трудностей, если вы уже хорошо умеете читать. Даже если вы сделаете пару ошибок, не страшно – лишь бы вас правильно поняли.

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

Такие фирмы прямо пишут в своих вакансиях что-то вроде «English – Upper-Intermediate». Высококлассному специалисту с большим опытом должно быть очень обидно упускать такие вакансии только из-за незнания языка.

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

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

Теория алгоритмов

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

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

Да потому, что в большинстве компаний нет выделенных проектировщиков (в Штатах их обычно называют «архитекторами программ», software architect), и никто выше тимлида не способен принять правильное решение о том, каким должен быть продукт, и как его следует писать. Впрочем, о проектировании мы поговорим чуть ниже, а сейчас речь о теории алгоритмов.

Алгоритм – это метод решения задачи. Он может быть правильным (если программа во всех случаях работает правильно) или неправильным – если она работает правильно не всегда. Он также может быть эффективным (если программа затрачивает минимально возможное количество ресурсов, особенно времени и оперативной памяти) или неэффективным, если намного больше.

Теория алгоритмов оценивает не точное количество ресурсов (которое на практике редко можно рассчитать), а характер его зависимости от размеров исходных данных, называемой  сложностью.

Например, цикл по всем элементам массива имеет вычислительную сложность O(N), где N – количество элементов массива. Это значит, что количество вычислительных операций пропорционально N.

Общеизвестно, что сложность быстрой сортировки (в среднем) равна O(N * logN), а сложность бинарного поиска – O(log N).

Поиск в хеш-таблице происходит еще быстрее: при правильно подобранных ключах и достаточном размере таблицы его сложность O(1), то есть вообще не зависит от размера входных данных! Этим и обусловлена широкая популярность хеш-таблиц, а также базирующихся на них словарей (dictionary) и множеств (set).

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

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

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

Теоретически, вы можете писать код, и не зная об алгоритмах, но это будет код школьника-троечника. Причем страшнее всего то, что вы этого даже не почувствуете!

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

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

А вот люди, ничего не знающие об алгоритмах, не получают никаких «звоночков». Они успешно пишут код, который прекрасно проходит все тесты (поскольку размер данных в тестах обычно невелик), но у пользователя работать не будет.

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

Хороших книг по теории алгоритмов очень много. Начать стоит с учебника С.М. Окулова «Программирование в алгоритмах«, доступного даже для школьников. Если вы по-настоящему любите математику, возьмитесь за классическую книгу Дональда Кнута «Искусство программирования» – там вы найдете не только описания алгоритмов, но и доказательства сложности многих из них. Ну, а если вы умеете читать по-английски и уже знаете основы теории – беритесь за «Продвинутые алгоритмы и структуры данных» Марчелло Ла Рокка.

Паттерны проектирования

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

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

Пожалуй, впервые важность проектирования была продемонстрирована в книге Ф.Брукса «Мифический человеко-месяц», вышедшей в далеком 1975-м, но она была ориентирована на менеджеров, а не на программистов.

В 1994-м Э. Гамма, Р. Хелм, Р. Джонсон и Д. Влиссидес (называемые «бандой четырех», «Gang of Four») опубликовали книгу «Паттерны объектно-ориентированного проектирования«, заложившую основы современного подхода к проектированию ПО.

В книге описано множество типовых приемов (которые авторы назвали «паттернами»), помогающих улучшить проекты проекты программ, и это стало основным фактором ее популярности. Стало модно задавать вопросы по паттернам проектирования на собеседованиях, и юные соискатели начали заучивать их наизусть.

(Я не рассматриваю SOLID, появившийся позднее, поскольку он слабо помогает проектировать – но заучить его для собеседований вам тоже придется).

К сожалению, навыки кодирования и проектирования очень сильно отличаются друг от друга, так что гениальный проектировщик вряд ли окажется еще и супер-кодировщиком (а когда такое все же случается, в мире появляется очередной Линус Торвальдс).

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

Разумеется, кодировщики, ничего не знающие о проектировании, считают проектировщика просто бездельником! Именно поэтому крупные компании не ставят проектировщика в подчинение руководителю команды кодировщиков, а придумывают для него особую должность «архитектора» (software architect).

Так понадобятся ли вам «паттерны проектирования»? Как проектировщик с огромным опытом, ответственно заявляю: вам понадобятся не только они, но и глубокое понимание проектирования в целом.

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

Отличный пример широко распространенного, но неправильного понимания паттерна проектирования дает паттерн Bridge (Мост). Начнем с правильного примера использования этого паттерна на C# (код взят из документации Microsoft):

using System;
using System.Data;

namespace IDbConnectionSample {
   class Program {
      static void Main(string[] args) {
         IDbConnection connection;

         // First use a SqlClient connection
         connection = new System.Data.SqlClient.SqlConnection(@"Server=(localdb)\V11.0");
         Console.WriteLine("SqlClient\r\n{0}", GetServerVersion(connection));
         connection = new System.Data.SqlClient.SqlConnection(@"Server=(local);Integrated Security=true");
         Console.WriteLine("SqlClient\r\n{0}", GetServerVersion(connection));

         // Call the same method using ODBC
         // NOTE: LocalDB requires the SQL Server 2012 Native Client ODBC driver
         connection = new System.Data.Odbc.OdbcConnection(@"Driver={SQL Server Native Client 11.0};Server=(localdb)\v11.0");
         Console.WriteLine("ODBC\r\n{0}", GetServerVersion(connection));
         connection = new System.Data.Odbc.OdbcConnection(@"Driver={SQL Server Native Client 11.0};Server=(local);Trusted_Connection=yes");
         Console.WriteLine("ODBC\r\n{0}", GetServerVersion(connection));

         // Call the same method using OLE DB
         connection = new System.Data.OleDb.OleDbConnection(@"Provider=SQLNCLI11;Server=(localdb)\v11.0;Trusted_Connection=yes;");
         Console.WriteLine("OLE DB\r\n{0}", GetServerVersion(connection));
         connection = new System.Data.OleDb.OleDbConnection(@"Provider=SQLNCLI11;Server=(local);Trusted_Connection=yes;");
         Console.WriteLine("OLE DB\r\n{0}", GetServerVersion(connection));
         }

      public static string GetServerVersion(IDbConnection connection) {
         // Ensure that the connection is opened (otherwise executing the command will fail)
         ConnectionState originalState = connection.State;
         if (originalState != ConnectionState.Open)
            connection.Open();
         try {
            // Create a command to get the server version
            // NOTE: The query's syntax is SQL Server specific
            IDbCommand command = connection.CreateCommand();
            command.CommandText = "SELECT @@version";
            return (string)command.ExecuteScalar();
         }
         finally {
            // Close the connection if that's how we got it
            if (originalState == ConnectionState.Closed)
               connection.Close();
         }
      }
   }
}

Интерфейс IDBConnection реализует Мост между клиентским приложением и базой данных (БД). Цель паттерна – полная изоляция клиента от деталей реализации каждой конкретной БД.

Конкретным объектом, реализующим этот интерфейс, может быть любой экземпляр класса, унаследованного от DBConnection (код создает экземпляры SqlConnectionOdbcConnection  и  OleDbConnection), но клиент никогда не должен узнать, какого именно.

Это позволяет разработчикам клиентского приложения сделать его по-настоящему мультиплатформным, а разработчикам Моста – постепенно добавлять поддержку новых БД, которая не заставит разработчиков клиента ничего менять.

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

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

Во-первых, вы не можете заранее предсказать все, что вам потребуется от языков программирования – а это значит, что ваш «мост», скорее всего, через некоторое время придется изменять, тогда как одна из целей настоящего Моста заключается именно в защите клиента от изменений (как вы думаете, будет ли когда-нибудь изменен IDBConnection?).

Во-вторых, вы не можете раз и навсегда определить даже вид соотношения между языками и программистами! Кто сказал, что каждый программист знает только один язык? Такие «мосты» только напрасно усложняют проект и не приносят никакой реальной пользы.

А вот пример того же паттерна с сайта Refactoring Guru. Здесь строится «мост» между пультами и управляемыми с их помощью устройствами.

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

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

Настоящий проектировщик не только использует паттерны правильно, но и придумывает свои. Вы же не считаете, что «банда четырех» описала все возможные паттерны?

Например, прекрасная книга Мартина Фаулера «Шаблоны корпоративных приложений» содержит паттерны проектирования приложений, предоставляющих пользователям удаленный доступ к базам данных.

Удачи вам в определении того, чего вам не хватает, и успешного устранения этих пробелов!

ОСТАВЬТЕ ОТВЕТ

Please enter your comment!
Please enter your name here