Bỏ qua để đến nội dung

2.5 Built-in Tools

Claude Code có sáu built-in tool để làm việc với codebase: Read, Write, Edit, Bash, Grep và Glob. Mỗi cái có mục đích riêng, và dùng sai tool cho một tác vụ là phí thời gian, phí context token, hoặc cả hai. Đề thi cố tình đưa ra những tình huống mà nhầm lẫn giữa các tool này dẫn thẳng tới đáp án sai.

Đây là phân biệt quan trọng nhất trong task statement này. Sai chỗ này là mất điểm.

Grep tìm trong NỘI DUNG file theo pattern. Dùng Grep khi bạn cần tìm text bên trong file. Nơi gọi hàm. Thông báo lỗi. Câu lệnh import. Phép gán biến. Bất cứ khi nào bạn tìm theo thứ file chứa, Grep là tool cần dùng.

// Find all files that call processLegacyOrder()
Grep: "processLegacyOrder"
// Find all error messages containing "timeout"
Grep: "timeout"
// Find all files that import a specific module
Grep: "import.*from 'utils/auth'"

Glob khớp ĐƯỜNG DẪN file theo pattern đặt tên. Dùng Glob khi bạn cần tìm file theo tên, phần mở rộng, hay cấu trúc thư mục. File test. File cấu hình. Toàn bộ file TypeScript trong một thư mục. Bất cứ khi nào bạn tìm file dựa trên đường dẫn, Glob là tool cần dùng.

// Find all test files
Glob: "**/*.test.tsx"
// Find all configuration files
Glob: "**/config.*"
// Find all MDX files in the domains directory
Glob: "content/domains/**/*.mdx"

Gói gọn trong một câu: Grep tìm thứ BÊN TRONG file. Glob tìm file theo TÊN.

Đề thi đưa ra những tình huống mà developer dùng sai tool. Dùng Glob để tìm nơi gọi hàm thì trượt — Glob khớp đường dẫn, không khớp nội dung. Dùng Grep để tìm file test theo pattern tên thì về mặt kỹ thuật vẫn ra kết quả (bằng cách tìm chuỗi “test” trong nội dung), nhưng đó là tool sai, và đề thi muốn bạn chỉ ra tool đúng.

Ba tool này lo phần thao tác file, mỗi cái tối ưu cho một use case khác nhau.

Edit sửa có mục tiêu dựa trên khớp text duy nhất. Bạn chỉ ra chính xác đoạn text cần tìm và đoạn thay thế. Nó nhanh và chính xác vì chỉ động vào đúng đoạn text bạn chỉ định.

Edit:
old_string: "function processOrder(id: string)"
new_string: "function processOrder(id: string, validate: boolean = true)"

Khi Edit thất bại: Edit yêu cầu đoạn text khớp duy nhất. Nếu đoạn text bạn đưa xuất hiện ở nhiều chỗ trong file, Edit không biết bạn muốn chỗ nào nên nó báo lỗi. Đó là cơ chế an toàn, không phải bug — nó chặn bạn sửa nhầm đoạn text không hề định đụng tới.

Khi Edit không tìm được mỏ neo duy nhất: đáp án của đề thi. Exam guide nêu đúng một phương án dự phòng là Read + Write. Đọc toàn bộ file, rồi Write lại phiên bản đã sửa đầy đủ. Cách này luôn chạy, vì bạn không còn bắt Edit phải đoán. Nó cũng tiêu tốn lượng token bằng cả file cho thứ thường chỉ là sửa một dòng, nên nó là phương án dự phòng chứ không phải mặc định.

Khi Edit không tìm được mỏ neo duy nhất: Claude Code hiện tại. Tài liệu Edit tool giờ cho bạn một nước đi rẻ hơn trước đã: mở rộng old_string thêm ngữ cảnh xung quanh cho tới khi nó khoanh đúng một vị trí, hoặc đặt replace_all: true nếu bạn thật sự muốn cập nhật mọi lần xuất hiện. Cả hai đều giữ bạn ở trong Edit và gần như không tốn thêm context. Trong công việc thật, làm vậy trước khi với tới Read + Write.

