Μετάβαση στο περιεχόμενο

Νοημοσύνη κώδικα

Το Control Center δίνει σε έναν πράκτορα τέσσερις τρόπους να κατανοεί τον κώδικα πέρα από την απλή ανάγνωσή του. Απαντούν σε πραγματικά διαφορετικά ερωτήματα, και ο λόγος που είναι τέσσερις και όχι ένας είναι ότι κανένας μεμονωμένος μηχανισμός δεν απαντά σε όλα.

Μηχανισμός Απαντά σε Κόστος
Γράφημα κώδικα Τι υπάρχει εδώ και τι εξαρτάται από αυτό Ένα πέρασμα ευρετηρίασης· φθηνό στην αναζήτηση
Διακομιστής γλώσσας Μεταγλωττίζεται αυτό όντως, και τι τύπος είναι αυτό Μια διεργασία διακομιστή ανά έργο
Δομική αναζήτηση Πού εμφανίζεται αυτό το ΣΧΗΜΑ Μια ανάλυση ανά αρχείο
Αποσφαλματωτής Τι ίσχυε αυτή τη στιγμή, σε αυτό το frame Μια σταματημένη διεργασία

Το κενό που καλύπτει ο διακομιστής γλώσσας

Ενότητα με τίτλο «Το κενό που καλύπτει ο διακομιστής γλώσσας»

Το γράφημα κώδικα χτίζεται από το tree-sitter: σύμβολα, ακμές κλήσεων, μια ακτίνα επίπτωσης. Είναι γρήγορο, είναι φθηνό και δεν γνωρίζει τίποτα για τους τύπους. Δεν μπορεί να σου πει αν η κλήση που μόλις έγραψες μεταγλωττίζεται, αν αυτό το πεδίο υπάρχει στο αντικείμενο ή αν η μετονομασία σου έχασε μια επανεξαγωγή.

Χωρίς διακομιστή γλώσσας, ένας πράκτορας το ανακαλύπτει τη στιγμή του τεστ — ή ποτέ. Γράφει κάτι εύλογο, προχωρά, και η αποτυχία εμφανίζεται τρεις γύρους αργότερα, κολλημένη σε μια διαφορετική αλλαγή.

Έτσι, το όφελος δεν είναι στην πραγματικότητα το εργαλείο lsp. Είναι ότι τα write, edit και apply_patch είναι τυλιγμένα: μετά από κάθε εγγραφή, το αρχείο περνά από τον διακομιστή γλώσσας και τα νεοεισαχθέντα διαγνωστικά ενσωματώνονται στο αποτέλεσμα του εργαλείου. Ο πράκτορας μαθαίνει ότι έσπασε το build τη στιγμή που το έσπασε, είτε σκέφτηκε να ρωτήσει είτε όχι.

Το να επαναλαμβάνεις όσα έχουν ήδη ειπωθεί στον πράκτορα είναι χειρότερο από το να μη πεις τίποτα: γεμίζει το αποτέλεσμα με θόρυβο που ο πράκτορας μαθαίνει να προσπερνά, και το ένα νέο σφάλμα χάνεται μαζί του. Ένα μητρώο ανά εκτέλεση καταγράφει ό,τι έχει αναφερθεί, με κλειδί τη σοβαρότητα, τον κωδικό και το μήνυμα — όχι τη θέση, ώστε ένα διαγνωστικό που απλώς κατέβηκε τέσσερις γραμμές να μην αναφέρεται ξανά ως νέο.

Οκνηρό, και ειλικρινές για την απουσία του

Ενότητα με τίτλο «Οκνηρό, και ειλικρινές για την απουσία του»

Κανένας διακομιστής δεν ξεκινά κατά την εκκίνηση. Ένας ξεκινά στο πρώτο αίτημα που τον χρειάζεται, το οποίο είναι σχεδόν πάντα η πρώτη επεξεργασία σε ένα αρχείο που διεκδικεί, και οι αδρανείς σαρώνονται και τερματίζονται. Ένας κεντρικός υπολογιστής που δεν αγγίζει ποτέ τη Dart δεν ξεκινά ποτέ αναλυτή.

Η ανίχνευση είναι μια τομή: ένας δείκτης ρίζας πρέπει να υπάρχει στο checkout και το εκτελέσιμο να επιλύεται. Ένας δείκτης μόνος του λέει «αυτό το έργο είναι Python» και τίποτα για το αν ο διακομιστής είναι εγκατεστημένος· ένα εκτελέσιμο που επιλύεται μόνο του λέει ότι ο κεντρικός υπολογιστής διαθέτει εργαλειοθήκη και τίποτα για αυτό το checkout. Μόνο τα δύο μαζί σημαίνουν ότι ένα ερώτημα μπορεί να πετύχει, και ένα εργαλείο που προσφέρεται και πάντα αποτυγχάνει είναι χειρότερο από ένα που λείπει.

