Новиков > путь в Big Tech – Telegram
Новиков > путь в Big Tech
184 subscribers
94 photos
192 links
От зеро-кодинга на стройке до написания высоконагруженных сервисов в Big Tech. 

Пишет SWE в Avito.ru (backend), в прошлом: .NET developer и сертифицированный специалист по использованию BIM.

Написать автору: @nvkv_ai

Книги: https://boosty.to/time2code
Download Telegram
Февраль 2024:

навыки
✔️ Потренировался очередной раз написать разные имплементации FizzBuzz'а. Одна из реализаций без единого if'а!
✔️ Погружаюсь глубже в Low Level Design и то как проектировать системы любой сложности с помощью ООП. Практикуюсь на C#, так как (в отличие от Go) язык обладает полной поддержкой основных парадигм ООП.
✔️ Познакомился с Фаззинг-тестированием и попрактиковался его применять с помощью стандартных пакетов тестирования в Go.
✔️ Начал изучать основные веб-уязвимости, рассмотрел 2 вида XSS-атак.
✔️ Написал проект с помощью TDD.

канал
✔️ Наладил регулярный темп выпуска новых постов: 2 раза в неделю
✔️ За февраль вышло 8 постов (2 из которых практические - по предотвращению XSS-уязвимостей)

прочее
✔️ Полностью отказался от IDE JetBrains Goland и перешел на опенс-сорсный редактор VS Code

#результаты
👍6
Межсайтовый скриптинг (XSS) в атрибутах HTML

Мы рассмотрели XSS атаки в серверных ответах API и затронули общий случай инъекции в HTML. Но еще важно упомянуть, что HTML-атрибуты часто становятся мишенью для XSS-атак.

На примере атрибута href научимся обрабатывать и такие случаи.

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

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

1. Всегда обрабатывайте (валидируйте) пользовательский ввод перед отображением на странице и энкодьте вывод. Используйте готовые библиотеки.
2. Устанавливайте явно заголовки, с которыми должен отвечать сервер.
3. Установите CSP.

Очевидно, это далеко не исчерпывающее руководство, но, если начать делать хотя бы это, то обезопасите себя от уязвимостей, про которые не все задумываются. Хабр в 2013 году, например, про это явно не думал :)

Исправленный код

#безопасность

package main

import (
"fmt"
"log"
"net/http"
)

func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", HelloHandler)

err := http.ListenAndServe(":4000", mux)
log.Fatal(err)
}

func HelloHandler(w http.ResponseWriter, req *http.Request) {
http.SetCookie(w, &http.Cookie{Name: "user", Value: "guest"})
http.SetCookie(w, &http.Cookie{Name: "password", Value: "qwerty123"})

helloTemplate := `
<html>
<head></head>
<body>
<p><a href="%s">Go back</a></p>
</body>
</html>
`
returnUrl := req.URL.Query().Get("return_url")
fmt.Fprintf(w, helloTemplate, returnUrl)
}
You are gonna need it (YAGNI v2.0)

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

Мой тимлид сразу обозначил, что подобные встречи у нас будут 1 раз в 2 недели, то есть 1 раз за спринт. Что оказалось удобно: не слишком часто, не слишком редко.

Встречи 1-1 (англ. one to one) проводятся с довольно прагматичной целью: узнать как у тебя дела, какие есть трудности и с каким настроением работаешь.

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

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

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

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

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

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

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

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

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

Поэтому, если чувствуете, что заскучали или устали, то обязательно поговорите со своим руководителем. Хороший менеджер знает, что счастливый сотрудник принесет ему гораздо больше денег, а значит, будет вкладываться в вас и будет полностью на вашей стороне.
👍10
Феномен Баадера-Майнхоф в действии и при чем здесь Фаззинг

Случалось ли с вами такое, что совершенно новая информация (с которой раньше вы никогда не сталкивались) начинает появляться в вашей жизни все чаще и чаще, как только вы про нее узнали?

Проще всего про это думать в разрезе автомобилей. Вы никогда раньше не видели Volkswagen Beetle в своем городе, но как только 1 раз заметили, то на следующий день можете встретить не один, а уже целую серию. Как будто они начали преследовать вас.



