cherkashin.dev
2.11K subscribers
256 photos
16 videos
278 links
Александр Черкашин. Бойскаут, Борец с перфекционизмом.

Для связи 👉 @cherkalexander

Фулстек разработчик в decisions.com. Работаю со стеком TypeScript, React, C#

Пишу о программировании и не только.


Блог: https://cherkashin.dev
Download Telegram
На прошлой неделе проверял почту и наткнулся на рассылку от Я.Практикума “Что нельзя делать хорошему менеджеру”. Одна из упоминаемых проблем – “Срезание углов” или экономия времени на продумывании ТЗ. Чтобы избежать проблемы понимания требований исполнителем – нужно научиться смотреть на ТЗ глазами новичка. Такое же правило я применяю при написании кода:

Нужно смотреть на код глазами новичка.

Представьте, что вы новый сотрудник и ничего не знаете кроме того, что написано в коде.

- Поймёт ли он, что именно происходит в коде?
- Почему это реализовано именно так?
👍123👏1
Мемоизация селекторов в Zustand

Если вы используете Zustand, то знаете, что computed значения реализуются с помощью селекторов.


const userPrs = useChartsStore((state) => {
return state.pullRequests.filter(pr => pr.author.id === state.user.id);
});


В примере выше:

- при каждом обновлении стейта значение селектора будет вычисляться заново
- это приведёт к ре-рендеру компонента, так как каждый раз мы постоянно возвращает новый массив по ссылке, а по-умолчанию используется строгое сравнение (old === new).

Чтобы решить эту проблему в Zustand есть хук useShallow , который сделает “поверхностное” сравнение предыдущего и нового значения. Если они равны — ре-рендер не произойдёт.


const userPrs = useChartsStore(useShallow((state) => {
return state.pullRequests.filter(pr => pr.author.id === state.user.id);
}));


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

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

Также он отмечает, что для этих целей можно использовать метод memoize из его библиотеки proxy-memoize (для redux есть reselect).

Аналогично immer’у proxy-memoize работает на основе Proxy. memoize запоминает предыдущий параметр функции и свойства к которым обращались в селекторе (для этого и нужен Proxy). При следующем выполнении функции, он проверит изменились ли используемые свойства, если нет — вернёт значение, вычисленное в прошлый раз.


const authorSelector = memoize((state) => state.pullRequests.filter(pr => pr.author.id === state.user.id));

const userPrs = useChartsStore(authorSelector);


Конечно, нужно помнить, что мемоизировать можно только “чистую” функцию — если она возвращает одни и те же значения в ответ на одни и те же аргументы

Так, обернув пару селекторов в memoize, я ускорил фильтрацию пул-реквестов в более чем 20 раз (900мс ⇒ 40мс).

#react #zustand #statemanagement #frontend
👍14🔥3🥴2🥱1
Generic React-компоненты

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

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

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

export interface SearchBoxProps<T> {
search: (text: string) => T[] | Promise<T[]>;
renderItem: (item: T) => React.ReactNode;
// ...
}

export function SearchBox<T>(props: SearchBoxProps<T>) {
// ...
}


А использование так

<SearchBox
search={() => [1, 2, 3]}
renderItem={(item) => <div>{item}</div>}
/>


TypeScript отлично выводит типы, но недавно я узнал, что их можно указать и в JSX. Раньше думал, что будут проблемы из-за угловых скобок < > в дженериках

<SearchBox<number>
search={() => [1, 2, 3]}
renderItem={(item) => <div>{item}</div>}
/>


💡 Когда использовать дженерики для реакт-компонентов?

Когда компонент может принимать:

- данные разных типов
- и рендер-пропсы/слоты, чтобы преобразовать эти данные в UI

Ещё один пример можете посмотреть вот в этом видео

#react #frontend #typescript
👍11🔥42
Сегодня принёс вам одну из лучших статей по код ревью.

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

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

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

Также ещё одна мысль, которую доносит автор — “код ревью не должно быть одинаковым для всех изменений”.

