Làm mới cấu trúc Công cụ cho nhà phát triển: di chuyển Công cụ cho nhà phát triển sang TypeScript

Tim van der Lippe
Tim van der Lippe

Bài đăng này là một phần trong một loạt bài đăng trên blog mô tả những thay đổi mà chúng tôi đang thực hiện đối với cấu trúc của DevTools và cách tạo cấu trúc đó.

Tiếp nối các bài viết về cách di chuyển sang mô-đun JavaScriptcách di chuyển sang Thành phần web, hôm nay, chúng tôi sẽ tiếp tục loạt bài đăng trên blog về những thay đổi mà chúng tôi đang thực hiện đối với cấu trúc của Devtools và cách xây dựng cấu trúc đó. (Nếu bạn chưa xem, chúng tôi đã đăng một video về công việc Nâng cấp cấu trúc của DevTools lên web hiện đại, với 14 mẹo về cách cải thiện các dự án web của bạn.)

Trong bài đăng này, chúng tôi sẽ mô tả hành trình 13 tháng của chúng tôi khi chuyển từ trình kiểm tra loại Trình biên dịch Closure sang TypeScript.

Giới thiệu

Do quy mô của cơ sở mã DevTools và nhu cầu cung cấp sự tự tin cho các kỹ sư làm việc trên đó, bạn cần phải sử dụng trình kiểm tra kiểu. Để đạt được mục tiêu đó, Công cụ cho nhà phát triển đã sử dụng Trình biên dịch Closure vào năm 2013. Việc sử dụng Closure đã giúp các kỹ sư DevTools tự tin thực hiện các thay đổi; trình biên dịch Closure sẽ thực hiện các bước kiểm tra kiểu để đảm bảo rằng tất cả các hoạt động tích hợp hệ thống đều được nhập đúng cách.

Tuy nhiên, theo thời gian, các trình kiểm tra loại thay thế đã trở nên phổ biến trong quá trình phát triển web hiện đại. Hai ví dụ đáng chú ý là TypeScriptFlow. Hơn nữa, TypeScript đã trở thành ngôn ngữ lập trình chính thức tại Google. Mặc dù các trình kiểm tra kiểu mới này ngày càng phổ biến, nhưng chúng tôi cũng nhận thấy rằng chúng tôi đang gửi các lượt hồi quy mà trình kiểm tra kiểu lẽ ra phải phát hiện được. Do đó, chúng tôi quyết định đánh giá lại lựa chọn trình kiểm tra loại và tìm ra các bước tiếp theo để phát triển trên DevTools.

Đánh giá trình kiểm tra kiểu

Vì DevTools đã sử dụng trình kiểm tra loại, nên câu hỏi chúng ta cần trả lời là:

Chúng ta có tiếp tục sử dụng Trình biên dịch Closure hay chuyển sang trình kiểm tra kiểu mới?

Để trả lời câu hỏi này, chúng tôi phải đánh giá trình kiểm tra kiểu theo một số đặc điểm. Vì việc sử dụng trình kiểm tra kiểu tập trung vào sự tự tin của kỹ sư, nên khía cạnh quan trọng nhất đối với chúng tôi là độ chính xác của kiểu. Nói cách khác: Trình kiểm tra kiểu có đáng tin cậy trong việc phát hiện các vấn đề thực sự không?

Chúng tôi tập trung đánh giá các hồi quy mà chúng tôi đã phát hành và xác định nguyên nhân gốc rễ của các hồi quy đó. Giả định ở đây là vì chúng ta đã sử dụng Trình biên dịch Closure, nên Closure sẽ không phát hiện được những vấn đề này. Do đó, chúng ta phải xác định xem có trình kiểm tra loại nào khác có thể xác định được hay không.

Độ chính xác của kiểu trong TypeScript