Во вторник сходил на митап местного Go-сообщества.

Обсудили 2 темы:
- фаззинг тестирование
- альтернативу buf в условиях импортозамещения и санкций

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

Фаззинг - вид тестирования, с которым я познакомился лишь в феврале. Что это такое и как его использовать в Go также описывал.

Удивительно, но мое поверхностное знакомство с темой заполнило секцию вопросов и ответов после доклада спикера. Оказалось, что из 40 человек в зале знакомых с темой было всего 3-4.

Еще я узнал, что:
- компания, которая выпускает свое железо, может испытывать с ним трудности при фаззинге
- фаззинг не самый популярный вид тестирования, поэтому многие библиотеки для работы с ним заброшены или обладают скудным функционалом; компаниям, которые заинтересованы в этом, приходится выделять отдельных людей для разработки своих решений (в частности, организатор митапа направил на создание фаззинг-инструмента junior-разработчика, который уже полгода этим занимается)
- 1 прогон теста для разработанного компонента занимает не менее 1-1.5 часа
- сейчас готовится стандарт, где будут закреплены требования к фаззинг тестированию проекта

Очевидно, что фаззинг необходим для ответственных проектов, где безопасность/отказоустойчивость превыше всего. При этом многие продуктовые компании могут неплохо обходиться без него, так как для них зачастую важнее Time To Market. И одно дело - запускать минутные юнит-тесты или даже e2e (которые могут занимать время на порядок дольше) и совсем другое - идти в фаззинг, который в разы может замедлить поставку новых фичей, требует мощного железа и разработчиков для его отдельной поддержки.
👍5
Межсервисная авторизация (Inter-Service authorization)

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

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

Реализация может быть разная: middleware, решения на Service Mesh и др. Многое будет зависеть от архитектуры приложения, ресурсов компании и команды разработки. Поэтому важно выбирать не самое лучшее/популярное, а то, что будет хорошо работать именно у вас.

Например, в Авито инфраструктурная команда это реализовала на базе Service Mesh. Как это устроено и почему сделано именно так рассказывали здесь.

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

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


package main

import (
"html/template"
"log"
"net/http"
"os"
)

func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", HelloHandler)

err := http.ListenAndServe(":4000", mux)
log.Fatal(err)
}

func HelloHandler(w http.ResponseWriter, req *http.Request) {
secret := os.Getenv("SERVICES_SECRET")
if secret == "" {
return
}

if secret != req.Header.Get("Authorization") {
return
}

helloTemplate := `
<html>
<head></head>
<body>
<p>Some secret data</p>
</body>
</html>
`
tmpl, err := template.New("name").Parse(helloTemplate)
if err != nil {
log.Fatal(err)
}
err = tmpl.Execute(w, "")
}


#безопасность
👍1
Паттерны проектирования

Когда я только начинал писать на С#, мне повсеместно попадались советы о том как важна эта тема и я тщетно пытался запомнить все сниппеты с кодом, которые реализовывали отдельные идеи.

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

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

Пару дней назад приступил к книге Head First Design Patterns (Eric Freeman, Elisabeth Robson).

Авторы явно знакомы с тем, как человек усваивает информацию: нас активно вовлекают в тему лирическими отступлениями, юмором и картинками, закрепляя прочитанное аналогиями из жизни.

Хорошая проработка такой книги на 99% закроет потребность в паттернах и переведет вас на другой уровень абстракции, который позволит проектировать систему с использованием подходов, давно оправдавших себя на практике.
👍6
Cross-Site Request Forgery (CSRF)

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

🕵️‍♂️ Веб-приложение уязвимо к CSRF-атакам, если обнаружено:

1. Отсутствие или ненадежная реализация CSRF-токена.
2. Наличие повторяющихся запросов на изменение данных без дополнительной проверки подлинности.
3. Автоматическое выполнение действий без подтверждения со стороны пользователя.

🔧 Обеспечить безопасность вашего приложения помогут:

1. Внедрение CSRF-токен в формы и проверка их на сервере перед выполнением действия.
2. Использование сессионных токенов для подтверждения подлинности пользователя.
3. Ограничение привилегий доступа к функциями изменения данных.