🐌 Проблемы медленных ревью

- Они медленные 🤷‍♂️  — замедляют релиз на несколько часов или дней
- Поверхностные — не улучшают качество кода, не происходит обмен знаниями.
- Если ревью происходит медленно — разработчики начинают создавать большие пул реквесты, что увеличивает нагрузку на ревьюера и делает код ревью ещё медленнее.

Подходы, которые описываются в статье

Автор долго придерживался принципа Ship / Show / Ask

- 🚢 Ship. Изменения простые, нет знаний, которыми стоит поделиться — льём в прод без ревью.
- 🔍 Show. Если изменения простые, но в них есть что-то полезное и ими стоит поделиться (например вы написали новую функцию или компонент), то — сливайте изменения, а потом просите о код ревью. Код ревью — не блокирующий. Я обычно называю это постревью.
- Ask. Если вы вносите сложные изменения — дождитесь код ревью и только потом сливайте изменения. Тут код ревью блокирующий.

Со временем он его немного перефразировал в Automate / Defer / Pair

- 🤖 Automate. Если в изменениях нет знаний, которыми стоило бы поделиться, а в коде особо нечего улучшить — пропускаем ревью и полагаемся на линтеры, статические анализаторы и тесты.
- ↪️ Defer (отложить). В зрелом и проверенном процессе разработки большинство изменений нужно ревьюить, но ревью не должно быть блокирующим. Это особенно хорошо работает, когда новая фича деплоится за фича флагом и можно спокойно получить фидбек после деплоя.
- 👯 Pair. Если корректность изменений очень важна: их сложно откатить или изменения связаны с оплатой, или же изменения достаточно сложные — то вам нужно работать в паре с ревьюером, созвониться и объяснить ему изменения, чтобы он был в контексте и смог сделать ревью быстрее и качественнее.

#codereview #fridayreading #weekphrase
🔥11👍62
function(): Promise 🆚 async function(): Promise

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

Пример

function fn(obj) {
const someProp = obj.someProp
return Promise.resolve(someProp)
}

async function asyncFn(obj) {
const someProp = obj.someProp
return Promise.resolve(someProp)
}

asyncFn().catch(err => console.error('Catched')) // => 'Catched'
fn().catch(err => console.error('Catched')) // => TypeError: Cannot read property 'someProp' of undefined


- при объявлении функции с ключевым словом async JavaScript гарантирует, что функция вернёт Promise, даже если в ней произошла ошибка
- если функция возвращает Promise, но объявлена без async то catch не поймает ошибку, если в функции произошла ошибка
- дело в том, что ошибки произошедшие внутри конструктора new Promise((resolve, reject) => { throw new ... }) промиса ловятся. Когда мы добавляем async, то по сути заворачиваем тело функции в new Promise((resolve, reject) ⇒ { })

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

function fn(obj) {
return new Promise((resolve, reject) => {
const someProp = obj.someProp;
resolve(someProp);
});
}


https://habr.com/ru/articles/475260/


#javascript #frontend
👍23🔥54
Как я обычно узнаю о новых фичах в C#

О новых фичах в C# я обычно узнаю с помощью подсказок Райдера — IDE для C# от JetBrains.

В общем, обновился, пишу код, и Райдер предлагает что-то исправить там, где раньше все было окей. Думаю — ну ок, давай исправим. Но ощущение, что он перепутал C# c JavaScript.

- string[] array = new [] { “a”, “b”, } предлагает заменить на string[] array = ["a", "b"]
- Array.Empty<string>() на []
- List<string> list = new (vowels) на List<string> list = [.. vowels]

Особенно интересен последний пример, очень уж напоминающий spread оператор из JS. И действительно, в C# его тоже завезли, правда по пути потеряли одну точку и назвали spread element.


int[] row0 = [1, 2, 3];
int[] row1 = [4, 5, 6];
int[] row2 = [7, 8, 9];

