Magang di JetBrains dan bagaimana saya hampir berhasil melakukannya

gambar

Seperti banyak pengembang muda, ketika ada keinginan untuk mencari pekerjaan / magang, saya melihat ke arah perusahaan IT keren.

Baru-baru ini saya mencoba masuk ke jajaran JetBrains dan di bawah potongan saya siap untuk berbagi pengalaman saya.

Mengapa “hampir” berhasil?


Tentunya Anda segera memiliki pertanyaan seperti itu.

Menurut pendapat saya, saya memiliki resume yang baik dengan banyak prestasi dan keterampilan yang baik, yang telah saya tingkatkan 8-9 tahun terakhir hari demi hari.

Saya menyelesaikan tugas tes (dan sepertinya baik bagi saya), sebelumnya mengunjungi kantor JB, yang berlokasi di kota saya, berbicara dengan HH dan beberapa pengembang perusahaan, dan sebagai hasilnya ditolak magang tanpa komentar.

Kemungkinan besar, alasannya terletak pada kenyataan bahwa JetBrains memilih siswa secara eksklusif untuk magang, dan saat ini saya baru saja lulus dari kelas 11 dan lulus ujian satu demi satu.

Nah, ini adalah kesempatan untuk setahun penuh untuk menarik diri dan melamar tahun depan.

Analisis tugas tes


Tenggat waktu untuk mengirimkan aplikasi untuk magang dan menguji tugas ujian sudah berakhir, yang berarti bahwa setiap orang yang menyelesaikannya, termasuk saya, dapat memposting analisis tugas ini sehingga tahun depan setiap siswa yang tertarik dapat membiasakan diri dengan perkiraan tingkat tugas sebelum memulai magang JB, dengan yang harus dia hadapi dan dalam hal ini untuk menarik ilmunya.

Saya melamar magang dengan tim pengembangan debugger Corotin untuk Kotlin.

Tugas tim ini selama magang bagi mereka yang berhasil tahun ini adalah menyelesaikan bagian debugger ini dan integrasinya dengan IDE.

Tugas itu sedikit diharapkan - untuk menulis debugger untuk PL kecil.

Saya tidak akan mengatakan bahwa itu rumit, justru sebaliknya. Tidak memerlukan pengetahuan mendalam tentang teori membangun penerjemah dan keterampilan yang keren. Namun demikian, mereka yang melamar magang di bidang ini setidaknya harus memiliki dasar-dasar ini dan mengatasi tugas ini tanpa masalah. Saya terkejut ketika saya memutuskan untuk mencari di github untuk kata kunci dari solusi "pesaing" saya dan menemukan 1-2 lebih atau kurang solusi yang tampak bekerja terhadap sekitar 6-7 repositori kosong atau dengan beberapa keping kode setelah orang menyerah. Mungkin saya terlihat buruk, tetapi hasilnya tidak menyenangkan saya. Jika posting ini akan dibaca oleh orang-orang yang meninggalkan tugas ini - tidak perlu melakukan ini di masa depan. Dalam kasus yang ekstrim, itu cukup untuk duduk di tugas selama beberapa hari dan saya yakin Anda akan menghadapinya.

Teks dari pencarian itu sendiri
Tujuan: mengimplementasikan eksekusi kode langkah-demi-langkah untuk bahasa pemrograman sepele Guu.

Perhatian: dalam uraian di bawah ini, beberapa poin penting sengaja dihilangkan. Sebagai aturan, mereka tetap pada kebijaksanaan Anda. Jika benar-benar tidak dapat dipahami, kirim surat ke (di sini adalah surat yang saya putuskan untuk dihapus).

Program Guu terdiri dari serangkaian prosedur. Setiap prosedur dimulai dengan sub baris (subname) dan berakhir dengan deklarasi prosedur lain (atau akhir file jika prosedur dalam file adalah yang terakhir). Eksekusi dimulai dengan sub main.

Tubuh prosedur adalah seperangkat instruksi, yang masing-masingnya berada pada baris terpisah. Tab atau spasi yang tidak signifikan dapat terjadi di awal baris. Baris kosong diabaikan. Tidak ada komentar tentang Guu.

Guu hanya memiliki tiga operator: - set (varname) (nilai baru) - pengaturan nilai integer baru untuk variabel. - panggilan (nama panggilan) - panggilan prosedur. Panggilan bisa bersifat rekursif. - print (varname) - cetak nilai variabel di layar.

