-
REPL, Cider, Emacs (часть 1/4)
Все части
Оглавление
Эта глава расскажет о REPL — фундаментальном свойстве Clojure. Так называют интерактивную работу с языком, когда программу наращивают постепенно. Мы рассмотрим, что такое REPL-driven development и почему, однажды познав, от него трудно отказаться.
Аббревиатура REPL происходит от четырех слов: Read, Eval, Print и Loop. Дословно они означают прочитать, выполнить, напечатать и повторить. REPL — устойчивый термин, под которым понимают интерактивный режим программы.
Многие современные языки предлагают интерактивный режим. Как правило, он запускается, если вызвать интерпретатор без параметров. Например, команды
python
илиnode
запустят интерактивные сеансы Python и Node.js. В Ruby для этого служит утилитаirb
(где i означает interactive). REPL поддерживают не только интерпретаторы, но и языки, которые компилируются в байт-код (Java, Scala) или машинный код (Haskell, SBCL).Несмотря на это разнообразие, именно в Лисп-системах REPL имеет решающее значение. Если в Python или Node.js его рассматривают как приятное дополнение, то в Лиспе он необходим. Разработка на любом Лиспе зависит от того, насколько хорошо вы взаимодействуете с REPL. На REPL так или иначе опираются все инструменты и практики, документация, видеоуроки и так далее.
В мире Лиспа ходит понятие REPL-driven development. Это стиль разработки, когда код пишут малыми порциями и запускают в REPL. С таким подходом сразу видно поведение программы. Легче проверить неочевидные случаи, например вызвать функцию с
nil
или обратиться к ресурсу, которого не существует.Другой полезный сценарий для REPL — извлечь данные из сети и исследовать их. Эта задача идеально ложиться на интерактивный режим. Как правило, HTTP-запрос предполагает несколько этапов: его подготовку, отправку, чтение тела и поиск нужных полей. Эти шаги проходят интерактивно методом проб и ошибок. Позже мы рассмотрим пример HTTP-запроса в сеансе REPL.
-
Teleward: a CAPTCHA bot for Telegram in Clojure + GraalVM
(This is a copy of the readme file from the repository.)
Teleward is a Telegram captcha bot written in Clojure and compiled with GraalVM native image. Runs on bare Linux/MacOS with no requirements. Fast and robust.
Why
Telegram chats suffer from spammers who are pretty smart nowadays. They don’t use bots; instead, they register ordinary accounts and automate them with Selenium + web-version of Telegram. Personally, I found Shieldy and other bots useless when dealing with such kind of spammers. This project aims the goal to finish that mess.
Another reason I opened Teleward for is to try my skills in developing Clojure applications with GraalVM. Binary applications are nice: they are fast, and they don’t need installing JDK. At the same time, they’re are still Clojure: REPL is here, and that’s amazing.
Features
- This is Clojure, so you have REPL! During development, you call Telegram API directly from REPL and see what’s going on.
- The bot can be delivered either as a Jar file or a binary file (with Graal).
- When Graal-compiled, needs no requirements (Java SDK, etc). The binary size is about 30 Mb.
- At the moment, supports only long polling strategy to obtain messages. The webhook is to be done soon.
- Keeps all the state in memory and thus doesn’t need any kind of a database. The only exception is the current offset value which is tracked in a file.
- Supports English and Russian languages.
- Two captcha styles: normal “1 + 2” and Lisp captcha “(+ 1 2)”.
- The
+
,-
, and*
operators are corresponding Unicode characters that prevent captcha from naive evaluation.
Algorithm
The bot listens for all the messages in a group. Once a new pack of messages arrives, the bot applies the following procedure to each message:
- Mark new members as locked.
- Send a captcha message to all members.
- Unless an author of a message is locked, delete that message.
- If a message is short and matches the captcha’s solution, unlock a user and delete the catpcha message.
- If a locked user has posted three messages with no solution, ban them.
- If a locked user hasn’t solved captcha in time, ban them as well.
Please note: the bot processes only messages no older than two minutes from now. In other words, the bot is interested in what is happening now (with a slight delay), but not in the far past. This is to prevent a sutuation what a bot had been inactive and then has started to consume messages. Without this condition, it will send captcha to chat members who have already joined and confuse them.
Java version
To make a Jar artefact, run:
make uberjar
The
uberjar
target callslein uberjar
and also injects theVERSION
file into it. The output file is./target/teleward.jar
.Binary version, Linux
Linux version is built inside a Docker image, namely the
ghcr.io/graalvm/graalvm-ce
one withnative-image
extension preinstalled. Run the following command:make docker-build
The output binary file appears at
./target/teleward
.Binary version, MacOS
-
Install GraalVM locally.
-
Install the “native image” extension:
gu install native-image
- Then
make
the project:
make
Setting Up Your Bot
-
To run the bot, first you need a token. Contact
@BotFather
in Telegram to create a new bot. Copy the token and don’t share it. -
Add your new bot into a Telegram group. Promote it to admins. At least the bot must be able to 1) send messages, 2) delete messages, and 3) ban users.
-
Run the bot locally:
teleward -t <telegram-token> -l debug
If everything is fine, the bot will start consuming messages and print them in console.
Configuration
See the version with
-v
, and help with-h
. The bot takes into account plenty of settings, yet not all of them are available for configuration for now. Below, we name the most important parameters you will need.-
-t, --telegram.token
: the Telegram token you obtain from BotFather. Required, can be set via an env variableTELEGRAM_TOKEN
. -
-l, --logging.level
: the logging level. Can bedebug, info, error
. Default isinfo
. In production, most likely you will seterror
. -
--telegram.offset-file
: where to store offset number for the nextgetUpdates
call. Default isTELEGRAM_OFFSET
in the current working directory. -
--lang
: the language for messages. Can been, ru
, default isru
. -
--captcha.style
: a type of captcha. Whenlisp
, the captcha looks like a lisp expression(+ 4 3)
. Any other value type will produce4 + 3
. The operator is taken randomly.
Example:
./target/teleward -t <...> -l debug \ --lang=en --telegram.offset-file=mybot.offset \ --captcha.style=normal
For the rest of the config, see the
src/teleward/config.clj
file.Deploying on bare Ubuntu
-
Buy the cheapest VPS machine and SSH to it.
-
Create a user:
sudo useradd -s /bin/bash -d /home/ivan/ -m -G sudo ivan sudo passwd ivan mkdir /home/ivan/teleward
- Compile the file locally and copy it to the machine:
scp ./target/teleward ivan@hostname:/home/ivan/teleward/
- Create a new
systemctl
service:
sudo mcedit /etc/systemd/system/teleward.service
- Paste the following config:
[Unit] Description = Teleward bot After = network.target [Service] Type = simple Restart = always RestartSec = 1 User = ivan WorkingDirectory = /home/ivan/teleward/ ExecStart = /home/ivan/teleward/teleward -l debug Environment = TELEGRAM_TOKEN=xxxxxxxxxxxxxx [Install] WantedBy = multi-user.target
- Enable autoload:
sudo systemctl enable teleward
- Manage the service with commands:
sudo systemctl stop teleward sudo systemctl start teleward sudo systemctl status teleward
For Jar, the config file would be almost the same except the
ExecStart
section. There, you specify something likejava -jar teleward.jar ...
.Health check
The bot accepts the
/health
command which it replies to “OK”.Further work
- Implement webhook.
- Add tests.
- Report uptime for
/health
. - More config parameters via CLI args.
- Widnows build.
© 2022 Ivan Grishaev
-
Невышедший подкаст про Лисп. Работа над ошибками
История о том, как я участвовал в подкасте про Лисп и Кложу, который не вышел в свет. Это ни в коем случае не обвинения любой из сторон, а попытка понять, что пошло не так. Кроме того, я вынес кое-какие уроки, чтобы подобное не повторилось в будущем.
Три месяца назад мне постучались одни Крутые Ребята с предложением выступить в подкасте. Тема — Лисп и Кложа. Назначили дату и время, записали подкаст. Еще до записи кое-что показалось мне странными, но я утешал себя тем, что ребята Крутые и знают, что делают. Поэтому оставил на их усмотрение.
Итак, записали, обменялись любезностями, супер-супер, огонь-огонь. Проходит месяц, другой. Ребята выпускают эпизоды, но моего среди них нет. Я спокойно жду, потому что знаю: у опытных подкастеров эпизоды расписаны на месяц. Кроме того, интересный материал иногда оставляют “в консервах”, то есть на случай, когда ничего интересного нет.
В какой-то момент не стерпел и вежливо спросил, как дела с выпуском. Оба собеседника получили сообщение и… тишина. Проходит семь часов, я аккуратно повторяю вопрос. Наконец отвечают: выпуска не будет. Слово за словом выясняется, что ведущий им недоволен, получилось очень сложно, слушатели не поймут.
Что ж, не будет и бог с ним, бывает. Но удивляюсь, почему нельзя было сказать об этом раньше вместо того, чтобы прятаться? Ведь Ребята такие Крутые: открытые, позитивные, вот это все. Но как только пошло не так, началась тишина.
Безо всяких претензий пишу им: понимаю, не в обиде. Можно выложу у себя в блоге? Для этого нужна дорожка ведущего: во время записи у меня осталась только моя дорожка. Сведу и смонтирую сам. Отвечают: нужно согласие Главного Человека, сейчас спросим его и ответим. Я аж поморщился от этой лжи, потому что у тех, с кем я разговаривал, было достаточно полномочий для принятия решения. Они всем управляют, и вдруг оказывается, нужно спросить. Стоит ли говорить, что через месяц мне никто не ответил.
По итогам событий я сделал следующие выводы.
(1) Перед выступлением нужно изучить аудиторию, понять ее уровень. Уже после согласия я понял, что это популярный подкаст по принципу “все обо всем”. Рассказывать такой аудитории про Кложу и Лисп трудно. Это специфичные технологии, и слушатель должен знать азы программирования. Если этого нет, рассказ про Лисп не принесет удовольствия ни одной из сторон, что и подтвердилось. Во время записи ведущий часто перебивал меня с просьбой объяснить тот или иной момент. Я, напротив, был недоволен, что мы топчемся на месте. К концу второго часа мы наконец выяснили, что код на Лиспе состоит из списков, и в нем есть REPL и макросы. Это десятая часть того, что я мог бы рассказать про Лисп подготовленной аудитории.
(2) Стороны должны согласовать тезисы. Когда я выступал в Подлодке, ведущий потребовал с меня тезисы и прислал образец с прошлого выпуска. Это в высшей степени правильно, потому что делает выпуск согласованным: гость и ведущий на одной волне. В случае с Крутыми Ребятами тезисы не понадобились: ведущий сказал, что подготовится сам. Позже он “ради интереса” попросил выслать тезисы для Подлодки, что я и сделал. Однако ни один тезис мы не обсудили.
(3) Если что-то идет не так, надо честно признаться и сказать правду, не дожидаясь разборок. Меня расстроило не то, что эпизод не вышел, а отношение по принципу “молчим, авось отъебется”. Не надо так, потому что со страниц блогов и проектов вы Крутые, и вдруг такое. Реально расстраиваешься.
(4) В то же время, это урок мне. Вдруг я тоже произвожу хорошее впечатление в блоге, а на деле сливаюсь? Не обидел ли я кого нибудь таким же образом? Пишу одно, делаю другое? Есть над чем подумать.
В завершение текста покажу одно видео. Выступает Илья Синельников, тема “Провал — это хорошо”. Одно из немногих видео, которое я однажды посмотрел и тепло вспоминаю. Если коротко — важно не то, как вы ведете себя, когда все хорошо. Истинно важно — когда дела идут не по плану; тут и проявляется самое важное. Полчаса удовольствия:
-
Сервис не смог
Иные сервисы уведомляют о всякой ерунде — новом дизайне, посте в блоге или просто какой-то дичи — и забывают о важных вещах. Например, проблемах с платежами и авторизацией. Вот три примера из практики.
До известных событий (которые в России нельзя назвать коротким словом) сервис Gumroad регулярно переводил деньги за книгу на Paypal. Как только Палка отвалилась, пропали и выплаты. Однако Gumroad не прислал ни одного письма об этом.
Как программист я понимаю: вызов к PayPal обернут в try/catch, ошибка падает в лог. Ладно, а что потом? Получили сотни ошибок, кто будет с ними разбираться?
По-хорошему надо так: делать задачу на выплату, повторять ее с интервалом в час, день, неделю. Если не вышло — слать пользователю письмо, а не молчать, словно ничего не происходит.
То же самое с Leanpub. У меня там короткая книжка на английском, и раз-два в месяц ее покупают. Выплаты прекратились, все молчком, никаких уведомлений. Неужели так трудно отправить письмо?
Наконец, Амазон. Какое-то время назад я жаловался, что не могу зайти в аккаунт. Думал, что блокирует Амазон, но все оказалось проще. Когда-то давно, подключая двухфакторную авторизацию, я поленился открыть Google Authenticator и выбрал вариант с смс — зря!
Оказалось, сервис, который отправляет смс, теперь не делает этого для российских номеров. Без понятния, как это происходит: возвращает ли он статус 403? Или отдает 200, чтобы молча выкинуть мое смс? И кто виноват? Я, потому что живу в России? Амазон, потому что сервис не работает, а к нему обращаются? Сервис, потому что не смог?
Кто кого должен уведомлять? Амазон меня, потому что дело происходит на странице Амазона? Или смс-сервис уведомляет Амазон, потому что первый не смог? Все сломано: смс не доходит, Амазон об этом не знает, сервис не считает нужным доложить. Скорей всего, записали в лог и забыли.
Как вы поняли, так быть не должно. Клиенту все равно, какие сервисы вызывает ваш сервис. Чей домен открыт в браузере, тот и уведомляет. Во всех случаях письма должны были прислать Gumroad, Leanpub и Amazon — то, что я вижу и с чем работаю. Не нужно переводить стрелки на сторонний сервис, который не смог. В данном случае не смогли упомянутые фирмы.
Все это я переживу, а вот взять на заметку стоит.
-
Use format rather than str
I’d like to share a Clojure trick I’ve been using for years. It’s simple, it makes your code safer; I always highlight it when making reviews. The trick is: don’t use the
str
function to concatenate strings. Instead, useformat
. A couple of examples:(let [username "Ivan"] (str "The user `" username "` has been created.")) ;; "The user `Ivan` has been created." (let [user-id 5234] (str "/files/uploads/" user-id)) ;; "/files/uploads/5234"
The first point in favour of this approach is,
str
turns nil into an empty string. Thus, when printing the final message, that’s unclear if a variable was an empty string ornil
:(let [username nil] (str "The user `" username "` has been created.")) ;; "The user `` has been created."
The difference between these two is important. Say, an empty string means broken validation; a title of a book, a name of a person must not be blank. But if I got nil, most likey I missed the key in a map because of a namespace:
(def user {:acme.user/id 5234 :acme.user/name "Ivan"}) (let [username (get user :id)] ...)
or the keyword/string case:
(def user (parse-json "user.json")) ;; {"id" 5234 "name" "Ivan"} (let [username (get user :name)] ...)
Now compare it to the
format
function. Thenil
value becomes"null"
when passed toformat
:(let [username nil] (format "The user `%s` has been created." username)) ;; "The user `null` has been created." (let [user-id nil] (format "/files/uploads/%s" user-id)) ;; "/files/uploads/null"
If I had my way, I would produce not
"null"
but"nil"
string fromnil
, but that’s not so important.The second point is much more serious.
Nil
values are extremely dangerous when building file paths or URLs. Imagine you’re about to delete files of a person who’s terminating their account. Most likely you store files on disk like this:files/<user-id>/avatars/... files/<user-id>/attachments/... files/<user-id>/uploads/...
Then you have a function that accepts the
user-id
parameter, then builds the right path and does recursive deletion:(defn drop-user-files [user-id] (let [path (str "files/" user-id)] (rm-rf-recur path)))
If you pass
nil
foruser-id
, the path will be"files/"
. Running that code will erase all the files of all users which would be a disaster. But if you have used format, the path would have been"files/null"
, which would just have thrown an exception saying there is no such a directory.One may say: add an assert clause for user-id right before you build a path. Like this:
(defn drop-user-files [user-id] (assert user-id "User-id is empty!") ...) ;; or (defn drop-user-files [user-id] {:pre [user-id]} ...)
In practice, you easily forget doing this and recall when the data is lost. I don’t see any reason for skipping that minor fix — change
str
toformat
— to reduce chances of a disaster.The same applies to S3 URLs. Although it’s a web-service, we all treat it as a file system. Composing S3 URLs reminds me of ordinary file paths. Again, if you’re about to drop user’s directory with uploads, be aware of the the same thing:
str
+nil
foruser-id
produce a broken path:(defn drop-s3-user-files [s3-client user-id] (let [path (str "files/" user-id)] (s3.client/delete-files s3-client path)))
If you pass
nil
into a function that recursively drops S3 files, the data is all gone.Of course, such an issue can be held with special Java classes like
Path
orURI
. But in fact, in Clojure we use them quite rarely. Most often we just concatenate plain strings as it’s enough for the task. It’s simpler and takes less code.I recommend using
str
for one purpose only — to coerce a non-string value to a string. For example:(str 5) ;; => "5" (str (random-uuid)) ;; => "154ac...b2642"
Briefly, it’s safe when the
str
function accepts strictly one argument. When there are more than one, I feel worried.I always keep in mind a real story about a guy who owned a small hosting company. He released the
rm -rf $FOO/$BAR
command among the whole park. Too sad for him, both of the env vars were unset, nor special bash flags terminating a script were set as well. The command turned intorm -rf /
with known consequences. A couple of missing vars has ruined one’s business. Bystr
-ing strings, especially when building file paths, you may easily mess up the same way (but I wish you won’t).Let’s recap:
str
turnsnil
into an empty string;- by reading such a message, you never know if a value was an empty string or nil;
- the difference between these two does matter;
- with that behaviour, it’s easy to build a weird file/S3 path and lost the data;
- instead,
format
turnsnil
into"null"
. This is much better than emptiness and solves the troubles mentioned above; - use
str
only to coerce a value to a string.
Safe coding!
-
Книга на Доброфайле
Сервис Gumroad, где продается электронная версия книги, ожидаемо перестал работать с российскими картами (а также остановил выплаты). Поэтому выкладываю книжку на российской площадке “Доброфайл”:
https://dobrofile.ru/?s=c2f86cd04
Все просто: переходите по ссылке, жмете “купить файл”. Никакой авторизации: ввели емейл и оплатили картой. На почту падает ссылка, которая действует 48 часов. Только что купил сам — работает.
Сервис довольно забавный: простой как лопата, весь такой спартанский. После тормозного, обвешанного скриптами Gumroad воспринимается как глоток воздуха. Админка товара открывается секунду — так можно было?
В общем, покупайте книжку на Доброфайле. В ближайшее время обновлю страницу проекта, публикацию на Хабре и другие места.
Напомню, что книжку, как печатную, так и электронную, всегда можно купить напрямую у меня. Напишите в личку, и я скину архив или вышлю почтой России в любую страну.
А еще я почти закончил черновик второй части, но об этом в следующий раз.
-
Правила пользования почтой
Начинающие программисты спрашивают, какую технологию учить: Питон, Джаву и прочее. Отвечая на эти вопросы, часто забываешь об электронной почте. Закрываю упущение: учитесь электронной почте! Это не так просто: вокруг почты сложился особый этикет, о котором не расскажут в онлайн-курсах. Вы должны знать его перед тем, как начнете искать работу.
Прежде всего: в деловой переписке почта важнее мессаджеров. Если есть несколько способов связаться с человеком, сначала пишите на почту. В мессаджер можно писать только по взаимному согласию или если нет других контактов.
Раздражает, когда в личку врывается незнакомый человек и вываливает стену текста. Или, наоборот, пишет сто сообщений словосочетаниями. Если у вас долгий разговор, представьтесь в мессаджере и попросите почту, чтобы написать туда развернутый текст.
В отправителе не должно быть ников вроде “nagibator666” или LenuSiK. Плохо, когда имя сгенерировано по шаблону “фамилия.годрождения”, например
semenova.81@mail.ru
. В отправителе должны быть имя и фамилия, желательно на том языке, на котором говорит получатель, скажем, Ivan Grishaev. Имя не должно быть двояким: вместо Alex пишите Alexey или Alexander.Самое важное правило: при ответе не цитируйте все письмо. Прямо сейчас, не читая этот текст до конца, откройте почтовый клиент и поставьте нужную настройку. На маке это последний чекбокс на закладке Composing. Называется он странно, но работает как надо: при ответе будет процитирован только выделенный текст, а если не выделено ничего, то ответ будет по умолчанию пустым.
Не помню, как сделать то же самое в Gmail, но там у меня выработалась привычка: после нажатия кнопки Reply я автоматом выполнял Ctrl-A Delete, чтобы стереть все из поля ввода.
Цитирование письма уходит корнями в прошлое. Раньше почтовые сервера не умели строить цепочки писем. Каждое письмо считалось отдельным. Чтобы был ясен контекст, старое письмо добавлялось цитатой к новому. Это в высшей степени избыточно и неэффективно. Позже письма научились сопоставлять по теме и префиксам
Re
, что тоже уродливо. И лишь затем придумали машинные заголовки с идентификаторами, где указано, на что отвечает это письмо.Беду с цитированием знает каждый, кто работал в больших фирмах. В обсуждении по почте борода старых писем растет как снежный ком, и каждый клиент работает с ними по своим правилам: отступами, тильдами или вертикальными чертами. Рано или поздно в тред отвечает красноглазик с консольным клиентом на Perl, где борода цитат выделена какими-нибудь
>>>
. В результате все видят пять экранов мусора с текстом недельной давности. Не недо так.Заполняйте письмо снизу вверх. Как это понимать? Обычно мы указываем получателя, тему, пишем текст и прикладываем файл. Из всех этих полей обязательно только первое — получатель. Как только он заполнен, легко отправить письмо неверным нажатием или комбинацией клавиш. За подобное письмо стыдно, оно вносит непонимание, приходится в спешке отправлять другое. Этого легко избежать методом “снизу вверх”.
Если вы отправляете файл, сначала приложите файл. Затем напишите текст. Введите тему. И только потом — отправителя. С таким подходом невозможно отправить письмо, пока оно не заполнено.
Что делать, если вы забыли приложить файл? Обычно его отправляют вдогонку со словами “забыл файлик, вот он”. Не делайте так, потому что на стороне получателя файл будет оторван от основного письма. Как его потом найти? Письмо с текстом человек найдет по ключевым словам, но как быть с файлом? Правильно сделать так: отправить повторное письмо с файлом и написать, что прошлое письмо без файла ушло по ошибке и его надо удалить. В этом случае все останутся довольны.
Ближайшая аналогия: вы отправили исходный код, и затем пишете: ой, не хватает одного файлика. Положите его в папку
foobar/lulz
, и все заработает. Это непрофессионально. Вы либо сдаете работу, либо нет. Письмо без файла, когда он должен там быть — испорченное письмо. Вместо попыток его исправить отправьте новое, на этот раз правильное.Еще о файлах. Если вы отправляете документы для просмотра, используйте PDF. Не ждите, что у собеседника установлен Word, Excel или Powerpoint — эти программы платные и открываются в сто раз медленней, чем PDF. Исключение возможно, когда вы оба работаете в этих программах.
Архивируйте файлы только в случаях, когда важна структура папок. Например, исходный код проекта. Распаковав архив, собеседник получит систему папок, готовую для сборки. Сжимать текстовые документы не нужно — это повышает энтропию и требует кликов. Не жмите изображения и другие бинарные файлы — экономия будет не более пяти процентов.
Если архиватор, то только zip. Своими rar, tar.gz и прочими платформо-зависимыми штучками вы огорчите получателя.
Файлы больше 10 Мб выносите в облачные хранилища. Желательно такие, что отдают файл по прямой ссылке, например S3. Если ссылка ведет на веб-страницу, где нужно доказать, что ты не робот, это грусть и печаль. По той же причине не пользуйтесь хостингом изображений — там показывают рекламу, открывают дичь в новых вкладках, словом, сплошное издевательство.
О композиции. Письмо начинается с обращения, затем с новой строки следует текст. Он состоит из предложений, каждое заканчивается точкой или знаками вопроса или восклицания. Три-пять предложений составляют абзац, которые отделяют пустой строкой. Абзацы обязательны, чтобы дать человеку передохнуть. Письмо заканчивается подписью без точки.
Подпись должна быть короткой, в идеале только имя. В крайнем случае — должность и минимальные контакты: телефон и ник в мессаджерах. Писать туда домашний телефон, номер ICQ и прочую дрянь не нужно. В подписи не должно быть логотипов и ссылок.
Если это вручную написанное письмо, а не рекламная рассылка, его тип должен быть plain text (без форматирования). Никаких болдов, италиков и прочих офисных штучек, ведь текст — уже мощный инструмент. Поток сознания нужно разбить на абзацы или пункты, важные вещи прописать отдельно. В крайнем случае поставить восклицательный знак или выделить звездочками.
На одно письмо приходится одна мысль. Нет ничего хуже письма, где просят собрать информацию по пяти пунктам. Очевидно, составить ответ на это письмо стоит всего дня, а то и недели. Если вы отправитель, пошлите пять писем, и желательно с паузами. Если вы получатель, ответьте на первый пункт с просьбой написать вам отдельные письма по остальным вопросам. Конечно, не в грубой манере “перепиши, пес”, а с пояснением, что так вам будет легче ответить.
Как видите, правил вышло прилично. Никто не ждет исполнения их всех, но применять их однозначно стоит. Остановитесь на чем-нибудь простом, например не цитировать все письмо в ответе, а затем каждый месяц вводите новую практику. Через несколько лет вы сами будете учить новичков почтовому этикету.
Важно понимать: оформление письма может сказать о вас многое. Изучая языки программирования и технологии, нужно помнить, что в конечном счете все сводится к общению. Чтобы запрограммировать задачу, ее сперва обсуждают и фиксируют результаты, и только потом садятся за код. Письмо — это первый ход при приеме на работу, и пусть он выглядит достойно.
-
Черно-белый режим
Иногда встречаю в интернете статьи о том, как уменьшить вред от гаджетов для глаз. Пишут обычные вещи: уменьшить яркость, включить ночной режим (меньше синего и больше желтого) и в том числе — черно-белый режим, когда цвета меняются на градации серого. Типа, меньше нагрузка на сетчатку и все такое.
Опровергаю: включать черно-белый режим не нужно. Несколько лет назад я проводил эксперимент — включил на ноуте и телефоне такой режим. В MacOS это делается галкой в меню Settings / Accessibility / Display / Color Filters, на айфоне — схожим образом.
Интерфейс выглядел так:
Почему этого делать не нужно? Ответ — при выключенном цвете нагрузка на глаза, как ни странно, больше. Объяснение этому простое: на сетчатке находятся разные рецепторы, в том числе палочки и колбочки. У них разные функции: первые отвечают за свет, вторые за цвет. Это ортогональные вещи: например, кошки отлично видят в темноте, но хуже нас различают цвета.
Убрав цвет, вы по сути выключаете колбочки, а это две трети сетчатки. Теперь палочки отвечают за поиск иконки или строки кода на мониторе. Я работал в таком режиме и быстро заметил: в черно-белом режиме трудно найти нужный элемент, глаза напрягаются. Выходит обратная вещь — вместо помощи только вредишь глазам.
Восприимчивость к цвету крайне важна. У нас, людей, этот навык крайне развит по сравнению с животными. Кто сорвал красный плод, тот выжил, а кому достался зеленый — увы. Мозг получает колоссальную информацию из цвета, и добровольно лишать себя этого по крайней мере странно.
Возвращаясь к первоначальному вопросу — как защитить глаза от гаджетов в вечернее и ночное время? Очень просто — не пользоваться гаджетами. Ночной там режим или нет, желтый фон, пониженное мерцания — все это ерунда. Экран — это тысяча мелких фонариков, которые светят вам в глаза. Регулируйте гаджет как угодно, сути это не меняет: вы смотрите на источник света, пусть и рассеянный. При любых настройках он будет вреден в вечернее время.
Наоборот, книга и вообще бумага не светятся. Она только отражает свет лампы, но это свет другого рода. Будучи отраженным от бумаги, он не вредит вам.
Это не значит, что я не туплю в экран вечером. Увы, порой случается. Однако понимаю грань допустимого и не обманываю себя режимами. Лучший режим гаджета перед сном — ВЫКЛ.
-
Вертикальный режим
Вчера попробовал вертикальный режим монитора. Это когда экран повернут на 90 градусов. Необычные ощущения: окна вытягиваются по высоте, а не ширине. Появляется зажатость по сторонам, начинаешь двигать шеей. Выглядит так:
Меня хватило где-то на час. Природу не обманешь: уж если наши глаза расположены по горизонтали, а не один над другим, то и монитор должен простираться в этом направлении. Словом, никакого секрета нет, только неудобство.
Мелкие плюсы разбиваются о суровую реальность. Ради интереса пошарьте экран кому-либо в вертикальном режиме — вас станут ненавидеть. Потому что собеседник увидит следующее:
Как здесь что-либо прочитать, я не знаю. То же самое в обратную сторону: если вывести нормальный шаринг на вертикальный экран, получим:
Возразят: шарь не весь экран, а только окно. Так даже лучше, потому что собеседник не увидит вкладки браузера, переписку с женой и прочее. Я тоже шарю только Емакс. Но иногда работа идет сразу в редакторе, терминале и браузере, и тут никуда не денешься. Пошарил, и начинается долгая песня с увеличением шрифтов, зумов и прочего.
Словом, хороший эксперимент, но нет.
-
Не смотрю и не слушаю
Не помню, говорил или нет: я не слушаю подкасты, не смотрю познавательные видео, сериалы, интервью Дудя, выступления Шульман и всех остальных. В Телеграме состою только в профильных чатах (программирование, кложа, джава). В RSS-читалке почти ничего не осталось — отписываюсь. Уж если начал признаваться, то вот еще — почти ничего не смотрел из Рича Хикки.
Это не потому, что я такой продуктивный и эффективный. Когда работа надоедает, я как и все туплю в интернет — читаю популярные ресурсы, Википедию, смотрю Ютуб, словом — трачу время, которое мог бы использовать на что-то созидательное.
Для себя я понял, что подкасты, познавательные видео и прочий контент, который считают полезным, для меня вовсе не полезен. После просмотра видео или подкаста я не могу ничего вспомнить. Ощущение, что вода уходит сквозь пальцы: сидел час за компом, а как встал, так словно и не было этого часа.
Недавно я смотрел выступление Максима Ильяхова. Он выступил прекрасно: манера речи, мимика, интонации, все это так живо и смешно. Но что он говорил — я не смог вспомнить уже через 15 минут. Это не Максим плохо выступил, это я такой. Поэтому отдаю отчет — тот просмотр был развлечением, попыткой увильнуть от дела.
С годами мне все более очевиден факт — запоминаешь только то, что мысленно пересказываешь и пробуешь на практике. Третьего не дано: никакие видео, короткие или длинные, никакие подкасты не продвинут вас так далеко, как пересказ и практика. Конечно, и от подкастов есть польза, но если выбирать средство с малой пользой и большой, то есть же разница, верно?
В моем понятии видео прочно связано с развлечением. Когда я вижу браузер и Ютуб, ни о каком обучении не может быть и речи. Так я воспитан: раньше вся информация была в виде текста (книги, учебники). Возможно, у молодежи дела обстоят по-другому, но как отец я знаю: ребенок, севший за комп “посмотреть лекцию по географии”, быстро переключается на мемы про Майнкрафт.
Испытываю схожий скепсис к популярным нынче курсам программирования и онлайн-платформам. Они нужны, но важно понимать: это вершина айсберга. Когда меня спрашивают, как изучить Кложу, я отвечаю одинаково — любой ценой устроиться в проект на ней. В этом случае курсы служат входным билетом, доказательством, что по крайней мере ты не идиот. Что будет дальше — никто не знает.
Эти убеждения долго зрели в моей голове, пока я не прочел “Думай как математик” Барбары Уокли. Книга затянута, можно было бы выкинуть треть, но ради некоторых мест стоит потерпеть. Барбара и ее коллеги высказывают мысль — чтобы что-то запомнить, нужно прочесть материал и многократно его пересказывать. Если что-то забыли, то ни в коем случае не читать, а потратить минимум пять минут на вспоминание. Это важнейший процесс мозга, который нельзя прерывать. Даже если вы не вспомнили материал, уже сам процесс выстраивает нужные связи, и при следующем чтении материал запомнится лучше. И конечно, любые знания нужно закреплять практикой.
Теперь скажите мне, как вы смотрите подкасты? Вы их потом вспоминаете, пересказываете, да? Прослушав подкаст о Кложе, бросаетесь писать на ней? Ведь нет же. Ничего не закрепляется. Все это развлечение. Я не ставлю это в вину, пусть каждый развлекается как хочет, но не обманывайте себя, будто чему-то учитесь. Это то же самое, что миф о 25 кадре или обучении во сне с плеером.
Вспоминаю, как в молодости увлекался 3D-моделированием. Интернет был никакой, поэтому заказывал по почте видеоуроки. Со временем понял, что смотрю их как футбол: другой человек работает, а ты бездумно смотришь с бутербродом в руке. Когда пытался повторить один из уроков — слепить простенького динозавра — обалдел от того, что ничего не могу. Что, помогли многочасовые просмотры уроков?
Лучшим источником знаний, на мой взгляд, остается текст. Полезней него ничего не придумали. К тексту удобно возвращаться, его удобно читать, пересказывать. С текстом удобно работать — редактировать, составлять выжимки и конспект, ужимать, насыщать примерами. Работа с текстом — крутейший навык, с которым каждый попрет по карьерной лестнице как локомотив.
Чтобы поделиться знаниями о Кложе, я специально выбрал книжный формат. Можно было настругать коротких видео, записать курсы или что-то похожее, но это все не то. Книга материальна и принадлежит только вам. Ее не потеряешь из-за истекшей кредитки или санкций. Когда читаешь книгу, работает другой отдел мозга, не связанный с туплением в комп. Лучше книги ничего быть не может.
Отказ от подкастов, митапов и познавательных видео несет свои преимущества. Подобно курильщику, который бросил и почувствовал табачный дым, понимаешь, сколько дерьма ты потреблял раньше. Становишься разборчивей: раньше смотрел и читал все подряд, а теперь — только иногда и если не возникло отторжения.
Наконец, меньше всего я хочу быть тем, кто пересказывает интернет. С отдельными людьми я почти не разговариваю, потому что знаю — все их мысли заимствованы из Ютуба и Телеграма. В этом легко убедиться: уже на второй реплике тебе кидают ссылку с “пруфом” — безусловно правдивой информацией, которую человек только что пересказал.
Не делайте так. Доверять можно только опыту, но не авторитетам из интервью, подкастов и образовательных курсов. Все это развлечение, что отнюдь не плохо, но — развлечение, выдаваемое за образование есть обман.