int[] example1 = [..row0, ..row1, ..row2];
List<int> example2 = [..row0, row1[0]];


В общем, в C# 12 поднасыпали синтаксического сахара для работы с коллекциями. А ещё прихватили Primary конструктор из Scala. Это когда конструктор объявляется сразу после имени класса.


public class BankAccount(string accountID, string owner)
{
public string AccountID { get; } = accountID;
public string Owner { get; } = owner;

public override string ToString() => $"Account ID: {AccountID}, Owner: {Owner}";
}


Подробнее о релизе C# 12 здесь.

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

#csharp
👍4😁2👎1
Если бы вас спросили, что самое сложное в вашей работе, чтобы вы ответили?
Я думаю одна из самых сложных вещей - читать чужой код, или свой собственный, написанный пару лет назад.

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

Вот основные моменты из статьи, если вам лень читать всю статью в пятницу да ещё и летом 😅

- Когнитивная нагрузка – это объем умственной работы, необходимый разработчику для выполнения задачи. Нашим приоритетом должно быть максимальное снижение такой нагрузки в проектах.
- Типы когнитивной нагрузки:
- Внутренняя — изначальная сложность задачи, её нельзя уменьшить.
- Внешняя — создается способом представления информации. То есть то, как написан код, решающий задачу.
- Знакомая и простая вещь — не одно и то же. Они ощущаются одинаково — та же легкость перемещения по пространству без особых умственных усилий, но по совершенно разным причинам. Каждый «умный» и нестандартный прием приведет к трате времени на обучение для остальных разработчиков. После того, как они его освоят, им будет легче работать с кодом. Именно поэтому не так легко понять, как можно упростить уже знакомый код.
- Нет никакой «упрощающей силы», влияющей на базу кода, кроме вашего сознательного выбора. Упрощение требует усилий, а люди часто спешат.
- Отладка в два раза сложнее написания кода. Следовательно, если вы пишете код максимально хитроумно, вы по определению недостаточно умны, чтобы его отладить.

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

- DRY (Don’t repeat yourself) — хотя в целом это хорошее и фундаментальное правило, его чрезмерное использование приводит к непосильной когнитивной нагрузке. У DRY есть альтернатива AHA Programming
- Вы можете слишком рано извлечь общую функциональность, основываясь на предполагаемом сходстве, которого на самом деле в долгосрочной перспективе может не быть. Это может привести к ненужным абстракциям, которые будет непросто модифицировать или расширять.
- «Небольшое копирование лучше, чем небольшая зависимость».

https://habr.com/ru/companies/ncloudtech/articles/818643/

#fridayreading #essential
👍11💯4
Как инспектировать разметку попапов

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

Способ 1: Открываете DevTools ⇒ Вкладка Sources ⇒ Далее наводите мышкой на свой элемент ⇒ Нажимаете F8, чтобы приостановить выполнение скрипта

Способ 2: Открываете DevTools ⇒ Находите тег body или тот тег, внутри которого появляется попап и нажимаете на нём правой кнопкой ⇒ Выбираете “Break on ⇒ subtree modification” и дебажите до тех пор пока не появится попап.

Способ 3: Мой любимый. Вставляете скрипт в консоль и у вас есть 3 секунды чтобы показать попап. Именно этим способом пользовался пока не узнал про первый 😄

setTimeout(() => { debugger }, 3000)


#frontend #devtools
🔥22👍32😁2
source-map-explorer

На прошлой неделе анализировал размер главного бандла мобильной версии нашего приложения.

В спешке мы накосячили с импортами и бандл раздулся.

🤔 Как анализировать?

Для анализа использовал утилиту source-map-explorer.

При запуске, утилита генирирует древовидную карту, с помощью которой можно увидеть, какие именно файлы попадают в бандл.

К сожалению, почему именно это что-то попало в бандл утилита не говорит.

👉 В чём была проблема?

Иногда мы ленимся и в файле компонента кладём какую-нибудь утилитную функцию, enum или константу, а потом где-то в другом месте импортируем их.

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