В общем случае, основным способом защиты является CSRF-токен, который добавляется к запросам и проверяется на сервере, чтобы удостовериться, что действия совершает пользователь, а не злоумышленник.

Для обеспечения максимальной безопасности токен должен быть:
- уникальным для каждой сессии (чтобы предотвратить повторное использование)
- зашифрованным (чтобы предотвратить возможность изменения токена злоумышленником)
- непредсказуемым (сгенерирован с использованием криптографически безопасного генератора по надежному алгоритму)

Подробнее о данном виде атак и методах защиты можно найти в шпаргалке OWASP.

Применяя данные советы, вы добавите еще один слой защиты к своим приложениям.

#безопасность
Март 2024:

навыки
✔️ Завершил практикум по безопасной разработке. Со всеми задачами можно познакомиться здесь. Планирую еще выпустить несколько постов по темам: IDOR, Path Traversal и SQL Injection.
✔️ Поучаствовал в оффлайн митапе Go-сообщества. Послушал про фаззинг и альтернативу buf.
✔️ Приступил к книге Head First Design Patterns, изучил 2 паттерна и реализовал их на Go
✔️ Продолжаю изучать ООП. Нравится, что эта парадигма заставляет думать иначе, нежели чем функциональный подход, однако, общие принципы к проектированию программы схожи.
✔️ Поделился рефлексией о том, как писать комментарии к своему коду.
✔️ Рефлексия о важности атомарных изменений. Частые коммиты лучше, чем редкие и большие.

карьера
✔️ Обсудил с тим-лидом возможности роста и будущий проект, который позволит перейти на следующий уровень экспертизы. Написал пост о важности коммуникации со своим руководителем.

прочее
✔️ Не открывал прошлую IDE (Goland) более месяца. Отлично справляюсь с VS Code. Можно сказать, что переход прошел легко.

#результаты
🔥3
Как хорошо вы знаете свой язык программирования? [1/2]

Речь не про знание исходного кода (про что очень любят спрашивать на собеседованиях, например, по Go: "как устроена мапа"), а про то, что есть неочевидные вещи, которые вынуждают вас ошибаться.

Сможете правильно сказать что выведет код представленный ниже?

Выберите свой ЯП и, не раздумывая, попробуйте ответить. Затем проверьте себя.


var sum = 90 + 010;
System.Console.WriteLine(sum);



func main() {
sum := 90 + 010
fmt.Println(sum)
}



sum = 90 + 010
print(sum)



public class Main
{
public static void main(String[] args) {
var sum = 90 + 010;
System.out.println(sum);
}
}



var sum = 90 + 010
console.log(sum)



fun main() {
var sum = 90 + 010
println(sum)
}



<?php
$sum = 90 + 010;
echo $sum;



sum = 90 + 010
print(sum)



var sum = 90 + 010
print(sum)


Ответы:

C#, Julia, Swift: 100
Go, Java, Javanoscript, PHP: 98
Kotlin, Python: ERROR!
🔥3
Как хорошо вы знаете свой язык программирования? [2/2]

"SyntaxError: leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers". - С такой ошибкой ругнется интерпретатор Python, если выполните код, предложенный выше.

Из 9 представленных языков синтаксис также не устроит и компилятор Kotlin. А остальные? :)

Ответ 100 кажется интуитивный, поэтому к C#, Julia и Swift вопросов нет, но почему другие допускают такую вольность или это чем-то обусловлено?

98 мы получаем, когда появляются ведущие нули перед числом и (!) оно представлено в восьмеричной системе счисления (все цифры меньше 8), то есть на 090 + 010 нормальный язык уже заругается, что, конечно, разумно.

Извини JavaScript, но когда вижу, что console.log(090 + 010) все также выведет 98 , мой мир логики рушится. Очень уважаю людей, кто не путается со всеми подобными особенностями JS.

Go солидарен с Python, но при этом допускает оба написания:
90 + 010 <=> 90 + 0o10

Очевидно, что нужно всегда предпочитать второй вариант (если возможно), чтобы исключить ложные интерпретации результата.

