ข้ามไปยังเนื้อหา

ความฉลาดของโค้ด

Control Center มอบสี่วิธีให้เอเจนต์ใช้ทำความเข้าใจโค้ดนอกเหนือไปจากการอ่านโค้ด ทั้งสี่วิธีตอบคำถามที่แตกต่างกันจริง ๆ และเหตุผลที่มีสี่วิธีแทนที่จะมีวิธีเดียวคือ ไม่มีกลไกใดกลไกหนึ่งตอบได้ครบทุกคำถาม

กลไก ตอบคำถาม ต้นทุน
กราฟโค้ด มีอะไรอยู่ตรงนี้ และมีอะไรบ้างที่พึ่งพามัน การจัดทำดัชนีหนึ่งรอบ ต้นทุนการค้นต่ำ
เซิร์ฟเวอร์ภาษา โค้ดนี้คอมไพล์ผ่านจริงหรือไม่ และไทป์นี้คืออะไร โปรเซสเซิร์ฟเวอร์หนึ่งตัวต่อโปรเจกต์
การค้นหาเชิงโครงสร้าง รูปร่างนี้ปรากฏที่ไหน การแยกวิเคราะห์หนึ่งครั้งต่อไฟล์
ดีบักเกอร์ อะไรเป็นจริง ณ ขณะนั้น ในเฟรมนั้น โปรเซสที่ถูกหยุดไว้

กราฟโค้ด สร้างขึ้นจาก tree-sitter: สัญลักษณ์ เส้นเชื่อมการเรียก และรัศมีผลกระทบ มันเร็ว ต้นทุนต่ำ และไม่รู้อะไรเลยเกี่ยวกับไทป์ มันบอกคุณไม่ได้ว่าการเรียกที่คุณเพิ่งเขียนจะคอมไพล์ผ่านไหม ฟิลด์นั้นมีอยู่บนออบเจกต์จริงหรือไม่ หรือการเปลี่ยนชื่อของคุณพลาดจุดที่ส่งออกซ้ำไปหรือเปล่า

ถ้าไม่มีเซิร์ฟเวอร์ภาษา เอเจนต์จะรู้ก็ตอนรันเทสต์ — หรือไม่ก็ไม่รู้เลย มันเขียนอะไรที่ดูสมเหตุสมผลแล้วไปต่อ ความล้มเหลวจึงโผล่ขึ้นมาอีกสามเทิร์นให้หลัง โดยไปพัวพันอยู่กับการเปลี่ยนแปลงคนละเรื่อง

ดังนั้นผลตอบแทนที่ได้จริง ๆ ไม่ใช่ตัวเครื่องมือ lsp แต่คือการที่ write, edit และ apply_patch ถูกห่อไว้: ทุกครั้งหลังการเขียน ไฟล์จะถูกส่งผ่านเซิร์ฟเวอร์ภาษา และผลวินิจฉัยที่เพิ่งเกิดขึ้นใหม่จะถูกพับรวมเข้าไปในผลลัพธ์ของเครื่องมือ เอเจนต์จึงรู้ว่าตัวเองทำให้บิลด์พังในจังหวะที่มันพังเลย ไม่ว่าจะนึกถามหรือไม่ก็ตาม

การพูดซ้ำสิ่งที่เอเจนต์รู้อยู่แล้วแย่กว่าไม่พูดอะไรเลย เพราะมันจะถมผลลัพธ์ด้วยสัญญาณรบกวนที่เอเจนต์เรียนรู้ที่จะกวาดตาข้ามไป พร้อมกันนั้นก็พาเอาข้อผิดพลาดใหม่เพียงข้อเดียวนั้นหลุดไปด้วย ทะเบียนต่อการรันหนึ่งชุดจะคอยติดตามว่าอะไรถูกแจ้งไปแล้ว โดยยึดความรุนแรง รหัส และข้อความเป็นคีย์ — ไม่ใช่ตำแหน่ง ดังนั้นผลวินิจฉัยที่แค่เลื่อนลงมาสี่บรรทัดจะไม่ถูกแจ้งซ้ำว่าเป็นของใหม่