Vì TypeScript là một ngôn ngữ lập trình được hỗ trợ chính thức tại Google và ngày càng phổ biến, nên chúng tôi quyết định đánh giá TypeScript trước. TypeScript là một lựa chọn thú vị, vì chính nhóm TypeScript sử dụng DevTools làm một trong các dự án kiểm thử của họ để theo dõi khả năng tương thích của họ với tính năng kiểm tra kiểu JavaScript. Kết quả kiểm thử tham chiếu cơ sở của họ cho thấy TypeScript đang phát hiện một lượng lớn vấn đề về loại – những vấn đề mà trình biên dịch Closure không nhất thiết phải phát hiện. Nhiều vấn đề trong số này có thể là nguyên nhân gốc rễ của các trường hợp hồi quy mà chúng tôi đang phát hành; do đó, chúng tôi tin rằng TypeScript có thể là một lựa chọn khả thi cho DevTools.

Trong quá trình di chuyển sang các mô-đun JavaScript, chúng tôi đã phát hiện ra rằng Trình biên dịch Closure đang phát hiện nhiều vấn đề hơn so với trước đây. Việc chuyển sang định dạng mô-đun chuẩn đã tăng khả năng Closure hiểu cơ sở mã của chúng tôi, do đó tăng hiệu quả của trình kiểm tra loại. Tuy nhiên, nhóm TypeScript đang sử dụng phiên bản cơ sở của DevTools trước khi di chuyển các mô-đun JavaScript. Do đó, chúng tôi phải tìm hiểu xem việc di chuyển sang các mô-đun JavaScript có làm giảm số lượng lỗi mà trình biên dịch TypeScript phát hiện hay không.

Đánh giá TypeScript

DevTools đã tồn tại hơn một thập kỷ, trong đó đã phát triển thành một ứng dụng web có kích thước đáng kể và nhiều tính năng. Tại thời điểm viết bài đăng trên blog này, DevTools chứa khoảng 150.000 dòng mã JavaScript của bên thứ nhất. Khi chạy trình biên dịch TypeScript trên mã nguồn, chúng tôi đã gặp phải một lượng lớn lỗi. Chúng tôi nhận thấy rằng mặc dù trình biên dịch TypeScript đã phát ra ít lỗi hơn liên quan đến độ phân giải mã (~2.000 lỗi), nhưng vẫn còn 6.000 lỗi khác trong cơ sở mã liên quan đến khả năng tương thích loại.

Điều này cho thấy rằng mặc dù TypeScript có thể hiểu cách phân giải các loại, nhưng nó đã tìm thấy một lượng lớn các loại không tương thích trong cơ sở mã của chúng tôi. Phân tích thủ công các lỗi này cho thấy TypeScript (trong hầu hết trường hợp) là chính xác. Lý do TypeScript có thể phát hiện những lỗi này còn Closure thì không là do trình biên dịch Closure thường sẽ suy luận một loại là Any, trong khi TypeScript sẽ thực hiện suy luận kiểu dựa trên các chỉ định và suy luận một kiểu chính xác hơn. Do đó, TypeScript thực sự hiểu rõ hơn về cấu trúc của các đối tượng và phát hiện ra các cách sử dụng có vấn đề.

Một điểm quan trọng cần lưu ý là việc sử dụng trình biên dịch Closure trong Công cụ của Chrome cho nhà phát triển bao gồm cả việc sử dụng thường xuyên @unrestricted. Việc chú thích một lớp bằng @unrestricted sẽ tắt hiệu quả các bước kiểm tra thuộc tính nghiêm ngặt của trình biên dịch Closure cho lớp cụ thể đó, tức là nhà phát triển có thể tuỳ ý mở rộng định nghĩa lớp mà không cần đảm bảo an toàn kiểu dữ liệu. Chúng tôi không tìm thấy bất kỳ bối cảnh lịch sử nào về lý do khiến việc sử dụng @unrestricted phổ biến trong cơ sở mã DevTools, nhưng điều này đã dẫn đến việc chạy trình biên dịch Closure ở chế độ hoạt động kém an toàn hơn đối với phần lớn cơ sở mã.

Một phân tích chéo về các hồi quy của chúng tôi với các lỗi loại TypeScript đã phát hiện cũng cho thấy sự trùng lặp, khiến chúng tôi tin rằng TypeScript có thể đã ngăn chặn những vấn đề này (miễn là các loại đó chính xác).

Thực hiện lệnh gọi any