Кстати говоря, Go также позволяет следующие префиксы: 0b, 0o, 0x - подробнее их описывал здесь.

Для чего вообще такой трюк с восьмеричной системой нужен? Например, для задания прав доступа в Unix при работе с файлами: 0777 (но лучше, конечно, 0o777).

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

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

Присмотритесь к книгам издательства Manning. У них есть серия 100 {Language} Mistakes and How To Avoid Them. Я сейчас дочитываю "100 Go Mistakes and How to Avoid Them" и это must have для всех, кто изучает Go, в связке с какой-нибудь базовой книгой или гайда, как, например, A Tour of Go.

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

Тренироваться.

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

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

Представьте: есть ваша ежедневная задача на полспринта и декомпозируйте ее на подзадачи. А теперь пригласите 2-х старших разработчиков, поставьте себе таймер на 30 минут, возьмите одну из подзадач и с уверенностью попробуйте решить.

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

. . .

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

Меня пригласили на второй этап - техническое интервью. Я попросил неделю времени, чтобы подготовиться, так как 2 года не было опыта прохождения тех. этапов.

Настал день интервью, где в течение 1.5 часов мы общались про Go, базы данных и немного про брокеров сообщений (в частности Kafka). Хочется отдать компании должное: собеседование на 90% состояло из практических задач, а я, к своей неудачи, всю прошлую неделю готовился к теории...

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

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

. . .

Зачем проходить собеседования в другие компании, если полностью доволен текущим местом? Подсознательно я чувствовал, что это может быть полезно и это действительно было по нескольким причинам:

0️⃣ Когда работаешь в крупной компании, то в какой-то момент начинает казаться, что все по плечу и ты с легкостью найдешь работу. Такие ситуации тебя приземляют и ты вспоминаешь, что нужно тренироваться, чтобы проходить технические секции. 1 неделя для тренировки - очень мало, комфортный темп 2-4 недели (зависит от класса компании, в FAANG могут уйти месяцы).

1️⃣ Даже легкое волнение влияет на внимательность. Пример с собеседования: инициализация слайса make([]int, 4) дает cap=4, len=4, но я почему-то воспринял это как cap=4, len=0, прочитав make([]int, 0, 4), несмотря на то, что я проговорил правило вслух. Такие вещи должны тренироваться до автоматизма, чтобы не совершать глупых ошибок.

2️⃣ Я понял, что сильно погружен в продуктовую / бизнес разработку, из-за чего отлично разбираюсь в продуктовых контекстах, запусках и сетапах AB-экспериментов, но могу быть неуверенным при работе с транзакциями, написании SQL-запросов или траблшутингом сервисов, так как работаю с этим мало.

3️⃣ За время, что я готовился к собеседованию, значительно увеличил свои знания в Go и Kafka, изучая лучшие практики и особенности работы.

4️⃣ Лучше узнал себя. Когда тебя спрашивает интервьюер о мотивации для смены работы, то пытаешься найти честный ответ и ты его действительно можешь найти. У меня теперь такой ответ есть, он и до этого был, но сейчас я его достаточно точно формализовал, чтобы стратегически с ним более плотно работать.

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

P.S.: Если будет интерес, то обязательно разберу задачи с собеседования, они довольно легкие, но по-своему полезные.
🔥6
Разбираем задачи с технической секции (платформа Go)

Решил остановиться на 3-х задачах, так как они довольно показательные, несмотря на простоту. Интервью еще включало задание на код-ревью готового пул-реквеста, написание SQL-запросов и сопутствующие вопросы про БД, транзакции и Kafka.

Так как стандартных средств форматирования для такого лонгрида мне не хватило, то тестирую telegraph.
С чего начинать код-ревью? [1/2]

ℹ️ Дисклеймер. Как именно давать обратную связь мы не затронем, но это очень важная тема, про которую нельзя забывать. Рекомендую материал по теме.

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

На собеседованиях также любят про это спрашивать. Задание может выглядеть так: "Представь, что этот код тебе пришел на код-ревью, как ты его прокомментируешь, апрувнишь ли"?