ทำงานแบบขี้เกียจ และซื่อตรงเมื่อไม่มีอยู่จริง

หัวข้อที่มีชื่อว่า “ทำงานแบบขี้เกียจ และซื่อตรงเมื่อไม่มีอยู่จริง”

ไม่มีเซิร์ฟเวอร์ตัวใดเริ่มทำงานตอนบู้ต ตัวหนึ่งจะเริ่มเมื่อมีคำขอแรกที่ต้องใช้มัน ซึ่งเกือบทุกครั้งคือการแก้ไขไฟล์ที่มันดูแลเป็นครั้งแรก ส่วนตัวที่ว่างอยู่จะถูกกวาดคืน โฮสต์ที่ไม่เคยแตะ Dart เลยก็จะไม่เริ่มตัววิเคราะห์

การตรวจหาคือส่วนตัดกัน: ต้องมีมาร์กเกอร์ที่รากโปรเจกต์อยู่ในเช็กเอาต์ และต้องค้นหาไบนารีเจอ มาร์กเกอร์อย่างเดียวบอกได้แค่ “โปรเจกต์นี้เป็น Python” และไม่ได้บอกว่าเซิร์ฟเวอร์ติดตั้งอยู่หรือไม่ ส่วนไบนารีที่ค้นหาเจออย่างเดียวก็บอกได้แค่ว่าโฮสต์มีชุดเครื่องมือ แต่ไม่ได้บอกอะไรเกี่ยวกับเช็กเอาต์นี้ มีเพียงทั้งสองอย่างพร้อมกันเท่านั้นที่แปลว่าคำค้นจะสำเร็จได้ และเครื่องมือที่ถูกเสนอให้ใช้แต่ล้มเหลวทุกครั้งก็แย่กว่าเครื่องมือที่ไม่มีอยู่เสียอีก

ทำไมต้องมีการค้นหาเชิงโครงสร้าง ในเมื่อมี grep อยู่แล้ว

หัวข้อที่มีชื่อว่า “ทำไมต้องมีการค้นหาเชิงโครงสร้าง ในเมื่อมี grep อยู่แล้ว”

grep จับคู่ตัวอักษร ซึ่งเป็นเครื่องมือที่เหมาะกับการหาชื่อ แต่ผิดสำหรับการหารูปร่าง

แพตเทิร์นเชิงโครงสร้างถูกแยกวิเคราะห์ด้วยไวยากรณ์เดียวกับไฟล์ ดังนั้น dispose($X) จึงหาการเรียกเจอไม่ว่าจะเว้นวรรค ห่อ หรือตัดบรรทัดอย่างไร และจะไม่จับคู่ตัวอักษรชุดเดียวกันที่อยู่ในสตริงลิเทอรัลหรือคอมเมนต์เลย

คุณสมบัติที่ทำให้มันเป็นมากกว่ารีจิกซ์ที่เพิ่มขั้นตอนเข้ามาคือ เมทาวาริเอเบิลที่ซ้ำกันในแพตเทิร์นเดียวต้องจับข้อความเดียวกัน:

if ($X != null) $X.dispose()

จะจับคู่กับโค้ดป้องกันที่ dispose สิ่งที่มันเพิ่งตรวจสอบ และจะไม่จับคู่กับอันที่ dispose อย่างอื่น นั่นคือบั๊กประเภทที่คุณเขียนออกมาเป็นข้อความให้ค้นหาไม่ได้เลย