Tại thời điểm này, chúng tôi phải quyết định giữa việc cải thiện việc sử dụng Trình biên dịch Closure hoặc di chuyển sang TypeScript. (Vì Flow không được hỗ trợ tại Google hoặc trong Chromium, nên chúng tôi đã phải bỏ qua lựa chọn đó.) Dựa trên các cuộc thảo luận và đề xuất của các kỹ sư Google đang làm việc trên công cụ JavaScript/TypeScript, chúng tôi đã chọn trình biên dịch TypeScript. (Gần đây, chúng tôi cũng đã xuất bản một bài đăng trên blog về cách di chuyển Puppeteer sang TypeScript.)

Lý do chính khiến chúng tôi chọn trình biên dịch TypeScript là khả năng cải thiện độ chính xác của loại, trong khi các lợi thế khác bao gồm sự hỗ trợ của các nhóm TypeScript nội bộ tại Google và các tính năng của ngôn ngữ TypeScript, chẳng hạn như interfaces (thay vì typedefs trong JSDoc).

Việc chọn trình biên dịch TypeScript đồng nghĩa với việc chúng tôi phải đầu tư đáng kể vào cơ sở mã DevTools và cấu trúc nội bộ của trình biên dịch này. Do đó, chúng tôi ước tính rằng cần ít nhất một năm để di chuyển sang TypeScript (dự kiến vào quý 3 năm 2020).

Thực hiện quá trình di chuyển

Câu hỏi lớn nhất còn lại: chúng ta sẽ di chuyển sang TypeScript như thế nào? Chúng tôi có 150.000 dòng mã và không thể di chuyển tất cả cùng một lúc. Chúng tôi cũng biết rằng việc chạy TypeScript trên cơ sở mã của mình sẽ phát hiện ra hàng nghìn lỗi.

Chúng tôi đã đánh giá nhiều phương án:

  1. Tìm tất cả lỗi TypeScript và so sánh với kết quả "vàng". Phương pháp này sẽ tương tự như phương pháp của nhóm TypeScript. Nhược điểm lớn nhất của phương pháp này là thường xuyên xảy ra xung đột hợp nhất, vì hàng chục kỹ sư làm việc trong cùng một cơ sở mã.
  2. Đặt tất cả các loại có vấn đề thành any. Về cơ bản, việc này sẽ khiến TypeScript ngăn chặn lỗi. Chúng tôi không chọn phương án này vì mục tiêu của quá trình di chuyển là loại chính xác, việc loại bỏ sẽ làm giảm hiệu quả.
  3. Sửa tất cả lỗi TypeScript theo cách thủ công. Việc này sẽ liên quan đến việc sửa hàng nghìn lỗi, rất tốn thời gian.

Mặc dù dự kiến sẽ mất nhiều công sức, nhưng chúng tôi đã chọn phương án 3. Ngoài ra, chúng tôi còn chọn phương án này vì một số lý do khác: ví dụ: phương án này cho phép chúng tôi kiểm tra tất cả mã và xem xét tất cả chức năng (bao gồm cả việc triển khai) một lần trong 10 năm. Từ góc độ kinh doanh, chúng tôi không cung cấp giá trị mới mà chỉ duy trì hiện trạng. Điều này khiến việc chứng minh lựa chọn 3 là lựa chọn chính xác trở nên khó khăn hơn.

Tuy nhiên, bằng cách sử dụng TypeScript, chúng tôi tin tưởng rằng mình có thể ngăn chặn các vấn đề trong tương lai, đặc biệt là các vấn đề liên quan đến hồi quy. Do đó, đối số không còn là "chúng tôi đang thêm giá trị kinh doanh mới" mà là "chúng tôi đang đảm bảo không mất giá trị kinh doanh đã có".

Hỗ trợ JavaScript của trình biên dịch TypeScript

Sau khi đảm bảo sự đồng thuận và phát triển kế hoạch chạy cả trình biên dịch Closure và TypeScript trên cùng một mã JavaScript, chúng tôi bắt đầu với một số tệp nhỏ. Phương pháp của chúng tôi chủ yếu là từ dưới lên: bắt đầu với mã cốt lõi và di chuyển lên cấu trúc cho đến khi đạt đến các bảng điều khiển cấp cao.