Thứ tự trong công việc thật:

  1. Thử Edit với mỏ neo ngắn nhất mà có khả năng là duy nhất.
  2. Khi khớp không duy nhất, mở rộng old_string cho tới khi nó khớp đúng một vị trí, hoặc dùng replace_all: true nếu muốn đổi mọi lần xuất hiện.
  3. Chỉ rơi về Read + Write khi cả hai cách trên không khoanh nổi mục tiêu.

Thứ tự trong đề thi có hai bước: Edit trước, Read + Write khi Edit thất bại. Hai thứ tự này thống nhất ở bước đầu. Đừng mặc định Read + Write cho mọi lần sửa. Đề thi trừ điểm chuyện đó vì nó đốt context token.

Cách bạn khám phá một codebase quan trọng ngang với việc bạn dùng tool nào. Có cách đúng và cách sai.

Sai: đọc hết mọi file ngay từ đầu. Nạp mọi file vào context trước khi biết mình cần gì là cách giết ngân sách context. Một codebase 200 file đọc đủ sẽ nuốt trọn context window, phần lớn là những file chẳng liên quan gì tới tác vụ. Không sai lầm khám phá nào đắt hơn cái này.

Đúng: khám phá tăng dần. Bắt đầu hẹp. Chỉ mở rộng khi cần.

  1. Grep để tìm điểm vào. Tìm tên hàm, tên class, hay thông báo lỗi làm điểm neo cho cuộc điều tra. Nó cho bạn biết file nào liên quan.

  2. Read để lần theo import và truy vết luồng. Khi đã biết file nào quan trọng, Read chúng để hiểu cấu trúc code. Lần theo câu lệnh import để phát hiện các file liên quan.

  3. Grep lại để truy vết chỗ sử dụng. Những file bạn đọc ở bước 2 có thể phơi ra hàm đó dưới một cái tên khác: một wrapper (submitOrder() gọi processOrder() bên trong) hoặc một barrel file re-export nó (export { processOrder as submitOrder }). Bên gọi tên mới không bao giờ nhắc tới tên gốc, nên lượt Grep đầu tiên của bạn không thấy chúng. Grep từng tên mới, trên toàn codebase, để có danh sách đầy đủ bên tiêu thụ. Mục kế tiếp đi qua một ví dụ.

  4. Chỉ Read đúng thứ cần. Mỗi file bạn đọc phải được biện minh bằng thứ bạn phát hiện ở bước trước đó.

Đó là context tối thiểu cho mức hiểu tối đa. Bạn vẽ bản đồ codebase dần dần, chỉ tiêu token cho những file thật sự dính tới tác vụ.

Truy vết chỗ dùng hàm qua các module wrapper

Phần tiêu đề “Truy vết chỗ dùng hàm qua các module wrapper”

Một pattern phổ biến trong codebase: một hàm được định nghĩa ở một module, re-export qua một wrapper, và được dùng qua tên của wrapper. Một lượt Grep đơn giản theo tên gốc sẽ bỏ sót mọi bên tiêu thụ đi qua wrapper.

Cách làm đúng:

  1. Grep tìm định nghĩa hàm để biết nó được định nghĩa ở đâu
  2. Read file định nghĩa để xác định các tên được export
  3. Grep từng tên được export trên toàn codebase để tìm mọi bên tiêu thụ
  4. Nếu hàm được re-export qua một barrel file (ví dụ index.ts), Grep theo tên module của barrel file để tìm những bên import từ đó

Cụ thể: processOrder được định nghĩa trong orders.ts. Barrel utils/index.ts re-export nó thành submitOrder, và ba trong năm bên tiêu thụ import submitOrder từ utils. Một lượt Grep processOrder tìm ra định nghĩa, dòng trong barrel và hai bên import tên gốc. Nó không tìm nổi ba bên còn lại, vì chuỗi processOrder không hề xuất hiện trong file của họ. Đọc barrel, phát hiện chỗ đổi tên, Grep submitOrder, và ba bên kia lộ ra.