Variabel dalam Guu memiliki cakupan global. Program di bawah ini akan menampilkan garis a = 2.

sub utama
atur 1
panggil foo
cetak a

sub foo
atur 2

Dan di sini adalah program paling sederhana dengan rekursi tak terbatas:

sub utama
panggilan utama

Anda perlu menulis juru bahasa selangkah demi selangkah untuk Guu. Ketika mulai, debugger harus berhenti pada baris dengan instruksi pertama di sub main dan menunggu perintah dari pengguna. Minimum yang diperlukan perintah debugger:

i - masuk ke, debugger masuk ke dalam panggilan (subname).
o - melangkahi, debugger tidak masuk ke dalam panggilan.
trace - print stack trace trace dengan nomor baris mulai dari ...
var - mencetak nilai semua variabel yang dideklarasikan.

Format komunikasi pengguna dengan debugger diserahkan kepada kebijaksanaan di atas. Anda dapat memilih antarmuka minimalis seperti GDB atau konsol atau UI grafis. Nama-nama perintah debugger dapat diubah jika diinginkan.

Untuk mengatasi masalah ini, Anda dapat menggunakan bahasa pemrograman apa pun dari TIOBE TOP 50 dan kompiler / juru bahasa sumber terbuka.

Saat mengevaluasi pekerjaan akan dievaluasi:

Kinerja keseluruhan program;
Kualitas kode sumber dan ketersediaan tes;
Mudah untuk memperluas fungsionalitas (misalnya, dukungan untuk pernyataan bahasa baru atau instruksi debugger).
Solusi dengan instruksi untuk membangunnya harus dipublikasikan ke repositori Git (misalnya, di GitHub atau BitBucket). Dalam respons Anda perlu menentukan tautan ke repositori. Tautan ke repositori GitHub pribadi juga cocok, hanya Anda yang perlu menambahkan saya ke dalamnya.

Saya menulis dalam C ++, Java dan Object Pascal.

Pada awalnya ada pemikiran untuk menulis semuanya di MPS saya yang sama, tapi saya pikir tidak akan nyaman untuk memeriksa karyawan JB, dan saya menyerahkan aplikasi 2 hari sebelum penutupan pengajuan (ujian semua sama ...), dan sudah malam di luar jendela - saya memutuskan untuk segera menulis semuanya dalam bahasa yang lebih terkenal.

Menurut pendapat saya, Pascal paling cocok untuk memecahkan masalah, setidaknya karena penerapan string yang paling nyaman ...

Setidaknya untukku. Selain itu, ada di TIOBE TOP 50, jadi saya berani meluncurkan IDE, yaitu Lazarus, karena dia tidak komersial :) dan mulai menyelesaikan masalah.

Terlepas dari kenyataan bahwa mereka memberi JB sebanyak 7 hari, saya butuh sekitar satu jam untuk menyelesaikan proyek, dan proyek itu ternyata sekitar 500 baris kode.

Di mana untuk memulai?


Pertama-tama, Anda perlu membayangkan bagaimana debugging kode akan bekerja pada akhirnya.

Kita perlu mengimplementasikan eksekusi kode langkah-demi-langkah - itu berarti setiap instruksi harus disajikan dalam bentuk struktur / kelas dan, secara umum, instruksi akan terlihat seperti daftar kelas-kelas ini atau, seperti dalam implementasi saya, merujuk satu sama lain membentuk urutan (saya akan menuliskan mengapa saya melakukannya nanti).

Untuk mendapatkan urutan ini, debugger kami perlu memproses kode dalam bahasa yang diusulkan, yang berarti kita juga perlu mengimplementasikan parser kecil, serta analisis sintaksis dan semantik kode.

Mari kita mulai dengan implementasi parser. Karena Karena bahasa Guu terdiri dari satu set token, dipisahkan oleh spasi, logis untuk menulis tokenizer kecil dan sederhana terlebih dahulu:

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; 

Selanjutnya, nyatakan enum dari token:

 type TGuuToken = (opSub, opSet, opCall, opPrint, opUnknown); const GuuToken: array[opSub..opPrint] of string = ( 'sub', 'set', 'call', 'print' ); 

Dan kelas instruksi itu sendiri, di mana kita akan mengurai baris kode:

 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; 

Dalam OpType instruksi akan disimpan, di OpArgs - sisa konstruksi.
OpLine, OpUnChangedLine - informasi untuk debugger.