ไม่มีเอนจินตัวที่สอง cc_natives โหลด tree-sitter และไวยากรณ์ของมันไว้แล้วสำหรับกราฟโค้ด พร้อมแคชคำค้นที่คอมไพล์ไว้และตารางภาษา ตัวจับคู่เป็น Dart ล้วน ๆ ที่ทำงานบนทรีซึ่งถูกแยกวิเคราะห์อยู่แล้ว การมีเอนจินเชิงโครงสร้างตัวที่สองจะหมายถึงตัวแยกวิเคราะห์ตัวที่สอง ชุดไวยากรณ์อีกชุดที่ต้องบิลด์และติดตั้งบนทุกแพลตฟอร์ม และคำตอบสองแบบต่อคำถามว่า “อะไรคือนิพจน์การเรียก”

ไวยากรณ์นิยามแค่ว่าไฟล์ทั้งไฟล์คืออะไร เมื่อแยกวิเคราะห์ dispose(x) เปล่า ๆ ที่ระดับบนสุดของ Dart มันคือลายเซ็นฟังก์ชันที่ถูกต้องสมบูรณ์ — การประกาศชื่อ dispose ที่รับพารามิเตอร์ x — โดยไม่มีข้อผิดพลาดตรงไหนบอกให้รู้เลย ตัวจับคู่ที่สร้างบนการแยกวิเคราะห์แบบนั้นจะมองหาการประกาศ ไม่เจอการเรียก แล้วรายงานว่า “ไม่พบรายการที่ตรงกัน” สำหรับแพตเทิร์นที่จริง ๆ แล้วไม่เคยถูกอ่านในแบบที่มันดูเหมือน

แพตเทิร์นจึงถูกไล่ลองในบันไดของโครงร่าง — คำสั่ง นิพจน์ สมาชิกคลาส ระดับบนสุด — และโครงร่างแรกที่ยอมรับมันได้จะเป็นผู้ชนะ เศษโค้ดที่ไม่มีบริบทใดยอมรับได้จะถูกรายงานว่าแยกวิเคราะห์ไม่ได้ แทนที่จะถูกค้นหาแล้วเงียบ ๆ ว่าไม่พบ

ทำไมการเขียนใหม่จึงพักไว้ก่อนแทนที่จะเขียนลงไปเลย

หัวข้อที่มีชื่อว่า “ทำไมการเขียนใหม่จึงพักไว้ก่อนแทนที่จะเขียนลงไปเลย”

การเขียนใหม่เชิงโครงสร้างคือการแก้ไขที่กว้างที่สุดที่เอเจนต์ทำได้: หนึ่งแพตเทิร์น สี่สิบไฟล์ และไม่มีใครอ่านผลลัพธ์ทีละบรรทัด — ไม่ทั้งโมเดล และไม่ทั้งคนที่สั่งให้ทำ การเขียนลงไปแล้วรายงานว่า “เสร็จแล้ว” หมายความว่าการดูการเปลี่ยนแปลงอย่างจริงจังครั้งแรกคือ git diff ที่รันตามมาทีหลัง

ast_edit จึงรายงานว่ามันจะทำอะไร — จำนวน ไฟล์ และจุดที่จับคู่ได้ — และการเปลี่ยนแปลงจะลงจริงก็ต่อเมื่อ resolve คอมมิตมัน นี่เองที่ทำให้ดิฟพร้อมปุ่มยอมรับ/ทิ้งมาอยู่ตรงหน้ามนุษย์ก่อนที่อะไรจะถูกเขียนลงดิสก์

การคอมมิตเป็นแบบทั้งหมดหรือไม่เอาเลย และทุกไฟล์จะถูกตรวจเทียบกับเนื้อหาที่บันทึกไว้ตอนพักการเปลี่ยนแปลง ระหว่างพักไว้กับตอนคอมมิต เอเจนต์อาจแก้ไฟล์ใดไฟล์หนึ่งในนั้นด้วยมือ รอบวินิจฉัยอาจเขียนมันใหม่ หรือฟอร์แมตเตอร์อาจแตะมันไปแล้ว — ถ้าคอมมิตต่อไปก็เท่ากับทิ้งงานนั้น และดิฟก็จะดูเหมือนเจตนา ความไม่ตรงกันแม้แต่จุดเดียวจะปฏิเสธการเปลี่ยนแปลงทั้งชุด เพราะการเขียนใหม่เชิงโครงสร้างที่ลงไปเพียงบางส่วนจะทิ้งทรีที่คอมไพล์ไม่ผ่านทั้งรูปร่างเดิมและรูปร่างใหม่