package domain

import (
"database/sql"
"fmt"
"time"
)

type UserStatusHistoryStore struct {
db *sql.DB
}

func New() UserStatusHistoryStore {
db, err := sql.Open("mysql", "username:password@tcp(127.0.0.1:3306)/jazzrecords")
if err != nil {
panic(err)
}

return UserStatusHistoryStore{
db: db,
}
}

func (s *UserStatusHistoryStore) Save(userID, partition, status string) {
query := fmt.Sprintf("insert into userstatushistory (user_id, status, inserted_at) values (%v, %v, %v, %v)", userID, partition, status, time.Now())
s.db.Exec(query)
}


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

Алгоритм (1/2):

0️⃣ Погружаюсь в контекст. Для этого достаточно прочитать описание задачи, по которой выполнен данный пул-реквест (далее ПР). Хорошая практика - линковать к ПР номер таски Джиры или другой системы учета тасок. В Авито, например, название git-ветки начинается с номера Jira issue (иначе линтер не пропустит), а Bitbucket это распознает и делает кликабельную ссылку, поэтому легко можно навигироваться к задаче. 

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

2️⃣ Если после п.1 вопросов не возникло, то ревью начинаем с первой измененной строчки, в нашем случае с обозначения пакета, обращаем внимание на нейминг. Очевидно, что название domain никак не характеризует свое содержание, поэтому первым предложением может стать переименование пакета в соответствии с назначением добавляемого функционала: storage - лучше. 

3️⃣ Далее импорты. Часто проблемой может быть их сортировка. Хотя настроенный CI/CD, конечно, не пропустит здесь вольность, но, если он вдруг не настроен и мы видим, что разработчик устроил беспорядок, то лучше ему порекомендовать исправить: например, использовать goimports или другие инструменты для форматирования, чтобы соблюсти код-стайл. 

4️⃣ Пока далеко не ушли, то следует обратить внимание на нейминг. Некорректный алиас в импортах хоть и не является блокером, но лучше также соблюдать стиль языка (рекомендации); спойлер - стараться делать кратко, используя одно существительное, если говорим про Go.

5️⃣ И снова про нейминг. Важно обращать внимание на именование переменных, функций и пр. Применительно к нашему примеру, если мы пакет рекомендовали переименовать в storage, то большинство названий можно упростить:
UserStatusHistoryStore -> UserStatusHistory и т.д. 

6️⃣ Лучшие практики. Тут многое еще зависит от специфики проекта, но применительно к нашему случаю можно предположить, что напрямую использовать db sql.DB - не очень хорошо, а лучше работать с соединениями или их пуллом, который будет инициироваться при старте приложения.

И это далеко не все. Мы рассмотрели лишь одну часть, которая носит больше рекомендательный характер на реальном ревью.

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

В следующем посте обсудим основные блокеры, без исправления которых ваш код не пропустят в прод 🚫
👍2
С чего начинать код-ревью? [2/2]

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

Алгоритм (2/2):

7️⃣ Тесты. Вся логика должна быть покрыта тестами. Обязательно. В крайних случаях, если может сорваться запуск эксперимента или нужен срочный хот фикс, допускается завести задачу на написание тестов и положить ее в будущий спринт. На интервью, если показывают только один файл, как на нашем примере, то обязательно проговорить, что должны быть тесты на новую логику.

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

9️⃣ Принципиальным моментом может стать сигнатура функции. Важно обращать внимание на то, что мы передаем (и как) и что возвращаем. В нашем примере можно заметить 2 вещи: 1) Мы работаем с базой данных, но не передаем контекст (ctx.Context) а значит теряем гибкость и другие полезные возможности; 2) Передаем партицию БД в функцию, чего мы делать явно не должны на этом уровне (c партициями работаем снаружи).

1️⃣0️⃣ Нагрузка. Если у вас высоконагруженное приложение, то крайне важно обратить внимание на создаваемую вашими изменениями нагрузку. Согласована ли она с оунерами сервиса и готовы ли мы к ней? Отсюда вытекают всевозможные оптимизации, такие как использование горутин (если возможно делать несколько действий асинхроонно) и работа с ними. Частая ошибка - пересоздавать какой-нибудь объект в цикле в то время, когда он не меняется при итерациях и есть удобная возможность объявить его до.

