Хочешь программировать? Научись думать как программист

Знаешь, я когда-то не умел “прогать”, писать программы, разрабатывать приложения, и то очередное слово которым называют простой труд “записывания решений для проблем”.

Точно так же как не умел держать ложку, читать и даже ходить. Удивительно, не правда?

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

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

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

Подобное произошло и с функциями, в то время я помогал подруге с математикой, естественно в свободное от баловства с программированием время. И тут меня осенило и я понял – функция это просто коробка, в которую что-то отправляется, и из которой что-то выходит (а иногда и что-то вне коробки меняется, но это такое, опасное баловство).
Причем это коробка является по сути надстройкой над обычными математическими функциями, графики в 5-7 классах помнишь? Ты “подставляешь” x чтобы получить y и потом рисуешь по точкам линию.

Такое же произошло и с указателями когда я баловался с Си.

Подобные понял моменты происходили со многими (если не со всеми) концептами программирования – я не мог что-то “понять”, спотыкаясь и кое-как решал задачу связанную с этой темой, а потом, уже далеко после этих спотыканий размышляя совершенно о другом я понимал. You know, the “eureka!” moment.

И теперь я сижу тут, спустя всего-то жалких 3 года опыта и размышляю: у многих так и не получается понять программирование, многие вроде понимают, но могут лишь поверхностно по шаблонам что-то выполнять – как я до того как приходило понимание. Но есть и те, у кого тоже “щелкнуло” и они поняли.

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

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

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

Вследствие чего: начнем.

Базовые концепты программирования

Которые не ограничиваются, но всегда включают:

  • assignation – присваивание – то с чего пожалуй мы практически всегда начинаем что-либо сколь серьезнее Hello world – x = 42,
  • iteration – итерация – массивы и все связанное с ними и им подобным “проходимым” – итерируемым,
  • delegation – делегирование, или в простонародье – функции – передача работы третьим “лицам”.

Почему эти 3? Потому что они являются основанием – базой – можно сказать любой программы: у нас есть данные (assignation) в некоторой структуре (iteration) которые мы для удобства перекидываем между хотя бы несколькими функциями (delegation). Абсолютно всегда нам нужно такое основание? Конечно нет, но тем не менее именно эти концепты ложат базу для любой практической задачи, потому что:

  • программирование это работа с данными – что-либо, которое отправляется куда-либо, должно иметь человеческие ярлыки – что отправляется и куда – за это и отвечают переменные – подписи что из себя представляет вон та переменная и вон та функция.
  • программирование это работа с реальными данными – люди, товары, заказы, статьи – абсолютная часть людских данных типизируема в коллекции – массивы – сходной структуры;
  • программирование это разумная работа – логические модули и абстракции – функции – присутствуют в любых программах, которые пишут нормальные люди – держать в уме тысячу строк кода которые делают ВСЕ в одной общей песочнице это веселье для отъявленных мазохистов;

Надеюсь я тебя не убедил, и ты готов покинуть страничку, до встречи!

Заинтересованных прошу проследовать в следующую камеру комнату:

Assignation

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

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

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

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

Некоторая часть памяти отводится под нужды оставленные компилятором и прочими добродетелями, остальная же отдается во власть программистов. У современных программистов власть она такая, иллюзорная – все по факту решает главенствующая машина компилятора\интерпретатора во главе с Garbage Collector-ом. У старых же интереснее – под любые нестандартные данные нужно было говорить где и сколько оперативной финансов памяти выделить.

Но итог один – при “создании” чего-либо, мы просим ОС (компьютер) застолбить место в ОЗУ под то, что мы туда положим и будем потом оттуда брать: например int x = 42 попросит компьютер выделить 4 байта (скорее всего, эта деталь зависит от процессора, ОС и компилятора, помимо прочих) в памяти под значение 0...00101010 по некому адресу, который от нас в случае высоких языков скроют, а в случае Си и пониже могут и показать (если мне оперативка не изменяет).