Lượt truy vết nhiều bước bắt được những bên tiêu thụ gián tiếp mà một lượt Grep đơn lẻ sẽ bỏ sót.

Ca này xuất hiện liên tục khi luyện thi: tìm mọi file gọi một hàm deprecated VÀ những file test có chạy qua nó. Trình tự đúng:

  1. Grep theo tên hàm — tìm mọi file có nội dung tham chiếu tới hàm đó, kể cả các test import nó trực tiếp (tìm theo nội dung)
  2. Glob tìm file test anh em — tìm file test đi cặp với từng bên gọi theo quy ước đặt tên, ví dụ OrderProcessor.tsOrderProcessor.test.tsx, kể cả khi test chạy qua hàm đó một cách gián tiếp thông qua module nguồn (khớp đường dẫn)
  3. Grep lại theo tên wrapper — khi một bên gọi phơi hàm ra qua wrapper (ví dụ applyLegacyOrder gọi processLegacyOrder bên trong), Grep tên wrapper để tìm những test phủ hàm đó một cách bắc cầu qua wrapper

Giả sử Grep cho thấy OrderProcessor.tsRefundHandler.ts gọi hàm deprecated. Glob **/OrderProcessor.test.***/RefundHandler.test.* để kéo về file test anh em của chúng, kể cả khi những test đó không nhắc tên processLegacyOrder. Và nếu một trong hai file nguồn bọc hàm đó dưới tên mới, Grep tên wrapper để bắt nốt các test còn lại.

Đây là Grep, rồi Glob, rồi Grep lần nữa — tìm theo nội dung cho tham chiếu trực tiếp, khớp đường dẫn cho các test kề bên, tìm theo nội dung cho phần phủ gián tiếp. Không phải Glob trước.

Một developer cần tìm mọi file gọi hàm deprecated processLegacyOrder() và tìm luôn mọi file test của những bên gọi đó. Trình tự tool nào là đúng?

  • A. Glob theo **/*processLegacyOrder* để tìm file của bên gọi, rồi Grep trong tập kết quả đó để tìm file test. Glob giải quyết danh sách file trước, nên lượt tìm theo nội dung chạy trên ít file hơn và nằm gọn trong ngân sách context
  • B. Read hết file nguồn để tự tìm hàm bằng tay, rồi Read hết file test để ghép chúng với bên gọi. Đọc mọi file cho tầm nhìn đầy đủ về từng chỗ gọi và từng test, nên không bên gọi nào bị sót vì lệch tên, và toàn bộ nội dung vẫn còn trong context cho các bước refactor sau
  • C. Grep processLegacyOrder để tìm bên gọi (lượt này cũng lộ ra các test import hàm trực tiếp), rồi Glob tìm file test anh em của từng bên gọi (ví dụ **/OrderProcessor.test.*) để bắt những test chạy qua hàm thông qua module nguồn mà không nhắc tên nó
  • D. Bash với find và xargs grep cho cả hai bước, vì một pipeline shell duy nhất định vị được cả bên gọi lẫn file test của chúng trong một lượt mà không phải đổi qua lại giữa các built-in tool
Đáp án & giải thích

Đúng: C

  • A — Glob khớp đường dẫn file, không khớp nội dung. Nó không tìm được nơi gọi hàm — nó chỉ khớp những file được đặt tên theo hàm, mà chuyện đó khó xảy ra. Hai tool bị dùng ngược.
  • B — Đọc hết mọi file ngay từ đầu là cách giết ngân sách context. Nó tiêu token vào những file không liên quan và đúng là anti-pattern mà đề thi trừ điểm.
  • C — Grep tìm trong nội dung file — đúng cho việc tìm bên gọi và mọi test tham chiếu hàm theo tên. Glob khớp đường dẫn file — đúng cho việc tìm file test đi cặp với từng file nguồn theo quy ước đặt tên, mà đó chính là cách test chạy qua hàm một cách gián tiếp. Đây là trình tự tối ưu.
  • D — Tuy về mặt kỹ thuật vẫn chạy được, cách này bỏ qua những built-in tool sinh ra cho đúng các tác vụ này. Đề thi kỳ vọng thí sinh chọn đúng built-in tool cho từng tác vụ.