1️⃣1️⃣ Работа с секретами. Обращайте внимание на то, как организована работа с переменными среды и различными секретами. Очевидно, что:
sql.Open("mysql", "username:password@tcp(127.0.0.1:3306)/jazzrecords") - недопустимо. Чувствительную информацию нужно получать из окружения или защищенного хранилища.

1️⃣2️⃣ Наличие уязвимостей. Так как мы работаем с БД, то важно проверить код на наличие инъекции. Подобный запрос уязвим:
query := fmt.Sprintf("insert into userstatushistory (user_id, status, inserted_at) values (?, ?, ?, ?)", userID, partition, status, time.Now())

Пользовательский ввод важно валидировать. Можно использовать различные средства предоставляемые библиотеками:

rows, err := db.Query("insert into userstatushistory (user_id, status, inserted_at) values (%v, %v, %v, %v)", user_id, status_ inserted_at)


После всех исправлений финальный код может выглядеть так:


// package domain
package storage

import (
"context"
"database/sql"
"fmt"
"os"
"time"
)

// type UserStatusHistoryStore struct {
type UserStatusHistory struct {
db *sql.DB
}

// func New() UserStatusHistoryStore {
func New() UserStatusHistory {
// db, err := sql.Open("mysql", "username:password@tcp(127.0.0.1:3306)/jazzrecords")
db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/jazzrecords", os.Getenv("user"), os.Getenv("password"), os.Getenv("ip")))
if err != nil {
panic(err)
}

// return UserStatusHistoryStore{
return UserStatusHistory{
db: db,
}
}

// func (s *UserStatusHistoryStore) Save(userID, partition, status string) {
func (s *UserStatusHistory) Save(ctx context.Context, userID, status string) error {
// query := fmt.Sprintf("insert into userstatushistory (user_id, status, inserted_at) values (%v, %v, %v, %v)", userID, partition, status, time.Now())
// s.db.Exec(query)
_, err := s.db.Query("insert into userstatushistory (user_id, status, inserted_at) values (%v, %v, %v)", userID, status, time.Now())
if err != nil {
return err
}

// ...

return nil
}


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

Если вы учли все вышеописанное, то с большой долей вероятности пройдете очередное код-ревью.
👍2
👤 Делаем CV с ATS >80%

Пару недель назад вдохновился постом Jerry Lee, который опубликовал "продающее" резюме.

Автор предлагает примерить на себя роль рекрутера и попробовать решить: наняли ли вы человека с таким резюме, как у вас? Держите в голове, что специалисты по найму тратят около 6 секунд на каждое CV. Очевидно, они не будут вчитываться в каждый ваш буллет.

F-формат

В ходе эксперимента, в котором захватывали движения глаз рекрутеров, пришли к выводу, что они читают в F-формате (к посту, приложу картинку, где это будет наглядно видно). Помните это, когда расставляете акценты.

XYZ

В блоке со своей карьерой соблюдайте формулу: XYZ (см. статью). Надеюсь, со временем тоже приведу к ней все полностью.

LLM считает ATS

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

Мое обновленное резюме доступно здесь. А самая первая версия выглядела так.
👍2
Апрель 2024:

навыки
✔️ Завершил курс по объектно-ориентированному программированию. Основные моменты разбирал здесь.
✔️ В рамках подготовки к собеседованию прочитал книгу “100 Go Mistakes and How to Avoid Them” и довольно подробно изучил Kafka, с которой раньше непосредственно работать не приходилось.
✔️ Написал первый пост, используя telegraph, где рассмотрел задачи с реального интервью.
✔️ Рассказал в 2-х частях как проводить код-ревью.
✔️ Завел репозиторий, где планирую выкладывать реализации основных паттернов проектирования на Go (уже выложены: observer и strategy).