Γιατί δομική αναζήτηση, όταν υπάρχει το grep

Ενότητα με τίτλο «Γιατί δομική αναζήτηση, όταν υπάρχει το grep»

Το grep αντιστοιχίζει χαρακτήρες. Είναι το σωστό εργαλείο για ένα όνομα και το λάθος για ένα σχήμα.

Ένα δομικό μοτίβο αναλύεται από τη γραμματική του ίδιου του αρχείου, οπότε το dispose($X) βρίσκει την κλήση όπως κι αν είναι αραιωμένη με κενά, τυλιγμένη ή σπασμένη σε γραμμές, και δεν αντιστοιχίζει ποτέ τους ίδιους χαρακτήρες μέσα σε ένα string literal ή ένα σχόλιο.

Η ιδιότητα που το κάνει κάτι περισσότερο από μια regex με επιπλέον βήματα είναι ότι μια μεταβλητή μοτίβου (metavariable) που επαναλαμβάνεται σε ένα μοτίβο πρέπει να συλλαμβάνει το ίδιο κείμενο:

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

αντιστοιχίζει έναν φρουρό (guard) που κάνει dispose σε αυτό που έλεγξε, και δεν αντιστοιχίζει έναν που κάνει dispose σε κάτι άλλο. Αυτή είναι μια κατηγορία σφαλμάτων που δεν μπορείς να εκφράσεις καθόλου κειμενικά.

Τρέχει στο tree-sitter που είναι ήδη συνδεδεμένο

Ενότητα με τίτλο «Τρέχει στο tree-sitter που είναι ήδη συνδεδεμένο»

Δεν υπάρχει δεύτερη μηχανή. Το cc_natives φορτώνει ήδη το tree-sitter και τις γραμματικές του για το γράφημα κώδικα, με προσωρινή μνήμη μεταγλωττισμένων ερωτημάτων και πίνακα γλωσσών. Ο αντιστοιχιστής είναι καθαρή Dart πάνω σε δέντρα που αναλύονταν ούτως ή άλλως. Μια δεύτερη δομική μηχανή θα σήμαινε έναν δεύτερο αναλυτή, ένα δεύτερο σύνολο γραμματικών προς κατασκευή και διάθεση σε κάθε πλατφόρμα, και δύο απαντήσεις στο «τι είναι έκφραση κλήσης».

Ένα απόσπασμα χρειάζεται κάπου να ζήσει

Ενότητα με τίτλο «Ένα απόσπασμα χρειάζεται κάπου να ζήσει»

Μια γραμματική ορίζει μόνο τι είναι ένα ολόκληρο αρχείο. Αναλυμένο σκέτο στο ανώτατο επίπεδο της Dart, το dispose(x) είναι μια απολύτως έγκυρη υπογραφή συνάρτησης — μια δήλωση με όνομα dispose που δέχεται παράμετρο x — χωρίς πουθενά κάποιο σφάλμα να το προδίδει. Ένας αντιστοιχιστής χτισμένος σε αυτή την ανάλυση ψάχνει δηλώσεις, δεν βρίσκει κλήσεις και αναφέρει «καμία αντιστοίχιση» για ένα μοτίβο που ποτέ δεν διαβαζόταν όπως φαίνεται.

Έτσι, ένα μοτίβο δοκιμάζεται σε μια κλίμακα πλαισίων — δήλωση, έκφραση, μέλος κλάσης, ανώτατο επίπεδο — και κερδίζει το πρώτο που το αποδέχεται. Ένα απόσπασμα που κανένα πλαίσιο δεν αποδέχεται αναφέρεται ως μη αναλύσιμο, αντί να αναζητηθεί και να μη βρεθεί σιωπηλά.

Γιατί μια αναδιατύπωση προετοιμάζεται σε στάδιο αντί να γράφεται απευθείας

Ενότητα με τίτλο «Γιατί μια αναδιατύπωση προετοιμάζεται σε στάδιο αντί να γράφεται απευθείας»

Μια δομική αναδιατύπωση είναι η ευρύτερη επεξεργασία που μπορεί να κάνει ένας πράκτορας: ένα μοτίβο, σαράντα αρχεία, και κανείς δεν διαβάζει το αποτέλεσμα γραμμή προς γραμμή — ούτε το μοντέλο ούτε το άτομο που το ζήτησε. Το να την εφαρμόσεις και να αναφέρεις «έτοιμο» σημαίνει ότι η πρώτη ειλικρινής ματιά στην αλλαγή είναι ένα git diff εκ των υστέρων.