P.S. А какими инструментами пользуетесь вы?

#frontend #performance #tools
👍7🤡1
Со стороны может показаться, что у нас, у программистов, очень лёгкая работа. Как любит говорить мой тренер по теннису: “мол успешные айтишники сидят где-нибудь в эмиратах, птягивают кофе в кафешках и в ус не дуют”. В чем-то он может и прав, но почему-то не у всех получается “войти в айти”. А те, кто вошел, только и говорят о выгорании.


Года 3 назад я писал, что я делаю, чтобы не перегореть. С тех пор добавилось ещё несколько привычек.

🎾 Я начал играть в большой теннис. Пока бегаешь по корту и чуть ли не выплёвываешь свои лёгкие, совсем забываешь о работе.


🏃‍♂️ С нового года я начал делать зарядку, увидел пост у Евгения Шушкова и тоже решил попробовать. Не уверен, что я чувствую себя намного лучше, но зарядка определённо помогает проснуться. Но что гораздо лучше, она помогает на какие-то 15 минут немного избавиться от тревожности.

- Обычно я делаю вот этот комплекс на 15 минут
- Или этот на 5 минут, когда лень, но чтобы не потерять привычку


💪 В дополнение к зарядке стараюсь пару раз в неделю подтягиваться или отжиматься. Или же делаю вот этот комплекс на пресс, первые 4 минуты, потом пресс умирает.


📱В мае я отписался от новостных каналов.

---

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

P.S.

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

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

Я вообще считаю, программирование это не про “писать код”, это про людей. И мы можем сделать жизнь людей лучше не только написав очередные 1000 строчек кода.
26
Первый шок от последних событий начал проходить, поэтому вчера я немного разобрал инбокс и нашёл в нём статью, которой решил с вами поделиться “4 тысячи заметок?! Как я веду личную базу знаний в Obsidian”.

Автор рассказывает, как ему надоело забывать важную информацию и он решил завести личную базу знаний в Obsidian, вдохновившись Zettelkasten.

Zettelkasten – это такая система ведения личных заметок, в которой каждая заметка:

- Атомарна. То есть независима и содержит одну мысль.
- Имеет контекст. Например, размечена по тегам, темам.
- Связана с другими знаниями в системе.

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

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

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

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

В общем, рекомендую прочитать статью целиком, тем более, что она совсем небольшая.

---

А расскажите,

- чем пользуетесь вы для ведения заметок?
- какие подходы и практики используете?

Я пользуюсь Notion. Но вот на днях пришло письмо от Miro, о том, что они “не могут обслуживать клиентов, зарегистрированных в запрещённом регионе” с 12 сентября. И кто сказал, что скоро Notion не пришлёт такое же письмо, возможно стоит начинать искать альтернативы …

#obsidian #fridayreading
1👍6
size-limit

Вот здесь я писал про source-map-explorer и о том, как с его помощью анализировать размер бандла. Но, чтобы не приходилось анализировать бандл, лучше предотвратить его раздувание.

Это можно сделать на этапе CI с помощью size-limit.


Как использовать

1. Устанавливаем size-limit и плагин @size-limit/file, необходимый для анализа web-приложений.

npm install --save-dev size-limit @size-limit/file


2. Добавляем когфиг size-limit в package.json

  "size-limit": [
{
"limit": "35 kB",
"path": "dist/app-*.js",
"gzip": true
}
],


3. Добавляем запуск size-limit в скрипт, который вы запускаете на CI

  "scripts": {
"size": "npm run build && size-limit",
"test": "vitest && eslint . && npm run size"
}


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

Также size-limit поддерживает задание временных лимитов на исполнение вашего кода и использует для этого “безголовый хром”.

#frontend #performance #tools #ci
10👍10
У нас на проекте есть 2 основных правила для написания стилей:

- У всех CSS-классов должен быть префикс dp-, выбранный по названию продукта
- Мы используем БЭМ — <block>__<element>--<modifier>