Chúng tôi có thể chạy song song công việc bằng cách thêm @ts-nocheck vào mọi tệp trong DevTools. Quá trình "sửa TypeScript" sẽ là xoá chú thích @ts-nocheck và giải quyết mọi lỗi mà TypeScript tìm thấy. Điều này có nghĩa là chúng tôi tự tin rằng mỗi tệp đã được kiểm tra và giải quyết được nhiều vấn đề về loại tệp nhất có thể.

Nhìn chung, phương pháp này đã hoạt động hiệu quả với ít vấn đề. Chúng tôi gặp phải một số lỗi trong trình biên dịch TypeScript, nhưng hầu hết các lỗi đó đều khó hiểu:

  1. Tham số không bắt buộc có kiểu hàm trả về any được coi là bắt buộc: #38551
  2. Việc gán thuộc tính cho một phương thức tĩnh của lớp sẽ làm gián đoạn phần khai báo: #38553
  3. Phần khai báo lớp con có hàm khởi tạo không có đối số và lớp cha có hàm khởi tạo đối số sẽ bỏ qua hàm khởi tạo con: #41397

Những lỗi này cho thấy rằng trong 99% trường hợp, trình biên dịch TypeScript là một nền tảng vững chắc để xây dựng. Có, những lỗi khó hiểu này đôi khi sẽ gây ra vấn đề cho DevTools, nhưng hầu hết thời gian, chúng đủ khó hiểu để chúng ta có thể dễ dàng giải quyết.

Vấn đề duy nhất gây nhầm lẫn là đầu ra không xác định của các tệp .tsbuildinfo: #37156. Tại Chromium, chúng tôi yêu cầu mọi bản dựng của cùng một cam kết Chromium đều phải có kết quả giống hệt nhau. Rất tiếc, các kỹ sư xây dựng Chromium của chúng tôi phát hiện ra rằng đầu ra .tsbuildinfo không xác định: crbug.com/1054494. Để giải quyết vấn đề này, chúng tôi phải vá tệp .tsbuildinfo (về cơ bản chứa JSON) và xử lý sau để trả về kết quả có thể xác định: https://crrev.com/c/2091448 May mắn thay, nhóm TypeScript đã giải quyết vấn đề ở thượng nguồn và chúng tôi sớm có thể xoá giải pháp này. Cảm ơn nhóm TypeScript đã tiếp nhận báo cáo lỗi và khắc phục kịp thời các vấn đề này!

Nhìn chung, chúng tôi hài lòng với độ chính xác (loại) của trình biên dịch TypeScript. Chúng tôi hy vọng rằng Devtools (một dự án JavaScript nguồn mở lớn) đã giúp củng cố khả năng hỗ trợ JavaScript trong TypeScript.

Phân tích hậu quả

Chúng tôi đã có thể tiến triển tốt trong việc giải quyết các lỗi loại này và dần tăng lượng mã do TypeScript kiểm tra. Tuy nhiên, vào tháng 8 năm 2020 (9 tháng kể từ khi bắt đầu di chuyển), chúng tôi đã kiểm tra và nhận thấy rằng với tốc độ hiện tại, chúng tôi sẽ không hoàn thành đúng hạn. Một trong các kỹ sư của chúng tôi đã tạo một biểu đồ phân tích để cho thấy tiến trình "TypeScriptification" (tên chúng tôi đặt cho quá trình di chuyển này).

Tiến trình di chuyển TypeScript

Tiến trình di chuyển TypeScript – Theo dõi các dòng mã còn lại cần di chuyển

Theo ước tính, thời điểm chúng tôi không còn dòng nào cần xử lý sẽ rơi vào khoảng từ tháng 7 đến tháng 12 năm 2021, tức là gần một năm sau thời hạn. Sau khi thảo luận với ban quản lý và các kỹ sư khác, chúng tôi đã đồng ý tăng số lượng kỹ sư làm việc để hỗ trợ việc di chuyển sang trình biên dịch TypeScript. Điều này có thể thực hiện được vì chúng tôi đã thiết kế quá trình di chuyển để có thể chạy song song, nhờ đó nhiều kỹ sư làm việc trên nhiều tệp khác nhau sẽ không xung đột với nhau.