карьера
✔️ Впервые за полтора года поучаствовал в техническом интервью по Go, БД и брокерам сообщений. Рефлексия здесь.
✔️ Обновил верстку своего резюме на github pages.
✔️ Обсудил с руководителем возможности для финансового роста, так как стало казаться, что моя квалификация стала опережать текущую компенсацию.
✔️ Взял на себя ответственность за онбординг нового бэкендера в команду (одна из компетенций инженера уровня senior).

прочее
✔️ Использовал 14 дней из накопленного отпуска, которые очень помогли разгрузиться. Крайне советую не пренебрегать этим, так как усталость в итоге сказывается не только на качестве вашей работы, но и отношениях с коллегами и близкими.

#результаты
🔥6👍2
В конце января обозначил ОКР'ы на этот год.

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

Фокусируемся .

1. Книги: Design Patterns и System Design

📌 Закончить до 31 мая

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

-

2. Новые роли в команде: Чемпион по безопасности и эксперт подачи в своей вертикали.

📌 До 24 мая

Запланировать день, когда проведу сессию по моделированию угроз с командой; также нарисовать схему взаимодействия всех сервисов и библиотек во время подачи объявления.

-

3. Менторство: Онбординг.

📌 Ближайшие 6 месяцев продолжаю активно погружать в разработку нового бэкендера.

И, конечно, же планирую ментально разгрузиться на Праздниках (повышаем эффективность). Чего и вам желаю!
👍3
Как все успевать?

Никак. Но есть альтернатива.

Несколько лет назад общался со знакомым на тему тайм-менеджмента и личной эффективности. Обсуждали обилие дел и недостаток времени.

В какой-то момент я задался вопросом: «Как все успевать, когда столько всего важного и срочного вокруг?».

Ответ меня поразил. Прозвучало: «Может быть тебе не нужно успевать все?».

* * *

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

Если емкость превышена, то начинается суета, стресс, снижается эффективность и в конечном счете страдает результат. Появляется синдром «как успеть все».

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

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

Сегодня хочу дать универсальный фреймворк, который работает для меня.

🧾 Фреймворк

1) Выписываем все срочные и/или важные задачи на ближайшую неделю.
2) Отбираем N задач, которые хотим сделать за 1 день (число для каждого индивидуальное, у меня, например, 5-6).
3) Распределяем задачи по 3-м зонам:

🟥 - зона фокуса (делаем в первую очередь, фактически цель дня, обязательно нужно выполнить)

🟨 - зона буфера (делаем по ходу дня, сюда попадают задачи, не требующие больших трудозатрат, но которые желательно выполнить за день без привязки ко времени)

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

4) Все, что не успели за день, отправляется в общий список на неделю (на следующий день автоматически не переносим!).

5) Для очередного дня применяем п.2.

6) По завершении недели оставшиеся задачи оцениваем: что перестало быть актуальным, а что лучше все-таки сделать. «Полезный остаток» отправляется в общий пул задач, из которого затем формируется п.1.

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

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

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

P.S.: Какую бы систему для планирование вы ни использовали, это гораздо лучше полного отсутствия системы.
👍101🥴1
{:permitted_use_established=>

Такой строчкой нас встречает официальный документ из Росреестра, заверенный ЭЦП.

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

В чем сложность покрыть формирование PDF-файла юнит-тестами? Форма стандартная, поля фиксированные. Полагаю, что изменение структуры случается крайне редко, но даже если нет, то добавить/удалить юниты ничего не стоит.

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

Вывод один: пишите тесты, увеличивайте code coverage. Поверьте, лучше написать тесты, а потом удалить, чем так и не написать.
💯31👍1
Не шутите с индексами, не верьте гарантиям и пишите надежный код

Работа с индексами массива сопряжена со множеством проблем. Тут и классическое "IndexOutOfRange", и, конечно же, получение неверного элемента из запрашиваемой позиции.

Возьмем простой код на Go. Нужно реализовать функцию getTag, которая ожидает в качестве аргумента массив и возвращает тэг нашей фичи. У нас также есть информация о том, что этот тип аргумента - "неизменный" легаси и у нас всегда приходит массив ровно с одним элементом. Разве может что-то плохое случиться, если получать значение прямо по индексу? Это выглядит быстро и удобно.

Абстрактный Петя так и решил, подготовил пул-реквест, отправил на код-ревью. Коллеги-синьоры, конечно, одобрили, так как все выглядело логично и лаконично.

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

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


package main

func main() {
tag := getTag([]string{"feature"})
if enableFeatureByTag(tag) {
print("OK")
return
}
print("FAILED")
}

func enableFeatureByTag(tag string) bool {
return tag == "feature"
}

func getTag(values []string) string {
if len(values) == 0 {
return ""
}
return values[0]
}


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

Оказалось, что в код попало то, чего не должно там быть в принципе. Гарантия, что массив состоит из одного значения, была нарушена, соседняя команда решила добавить еще одно значение ("json"), очевидно, не слышав ни про какие договоренности. Это же массив, почему бы его так и не использовать?

* * *

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


package main

func main() {
tag := getTag([]string{"json", "feature"})
if enableFeatureByTag(tag) {
print("OK")
return
}
print("FAILED")
}

func enableFeatureByTag(tag string) bool {
return tag == "feature"
}

func getTag(values []string) string {
for _, v := range values {
if v == "feature" {
return v
}
}
return ""
}


Герои вымешлены, все совпадения случайны :)

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

