
مثل العديد من المطورين الشباب ، عندما تكون هناك رغبة في العثور على وظيفة / تدريب داخلي ، فإنني أتطلع إلى اتجاه شركات تكنولوجيا المعلومات الرائعة.
لقد حاولت مؤخرًا الدخول في صفوف JetBrains وتحت القدر ، أنا مستعد لمشاركة تجربتي.
لماذا نجح "تقريبًا"؟
بالتأكيد لديك على الفور مثل هذا السؤال.
في رأيي ، لدي سيرة ذاتية جيدة مع مجموعة من الإنجازات ومهارة جيدة ، والتي قمت بتحسينها في آخر 8-9 سنوات يومًا بعد يوم.
لقد أكملت مهمة الاختبار (وهي تبدو جيدة بالنسبة لي) ، قمت سابقًا بزيارة مكتب JB ، الموجود في مدينتي ، وتحدثت إلى HH وبعض مطوري الشركة ، ونتيجة لذلك تم رفض تدريب داخلي دون أي تعليقات.
على الأرجح ، يكمن السبب في حقيقة أن JetBrains تختار الطلاب حصريًا للتدريب ، وفي الوقت الحالي تخرجت للتو من 11 وحصلت على امتحانات واحدة تلو الأخرى.
حسنًا ، هذه مناسبة لمدة عام كامل للتقدم والتقدم للعام المقبل.
تحليل مهمة الاختبار
انتهت المواعيد النهائية لتقديم طلبات التدريب الداخلي واختبار مهام الاختبار ، مما يعني أنه يمكن لكل من حلها ، بمن فيهم أنا ، نشر تحليل لهذه الواجبات حتى يتسنى لأي طالب مهتم في العام المقبل التعرف على المستوى التقريبي للواجبات قبل بدء التدريب الداخلي JB ، مع التي سوف تضطر إلى مواجهة وفي هذه الحالة لسحب علمه.
تقدمت بطلب للحصول على تدريب داخلي مع فريق تطوير مصحح أخطاء Corotin في Kotlin.
ستكون مهمة هذا الفريق خلال فترة تدريب لأولئك الذين ضربوه هذا العام هي إنهاء هذا الجزء من المصحح وتكامله مع IDE.
كانت المهمة متوقعة قليلاً - كتابة مصحح أخطاء PL صغير.
لن أقول أنه معقد ، بل هو العكس. لا يتطلب أي معرفة متعمقة لنظرية بناء المترجمين ومهارة رائعة. ومع ذلك ، يجب أن يكون لدى المتقدمين للحصول على تدريب داخلي في هذا المجال هذه الأساسيات والتعامل مع هذه المهمة على الأقل دون أي مشاكل. لقد فوجئت عندما قررت البحث في github عن الكلمات الرئيسية عن حلول "المنافسين" ووجدت ما بين حلين أو أكثر من الحلول التي تبحث عن العمل مقابل حوالي 6-7 مستودعات فارغة أو مع بضعة أجزاء من التعليمات البرمجية التي استسلم الناس بعدها. ربما كنت أبحث بشكل سيء ، لكن النتائج لم ترضيني. إذا كان سيتم قراءة هذا المنشور من قبل الأشخاص الذين تخلوا عن هذه المهمة - لا حاجة للقيام بذلك في المستقبل. في الحالة القصوى ، كان كافياً الجلوس في المهمة لبضعة أيام ، وأنا متأكد من أنك ستتعامل معها.
نص السعي نفسهالهدف: تنفيذ التعليمات البرمجية خطوة بخطوة للغة البرمجة التافهة Guu.
انتباه: في الوصف أدناه ، تم حذف بعض النقاط المهمة عن عمد. كقاعدة عامة ، فإنها تظل وفقًا لتقديرك. إذا كان الأمر غير مفهوم تمامًا ، فاكتب إلى (هنا هو البريد الذي قررت إزالته).
يتكون برنامج Guu من مجموعة من الإجراءات. يبدأ كل إجراء بالخط الفرعي (اسم فرعي) وينتهي بإعلان إجراء آخر (أو نهاية الملف إذا كان الإجراء الموجود في الملف هو الأخير). يبدأ التنفيذ بـ sub main.
نص الإجراء هو مجموعة من التعليمات ، كل منها في سطر منفصل. قد تحدث علامات تبويب أو مسافات غير مهمة في بداية السطر. يتم تجاهل خطوط فارغة. لا توجد تعليقات على Guu.
لدى Guu ثلاثة عوامل تشغيل فقط: - (varname) (قيمة جديدة) - تعيين قيمة عددية جديدة للمتغير. - استدعاء (اسم فرعي) - استدعاء الإجراء. يمكن أن تكون المكالمات متكررة. - طباعة (varname) - طباعة قيمة المتغير على الشاشة.
المتغيرات في Guu لها نطاق عالمي. سيعرض البرنامج أدناه السطر أ = 2.
الفرعية الرئيسية
ضبط 1
استدعاء فو
طباعة
فو الفرعي
ضبط 2
وهنا أبسط برنامج مع العودية لانهائية:
الفرعية الرئيسية
استدعاء الرئيسي
تحتاج إلى كتابة مترجم فوري ل Guu. عند بدء تشغيله ، يجب أن يتوقف المصحح على السطر مع التعليمة الأولى في المفتاح الفرعي وينتظر الأوامر من المستخدم. مجموعة الحد الأدنى المطلوبة من أوامر مصحح الأخطاء:
i - خطوة إلى ، يذهب المصحح داخل استدعاء (اسم فرعي).
o - تخطى ، المصحح لا يذهب داخل المكالمة.
trace - تنفيذ تتبع مكدس الطباعة مع بدء أرقام الأسطر من ...
فار - قيم طباعة جميع المتغيرات المعلنة.
يتم ترك تنسيق اتصال المستخدم مع المصحح لتقدير أعلاه. يمكنك اختيار واجهة بسيطة مثل GDB أو وحدة تحكم أو واجهة مستخدم رسومية. يمكن تغيير أسماء أوامر مصحح الأخطاء إذا رغبت في ذلك.
لحل هذه المشكلة ، يمكنك استخدام أي لغة برمجة من TIOBE TOP 50 ومترجم / مترجم مفتوح المصدر.
عند تقييم العمل سيتم تقييمه:
الأداء العام للبرنامج ؛
جودة شفرة المصدر وتوافر الاختبارات ؛
سهولة توسيع الوظيفة (على سبيل المثال ، دعم بيانات اللغة الجديدة أو إرشادات تصحيح الأخطاء).
يجب نشر حل يتضمن تعليمات للبناء على مستودع Git (على سبيل المثال ، على GitHub أو BitBucket). في الاستجابة تحتاج إلى تحديد رابط للمستودع. ارتباط إلى مستودع GitHub خاص مناسب أيضًا ، فقط ستحتاج إلى إضافتي إليه.
أنا أكتب بلغة C ++ و Java و Object Pascal.
في البداية كانت هناك أفكار لكتابة كل شيء في نفس MPS ، لكنني اعتقدت أنه لن يكون من المناسب للغاية التحقق من موظف JB ، وقد قدمت الطلب قبل يومين من إغلاق التقديم (الامتحانات كلها ...) ، و كان المساء بالفعل خارج النافذة - قررت أن أكتب بسرعة كل شيء بلغات أكثر شهرة.
في رأيي ، Pascal هو الأنسب لحل المشكلة ، على الأقل بسبب التنفيذ الأكثر ملاءمة للسلاسل ...
على الأقل بالنسبة لي. بالإضافة إلى ذلك ، فهو في TIOBE TOP 50 ، لذلك أطلقت بجرأة IDE ، لعازر ، لأن انه ليس تجاري :) وبدأ في حل المشكلة.
على الرغم من حقيقة أنهم منحوا 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;
بعد ذلك ، أعلن التعداد من الرموز:
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 هو مؤشر إلى العبارة التالية. إذا كانت مساوية للصفر (خالية في Pascal) ، فلا توجد تعليمات أخرى وتحتاج إلى إكمال الكود أو العودة عبر مكدس رد الاتصال.
OpReg عبارة عن سجل مؤشر صغير ، سيتم استخدامه لاحقًا لتحسين صغير لتنفيذ التعليمات البرمجية.
بعد كتابة إعلان الفصل - قررت أن الحل الأكثر ضغطًا وجمالًا هو إضافة المحلل اللغوي والتحليل القليل في مُنشئه ، وهو ما قمت به بعد ذلك:
constructor TGuuOp.Create(LineNum: Cardinal; Line:string); 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
نحن هنا نتحقق بشكل أساسي من بداية الإنشاء (أي الكلمة الأولى) ، ثم ننظر إلى الرموز المتبقية وأرقامها. إذا كان هناك خطأ ما في الشفرة ، فسنعرض خطأ.
في الجزء الرئيسي من الكود ، نقرأ ببساطة الكود الموجود في TStringList من الملف ، ونستدعي إلى مُنشئي TGuuOp سطراً سطراً ، ونخزن المؤشرات إلى مثيلات الفئة في GuuOps: TList.
الإعلانات المبوبة:
var LabelNames: TStringList; GuuOps, GuuVars: TList; SubMain: TGuuOp = nil;
جنبا إلى جنب مع تحليل الشفرات ، سيكون من الجميل القيام ببعض الإجراءات الأخرى:
procedure ParseNext(LineNum: Cardinal; Line: string); 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
في هذه المرحلة ، يمكنك التحقق من نقاط الدخول في وقت إعادة التعريف والتفكير في OpReg - لقد استخدمتها لتخزين مؤشر لمتغير Guu.
عند الحديث عن المتغيرات ، أخذت هذه الشفرة الصغيرة في وحدة منفصلة:
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.
الآن لدينا رمز تحليل يبدو أنه صحيح في بناء الجملة. يبقى تحليله ويمكنك البدء في أداء الشيء الأكثر أهمية - تصحيح الأخطاء.
بعد ذلك ، تحتاج إلى تنفيذ تحليل دلالي صغير وإعداد كل شيء في وقت واحد لتنفيذ التعليمات البرمجية وتصحيح الأخطاء:
procedure CheckSemantic; 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 من كل رمز ، اكتب مؤشرًا إلى الرمز المميز التالي.
بالنسبة لشفرة الاتصال للمكالمات ، نحن نقوم بذلك بطريقة صعبة وبسيطة - في NextOp نكتب مؤشرًا إلى نقطة الدخول المسماة.
نحن أيضا التحقق من متغيرات الإخراج من خلال بيان الطباعة ...
ربما لم يتم الإعلان عنها قبل الختام؟
الآن تحتاج إلى تنفيذ تنفيذ التعليمات البرمجية. العودة إلى فئة TGuuOp وتطبيق طريقة الخطوة:
function TGuuOp.Step(StepInto: boolean; CallBacks: TList; Trace: TStringList): TGuuOp; 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;
لتجنب انتهاك الوصول في حالة حدوث حلقة - من الأفضل الحد من المكدس ، وهو ما قمت به.
ثابت STACK_SIZE = 2048 ، أعلن أعلاه مسؤولة فقط عن هذا.
الآن ، أخيرًا ، حان الوقت لكتابة التعليمات البرمجية الرئيسية الخاصة بمُصحح الأخطاء:
var code: TStringList; c: Cardinal; cmd: string; CallBacks: TList; Trace: TStringList; DebugMode: boolean = true; begin if ParamCount > 0 then begin
حسب حالة الوظيفة ، يمكن تنفيذ الواجهة كما تريد.
سيكون من الممكن تنفيذ واجهة مستخدم كاملة ، وإدخال SynEdit في المشروع ، ولكن في رأيي ، إنه عمل فارغ لا يعكس المهارة ، إلى جانب أنه لن يتم دفع مقابله :)
لذلك قصرت نفسي على وحدة تحكم صغيرة واجهة المستخدم.
الكود أعلاه ليس معقدًا ، لذا يمكنك تركه دون تعليق. في ذلك ، نأخذ TGuuOp الجاهزة ونطلق عليها خطوة.
لقطات من المشكلة التي تم حلها:


خطأ في إخراج المعلومات:


رابط إلى مستودع الحل الخاص بي:
انقرالنتائج
لا توجد نتائج محددة. سيتعين عليّ تخصيص معظم الصيف لقضاء إجازة مزدحمة والبحث عن جامعة (جيدًا ، في حالة اجتياز الاختبار جيدًا بالطبع) ، بدلاً من شهرين من العمل والتدريب في فريق JetBrains.
ربما في العام القادم ستظهر مشاركة جديدة على Habré ، تصف بالفعل عملية التدريب الداخلي في JB أو في شركة أخرى مثيرة للاهتمام بالنسبة لي :)