Tại thời điểm này, quá trình TypeScriptification đã trở thành một nỗ lực trên toàn nhóm. Nhờ sự trợ giúp bổ sung, chúng tôi đã hoàn tất quá trình di chuyển vào cuối tháng 11 năm 2020, tức là 13 tháng sau khi bắt đầu và hơn một năm trước thời điểm dự kiến ban đầu.

Tổng cộng, có 771 danh sách thay đổi (tương tự như Yêu cầu kéo) do 18 kỹ sư gửi. Lỗi theo dõi của chúng tôi (https://crbug.com/1011811) có hơn 1200 bình luận (hầu hết đều là bài đăng tự động từ danh sách thay đổi). Trang tính theo dõi của chúng tôi có hơn 500 hàng cho tất cả các tệp cần chuyển đổi sang Typescript, người được giao và danh sách thay đổi mà các tệp đó được "Chuyển đổi sang Typescript".

Giảm thiểu tác động của hiệu suất trình biên dịch TypeScript

Vấn đề lớn nhất mà chúng ta đang phải giải quyết hiện nay là hiệu suất chậm của trình biên dịch TypeScript. Do số lượng kỹ sư xây dựng Chromium và DevTools, nút thắt cổ chai này gây tốn kém. Đáng tiếc là chúng tôi không thể xác định được rủi ro này trước khi di chuyển. Chỉ khi di chuyển phần lớn tệp sang TypeScript, chúng tôi mới phát hiện thấy thời gian dành cho các bản dựng Chromium tăng lên đáng kể: https://crbug.com/1139220

Chúng tôi đã báo cáo vấn đề này cho nhóm biên dịch TypeScript của Microsoft, nhưng đáng tiếc là họ xác định hành vi này là có chủ ý. Chúng tôi hy vọng họ sẽ xem xét lại vấn đề này. Trong thời gian chờ đợi, chúng tôi đang nỗ lực giảm thiểu tác động của hiệu suất chậm trên phía Chromium nhiều nhất có thể.

Đáng tiếc là các giải pháp hiện có không phải lúc nào cũng phù hợp với những người đóng góp không phải là nhân viên của Google. Vì các nội dung đóng góp nguồn mở cho Chromium rất quan trọng (đặc biệt là các nội dung từ nhóm Microsoft Edge), nên chúng tôi đang tích cực tìm kiếm các giải pháp thay thế phù hợp với tất cả các cộng tác viên. Tuy nhiên, hiện tại chúng tôi chưa tìm ra giải pháp thay thế phù hợp.

Trạng thái hiện tại của TypeScript trong Công cụ cho nhà phát triển

Hiện tại, chúng tôi đã xoá trình kiểm tra loại trình biên dịch Closure khỏi cơ sở mã và chỉ dựa vào trình biên dịch TypeScript. Chúng ta có thể viết các tệp do TypeScript tạo và sử dụng các tính năng dành riêng cho TypeScript (chẳng hạn như giao diện, thành phần chung, v.v.) giúp ích cho chúng ta hằng ngày. Chúng tôi đã tăng cường sự tự tin rằng trình biên dịch TypeScript sẽ phát hiện lỗi loại và hồi quy. Đây là điều chúng tôi hy vọng sẽ xảy ra khi bắt đầu công việc di chuyển này. Quá trình di chuyển này, giống như nhiều quá trình khác, diễn ra chậm, phức tạp và thường gặp nhiều thách thức. Tuy nhiên, khi nhận được những lợi ích, chúng tôi tin rằng quá trình này rất đáng để thực hiện.

Tải các kênh xem trước xuống

Hãy cân nhắc sử dụng Chrome Canary, Dev hoặc Beta làm trình duyệt phát triển mặc định. Các kênh xem trước này cho phép bạn sử dụng các tính năng mới nhất của DevTools, kiểm thử các API nền tảng web tiên tiến và giúp bạn tìm thấy vấn đề trên trang web của mình trước khi người dùng phát hiện ra!

Liên hệ với nhóm Chrome DevTools

Hãy sử dụng các lựa chọn sau để thảo luận về các tính năng, bản cập nhật mới hoặc bất kỳ nội dung nào khác liên quan đến Công cụ cho nhà phát triển.