Стажировка в JetBrains и как мне почти удалось попасть на неё
Как и многие молодые разработчики, когда появляется желание найти работу/стажировку — я смотрю в сторону крутых IT компаний.
Недавно я попробовал попасть в ряды JetBrains и под катом готов поделиться полученным опытом.
Почему «почти» удалось?
Наверняка сразу у вас встает такой вопрос.
На мой взгляд у меня имеется неплохое резюме с кучей ачивок и хороший скилл, который я день за днем совершенствую последние 8-9 лет.
Я выполнил тестовое задание (и как мне кажется хорошо), ранее посещал офис JB, который благо находится в моем городе, общался с HH и некоторыми разработчиками компании и в итоге получил отказ на стажировку без каких-либо комментариев.
Скорее всего причина таится в том, что на стажировку JetBrains отбирает исключительно студентов, а я на данный момент только выпустился из 11-го и сдаю один за другим экзамены.
Что ж, это повод в течении ещё целого года поднатаскать себя и подать заявку на следующий год.
Разбор тестового задания
Сроки подачи заявок на стажировку и проверки тестовых заданий закончились, а это значит, что все, кто их решил, включая меня — могут выложить разбор этих заданий, чтобы в следующем году любой желающий студент мог перед началом стажировок JB ознакомиться с примерным уровнем заданий, с которыми ему придется столкнуться и в случае чего подтянуть свои знания.
Я подавал заявку на стажировку в команду разработки отладчика корутин для Kotlin.
Задачей этой команды на стажировке у тех, кто на неё попал в этом году будет доработка этой части отладчика и её интеграция с IDE.
Задание было немного ожидаемым — написать отладчик для небольшого ЯП.
Я бы не сказал, что оно сложное, скорее наоборот. Оно не требует каких-либо глубоких знаний теории построения трансляторов и крутого скилла. Но тем не менее, те, кто подают заявку на стажировку по этому направлению, как минимум должны владеть этими основами и без проблем справиться с этим заданием. Я был удивлен, когда решил поискать на github’е по ключевым словам решения моих «конкурентов» и нашел 1-2 более-менее на вид рабочих решения против около 6-7 пустых репозиториев либо с парой кусков кода, после которых люди опустили руки. Возможно я плохо искал, но тем не менее результаты меня не порадовали. Если этот пост будут читать люди, которые забросили это задание — не надо в будущем так делать. В крайнем случае достаточно было посидеть над заданием пару дней и я уверен — вы бы с ним справились.
Сам текст задания
Задача: реализовать пошаговое исполнение кода для тривиального языка программирования Guu.
Внимание: в описании ниже заведомо опущены некоторые существенные моменты. Как правило, они остаются на ваше усмотрение. Если будет совсем непонятно, пишите на (тут почта, которую я решил убрать).
Программа на Guu состоит из набора процедур. Каждая процедура начинается со строки sub (subname) и завершается объявлением другой процедуры (или концом файла, если процедура в файле последняя). Исполнение начинается с sub main.
Тело процедуры – набор инструкций, каждая из которых находится на отдельной строке. В начале строки могут встречаться незначимые символы табуляции или пробелы. Пустые строки игнорируются. Комментариев в Guu нет.
В Guu есть лишь три оператора: — set (varname) (new value) – задание нового целочисленного значения переменной. — call (subname) – вызов процедуры. Вызовы могут быть рекурсивными. — print (varname) – печать значения переменной на экран.
Переменные в Guu имеют глобальную область видимости. Программа ниже выведет на экран строку a = 2.
sub main
set a 1
call foo
print a
sub foo
set a 2
А вот простейшая программа с бесконечной рекурсией:
sub main
call main
Необходимо написать пошаговый интерпретатор для Guu. При его запуске отладчик должен останавливаться на строчке с первой инструкцией в sub main и ждать команд от пользователя. Минимально необходимый набор команд отладчика:
i – step into, отладчик заходит внутрь call (subname).
o – step over, отладчик не заходит внутрь call.
trace – печать stack trace исполнения с номерами строк, начиная с main…
var – печать значений всех объявлённых переменных.
Формат общения пользователя с отладчиком остаётся на выше усмотрение. Вы можете выбрать как минималистичный GDB-like интерфейс, так и консольный или графический UI. Названия команд отладчика можно при желании изменить.
Для решения этой задачи вы можете использовать любой язык программирования из TIOBE TOP 50 и open-source компилятором/интерпретатором.
При оценке работы будет оцениваться:
Общая работоспособность программы;
Качество исходного кода и наличие тестов;
Простота расширения функциональности (например, поддержка новых операторов языка или инструкций отладчика).
Решение с инструкцией по его сборке нужно опубликовать в Git-репозиторий (например, на GitHub или BitBucket). В ответе нужно указать ссылку на репозиторий. Подойдёт и ссылка на приватный GitHub-репозиторий, только в него потребуется добавить меня.
Я пишу на C++, Java и Object Pascal.
Сначала были мысли написать все на моем же ЯП (Mash), но я подумал, что это будет не очень удобно проверять сотруднику JB, да и заявку я подал за 2 дня до закрытия подачи (экзамены все-же. ), да и за окном уже был вечер — решил я все быстренько написать на более известных языках.
Для решения задачи Pascal на мой взгляд подходит больше всего, как минимум из-за наиболее удобной реализации строк…
По крайней мере для меня. К тому же он находится в TIOBE TOP 50, так что я смело запустил IDE, а именно — Lazarus, т.к. он не коммерческий 🙂 и приступил к решению задачи.
Несмотря на то, что JB дают аж целых 7 дней, по времени у меня в сумме ушло около часа, а проект получился примерно в 500 строк кода.
С чего начать?
Прежде всего нужно представить, как будет в итоге работать отладка кода.
Нам нужно реализовать пошаговое выполнение кода — значит каждая инструкция должна быть представлена в виде структуры/класса и в общем инструкции должны выглядеть как список этих классов или, как в моей реализации — ссылаться друг на друга образуя последовательность (позже распишу почему я так сделал).
Чтобы получить эту последовательность, нашему отладчику нужно обработать код на предложенном языке, значит нам также нужно реализовать небольшой парсер, а также синтаксический и семантический анализ кода.
Начнем с реализации парсера. Т.к. язык Guu состоит из набора токенов, разделяемых пробелом, то логично первым делом написать небольшой и простой токенайзер:
function GetToken(s: string; tokenNum: word): string; var p: word; begin s := Trim(s); s := StringReplace(s, ' ', ' ', [rfReplaceAll]); while tokenNum > 1 do begin p := Pos(' ', s); if p > 0 then Delete(s, 1, p) else begin s := ''; break; end; dec(tokenNum); end; p := Pos(' ', s); if p > 0 then Delete(s, p, Length(s)); Result := s; end;
Далее объявляем enum из токенов:
type TGuuToken = (opSub, opSet, opCall, opPrint, opUnknown); const GuuToken: array[opSub..opPrint] of string = ( 'sub', 'set', 'call', 'print' );
И сам класс инструкции, в которую будем разбирать строки кода:
type TGuuOp = class public OpType : TGuuToken; OpArgs : TStringList; OpLine : Cardinal; OpUnChangedLine: string; NextOp : TGuuOp; OpReg : Pointer; function Step(StepInto: boolean; CallBacks: TList; Trace: TStringList): TGuuOp; constructor Create(LineNum: Cardinal; Line:string); destructor Destroy; override; end;
В OpType будет храниться инструкция, в OpArgs — остальные части конструкции.
OpLine, OpUnChangedLine — информация для отладчика.
NextOp — указатель на следующую инструкцию. Если он равен nil (null в Pascal), то далее нет инструкций и нужно завершить выполнение кода, либо вернуться по callback стеку.
OpReg — небольшой указатель-регистр, который будет использоваться далее для небольшой оптимизации выполнения кода.
После того, как было написано объявление класса — я решил, что наиболее компактным и красивым решением было бы дописать парсер и небольшой синтаксический анализ в его конструкторе, что я дальше и сделал:
constructor TGuuOp.Create(LineNum: Cardinal; Line:string); (* * That method parse code line. *) var s: string; w: word; begin inherited Create; OpArgs := TStringList.Create; OpLine := LineNum; OpUnChangedLine := Line; NextOp := nil; OpReg := nil; s := GetToken(Line, 1); OpType := TGuuToken(AnsiIndexStr(s, GuuToken)); case OpType of opSub : begin // sub s := GetToken(Line, 2); if Length(s) > 0 then OpArgs.Add(s) else begin writeln('[Syntax error]: Invalid construction "sub" at line ', OpLine, '.'); halt; end; if Length(GetToken(Line, 3)) > 0 then begin writeln('[Syntax error]: Invalid construction "', Line, '" at line ', OpLine, '.'); halt; end; end; opSet : begin // set OpArgs.Add(GetToken(Line, 2)); OpArgs.Add(GetToken(Line, 3)); w := 1; while w < Length(OpArgs[1]) + 1 do begin if not (OpArgs[1][w] in ['0'..'9']) then begin writeln('[Syntax error]: Invalid variable assigment "', Line, '" at line ', OpLine, '.'); halt; end; inc(w); end; if (Length(OpArgs[0]) = 0) or (Length(OpArgs[1]) = 0) or (Length(GetToken(Line, 4)) >0) then begin writeln('[Syntax error]: Invalid construction "', Line, '" at line ', OpLine, '.'); halt; end end; opCall : begin // call s := GetToken(Line, 2); if Length(s) > 0 then OpArgs.Add(s) else begin writeln('[Syntax error]: Invalid construction "call" at line ', OpLine, '.'); halt; end; if Length(GetToken(Line, 3)) > 0 then begin writeln('[Syntax error]: Invalid construction "', Line, '" at line ', OpLine, '.'); halt; end; end; opPrint: begin // print s := GetToken(Line, 2); if Length(s) > 0 then OpArgs.Add(s) else begin writeln('[Syntax error]: Invalid construction "print" at line ', OpLine, '.'); halt; end; if Length(GetToken(Line, 3)) > 0 then begin writeln('[Syntax error]: Invalid construction "', Line, '" at line ', OpLine, '.'); halt; end; end; else begin writeln('[Syntax error]: Invalid token "', s, '" at line ', OpLine, '.'); halt; end; end; end; destructor TGuuOp.Destroy; begin FreeAndNil(OpArgs); inherited; end;
Тут мы по сути проверяем начало конструкции (т.е. первое слово), а затем смотрим на остальные токены и их количество. Если с кодом что-то явно не правильно — выводим ошибку.
В главном куске кода мы просто читаем из файла код в TStringList, построчно вызываем конструкторы TGuuOp и сохраняем указатели на экземпляры классов в GuuOps: TList.
var LabelNames: TStringList; GuuOps, GuuVars: TList; SubMain: TGuuOp = nil;
Совместно с парсингом кода хорошо бы выполнить ещё пару действий:
procedure ParseNext(LineNum: Cardinal; Line: string); (* * Parsing code lines and define variables and labels. *) var Op: TGuuOp; GV: TGuuVar; c: cardinal; begin if Trim(Line) <> '' then begin Op := TGuuOp.Create(LineNum, Line); GuuOps.Add(Op); case Op.OpType of opSet: begin // define variable and/or optimisation var calling GV := nil; c := 0; while c < GuuVars.Count do begin if TGuuVar(GuuVars[c]).gvName = Op.OpArgs[0] then begin GV := TGuuVar(GuuVars[c]); break; end; inc(c); end; if GV = nil then begin GV := TGuuVar.Create(Op.OpArgs[0]); GuuVars.Add(GV); end; Op.OpReg := GV; end; opSub: begin // Check for label dublicade declaration if Op.OpArgs[0] = 'main' then SubMain := Op; if LabelNames.IndexOf(Op.OpArgs[0]) <>-1 then begin writeln('[Error]: Dublicate sub "', Op.OpArgs[0], '" declaration at line ', Op.OpLine, '.'); halt; end else LabelNames.Add(Op.OpArgs[0]); end; end; end; end;
На данном этапе можно проверить точки входа на момент переопределения и вспомнить про OpReg — его я использовал для хранения указателя на Guu переменную.
К слову о переменных — вынес этот небольшой кусок кода в отдельный юнит:
unit uVars; interface uses Classes, SysUtils; type TGuuVar = class public gvName: string; gvVal: variant; constructor Create(VarName: string); end; implementation constructor TGuuVar.Create(VarName: string); begin inherited Create; gvName := VarName; gvVal := 0; end; end.
Теперь у нас есть распарсенный код, который по синтаксису вроде бы правильный. Осталось его проанализировать и можно приступать к выполнению и самому главному — отладке.
Далее следует реализовать небольшой семантический анализ и попутно подготовить все к выполнению и отладке кода:
procedure CheckSemantic; (* * Semantic analyse and calls optimisation. *) var c, x: cardinal; op: TGuuOp; begin if GuuOps.Count > 0 then begin if TGuuOp(GuuOps[0]).OpType <> opSub then begin writeln('[Error]: Operation outside sub at line ', TGuuOp(GuuOps[0]).OpLine, '.'); halt; end; c := 0; while c < GuuOps.Count do begin case TGuuOp(GuuOps[c]).OpType of opSub:; opCall: begin TGuuOp(GuuOps[c - 1]).NextOp := TGuuOp(GuuOps[c]); x := 0; op := nil; while x < GuuOps.Count do begin if TGuuOp(GuuOps[x]).OpType = opSub then if TGuuOp(GuuOps[x]).OpArgs[0] = TGuuOp(GuuOps[c]).OpArgs[0] then begin op := TGuuOp(GuuOps[x]); break; end; inc(x); end; if op <>nil then TGuuOp(GuuOps[c]).OpReg := op else begin writeln('[Error]: Calling to not exist sub "', TGuuOp(GuuOps[c]).OpArgs[0], '" at line ', TGuuOp(GuuOps[c]).OpLine, '.'); halt; end; end; opPrint: begin TGuuOp(GuuOps[c - 1]).NextOp := TGuuOp(GuuOps[c]); x := 0; while x < GuuVars.Count do begin if TGuuVar(GuuVars[x]).gvName = TGuuOp(GuuOps[c]).OpArgs[0] then begin TGuuOp(GuuOps[c]).OpReg := TGuuVar(GuuVars[x]); break; end; inc(x); end; if TGuuOp(GuuOps[c]).OpReg = nil then begin writeln('[Error]: Variable "', TGuuOp(GuuOps[c]).OpArgs[0], '" for print doesn''t exist at line ', TGuuOp(GuuOps[c]).OpLine, '.'); end; end; else TGuuOp(GuuOps[c - 1]).NextOp := TGuuOp(GuuOps[c]); end; inc(c); end; end; end;
В TGuuOp.NextOp каждого токена записываем указатель на следующий за ним токен.
Для опкода call делаем все хитро и просто — в NextOp записываем указатель на вызываемую точку входа.
Также проверяем выводимые переменные через инструкцию print…
Может быть их не объявили перед выводом?
Теперь нужно реализовать выполнение кода. Возвращаемся к классу TGuuOp и реализуем метод Step:
function TGuuOp.Step(StepInto: boolean; CallBacks: TList; Trace: TStringList): TGuuOp; (* * That method execute instruction. *) var Op: TGuuOp; CBSize: Cardinal; begin case OpType of opSub: begin Trace.Add('-> Sub "' + OpArgs[0] + '"'); Result := NextOp; end; opCall: begin if StepInto then begin if NextOp <> nil then CallBacks.Add(NextOp); Result := TGuuOp(OpReg); end else begin Op := TGuuOp(OpReg); CBSize := CallBacks.Count; while ((Op <> nil) or (CallBacks.Count > CBSize)) and (Trace.Count < STACK_SIZE) do begin if Op = nil then begin Op := TGuuOp(CallBacks[CallBacks.Count - 1]); CallBacks.Delete(CallBacks.Count - 1); Trace.Delete(Trace.Count - 1); end; Op := Op.Step(StepInto, CallBacks, Trace); end; Result := NextOp; end; end; opPrint: begin writeln(TGuuVar(OpReg).gvName, ' = ', TGuuVar(OpReg).gvVal); Result := NextOp; end; opSet: begin TGuuVar(OpReg).gvVal := OpArgs[1]; Result := NextOp; end; end; end;
Чтобы избежать access violation в случае зацикливания — лучше ограничить стек, что я и сделал.
Константа STACK_SIZE = 2048, объявленная выше как раз отвечает за это.
Теперь наконец настало время написать основной код нашего отладчика:
var code: TStringList; c: Cardinal; cmd: string; CallBacks: TList; Trace: TStringList; DebugMode: boolean = true; begin if ParamCount > 0 then begin // Initialisation if not FileExists(ParamStr(1)) then begin writeln('[Error]: Can''t open file "', ParamStr(1), '".'); halt; end; if ParamCount > 1 then if LowerCase(ParamStr(2)) = '/run' then DebugMode := false; code := TStringList.Create; code.LoadFromFile(ParamStr(1)); GuuOps := TList.Create; GuuVars := TList.Create; // Parsing and preparing LabelNames := TStringList.Create; c := 0; while c < code.Count do begin ParseNext(c + 1, Trim(code[c])); inc(c); end; FreeAndNil(LabelNames); CheckSemantic; if SubMain = nil then begin writeln('[Error]: Sub "main" doesn''t exist!'); halt; end; // Start code execution CurrentOp := SubMain; CallBacks := TList.Create; Trace := TStringList.Create; if DebugMode then begin //Out code and features ClrScr; writeln('Code for debugging:'); writeln('. '); c := 0; while c < code.Count do begin writeln(FillSpaces(IntToStr(c + 1), 4), '| ', code[c]); inc(c); end; writeln('"""""'); FreeAndNil(code); writeln(sLineBreak, 'Features:', sLineBreak, '* i - step into.', sLineBreak, '* o - step over.', sLineBreak, '* trace - print stack trace.', sLineBreak, '* var - print variables list.', sLineBreak, '* x - exit.', sLineBreak); // Execution loop while ((CurrentOp <>nil) or (CallBacks.Count > 0)) and (Trace.Count < STACK_SIZE) do begin write('Line ', CurrentOp.OpLine, ' ~>'); readln(cmd); // Execute commands if cmd = 'i' then CurrentOp := CurrentOp.Step(true, CallBacks, Trace) else if cmd = 'o' then CurrentOp := CurrentOp.Step(false, CallBacks, Trace) else if cmd = 'trace' then begin writeln('| Trace:'); c := 0; while c < Trace.Count do begin writeln('| ', Trace[c]); inc(c); end; writeln('| ->Line ', CurrentOp.OpLine, ': "', CurrentOp.OpUnChangedLine, '".') end else if cmd = 'var' then begin writeln('| Variables list:'); c := 0; while c < GuuVars.Count do begin writeln('| ', TGuuVar(GuuVars[c]).gvName, ' = ', TGuuVar(GuuVars[c]).gvVal); inc(c); end; end else if cmd = 'x' then halt; // Check for method end & make callback if (CurrentOp = nil) and (CallBacks.Count >0) then begin CurrentOp := TGuuOp(CallBacks[CallBacks.Count - 1]); CallBacks.Delete(CallBacks.Count - 1); Trace.Delete(Trace.Count - 1); end; end; end else begin // Only run mode (/run) FreeAndNil(code); while ((CurrentOp <> nil) or (CallBacks.Count > 0)) and (Trace.Count < STACK_SIZE) do begin CurrentOp := CurrentOp.Step(false, CallBacks, Trace); if (CurrentOp = nil) and (CallBacks.Count >0) then begin CurrentOp := TGuuOp(CallBacks[CallBacks.Count - 1]); CallBacks.Delete(CallBacks.Count - 1); Trace.Delete(Trace.Count - 1); end; end; end; if Trace.Count >= STACK_SIZE then writeln('[Runtime error]: Stack overflow!'); FreeAndNil(CallBacks); FreeAndNil(Trace); end else writeln( 'Guu debugger v1.0.', sLineBreak, 'Author: Pavel Shiryaev (@RoPi0n).', sLineBreak, 'Run: svmc guu_debugger.vmc [arg]', sLineBreak, 'Args:', sLineBreak, ' /run - Run Guu code.' ); end.
По условию задания интерфейс можно реализовать как угодно.
Можно было бы реализовать полноценный UI, прикрутить SynEdit к проекту, но на мой взгляд — это пустой труд, который не отразит скилл, да и к тому же, за который не заплатят 🙂
Так что я ограничился небольшим консольным UI.
Код выше не является чем-то сложным, так что его можно оставить без комментариев. В нем мы берем готовые TGuuOp'сы и вызываем их Step.
Скрины решенной задачки:
Вывод информации об ошибках:
Ссылка на репозиторий моего решения: клац
Итоги
Итогов особо нет. Придется мне посвятить большую часть лета насыщенному отдыху и поиску вуза (ну, на случай если ЕГЭ я сдам хорошо, конечно), вместо двух месяцев работы и обучения в команде JetBrains.
Возможно в следующем году на Хабре появится новый пост, уже описывающий процесс стажировки в JB либо в другой интересной мне компании 🙂
- jetbrains internship
- стажировка в jetbrains
- Программирование
- Алгоритмы
- Компиляторы
Dmitri Nesteruk
Поскольку мы постепенно двигаемся к развязке всей этой истории, я решил посветить эту часть моего повествования моей работе в JetBrains (месте где я провел 5½ лет). JetBrains, для тех кто почему-то не знает, это компания, которую подняли три разработчика из России. Компания занимается производством инструментария для разработки, в т.ч. IDE, тулы командной работы, и даже собственный язык программирования!
Тут будет рассказ про то как я там работал и как я оттуда ушел и что я вообще обо всем этом думаю.
Для начала следует пояснить, что попал я туда очень… странно. Вся история достаточно странная и не очень укладывается. Дело было так: сидел я 2½ года дома и искал работу, но ничего не получалось. И вот, пошел я как-то в одно агенство искать работу — кстати, в то агенство которое мне нашло, собственно, мою первую в РФ работу (так что посыл уже нехороший). Сунулся к ним, они предложили сходить отсобеседоваться в некую достаточно стремную немецкую контору, ооочень сильно смахивующую на тот процессконтрол, который я попал несколькими годами ранее.
Это отдельная история, причем забавная. Контора куда меня послали находилась в заводе ЛОМО (Ленинградское Опто-Механическое Объединение), большом страшном здании которое, как и всю бывшую в СССР индустрию, распилили и раздали арендаторам. Само местоположения работы делало все это невозможным, т.к. ездить каждый день в Старую Деревню не имея машины (а ЗСД вроде бы в том направлении еще не было) мне не представлялось возможным, но, я решил сходить чисто из спортивного интереса интереса.
И это был провал. Это был провал запредельных масштабов. По каким-то причинам, я пришел туда после праздника, который был днем ранее, и на котором был алкоголь. Я не выспался, простоял час в метро, был под стрессом, собачки возле здания тоже доставили (тогда я ездил без оружия, о чем жалею, сейчас я блохастых раскидываю на ура). Охрана попытавшаяся забрать мой паспорт на входе тоже не порадовала — если перед собеседованием у вас пытаются забрать паспорт, поверьте, работать там не в ваших интересах.
Сама контора, филиал немецкой продуктовой фирмы, которая делала машины для подсчета денег. Единственное что думалось мне в тот момент: я хотел бы иметь машину чтобы считать свои горы денег. Всё. Но вот, начинается собеседование, мне задают вопросы, простые очевидные вопросы вроде «что такое .NET», а мой мозг просто берет и посылает меня нафиг. Полностью. Я не могу ответить ни на что. Такое впечатление, что на совокупность стрессоров, мое сознание просто ответило: нет, дружок, не сегодня, иди отдхони пожалуй.
Это был позор. Я думаю что те люди кто меня собеседовал тоже прифигели: человек вроде MVP, постит дофига интересного, подкаст, юзергруппа, и тут… но поделом, хорого что мне туда не светило, там были те же CMMI и прочие дурные запахи плохого кодинга и низких зарплат.
Связавшись снова с КА, а честно сказал им что «не судьба» и попросил их найти что-нибудь еще. Они сказали что да, вот тут есть контора, JetBrains называется, мы твое резюме зашлем. И пропали — я от них больше ничего никогда не услышал. Зато появился сам JetBrains.
Когда я пришел на собеседование в JB, в руках у кадровички (как-то невежливо звучит… от сотрудника отдела кадров компании, вот, сорян) было вовсе не то резюме которое должно было послать КА, а другое, которое я посылал сам в JB двумя годами ранее и которое было просто проигнорировано. Что это было? Думаю вы догадываетесь: КА послало мое фактическое резюме в контору, получило это свое «нам не интересно», а мня все-таки позвали т.к. все контакты были на моем CV которое уже было там. Этично ли такое поведение со стороны нанимателя? Это вам решать, но премия ускользнула из рук КА в этом случае, это точно.
Как я уже говорил, JB дал мне минимально возможную для сотрудника зарплату, но на самом деле нет. На самом деле я, как человек, который любит ломать комедию и даже в критическом положении понимал что нужно свой кусочек хлеба отгрызть, честно сказал: ребята, все супер, только я это, резидент Англии, а у вас офис в Чехии, давайте это, как-то, контрактинг и все такое. JB согласился. А когда такая пьянка пошла, мне пришло письмо, в котором меня попросили посчитать, сколько же нужно мне платить чтобы gross чтобы net был такой, как мне обещали. И вот тут стало совсем весело… думаю вы все уже догадались что я сделал.
Теперь, пожалуй, самое важное, для тех кто хочет там работать: JB обалденный работодатель и я глубоко им благодарен. Без них, я бы не сделал первый шаг на пути в нормальным деньгам и нормальной жизни. Интересные задачи, нормальные коллеги, отсутствие процессов и драмы на работе, бесплатная еда, спортзал, полеты бизнес-классом над Атлантикой — все это было прекрасно. Точнее было бы прекрасно если бы мне было лет 20 и у меня не было ребенка и более четкого понимания что все это золотая клетка и что активный доход — это вообще адъ. Но если вам 20 с хвостиком, JB — почти идеальное место работы. Я сам привел в компанию 2 человека и вижу, что их прет от работы там… и это хорошо!
При этом, нельзя сказать, что JB это тотальный идеал. Действительно, в большинстве продуктовых команд хорошо и можно много творить. Деньги вы можете посмотреть на Glassdoor, они, как бы сказать, даже по индустрии середнячок, и я не думаю что кто-то из девелоперов делает там «соточку» (а для меня это критерий, увы). Спектр задач узко специализирует вас в область алгоритмов что, с одной стороны, хорошо, с другой ограничивает вас в плане применения, например, real-life фреймворков. Мне в то время было с высокой башни и на то, и на то, и как я уже говорил, все мысли были о том как все это переиграть в свою пользу. Инфраструктура у JB была откровенно так себе — как я уже писал, я работал на достаточно плохом железе, на серверах где работали всякие CI системы тоже все «не подарок». Офисные реалии до переезда были так себе, читайте мой эпос про рестораны в предыдущих частях повествования.
Чтобы понять, как отжать себе маленький кусочек рая, я задал простой вопрос: если в JB так офигенно, то куда же уходят люди? Оказалось, что программисты если оттуда и уходят, то идут в именитые конторы вроде Microsoft или Google. Депрессирует ли это меня? Да, пожалуй. Во-первых, потому, что хоть зарплаты в этих местах и в разы больше, чем в JetBrains (это не должно удивлять), но корпоративная культура в этих энтерпрайзах… это что-то с чем-то. Microsoft, когда я ходил к ним в гости по работе — это просто бизнес, ребята в костюмах, разные отделы которые друг друга ненавидят, куча внутренней конкуренции и, самое главное, эта ситуация «лебедь, рак и щука» когда компания даже имея классные продукты (Нокия итд.) умудряется продалбывать целые сегменты рынка. Ну или Синофский который пытался убить дотнет… все эти истории плохо пахнут. Да, посмотрите на сегодняшний день, что у компании с ее главной ОСью? Да ничего. Все так же уныло и безыдейно, а в приоритетах уже только Azure.
Про другие конторы вроде Гугла (чего стоит внутреннее видео где сотрудники плачут после выборов бога-императора Трампа) у меня примерно такое же мнение: как только ты видишь в лексиконе компании такие слова как diversity, читать уже не стоит, т.к. белых гетеромужчин там очевидно угнетают (гребаные расисты-гетеросексисты!). А гугол как раз отличился 50-ю оттенками толерастии, так что я бы туда не пошел чисто по политическим соображениям. Примечательно что технический директор JetBrains ушел программистом именно в гугл… программистом, а не менеджером или руководителем продукта! Он потом вернулся, но… этот факт многое говорит о положении JB в табели о рангах ИТ индустрии. Покажите мне еще компанию, где техдир уходит на позицию кодера!
Короче, посмотрев на это, я все понял: какого-то прогресса ребята, уходящие из JB, не нашли, это не «качественный скачок» а просто развитие карьеры, где ты уезжаешь в долину на «сытные» для обычного айтишника 200к в год (налоги и конские траты можете посчитать самостоятельно). Только один человек из тех что я знаю стал исключением, и про него мы вскользь поговорим, но не сегодня. Сегодня у нас обсуждение того куда уходят люди из JB.
Нужно понимать, что я, работая в JB, не работал в JB. У меня даже в контракте было написано «не сотрудник», поэтому я не имел некоторых плюшек вроде медстраховки (до сих пор нет, сорян), но, с другой стороны, я имел плюшку в том, что будучи приписан к Пражскому офису, я был лишен всей бюрократии связанной с РФ. И это было хорошо, т.к. в Европе бизнес делается намного проще: все очень неформально и «был ли мальчик», этого тоже никто не узнает.
Команда евангелизма внутри JB — это вообще как отдельная компания. Там только иностранцы (в РФ мало у кого есть свободный инглиш, а тем у кого есть — мой респект и уважуха, вы крутые!), поэтому у нас в основном были люди из США, Британии и стран Европы где с английским лучше чем в Окоянной. Нельзя сказать что у них у всех идеальный английский, но но достаточно хорош чтобы рассказывать и делать видосики, писать статьи и все в этом духе. Как люди они… вменяемые. Серьезно, команда евангелизма это команда абсолютно адекватных людей, с похожими на мои взгляды на жизнь. Так же как и основной (программистский) состав JB, в евангелизме не любят буллшит и непрофессионализм, и все люди стараются чтобы «было хорошо».
В целом, все евангелисты были старше меня. И я, опять же, задал себе вопрос: вот эти ребята иногда уходят (текучка в евангелизме намного больше чем в основном JB, сюрприз!), а куда они уходят-то? Ведь явно куда-то где лучше, где больше бабла, они же не настолько наивны чтобы менять работу «идейно», у них дома, машины, семьи, яхты и вся эта кухня, за все это надо платить. И вот тут я кажется выиграл свой первый джэкпот.
Одного из наших евангелистов звали John Lindquist. Был он у нас недолго, а ушел потому, что сделал немало приносящий проект egghead.io — сайт с видосиками по разным веб технологиям вроде Node.js. Вся эта тема с курсами меня заинтриговала: я конечно не верил что там какие-то золотые горы, но знал что есть по крайней мере одна контора, Pluralsight, которая платит авторам чтобы они что-то там для нее писали.
Все мои попытки «питчить» курсы в Pluralsight были неуспешны: мне просто не отвечали. А потом я заметил что еще у одного автора, James Kovacs, который тоже какое-то время работал в JB (а потом ушел и вообще исчез куда-то), есть там какие-то курсы, и я просто попросил его пробросить контакт. И вот тут мне уже ответили, я предложил курс (по MATLAB), мне его зааппрувили и… предложили $4k за производство курса!
Я, если честно, прифигел. Жил я в то время небогато, а тут контора давала 4 тысячи за производство 3-5 часов видеоконтента, плюс еще давала после этого процент с просмотра этого же контента. Я подумал, что даже если делать по курсу в месяц, лишние 4к в месяц будет ой как неплохо, а если еще и пассив какой-то будет капать, то вообще хорошо!
Я написал несколько курсов, но один из курсов чего-то не задался: писал я его долго, Pluralsight были в раздумье то ли я им сделал что они хотели, достаточно ли он отличается от того что у них уже есть. И так далее. Но потом курс все-таки выпустили и он начал приносить, эмм, около $4k пассива.
Тут я сильно прифигел, так что прям задумался. Одно дело активный доход, а тут тебе такая же сумма сама в месяц падает, пиши-не пиши. Мне это как бы все понравилось и я взял и написал для Pluralsight еще много курсов на интересные технические темы. И сам обучился, и денег поднял еще больше, как на единовременных выплатах так и на роялти, которые со временем только росли.
На тот момент, работать евангелистом, при моем отвращении к поездкам и социуму, уже было как-то совсем глупо: деньги от этого евангелизирования не делали никакой погоды вообще (дошло даже до того, что я забыл подать инвойс на несколько тысяч и спохватился только несколькими месяцами спустя… мне было пофиг), меня это все только отвлекало. Я продложил писать курсы для Pluralsight и одновременно попробовал написать что-то для другой площадки, Udemy. И что вы думаете: за один год работы (один suka год. ) мой график выглядел вот так:
$20k пассива… вы только вдумайтесь, у нас пиковая зарплата разработчика это $5k (и эта сумма не растет уже лет 10!), больше этого платят мало кому, вакансий таких практически нет, и даже если ты и получишь больше, тебе нужно за эти деньги работать… Вообщем, если сложить это и то что я делал на Pluralsight, ну, по идее все это повествование можно закрывать, т.к. на такие деньги уже можно как-то худо-бедно жить, кушать в ресторанах (ну, не всегда с мишленовскими звездами), и заниматься персональными проектами. Можно даже купить себе что-нибудь, например… первую в жизни машину!
Весь мой эпос по идее должен закончиться на этой ноте как на «грани возможного», и так бы и было, не познакомься я в JB, будучи еще там, с одним разработчиком который, уволившись оттуда, не пошел работать по найму, а решил делать свой собственный стартап. Но это — тема для другого разговора. А сейчас, я лишь скажу что я не жалею что провел более 5 лет в JetBrains. Если у вас есть шанс идти туда работать — идите, т.к. по РФ в плане кодинга трудно найти что-то лучше. ■
Как попасть в jetbrains
Если уж говорить об IDE, то только с человеком, который сам приложил руку к всемирно известным продуктам. IntelliJ IDEA, PyCharm, WebStorm, RubyMine - это далеко не все продукты, в разработке которых участвовал Дмитрий Жемеров, ветеран JetBrains. Дмитрий заглянул к нам в гости, и вместе мы прошлись по основным этапам развития такого важного для разработчика инструмента, заглянули «под капот» IDE и обсудили её устройство на различных уровнях: от базовых функций вроде текстового редактора, до взаимодействия IDE с плагинами. Бонусом — рассказ про то, как устроена работа над продуктами JetBrains из первых уст.
P.S. Конечно, не обошлось без сравнений различных продуктов. Но упоминаний XCode в выпуске крайне мало, ведь мы не iOSный подкаст!
Дмитрий Жемеров
Ветеран Jetbrains.
Стало интересно? Послушайте весь выпуск!
Содержание
00:00:40 - Знакомство с гостем
00:02:20 - Историческая справка: с чего все началось
00:05:00 - 2000-e: crossing refactoring rubicon.
00:05:50 - История появления JetBrains
00:08:00 - Развитие Eclipse и Netbeans
00:08:50 - Web based IDE
00:13:00 - IDE в наши дни; LSP
00:15:00 - Производительность IDE
00:19:50 - Обзор основных инструментов
00:27:40 - Устройство IDE: базовые функции
00:31:00 - Устройство IDE: дополнительные функции
00:32:00 - Про визуальные редакторы
00:36:00 - Плагины для IDEA
00:47:00 - Про Android Studio
00:50:15 - Как написать свою IDE?
00:56:00 - Дмитрий о JetBrains
01:07:45 - Как попасть в JetBrains
01:10:10 - Подведение черты
01:11:50 - Вопрос Стасу
- Концепт Xcode под iPad
- Language Server Protocol
- Документация API для плагинов IDE
JetBrains замораживает продажи в России и Беларуси, уходит из России
JetBrains приостанавливает продажи в России и Беларуси, а также R&D — в России.
Текст: Отдел новостей Теги: mic, jetbrains
Нашли ошибку в тексте-выделите ее и нажмите Ctrl+Enter. Нашли ошибку в тексте-выделите ее и нажмите кнопку «Сообщить об ошибке»."
Читайте также
Как разработчик в Польше работал курьером (но потом всё получилось)
@dzikpic, канал для айтишников в Польше, рассказал историю Александра. Перед тем, как попасть в польскую компанию, он два месяца доставлял еду в Glovo. Каково это — ездить на велосипеде по 10-12 часов в день и почему маникюрщица зарабатывает больше разработчика.
Айтишник купил дом в Польше. Как получить разрешение в 2023, когда отказов больше
@dzikpic, канал для ИТ-экспатов в Польше, рассказывает историю белорусского айтишника, который купил дом в Гданьске, с комментариями эксперта. Обсудить историю можно в чате.