Έτσι, το ast_edit αναφέρει τι θα έκανε — τον αριθμό, τα αρχεία, τα σημεία που αντιστοιχίστηκαν — και η αλλαγή εφαρμόζεται μόνο όταν την υποβάλει το resolve. Αυτό είναι επίσης που επιτρέπει σε ένα diff και μια Αποδοχή/Απόρριψη να μπουν μπροστά από έναν άνθρωπο πριν φτάσει οτιδήποτε στον δίσκο.

Η υποβολή είναι όλα ή τίποτα και κάθε αρχείο ελέγχεται απέναντι στο περιεχόμενο που καταγράφηκε τη στιγμή της προετοιμασίας. Μεταξύ προετοιμασίας και υποβολής, ο πράκτορας μπορεί να έχει επεξεργαστεί χειροκίνητα ένα από αυτά τα αρχεία, ένα πέρασμα διαγνωστικών μπορεί να το έχει ξαναγράψει ή ένας formatter μπορεί να το έχει πειράξει — το να υποβάλεις ούτως ή άλλως θα ακύρωνε αυτή την εργασία και το diff θα φαινόταν σκόπιμο. Οποιαδήποτε ασυμφωνία απορρίπτει ολόκληρη την αλλαγή, γιατί μια μερικώς εφαρμοσμένη δομική αναδιατύπωση αφήνει ένα δέντρο που δεν μεταγλωττίζεται σε καμία από τις δύο μορφές.

Τι απαντά ένας αποσφαλματωτής που δεν μπορεί μια εντολή εκτύπωσης

Ενότητα με τίτλο «Τι απαντά ένας αποσφαλματωτής που δεν μπορεί μια εντολή εκτύπωσης»

Ο κύκλος της εντολής εκτύπωσης είναι: πρόσθεσε μια εκτύπωση, τρέξε ξανά το όλο πράγμα, διάβασε την έξοδο, διέγραψε την εκτύπωση. Κοστίζει μια πλήρη εκτέλεση τεστ ανά ερώτημα και απαντά μόνο στο ερώτημα που σκέφτηκες να θέσεις πριν την εκτέλεση.

Ένα σταματημένο frame απαντά ταυτόχρονα σε κάθε ερώτημα για εκείνη τη στιγμή — κάθε τοπική μεταβλητή, κάθε καλούντα και μια έκφραση που αξιολογείται στο δικό του πεδίο. Για μια «κολλημένη» διεργασία ή μια αποτυχία που αναπαράγεται μία φορά στις δέκα εκτελέσεις, αυτή είναι η διαφορά ανάμεσα σε ένα απόγευμα και ένα λεπτό.

Περιορισμένο όπως ένα περίβλημα, και για τον ίδιο λόγο

Ενότητα με τίτλο «Περιορισμένο όπως ένα περίβλημα, και για τον ίδιο λόγο»

Ένας προσαρμογέας αποσφαλμάτωσης κατέχει μια σταματημένη διεργασία που κρατά ό,τι κρατά αυτή η διεργασία — μια θύρα, ένα κλείδωμα, μια σύνδεση βάσης δεδομένων. Μια συνεδρία που δεν την οδηγεί κανείς είναι μια διαρροή που επιβιώνει πέρα από τον γύρο που τη ξεκίνησε. Έτσι, υπάρχει ένα αυστηρό TTL, ακριβώς μία συνεδρία ανά συνομιλία, και ένα δεύτερο launch απορρίπτεται αντί να αντικαθιστά σιωπηλά το πρώτο: ένας πράκτορας που ξεκινά ένα δεύτερο χωρίς να τερματίσει το πρώτο έχει σχεδόν σίγουρα χάσει την επαφή με το πρώτο.

Το Debug Adapter Protocol μοιράζεται την πλαισίωση Content-Length του Language Server Protocol και τίποτα πάνω από αυτή — διαφορετικά κλειδιά συσχέτισης, διαφορετική μορφή αποτυχίας και έναν τρίτο τύπο μηνύματος (τα events) χωρίς αντίστοιχο στο LSP. Η πλαισίωση ζει σε ένα σημείο· τα πρωτόκολλα κάθονται πάνω της ξεχωριστά.

Το να γράψεις την πλαισίωση δύο φορές θα σήμαινε δύο απαντήσεις στο «ένα μήνυμα απλώνεται σε δύο τμήματα», «ένα τμήμα περιέχει τρία μηνύματα» και «ο διακομιστής έγραψε ένα banner στο stdout πριν το πρώτο του frame», και μόνο μία από αυτές θα έπαιρνε τη διόρθωση.