สิ่งที่ดีบักเกอร์ตอบได้ แต่คำสั่ง print ตอบไม่ได้

หัวข้อที่มีชื่อว่า “สิ่งที่ดีบักเกอร์ตอบได้ แต่คำสั่ง print ตอบไม่ได้”

วงจรของคำสั่ง print คือ: ใส่ print รันทั้งหมดใหม่อีกครั้ง อ่านผลลัพธ์ แล้วลบ print ทิ้ง มันกินการรันเทสต์เต็มรอบต่อหนึ่งคำถาม และตอบได้เฉพาะคำถามที่คุณนึกออกก่อนจะรัน

เฟรมที่ถูกหยุดไว้ตอบทุกคำถามเกี่ยวกับช่วงเวลานั้นได้พร้อมกันในครั้งเดียว — ตัวแปรโลคอลทุกตัว ผู้เรียกทุกตัว และนิพจน์ที่ถูกประเมินในสโคปของเฟรมนั้นเอง สำหรับโปรเซสที่ค้างตายหรือความล้มเหลวที่เกิดซ้ำเพียงครั้งเดียวในสิบรอบ นั่นคือความต่างระหว่างหนึ่งบ่ายกับหนึ่งนาที

ถูกจำกัดขอบเขตเหมือนเอนโคลเจอร์ และด้วยเหตุผลเดียวกัน

หัวข้อที่มีชื่อว่า “ถูกจำกัดขอบเขตเหมือนเอนโคลเจอร์ และด้วยเหตุผลเดียวกัน”

อะแดปเตอร์ดีบักเป็นเจ้าของโปรเซสที่ถูกหยุดไว้ซึ่งยึดอะไรก็ตามที่โปรเซสนั้นยึดอยู่ — พอร์ต ล็อก หรือการเชื่อมต่อฐานข้อมูล เซสชันที่ไม่มีใครขับเคลื่อนคือการรั่วไหลที่อยู่ยงกว่าเทิร์นที่เริ่มมัน ดังนั้นจึงมี TTL แบบตายตัว มีเซสชันเดียวต่อการสนทนาหนึ่งรายการ และการเรียก launch ครั้งที่สองจะถูกปฏิเสธแทนที่จะเงียบ ๆ แทนที่ตัวแรก: เอเจนต์ที่เริ่มตัวที่สองโดยไม่จบตัวแรกเกือบแน่นอนว่าตามตัวแรกไม่เจอแล้ว

Debug Adapter Protocol ใช้การแบ่งเฟรมด้วย Content-Length ร่วมกับ Language Server Protocol และไม่มีอะไรที่เหนือกว่านั้นเหมือนกัน — คีย์สำหรับจับคู่ต่างกัน รูปร่างความล้มเหลวต่างกัน และมีข้อความชนิดที่สาม (เหตุการณ์) ที่ไม่มีคู่เทียบใน LSP การแบ่งเฟรมอยู่ในที่เดียว ส่วนโปรโตคอลวางทับอยู่แยกกัน

การเขียนการแบ่งเฟรมซ้ำสองที่จะหมายถึงคำตอบสองชุดต่อ “ข้อความหนึ่งทอดข้ามสองชังก์” “ชังก์หนึ่งบรรจุสามข้อความ” และ “เซิร์ฟเวอร์เขียนข้อความแบนเนอร์ลง stdout ก่อนเฟรมแรก” และมีเพียงชุดเดียวเท่านั้นที่จะได้รับการแก้