NextOp adalah pointer ke pernyataan selanjutnya. Jika sama dengan nil (nol dalam Pascal), maka tidak ada instruksi lebih lanjut dan Anda perlu menyelesaikan kode atau kembali melalui tumpukan panggilan balik.

OpReg adalah register-pointer kecil, yang nantinya akan digunakan untuk optimasi kecil dari eksekusi kode.

Setelah deklarasi kelas ditulis - saya memutuskan bahwa solusi yang paling ringkas dan indah adalah menambahkan parser dan sedikit penguraian dalam konstruktornya, yang saya lakukan selanjutnya:

 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 <name> 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 <var> <value> 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 <name> 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 <var> 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; 

Di sini kita pada dasarnya memeriksa awal konstruksi (mis. Kata pertama), dan kemudian melihat token yang tersisa dan nomor mereka. Jika ada yang salah dengan kode, kami menampilkan kesalahan.

Dalam potongan kode utama, kita cukup membaca kode di TStringList dari file, memanggil konstruktor TGuuOp baris demi baris, dan menyimpan pointer ke instance kelas di GuuOps: TList.

Pengumuman:

 var LabelNames: TStringList; GuuOps, GuuVars: TList; SubMain: TGuuOp = nil; 

Bersama-sama dengan parsing kode, alangkah baiknya melakukan beberapa tindakan lagi:

 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; 

Pada tahap ini, Anda dapat memeriksa titik masuk pada saat redefinisi dan mengingat OpReg - Saya menggunakannya untuk menyimpan pointer ke variabel Guu.

Berbicara tentang variabel, saya mengambil sepotong kecil kode ini ke unit yang terpisah:

 unit uVars; {$mode objfpc}{$H+} 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. 

Sekarang kita telah mem-parsing kode yang tampaknya benar dalam sintaksis. Masih menganalisa dan Anda dapat mulai melakukan hal yang paling penting - debugging.

Selanjutnya, Anda perlu menerapkan analisis semantik kecil dan secara bersamaan menyiapkan semuanya untuk eksekusi kode dan debugging:

 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; 

Di TGuuOp.NextOp dari setiap token, tulis pointer ke token berikutnya.
Untuk opcode panggilan, kami melakukannya dengan rumit dan sederhana - di NextOp kami menulis sebuah pointer ke titik entri yang disebut.

Kami juga memeriksa variabel output melalui pernyataan cetak ...

Mungkin mereka tidak diumumkan sebelum kesimpulan?

Sekarang Anda perlu mengimplementasikan eksekusi kode. Kembali ke kelas TGuuOp dan terapkan metode 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; 

Untuk menghindari pelanggaran akses jika terjadi loop - lebih baik membatasi stack, yang saya lakukan.
STACK_SIZE = 2048 yang konstan, yang dinyatakan di atas hanya bertanggung jawab untuk ini.

Sekarang akhirnya saatnya untuk menulis kode utama debugger kami:

 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 <guu source file> [arg]', sLineBreak, 'Args:', sLineBreak, ' /run - Run Guu code.' ); end. 

Dengan kondisi pekerjaan, antarmuka dapat diimplementasikan sesuka Anda.

Mungkin saja untuk mengimplementasikan UI sepenuhnya, mengacaukan SynEdit ke proyek, tetapi menurut saya itu adalah pekerjaan kosong yang tidak akan mencerminkan keterampilan, dan selain itu tidak akan dibayar :)

Jadi saya membatasi diri pada UI konsol kecil.

Kode di atas tidak rumit, sehingga Anda dapat meninggalkannya tanpa komentar. Di dalamnya, kami mengambil TGuuOp siap pakai dan menyebutnya Langkah.

Tangkapan layar dari masalah yang diselesaikan:

gambar

gambar

Keluaran Informasi Kesalahan:

gambar

gambar

Tautan ke repositori solusi saya: klik

Ringkasan


Tidak ada hasil tertentu. Saya harus mencurahkan sebagian besar musim panas untuk liburan yang sibuk dan mencari universitas (tentu saja, jika saya lulus ujian dengan baik, tentu saja), alih-alih bekerja dan berlatih selama dua bulan di tim JetBrains.

Mungkin tahun depan posting baru akan muncul di Habré, sudah menggambarkan proses magang di JB atau di perusahaan lain yang menarik bagi saya :)

Source: https://habr.com/ru/post/id454354/


All Articles