Объяснение сложных концепций программирования на простых аналогиях

0
3712
views

Перевод статьи «Hard Coding Concepts Explained with Simple Real-life Analogies».

Простое объяснение сложных концепций

Как объяснить концепции потоков, промисов, линтинга и декларативного программирования пятилетнему ребенку.

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

Я буду продвигаться от самых простых концепций к более сложным. Давайте начнем с программирования как такового.

Программирование

Программирование похоже на кулинарный рецепт

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

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

В некоторых рецептах вы встретите if-предложения, например, разные меры продуктов при готовке на 2, 4 или 8 человек. В других рецептах встречаются циклы: «продолжайте взбивать, пока…»

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

Использование специальных инструментов и полуфабрикатов похоже на включение и использование в вашем коде чужих пакетов.

// The making of a cupcake// First steps:
$ npm install cake-mix$ npm install cupcake-pan

NPM это менеджер пакетов для Node.js — очень популярного фреймворка для написания приложений на JavaScript. Продолжая проводить аналогии, можно сказать, что Node.js это «кухня». В ней вы можете выполнять строки ваших рецептов, используя встроенные модули вроде духовки или раковины.

Изучение программирования

Изучение программирования напоминает похудение

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

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

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

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

Отфильтровав источники знаний, следует приступить к упражнениям! Отрабатывайте все изученные навыки, но не просто еще раз повторяя материал в книге. Усложняйте себе задачу и делайте что-нибудь по той же теме, но немного иначе. Если вам повезет, вы застрянете! А в попытках разблокироваться вы непременно изучите что-нибудь еще.

Упражнения хороши как для тела, так и для ума.

Переменные

Переменные можно сравнить с ярлыками

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

Переменные не содержат данных. Они просто указывают на них. Данные хранятся в памяти компьютера. Переменные можно сравнить с ярлыками, которыми вы помечаете свои электронные письма (или заметки, или файлы).

В Gmail ярлык это указатель на email или список email-ов. Многие ярлыки могут указывать на одни и те же email-ы. Это похоже на назначение существующей переменной в качестве значения для другой переменной:

let work = [email1, email2, email3];let important = work;

Обе переменные работают и указывают на один и тот же список email-ов.

Некоторые переменные представляют собой постоянные ссылки. Они не могут быть изменены. Это как ярлык «отправлено» в Gmail. Обычные ярлыки можно переназначить и сделать так, чтобы они указывали на другой список email-ов, но мы не можем изменить ярлык «отправлено». Нельзя сделать так, чтобы ярлык «отправлено» указывал на другой список email-ов. Можно только добавить email-ы в список.

const sent = [];
// You cannot change the meaning of sent now
// But you can add more values to it:
sent.push(new Email());

Ошибки и исключения

Ошибки и исключения похожи на воспитание детей

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

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

Мне нравится фраза «прислушивайтесь к своему коду», потому что именно сообщения об ошибках позволяют коду развиваться.

Здесь все в точности, как с воспитанием детей.

Путем проб и ошибок я, как отец, пришел к пониманию, что посредством плохого поведения дети коммуницируют с нами. Так получается, потому что их мышление еще недостаточно логичное.

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

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

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

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

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

// Hey kidsif (stranger.offersYou(chocolate)) {  doNotAccept();  doNotTalkTo(stranger);  walkAway();}
if (stranger.triesToForceYouToDoSomething()) {  screamFor(help);  runAway();  call(911);}

Реактивное программирование и потоки

Реактивное программирование и аналогия с подписками

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

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

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

Вы можете представлять себе события как нечто, что может произойти в будущем. Например, Джейн (владелица магазинчика) всегда делает твиты об интересных событиях в своем магазине. Каждый ее твит можно считать «событием». Если вы просмотрите ленту Джейн в Twitter, вы увидите последовательность «событий» (поток событий).

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

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

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

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

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

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

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

Функции обратного вызова и промисы

Функция обратного вызова это как имя, названное в Старбаксе

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

Имя, которое вы назвали, можно считать функцией обратного вызова (колбек-функцией). Ее вызвал объект, который вы запрашивали.

Это как когда вы заказываете кофе в Starbucks. Они записывают ваш заказ и ваше имя, а потом вы ждете, пока вас не позовут. Когда вас позвали, вы получаете свой латте.

starbucks.makeMeALatte({ type: 'Vanilla', size: 'Grande' }, Samer);
// "Samer" here is the callback function.
// When the Latte is ready, the barista will call Samer 
// with the ready object
// We define a function Samer to process the ready object
 function Samer(readyLatte) {  
// drink readyLatte}

Теперь представьте, что вы заказали что-то одно, но дали вам другое. Какой-то загадочный объект. И они обещают вам, что этот загадочный объект может в конечном итоге превратиться в ту вещь, которую вы изначально заказывали. (Примечание: promise в переводе с английского «обещать, обещание»).

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

Это как если мы попросили курицу дать нам цыпленка, а она дала нам яйцо. Это яйцо может успешно превратиться в цыпленка или же может оказаться бесполезным.

const egg = chicken.makeChick();   // It's a promise!
egg.then(chick => raiseChick())    // Success outcome   
.catch(badEgg => throwBadEgg())    // Fail outcome

Очереди и стеки

Стеки

Мы часто применяем для хранения и использования элементов данных две популярные структуры данных: стек (LIFO) и очередь (FIFO).

LIFO расшифровывается как Last In First Out («последним пришел — первым ушел»), а FIFO — как First In First Out («первым пришел — первым ушел»).

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

Последняя положенная в стопку тарелка моется первой. Это LIFO.

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

Парное программирование

Парное программирование

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

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

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

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

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

Линтинг и автоматизация задач

Автоматизация задач

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

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

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

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

Императивное и декларативное программирование

Императивное программирование похоже на езду на машине с механикой

В любом деле есть два аспекта: «что» и «как». То есть, что именно нужно сделать и как вы собираетесь это делать.

Императивное программирование это «как». Декларативное — «что».

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

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

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

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

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

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

Assembly это настоящий императивный низкоуровневый компьютерный язык с инструкциями в чистом виде, прямо переводимыми в машинный код.

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

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

А каковы ваши любимые аналогии? Поделитесь в комментариях!

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

Please enter your comment!
Please enter your name here