4 байта это 32 бита – 32 нолика или единички в которых в двоичной системе может хранится или от -2,147,483,648 до 2,147,483,647 или от 0 до 4,294,967,295 десятичных значений. Или-или потому что по старинке наличие знака минус хранилось как бит входящий в общие 32 – или получай 2 миллиарда значений в обе стороны с занятым 32-ым битом под знак, или 4 миллиарда в одну, со свободным под размер самого числа битом, что тебе больше подходит. Естественно “размер” может быть и 1 байт и все ~8-16++ для менее или более весомых типов числовых данных.

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

В случаях же, когда мы хотим что-то посерьезнее чисел, добрые ладошки GC (Garbage Collector-а) закрывают нам глаза, и он нежно шепчет “не переживай, я сам со всем справлюсь” потихоньку в сторонке пальцами на ногах выделяя память в специальной области, где помимо чтения и записи доступно пересоздание или расширение размера выделения – хоть 4 хоть ~2e9 байт.

Когда мы создаем, к примеру, массив, создается поле “заголовка” какой размер и “откуль-докуль” ширится массив, если нам нужно больше размера чем было выделено, есть два пути:
– если место после “докуль” еще свободно, можно просто расшириться,
-иначе нужно переезжать – берется какой-то размер, как правило в два раза больший текущего и выделяется новая область
туда копируются старые значения (а чаще ссылки на значения разбросанные где-то так по памяти) и следом вставляются новые данные.

Поэтому спасибо GC и абстракциям что сейчас мы можем еще раз прочитать текст выше, поморгать, и забыв как страшный сон, просто сделать array.push('whatever').

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

Iteration

The fun part.

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

Поздравляю, ты изобрел массив и цикл в 6 лет!

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

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

Но.. давай потерпим секунду. Ты сейчас где? В прихожей, правильно. То есть мы можем записать твой дом как:

home = ['прихожая']

Мы собрались на кухню, поэтому сразу ее и добавим: home = ['прихожая', 'кухня']. Что у нас получилось? Массив. Я вроде говорил что программирование это работа с реальными данными?

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

home = ['прихожая', 'кухня', 'гостиная']

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

Давай внесем структуру в твой дом:

home = [
    { name: 'прихожая', doors: ['кухня', 'гостиная', 'подъезд'] },
    { name: 'кухня', doors: ['прихожая'] },
    { name: 'гостиная', doors: ['прихожая'] },
]

Ура, теперь у нас есть комнаты (хеши с видом { name, doors }) и в каждой комнате даже есть одна или более дверей! Ты даже можешь сбежать в подъезд, если тебе все еще неуютно в таком пустом доме, даже присесть негде!

..., {
    name: 'гостиная',
    doors: ['прихожая'],
    furniture: [{ name: 'кресло', location: 'слева от двери' }],
}, ...

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

Если хочешь, можешь заняться оборудованием своего дома дальше – просто скопируй код выше в консоль браузера (F12 или сочетание клавиш Ctrl+Shift+I), не забудь обновить часть гостинной чтобы было на что присесть во время этого трудоемкого процесса!
Если уже успел скопировать дом с тремя комнатами, можешь обновить только прихожую через home[2] = ...

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

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

  • home это массив, очевидно, внутри которого находится несколько:
  • “комнат” – home[0] первый элемент массива home, к примеру – это хэш, или же тоже массив, только ассоциативный, под которыми мы по уговору подразумеваем комнату,
  • комната это абстракция, в которой мы договорились рассказать ее название (name), куда “ведут” (опять же по уговору) из нее двери (doors), и опционально какая в ней есть мебель (furniture)
  • мебель, при этом, тоже абстракция по договоренности – мы говорим что это за мебель такая (опять name) и где она “находится” – такая же абстракция как и двери – (location)

Надеюсь ты заметил – несмотря на то что мы можем говорить о home как о каком-то доме, по факту это просто данные с ярлыками, которые мы придумали – ты можешь добавить, например, walls в свои комнаты, и считать что это обои на стенах, но пока ты не убедишь меня или кого-либо еще в этом, они остаются обоями лишь в “твоем воображении”.

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

home = [
    [[1, 2, -1], 'прихожая'],
    [[0], 'кухня'],
    [[0], 'гостинная', [[-1, 0], 'кресло']],
]