Однако есть большой пласт легаси стилей, где эти правила не используются.

И вот, при добавлении monaco-editor’а (текстовый редактор на котором построен VS Code) в новую фичу, прилетело ишью — у горизонтального и вертикального скролл-баров разная толщина.

А вся проблема в том, что наши глобальные стили для класса slider применяются к элементу с таким же классом в monaco-editor.

Именно эту проблему и решают префиксы — позволяют избежать коллизии имён с библиотечными классами.

#css #bem
1👍6❤‍🔥3
Notion 👋

Только ленивый не написал, что 9 сентября Notion прекращает работу для российских пользователей. Пару дней назад и у меня появилась желтая плашка, которая говорит “на выход”. Говорят, пользователи платных подписок точно потеряют доступ. Посмотрим, будет ли работать Notion через VPN для пользователей на бесплатном тарифе или нет.


У меня пока план миграции такой:

1. Экспортировал все данные из Notion
2. Импортировал в российские аналоги - Yonote и Teamly, на случай если понадобятся старые заметки в удобно читаемом формате (ну не совсем он удобный)
3. Обмазываюсь плагинами в Obsidian и пытаюсь разобраться, что лучше “папки или связи”


Теперь кратко про Obsidian:

- Настроил интеграцию с гитом, чтобы бэкапить всё на гитхаб
- Шаблоны Notion заменил на плагин Templates
- Настроил некоторые горячие клавиши, как в VS Code.
- Вместо баз данных Notion, установил плагин Projects. Он позволяет отображать данные в виде таблицы или канбан доски, как это было в Notion.
- Аналога инлайн баз данных в Obsidian нет, но есть dataview. С помощью SQL подобного языка запросов Dataview Query Language, можно писать запросы ко всем заметкам в базе знаний.
- Для миграции заметок из Notion можно использовать плагин Obsidian Importer. Я пока импортировал только, базу с постами для телеграмм. Импортировалось отлично. Нужно делать экспорт в HTML формате, не в Markdown. После импорта, настроил отображение с помощью плагина Projects и получилось примерно как было в Notion. И ощущение, что после этого Obsidian стал иногда подвисать, посмотрим как будет работать дальше.
- Создал папку для zero-links - по сути теги в виде страниц имена, которых начинаются с двух нулей, для оптимизации поиска.
- Так как инлайн баз данных нет, для ведения задач собираюсь попробовать плагин Obsidian Tasks.


Если вам интересна тема Notion и как с него съехать, то вам на канал Марии. Она активно освещает текущую ситуацию, и альтернативные продукты.

P.S. Расскажите, как вы ведёте базу знаний или список задач. На что мигрируете, если пользовались Notion? Тупит ли Obsidian у вас? 😅

#notion #obsidian
1👍122
⏭️ Соседние селекторы в CSS

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

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


.new-feature-element ~ .element-with-scroll {
// тут фиксим стили, они применяются к .element-with-scroll
}


Посмотрел на разметку, понял что .element-with-scroll всегда идёт непосредственно после .new-feature-element поэтому можно поменять на +:


.new-feature-element + .element-with-scroll {
// тут фиксим стили, они применяются к .element-with-scroll
}


Основные различия + и ~:

- + выбирает только один элемент, который непосредственно следует за первым элементом.
- ~ выбирает все элементы, которые следуют за первым элементом, но не обязательно сразу за ним.

Посмотрел на разметку ещё раз и понял, что элементы видны всегда, а не по условию, поэтому просто пофиксил стили для .element-with-scroll без всяких соседних селекторов, но хоть потренировался.

#css
1👍14
Кажется, кто-то узнал, что программист приехал на отдых.

Сначала аэропорт SSH, теперь номер 11101, а дальше что?

Бустани канал, чтобы оставаться в курсе, а то мало ли что ...