Năm câu trắc nghiệm theo format đề thi về Built-in Tools. Chọn đáp án trước, rồi mở phần giải thích.

Một developer cần tìm mọi file gọi hàm deprecated processLegacyOrder() và tìm luôn mọi file test của những bên gọi đó. Trình tự tool nào là đúng?

  • A. Glob theo *processLegacyOrder* để tìm bên gọi, rồi Grep để tìm các file test tương ứng
  • B. Read mọi file nguồn để định vị hàm bằng tay, rồi đọc luôn mọi file test
  • C. Bash với find nối ống qua xargs grep cho cả hai bước
  • D. Grep processLegacyOrder để tìm bên gọi, rồi Glob tìm file test của chúng (ví dụ **/*.test.tsx)
Đáp án & giải thích

Đúng: D

  • D đúng vì Grep tìm trong nội dung file, đúng thứ cần để tìm bên gọi, còn Glob khớp đường dẫn file, đúng thứ cần để tìm file test theo pattern tên. Tìm theo nội dung trước, rồi khớp đường dẫn.
  • A sai vì nó dùng ngược hai tool. Glob khớp đường dẫn, nên nó chỉ tìm được file đặt tên theo hàm, mà bên gọi đâu có được lưu như vậy.
  • B sai vì nạp mọi file trước khi biết file nào quan trọng là tiêu ngân sách context vào code không liên quan. Đây là anti-pattern mà đề thi trừ điểm thẳng tay nhất.
  • C sai vì nó chạy được nhưng né mất những built-in tool sinh ra đúng cho hai tác vụ này. Đề thi kỳ vọng đúng built-in tool cho từng tác vụ.

Một developer thử dùng Edit để thay một lệnh gọi hàm, nhưng Edit thất bại vì đoạn text xuất hiện 3 lần trong file. Exam guide nêu cách khắc phục nào?

  • A. Dùng Bash với sed để thay cả ba lần xuất hiện cùng lúc
  • B. Thêm một comment tạm để đoạn cần sửa thành duy nhất, Edit dòng đó, rồi xóa comment
  • C. Tách file ra để mỗi lần xuất hiện nằm ở một module riêng, rồi Edit từng cái
  • D. Rơi về Read để nạp toàn bộ file, rồi Write lại file đã sửa đầy đủ
Đáp án & giải thích

Đúng: D

  • D đúng vì exam guide nêu Read + Write là phương án dự phòng được ghi nhận khi Edit không tìm được đoạn neo duy nhất: nạp toàn bộ file bằng Read, rồi Write phiên bản đã sửa đầy đủ.
  • A sai vì sed bỏ qua luôn cơ chế kiểm tra khớp duy nhất của Edit, nên nó có thể viết đè cả hai lần xuất hiện đáng ra phải giữ nguyên.
  • B sai vì sửa file để một lần sửa sau khả thi là cách làm giòn, và nó để lại rác mỗi khi bước dọn dẹp bị quên.
  • C sai vì tái cấu trúc codebase để lách một giới hạn của tool là công sức không tương xứng.

Một developer cần tìm mọi file test TypeScript trong project. Nên dùng tool và pattern nào?

  • A. Grep “test” để tìm mọi file có nhắc tới chuyện test ở bất kỳ đâu trong cây thư mục
  • B. Read thư mục gốc project để liệt kê mọi file trong cây, rồi lọc danh sách đó bằng tay
  • C. Glob theo **/*.test.tsx, khớp các file test theo quy ước đặt tên trên đường dẫn
  • D. Bash với find . -name “*.test.tsx”, gọi ra shell thay vì dùng tool tìm theo đường dẫn có sẵn
Đáp án & giải thích

Đúng: C

  • C đúng vì Glob khớp đường dẫn file theo pattern đặt tên, và tìm file theo phần mở rộng đúng là việc nó sinh ra để làm. Nó trả về các đường dẫn khớp, sắp theo thời gian sửa đổi.
  • A sai vì Grep tìm trong nội dung, không tìm đường dẫn. File test thường có chứa chữ “test”, nhưng khối code bình thường cũng vậy, nên kết quả vừa thiếu vừa nhiễu.
  • B sai vì liệt kê thư mục không lọc theo pattern gì cả. Đó là làm thủ công thay cho một tool chuyên dụng.
  • D sai vì find chạy được, nhưng đề thi kỳ vọng dùng built-in tool Glob cho các lượt tìm theo đường dẫn.

Một developer cần hiểu một module phức tạp hoạt động thế nào. Họ bắt đầu bằng cách đọc cả 47 file nguồn trong module. Cách này sai ở đâu?

  • A. Không sai gì cả, vì đọc hết cho mức hiểu đầy đủ nhất
  • B. Nó tiêu ngân sách context trước khi biết thứ gì quan trọng; nên Grep tìm điểm vào rồi Read đúng những file đó
  • C. Họ nên chạy Glob trước, để lọc tập file lại trước khi bắt đầu đọc
  • D. Họ nên hỏi team xin tài liệu thay vì đọc code
Đáp án & giải thích

Đúng: B

  • B đúng vì nạp 47 file trước khi biết file nào liên quan là tiêu context window vào phần lớn là code không liên quan. Lộ trình tăng dần là Grep tìm một điểm neo — tên hàm, tên class hay một chuỗi lỗi — rồi Read lan ra từ thứ tìm được, lần theo import và truy vết luồng.
  • A sai vì phần lớn trong 47 file đó chẳng dính gì tới câu hỏi đang đặt ra. Đây là anti-pattern lớn nhất trong khám phá codebase.
  • C sai vì Glob lọc theo tên, không lọc theo mức liên quan. Biết file nào là .ts chẳng nói gì về file nào quan trọng với cuộc điều tra này.
  • D sai vì tài liệu có thể không tồn tại hoặc đã lỗi thời. Đề thi đang kiểm tra chiến lược khám phá code.

Một hàm processOrder được định nghĩa trong orders.ts. Barrel file utils/index.ts re-export nó dưới tên mới submitOrder. Năm file dùng hàm này: 2 file import processOrder trực tiếp từ orders.ts, 3 file import từ utils. Một lượt Grep đơn giản theo “processOrder” tìm ra định nghĩa, barrel file và 2 bên import trực tiếp, nhưng bỏ sót 3 bên còn lại. Vì sao?

  • A. Grep có giới hạn số file tích hợp sẵn và dừng lại khi đã tìm đủ một số lượng khớp nhất định
  • B. Pattern Grep cần regular expression thì mới khớp được tên hàm
  • C. 3 bên bị sót nằm ở một nhánh khác của repository và chưa được checkout
  • D. 3 bên bị sót import submitOrder, nên chuỗi processOrder không hề xuất hiện trong file của họ
Đáp án & giải thích

Đúng: D

  • D đúng vì barrel đã đổi tên export. Một bên tiêu thụ viết import { submitOrder } from “utils” rồi gọi submitOrder() thì không chứa lần xuất hiện nào của processOrder, nên tìm nguyên văn tên gốc không thể chạm tới nó. Lộ trình đáng tin là nhiều bước: Grep tìm định nghĩa, Read barrel để biết mọi tên được export, rồi Grep lại từng tên đó.
  • A sai vì Grep không áp giới hạn file mặc định nào. Nó tìm mọi thứ trong phạm vi được giao.
  • B sai vì khớp nguyên văn là quá đủ cho các tham chiếu trực tiếp, và nó cũng khớp utils.processOrder y như vậy. Thứ đánh bại nó là chỗ đổi tên ở barrel, không phải việc thiếu regular expression.
  • C sai vì Grep tìm trên working tree hiện tại. Các nhánh khác không nằm trong phạm vi cuộc điều tra này.