То в моем воображении это означает точно тот же дом что и ранее, просто вместо хешей я решил везде использовать массивы, вместо названий комнат я просто записал индексы в массиве home а вместо расположения мебели я придумал систему координат где -1 это “слева” а 0 это “в центре перед дверью”.

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

Что я хочу сказать? Массив это то, что хочешь ты. Просто из опыта умные люди посмотрели и решили что большинство данных проще представить нумерованным списком – массивом – или очередью, или “словарем” – еще одним названием для Hash Map.

Чтобы понять массивы (и структуры данных), тебе нужно понять что ты хочешь этим массивом абстрагировать – дом, людей, клетки на шахматной доске, или просто набор случайных цифр. Чем лучше ты научишься убеждать себя в абстракциях, тем легче будет “понимать” что posts это просто записи в блоге, а не какой-то “массив с объектами в которых есть title, content, author“.

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

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

Delegation

И тут мы подходим, наконец, к последней остановке – делегация (действий).

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

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

Не слишком сложно? Поздравляю, ты знаешь что такое function в программировании.

Учитывая что все программирование это командование чем-либо, как правило компьютером, то функции это единичные задачи в которые мы можем заключать набор действий, включая другие функции:

Менеджер назначенный следить за закупками может точно так же назначить своего помощника обзванивать транспортные компании и составить список компаний с выгодными условиями;

Точно так же мы можем сделать функцию “вывод” которая нарисует дом из рассказа про массивы, внутри которой мы для каждой комнаты вызовем функцию “нарисовать комнату”, которая может в свою очередь дергать “нарисовать дверь” и\или “нарисовать мебель”, а может и не дергать, делая все самостоятельно – как уже говорил, командуем мы, и решаем как нам будет удобнее менять команды тоже мы.

Note for future generations, или же памятка на будущие приключения:

В идеале все функции которые мы придумываем должны быть чистыми – pure – то есть не вызывающих каких-либо изменений за пределами своих прямых полномочий (области видимости), но в реальном мире мы часто сталкиваемся с другими программистами с другими взглядами, и вмешательство в “общую песочницу” неизбежно.
Что делает нашу закрытую коробку не такой уж закрытой – из нее торчат провода и они трогают все вокруг – задают свои взгляды (присваивают общие переменные) и\или дергают другие “открытые” коробки (вызывают функции, которые присваивают общие переменные).

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

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

И вроде бы все что можно было кратко пояснить, но у интересующегося может возникнуть вопрос: а как же “ифы”?! Что более политкорректно означает: а что насчет контролирующих выражений (control statements), в простонародье if, else, а иногда и switch.

Control

Как бы комично не было, они важны, но для многих программ они не обязательны (хоть и очень полезны и стандартны).

К примеру, такой классический пример, если x равно true то сделать то-то, иначе то-то. Его можно записать без явного if:

...
const choices = {
    true: function doActionA(){ ... },
    false: function doActionB(){ ... }
}
choices[x]()
...

Удивительно, просто. и, как ни странно, в случаях больших деревьев условий применяется подобный подход – присваивается дерево доступных исходов, потом по нему проходит проверяльщик. Получается удобнее и более редактируемо нежели полотно из вложенных if-else.

Другой классический пример – если x равно что-то, то переменную y сделать равной 37 + переменная z, иначе присвоить 10:

const xSomething = {
    true: function(_z) { return 37 + _z },
    false: function() { return 10 }
}
var y = xSomething[x == 'что-то']()

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

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

const data = { x: 42, y: 24 }
let z
if (a === 'x')
    z = data.x
else
    z = data.y

сделать просто:

const data = { x: 42, y: 24 }
let z = data[a]

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


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

Что оправдывает мою изначальную задумку сделать первую статью-обозритель после которой писать еще 3, по каждому концепту, чего я очень не хотел делать – “серии уроков” привязывают к серии и теме, ограничивая “свободу передвижения”.

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

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

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

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

В остальном – удачи в поимке своего щелчка и большего понимания.

nil commento load