#vacation
😁223🍌2
Пока я сижу в Шереметьево и жду свой задерживающийся рейс в Мумбаи, расскажу вам кринжовую историю, которая произошла со мной сегодня утром в аэропорте Каира.

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

Обычное утро, заказываем убер и едем в аэропорт, мы жили в районе Гелиополис, поэтому доехали минут за 20. Рассчитываюсь с водителем и идём в аэропорт. Перед металлоискателями говорю жене: "Надеюсь, что мы пройдём всё очень быстро", и тут ощущаю тревожную лёгкость - рюкзак с ноутом и российским паспортом остался на заднем сидении такси.

Мысленно попрощавшись с ноутом и параллельно убеждая жену, что таксист ещё не успел его кому-то втюхать, пишу в поддержку убера. Чтобы связаться с водителем, я прошу номер телефона сотрудника аэропорта - российский номер не подходит. Спустя 10 минут попыток - связаться с таксистом так и не получилось. А Убер просит сообщить ему, если нам не получится вернуть рюкзак в течение 24 часов. Пока сотрудник аэропорта зовёт полицию - я понимаю, что придётся лететь домой без ноута и прикидываю, где купить новый макбук в Москве перед командировкой.

Спустя 5 минут приходит худощавый египетский полицейский со стволом, который повторяет каждые 10 секунд, чтобы меня успокоить: "Don't worry, I will help you. It is my job.". Он мне напомнил брутального копа из американских фильмов. Изучив профиль таксиста, он уходит смотреть по камерам куда скрылся злоумышленник.

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

Когда со всем разобрались, завязывается диалог с полицейским (П):

Я: Как вы его нашли?
П: Это просто моя работа! "Как твои дела?" - спрашивает он на русском
Я: Очень хорошо! "Я бы сказал иначе, но не хочу материться." - переходим обратно на английский.
П: Ты куришь? Может хочешь, что-нибудь выпить?
Я: Нет, ещё раз большое спасибо.
П: Египтяне хорошие люди, приезжайте к нам снова.
Я: Непременно.

Подходим к металлоискателям, где другие "хорошие" египтяне разводят нас на 200 фунтов (400 рублей), но это уже совсем другая история.

P.S. Таксист не говорил по-английски, но он несколько раз повторил "Uber, SMS". Думаю, убер просто связался с таксистом и тот вернулся назад, а полицейский брутально присвоил всю славу себе. Но мы конечно всё равно ему благодарны.

Расскажите, какие подобные истории происходили с вами? 😅

#about_me #vacation
4👍19🔥7😁7
Пока я Индии поедаю сабудану и досу, мне в Курск пришла книга "Мама, Я тимлид!" от Жени Черкасова. Я конечно не тимлид, но книжку обязательно прочитаю.

Женя ведёт ламповый и очень уютный канал о личной и командной эффективности. Вот некоторые из моих любимых постов:
- Готовься к увольнению с первого дня
- Как всё успеть
- Сколько я заработал с блога
- Как на самом деле строятся процессы

А ещё Женя летает на параплане 😱, обязательно посмотрите.

А мне расскажите, читали ли вы книгу и как она вам.

#рекомендую_канал

@cherkashindev
4👍4
Отдельная страница для задач в Обсидиан

В 🇯🇵 Notion мне всегда очень нравилось, что каждая запись в базе - это отдельная страница, которая может содержать что угодно - от простых заметок, до вложенных баз или таблиц.

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

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

Хотя в обсидиан это не поддерживается из коробки, но всё же достаточно легко реализуется:

- Для задач использую obsidian-tasks плагин, по сути это просто ToDo лист
- Если нужно сделать много записей по задаче или приложить скриншоты - то просто превращаем задачу в ссылку на заметку
- Оборачиваем задачу в Wikilink [[]] и нажимаем на неё, заметка с именем задачи создастся автоматически
- Заметка создастся в стандартной папке для заметок, папку можно задать в Settings => Files and links => Folder to create new notes in.

#obsidian #notion

@cherkashindev
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍102