Накидал примерный прототип диаграммы гантта.
Красоты в нем, как видите, пока нет, но на то он и прототип. Сделан он вручную на свг. Это такой векторный графический формат, в котором изображение представлено тегами, типа как в хтмл. Причем элементы изображения можно так же оформлять с помощью цсс, например, добавлять ховеры.
Далее предстоит написать джаваскрипт, автоматически генерирующий такие свг-элементы.
На этой неделе придумывал идеи для собственного проекта, на котором можно было бы применять новые знания.
Вариантов было много: чат-боты для телеграма, тайм-трекер, текстовый квест, приложение для учета расходов, симулятор эволюции. Сложность в том, что нужно выбрать что-то достаточно простое, чтобы достичь какого-то видимого результата за пару недель, и чтобы этот результат при этом был более-менее интересным.
В результате решил делать приложение для рисования диаграмм гантта в браузере. Диаграмма гантта — это такие горизонтальные столбцы, отображающие протекание каких-то событий во времени. Вот картинка из гугла для наглядности:
План примерно следующий:
Сверстать в хтмл шаблон того, как будет выглядеть диаграмма.
Написать на джаваскрипте программу, которая будет на основе файла с данными рисовать страницу с диаграммой.
Добавить интерактивности, чтобы столбики можно было создавать и редактировать прямо на странице, а не в файле с данными.
Сделать бекэнд, чтобы диаграммы можно было сохранять.
Запустить все это на сервере с привязанным доменом.
Я расплывчато представляю, как сделать первые два шага. По оптимистичной оценке, планирую их реализовать за пару недель. А вот как делать интерактив и бекэнд, пока не представляю совсем. Придется разбираться по ходу дела.
Параллельно с проектом прошел курс «программирование, управляемое данными» и закрепил чтением соответствующей главы из СИКП. Попробую описать, что такое помеченные данные и для чего они нужны.
Пару недель назад я писал о том, как на основе пар создавать составные данные, упаковав ими несколько разных значений в один объект. Вот, например функция для создания точки на плоскости:
const makePoint = (x, y) => cons(x, y);
Имея точки, можно создавать объекты посложнее, например линию или прямоугольник.
const makeLine = (startPoint, endPoint) =>
cons(startPoint, endPoint);
const makeRect = (topLeft, bottomRight) =>
cons(topLeft, bottomRight);
Прямоугольник тут расположен параллельно горизонту, поэтому для однозначного его представления достаточно координат двух диагонально противоположных углов. Способ этот не единственный. Такой же прямоугольник можно выразить одной точкой и парой чисел, описывающих его ширину и высоту:
const makeRect2 = (topLeft, width, height) =>
cons(topLeft, cons(width, height));
Угол прямоугольника при этом не обязательно брать верхний левый, подойдет любой. Если существует много разных способов представить один и тот же объекет, то какой выбрать?
Выбирать один совсем не обязательно. Можно реализовать все способы, а потом использовать их по ситуации. Когда у нас есть два противоположных угла — вызывать makeRect. Когда есть один угол и ширина с высотой — вызывать makeRect2.
a = makeRect(makePoint(0, 2), makePoint(4, 1));
b = makeRect(makePoint(0, 0), 3, 5));
Со множеством вариантов представления одного объекта есть проблема. Допустим у нас есть некий прямоугольник rect
, и мы хотим узнать его ширину. В зависимости от того, каким образом представлен этот прямоугольник, к нему нужно применить разные селекторы: rectWidth
или rect2Width
.
Как определить, каким образом был создан объект? Можно при его создании оставить об этом метку (другим словом — тег).
const makeRect = (topLeft, bottomRight) =>
tag("twoPoints", cons(topLeft, bottomRight));
const makeRect = (topLeft, width, height) =>
tag("onePoint", cons(topLeft, cons(width, height)));
Объекты, которые помимо данных содержат тег с типом, и есть помеченные данные.
Теперь, чтобы узнать ширину любого прямоугольника нужно сначала узнать его тип, посмотрев на метку, и, в зависимости от типа прямоугольника, применить нужный селектор:
tag = typeTag(rect);
if (tag === "twoPoints") {
width = rectWidth(rect);
};
if (tag === "onePoint") {
width = rect2Width(rect);
};
Способ рабочий, но весьма неудобный. Если в программе для прямоугольника предусмотрено двадцать вариантов представления, то придется проверять объект на соответствие каждому из них. А при добавлении двадцать первого варианта придется искать в коде все эти проверки и добавлять еще одну.
Было бы намного лучше, если один и тот же селектор rectWidth работал вне зависимости от того, какого типа прямоугольник ему дали. О способах реализовать такой селектор в другой раз.
Хочу немного рассказать про крутейший сайт, на который вчера сослался: wiki.c2.com.
В последнее время, когда я гуглю какие-нибудь фундаментальные вопросы касательно программирования, то все чаще в топе появляются ссылки на этот сайт. Причем информация на нем всегда настолько по делу, что теперь я незнакомые термины ищу сначала там, а потом уже в гугле.
Вдобавок из книжки «инноваторы» про историю ИТ, узнал, что это и есть оригинальная вики, с которой википедия потом копировала принцип.
Короче, всем рекомендую. В одном только разделе про историю программирования можно залипнуть на несколько часов.
Доделываю практические задачи из курса про последовательности. Самая сложная из них, пожалуй, задача про ферзей.
Дан список чисел, типа [2, 4, 1, 4]
. Он описывает положение ферзей на шахматной доске. Значение в списке соответствует строке, а положение элемента — столбцу. В столбце, получается, всегда только один ферзь. Нужно по этому списку определить, атакует ли друг друга хоть одна пара ферзей.
Когда я не знаю, с какой стороны подступиться к задаче, то стараюсь сначала описать решение в наиболее общих терминах, а потом постепенно перехожу ко все более частным подробностям. В этом помогает прием «принимать желаемое за действительное».
Например: как определить, что ни один ферзь на доске не атакует другого? Нужно проверить, атакует ли первый ферзь какого-нибудь из следующих, и повторить эту проверку для каждого ферзя по очереди.
Этот алгоритм так и записываю, как будто у меня уже есть функция isFirstSafe
, которая имея список позиций, проверяет на безопасность первого ферзя.
Потом начинаю думать, как реализовать эту функцию isFirstSafe
. Чтобы проверить что первый ферзь в безопасности, нужно проверить, атакует ли его каждый из последующих ферзей. Чтобы это сделать, нужно знать, находятся ли две фигуры на одной диагонали. Но пока что не задумываюсь, как это проверить, а делаю вид, что для этого есть уже готовая функция sameDiagonal
. В конце остается написать и ее.
В результате, вместо одной сложной и непонятной задачи нужно решить несколько штук попроще. На скриншоте мое итоговое решение.
Суть подхода в том, что если пока не знаешь, как решить каждую из подзадач в отдельности, это не должно мешать решать остальные. Вот неплохая ссылка на тему: http://wiki.c2.com/?WishfulThinking.
Во-первых, проект помог освоиться в запутанной инфраструктуре джаваскрипта, в которой я долго не мог разобраться самостоятельно. Когда я открывал чей-нибудь проект на гитхабе, то приходил в растерянность от огромного количества непонятно для чего предназначенных файлов. Также было неясно, как начать разрабатывать что-то свое. Что нужно писать в package.json, для чего предназначен Makefile, нужен ли мне бабель и, если нужен, то как им пользоваться?
Проект дает алгоритм действий, как правильно настроить рабочее окружение, чтобы написать свое приложение и опубликовать его в каталоге нпм.
Во-вторых, начал понимать, как следует организовывать код проекта, чтобы его было легко поддерживать и расширять.
Когда я до этого пытался писать какие-то свои программы, то часто просто складывал все в один файл. А когда его объем начинал приближаться к паре сотен строк, то ориентироваться в нем становилось настолько трудно, что я забрасывал всю затею.
Итоговый код всего проекта содержит 175 строк кода. При этом он логично разделен на отдельные модули примерно по 30 строк, так что продолжение работы над вряд ли вызовет затруднения.
За пять дней работы я выполнил 9 заданий и сделал 31 коммит в проекте. В сумме на это ушло 12 часов, то есть примерно по 2,5 часа в день.
Проектом остался доволен. За неделю интенсивной работы узнал столько нового, на что обычно уходит не меньше месяца расслабленного самостоятельного изучения.
Продолжу проходить на хекслете курсы по фронтенду, читать книжки и делится узнанным тут. До следующего проекта минимум три недели, а попробовать применить новые знания на практике руки чешутся уже сейчас. Ближайшие пару дней подумаю, какой бы проект можно было начать пилить.
Ура! Только что получил подтверждение о прохождении первого проекта на хекслете. В последних трех заданиях нужно было добавить в приложение по отдельной игре.
В одной определяешь, является ли число простым. В другой отгадываешь пропущенный элемент арифметической последовательности. В третьей балансируешь число (то есть, преобразовываешь число так, чтобы любые две цифры в нем отличались максимум на единицу, а сумма всех цифр была как в изначальном числе. Например: 4181 → 3344).
Над балансировкой пришлось попотеть, и то, в итоге мое решение получилось хоть и рабочим, но довольно некрасивым. В остальном, последние шаги дались легче всего, так как после построения правильной архитектуры программы, добавлять в нее новые игры не сложнее, чем решать упражнения на тривиальные алгоритмы.
Сегодня отпраздную завершение проекта, а завтра напишу подброно о своих впечатлениях и о том, сколько затратил времени на выполнение.
Закончил на проекте пятый и шестой шаг (из девяти). Так что половина уже пройдена. На пятом застрял дольше всего, так как долго не мог придумать архитектуру игры, которая устроила бы ментора.
Игра состоит в том, что юзер отвечает на вопросы в консоли пока не ошибется. После трех правильных ответов объявляется победа и игра заканчивается. В пакете есть несколько игр с разными вопросами. В одной, например, угадываешь, четное ли число. В другой ищешь НОД двух чисел.
Условие правильной архитектуры в том, чтобы можно было добавлять файлы с новыми играми, не изменяя при этом уже существующий код.
Первое решение, которое пришло в голову — создать в главном файле проекта функцию run-game которая в зависимости от аргументов задает разные вопросы. Аргументы эти получать из модуля отдельной игры some-game.js , импортированного в главный файл. Проблема этого варианта в том, при добавлении в проект модуля с новой игрой, придется каждый раз редактировать основной файл, чтобы импортировать в него новую игру.
Правильное решение заключается в том, чтобы в модуль с игрой импортировать функцию run-game . Называется этот метод «инверсия зависимостей», потому что низкоуровневый модуль тут зависит от высокоуровнего, хотя обычно принято наоборот.
Прошел в проекте еще пару чекпоинтов. На третьем шаге закончилась подготовка и началась активная разработка самой игры.
Код проекта теперь автоматически анализируется при помощи приложений codeclimate, eslint, travis.
После каждого коммита на гитхаб codeclimate пытается найти в нем признаки говнокода вроде слишком глубокой вложенности циклов и условий, чрезмерное дублирование и т. п. По итогу анализа выставляет проекту оценку того, насколько легко в нем поддерживать код.
Проверяет код на соответствие разного рода стилистическим правилам. Нужен, чтобы код выглядил аккуратно и стандартизованно, чтобы нигде не было лишних пробелов, неправильных кавычек, чтобы табулиция была везде одинковая. Можно подключить любые правила, которые посчитаешь нужными.
После каждого коммита запускает тесты, чтобы удостовериться, что этот коммит ничего не сломал. В нашем проекте пока никаких тестов нет, вместо них код проходит проверку еслинтом.
После подключения всего вышеперечисленного, на странице моего проекта в нпм теперь красуются два бейджика, сообщающих, что код в нем прекрасен.
Отправил на проверку два первых шага в проекте.
На первом шаге ученика «проводят за ручку» через последовательность действий, которые нужно совершить для публикации своего пакета на npmjs.com. Действий этих не так уж мало: заполнить конфигурационный файл package.json , добавить в него в качестве зависимостей бабель, установить зависимости. Написать npm-скрипты для запуска бабеля. Написать make-скрипты для запуска npm-скриптов. Опубликовать, наконец, пакет.
Второй шаг покороче: нужно подключить библиотеку для чтения ввода в консоль.
На этом подготовка к написанию непосредственно программы не заканчивается. На следующем шаге предстоит подключить инструменты для автоматической проверки качества кода.
После первых двух шагов получаем опубликованную в каталоге npm текстовую игру. Пока что игра заключается в вводе разных имен в консоль и получении приветствия в ответ. Любители подобного геймплея могут установить пакет себе:
npm install -g braingames-ignat