1. Забудьте про явную завязку на любые индексы, используйте безопасные конструкции (например, итерация по значениям массива). Заворачивайте коллекции в обертки с безопасным доступом к значениям и т.д.
2. Не верьте договоренностям, особенно словесным. Верьте написанному коду и тому, что может произойти в нем, исходя из общей логики и передаваемых аргументов (если код ответственный, то используйте фаззинг).
3. Лучше надежный и читаемый код, чем хрупкий, но сверхоптимизированный.
4. Помните, что код пишут люди, а им свойственно ошибаться.
🤔2
Как сделать продакшн среду стабильнее?

Одна из лучших (на мой взгляд) практик в биг техе - это проведение разбора инцидента после какого-либо происшествия на проде с привлечением всех причастных и заведением соответствующих артефактов.

В Авито это называется Live Site Review (LSR) и хорошо описано в playbook. Очевидно, идея не нова, а берет свое начало из лучших SRE практик.

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

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

1. На инцидент заводится задача, которая затем станет основным артефактом о произошедшем.
2. Опустим подробности процесса починки, который сопряжен с большим количеством "веселья" (особенно, когда начинается цепочка с призывом ответственных в канал по решению проблемы). После исправления - фиксируем все, что может помочь в дальнейшем разборе, в задаче из п.1 (обязательно должна быть ссылка на тред(ы), где обсуждалась и решалась проблема).
3. Как правило, в больших компаниях есть человек, который фасилитирует данный процесс - инцидент-менеджер. Такой человек следит за всем: от появления первого сообщения о проблеме до момента, когда работы направленные на исправление первопричин произведены и все задокументировано. Одной из обязанностей менеджера является организация встречи со всеми причастными, где будут выясняться причины произошедшего и формироваться экшн-айтемы.
4. Итак, инцидент-менеджер создал встречу и пригласил всех. Теперь необходимо вместе пробежаться по чек-листу (как правило, задача из п.1 имеет унифицированный шаблон для разбора инцидентов), собрать фактуру и зафиксировать всю информацию. Для лучшего понимания предлагаю посмотреть "список типовых вопросов, которые стоит обсудить при разборе LSR".
5. Финальным аккордом должны стать экшн-айтемы с назначенными ответственными и обозначенными сроками выполнения. Очевидно, что такие задачи имеют приоритет при взятии в работу.

За 2 года в компании меня дважды приглашали на разбор инцидента, и это всегда очень интересно. По сути, вся встреча - это мозговой штурм с ответами на вопросы: "Можно ли быстрее было среагировать на проблему?", "Почему это допустили?", "Чего не хватило при тестировании?" и пр.

Удивительно, после LSR, когда о проблеме известно почти все, а обстоятельства очевидны, - видеть какая мелочь могла породить серьезный инцидент.

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

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

Напишите в комментариях как в вашей компании организована практика по разбору инцидентов и насколько считаете такой процесс полезным ⤵️
👍1