ความฉลาดของโค้ด
Control Center มอบสี่วิธีให้เอเจนต์ใช้ทำความเข้าใจโค้ดนอกเหนือไปจากการอ่านโค้ด ทั้งสี่วิธีตอบคำถามที่แตกต่างกันจริง ๆ และเหตุผลที่มีสี่วิธีแทนที่จะมีวิธีเดียวคือ ไม่มีกลไกใดกลไกหนึ่งตอบได้ครบทุกคำถาม
| กลไก | ตอบคำถาม | ต้นทุน |
|---|---|---|
| กราฟโค้ด | มีอะไรอยู่ตรงนี้ และมีอะไรบ้างที่พึ่งพามัน | การจัดทำดัชนีหนึ่งรอบ ต้นทุนการค้นต่ำ |
| เซิร์ฟเวอร์ภาษา | โค้ดนี้คอมไพล์ผ่านจริงหรือไม่ และไทป์นี้คืออะไร | โปรเซสเซิร์ฟเวอร์หนึ่งตัวต่อโปรเจกต์ |
| การค้นหาเชิงโครงสร้าง | รูปร่างนี้ปรากฏที่ไหน | การแยกวิเคราะห์หนึ่งครั้งต่อไฟล์ |
| ดีบักเกอร์ | อะไรเป็นจริง ณ ขณะนั้น ในเฟรมนั้น | โปรเซสที่ถูกหยุดไว้ |
ช่องว่างที่เซิร์ฟเวอร์ภาษาอุด
หัวข้อที่มีชื่อว่า “ช่องว่างที่เซิร์ฟเวอร์ภาษาอุด”กราฟโค้ด สร้างขึ้นจาก tree-sitter: สัญลักษณ์ เส้นเชื่อมการเรียก และรัศมีผลกระทบ มันเร็ว ต้นทุนต่ำ และไม่รู้อะไรเลยเกี่ยวกับไทป์ มันบอกคุณไม่ได้ว่าการเรียกที่คุณเพิ่งเขียนจะคอมไพล์ผ่านไหม ฟิลด์นั้นมีอยู่บนออบเจกต์จริงหรือไม่ หรือการเปลี่ยนชื่อของคุณพลาดจุดที่ส่งออกซ้ำไปหรือเปล่า
ถ้าไม่มีเซิร์ฟเวอร์ภาษา เอเจนต์จะรู้ก็ตอนรันเทสต์ — หรือไม่ก็ไม่รู้เลย มันเขียนอะไรที่ดูสมเหตุสมผลแล้วไปต่อ ความล้มเหลวจึงโผล่ขึ้นมาอีกสามเทิร์นให้หลัง โดยไปพัวพันอยู่กับการเปลี่ยนแปลงคนละเรื่อง
ดังนั้นผลตอบแทนที่ได้จริง ๆ ไม่ใช่ตัวเครื่องมือ lsp แต่คือการที่ write, edit และ apply_patch ถูกห่อไว้: ทุกครั้งหลังการเขียน ไฟล์จะถูกส่งผ่านเซิร์ฟเวอร์ภาษา และผลวินิจฉัยที่เพิ่งเกิดขึ้นใหม่จะถูกพับรวมเข้าไปในผลลัพธ์ของเครื่องมือ เอเจนต์จึงรู้ว่าตัวเองทำให้บิลด์พังในจังหวะที่มันพังเลย ไม่ว่าจะนึกถามหรือไม่ก็ตาม
แจ้งเฉพาะผลวินิจฉัยใหม่
หัวข้อที่มีชื่อว่า “แจ้งเฉพาะผลวินิจฉัยใหม่”การพูดซ้ำสิ่งที่เอเจนต์รู้อยู่แล้วแย่กว่าไม่พูดอะไรเลย เพราะมันจะถมผลลัพธ์ด้วยสัญญาณรบกวนที่เอเจนต์เรียนรู้ที่จะกวาดตาข้ามไป พร้อมกันนั้นก็พาเอาข้อผิดพลาดใหม่เพียงข้อเดียวนั้นหลุดไปด้วย ทะเบียนต่อการรันหนึ่งชุดจะคอยติดตามว่าอะไรถูกแจ้งไปแล้ว โดยยึดความรุนแรง รหัส และข้อความเป็นคีย์ — ไม่ใช่ตำแหน่ง ดังนั้นผลวินิจฉัยที่แค่เลื่อนลงมาสี่บรรทัดจะไม่ถูกแจ้งซ้ำว่าเป็นของใหม่
ทำงานแบบขี้เกียจ และซื่อตรงเมื่อไม่มีอยู่จริง
หัวข้อที่มีชื่อว่า “ทำงานแบบขี้เกียจ และซื่อตรงเมื่อไม่มีอยู่จริง”ไม่มีเซิร์ฟเวอร์ตัวใดเริ่มทำงานตอนบู้ต ตัวหนึ่งจะเริ่มเมื่อมีคำขอแรกที่ต้องใช้มัน ซึ่งเกือบทุกครั้งคือการแก้ไขไฟล์ที่มันดูแลเป็นครั้งแรก ส่วนตัวที่ว่างอยู่จะถูกกวาดคืน โฮสต์ที่ไม่เคยแตะ Dart เลยก็จะไม่เริ่มตัววิเคราะห์
การตรวจหาคือส่วนตัดกัน: ต้องมีมาร์กเกอร์ที่รากโปรเจกต์อยู่ในเช็กเอาต์ และต้องค้นหาไบนารีเจอ มาร์กเกอร์อย่างเดียวบอกได้แค่ “โปรเจกต์นี้เป็น Python” และไม่ได้บอกว่าเซิร์ฟเวอร์ติดตั้งอยู่หรือไม่ ส่วนไบนารีที่ค้นหาเจออย่างเดียวก็บอกได้แค่ว่าโฮสต์มีชุดเครื่องมือ แต่ไม่ได้บอกอะไรเกี่ยวกับเช็กเอาต์นี้ มีเพียงทั้งสองอย่างพร้อมกันเท่านั้นที่แปลว่าคำค้นจะสำเร็จได้ และเครื่องมือที่ถูกเสนอให้ใช้แต่ล้มเหลวทุกครั้งก็แย่กว่าเครื่องมือที่ไม่มีอยู่เสียอีก
ทำไมต้องมีการค้นหาเชิงโครงสร้าง ในเมื่อมี grep อยู่แล้ว
หัวข้อที่มีชื่อว่า “ทำไมต้องมีการค้นหาเชิงโครงสร้าง ในเมื่อมี grep อยู่แล้ว”grep จับคู่ตัวอักษร ซึ่งเป็นเครื่องมือที่เหมาะกับการหาชื่อ แต่ผิดสำหรับการหารูปร่าง
แพตเทิร์นเชิงโครงสร้างถูกแยกวิเคราะห์ด้วยไวยากรณ์เดียวกับไฟล์ ดังนั้น dispose($X) จึงหาการเรียกเจอไม่ว่าจะเว้นวรรค ห่อ หรือตัดบรรทัดอย่างไร และจะไม่จับคู่ตัวอักษรชุดเดียวกันที่อยู่ในสตริงลิเทอรัลหรือคอมเมนต์เลย
คุณสมบัติที่ทำให้มันเป็นมากกว่ารีจิกซ์ที่เพิ่มขั้นตอนเข้ามาคือ เมทาวาริเอเบิลที่ซ้ำกันในแพตเทิร์นเดียวต้องจับข้อความเดียวกัน:
if ($X != null) $X.dispose()จะจับคู่กับโค้ดป้องกันที่ dispose สิ่งที่มันเพิ่งตรวจสอบ และจะไม่จับคู่กับอันที่ dispose อย่างอื่น นั่นคือบั๊กประเภทที่คุณเขียนออกมาเป็นข้อความให้ค้นหาไม่ได้เลย
มันทำงานบน tree-sitter ที่เชื่อมอยู่แล้ว
หัวข้อที่มีชื่อว่า “มันทำงานบน tree-sitter ที่เชื่อมอยู่แล้ว”ไม่มีเอนจินตัวที่สอง 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 ก่อนเฟรมแรก” และมีเพียงชุดเดียวเท่านั้นที่จะได้รับการแก้
ที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “ที่เกี่ยวข้อง”- ค้นหาโค้ดด้วยกราฟโค้ด — ด้านสัญลักษณ์และผลกระทบ
- ดีบักการทดสอบที่ล้มเหลวด้วยเอเจนต์ — ดีบักเกอร์แบบครบวงจร
- รีแฟกเตอร์โค้ดเชิงโครงสร้าง —
ast_grepและast_edit - เครื่องมือเอเจนต์ในตัว — ทุกการทำงานและอาร์กิวเมนต์