Đường dẫn để đánh máy 4 triệu dòng mã Python. Phần 1

Hôm nay, chúng tôi xin lưu ý các bạn phần đầu tiên của bản dịch tài liệu về cách Dropbox xử lý kiểm soát kiểu mã Python.

Đường dẫn để đánh máy 4 triệu dòng mã Python. Phần 1

Dropbox viết rất nhiều bằng Python. Đó là ngôn ngữ mà chúng tôi sử dụng cực kỳ rộng rãi, cho cả dịch vụ phụ trợ và ứng dụng khách trên máy tính để bàn. Chúng tôi cũng sử dụng Go, TypeScript và Rust rất nhiều nhưng Python là ngôn ngữ chính của chúng tôi. Xem xét quy mô của chúng tôi và chúng tôi đang nói về hàng triệu dòng mã Python, hóa ra việc gõ động của mã như vậy làm phức tạp sự hiểu biết của nó một cách không cần thiết và bắt đầu ảnh hưởng nghiêm trọng đến năng suất lao động. Để giảm thiểu vấn đề này, chúng tôi đã bắt đầu chuyển dần mã của mình sang kiểm tra kiểu tĩnh bằng cách sử dụng mypy. Đây có lẽ là hệ thống kiểm tra kiểu độc lập phổ biến nhất dành cho Python. Mypy là một dự án nguồn mở, các nhà phát triển chính của nó làm việc trong Dropbox.

Dropbox là một trong những công ty đầu tiên triển khai kiểm tra kiểu tĩnh trong mã Python ở quy mô này. Mypy được sử dụng trong hàng ngàn dự án ngày nay. Công cụ này vô số lần, như họ nói, "đã được thử nghiệm trong trận chiến." Chúng ta đã đi một chặng đường dài để đến được vị trí hiện tại. Trên đường đi, có rất nhiều chủ trương không thành công và những thử nghiệm thất bại. Bài đăng này đề cập đến lịch sử kiểm tra kiểu tĩnh trong Python, từ những khởi đầu khó khăn như một phần trong dự án nghiên cứu của tôi, cho đến ngày nay, khi kiểm tra kiểu và gợi ý kiểu đã trở nên phổ biến đối với vô số nhà phát triển viết bằng Python. Các cơ chế này hiện được hỗ trợ bởi nhiều công cụ như IDE và bộ phân tích mã.

Đọc phần thứ hai

Tại sao kiểm tra loại là cần thiết?

Nếu bạn đã từng sử dụng Python gõ động, bạn có thể có chút nhầm lẫn về lý do tại sao gần đây lại có sự ồn ào xung quanh việc gõ tĩnh và mypy. Hoặc có thể bạn thích Python chính xác vì kiểu gõ động của nó và những gì đang xảy ra chỉ khiến bạn khó chịu. Chìa khóa cho giá trị của kiểu gõ tĩnh là quy mô của các giải pháp: dự án của bạn càng lớn, bạn càng nghiêng về kiểu gõ tĩnh và cuối cùng, bạn càng thực sự cần đến nó.

Giả sử một dự án nhất định đã đạt đến kích thước hàng chục nghìn dòng và hóa ra có một số lập trình viên đang làm việc trên đó. Nhìn vào một dự án tương tự, dựa trên kinh nghiệm của chúng tôi, chúng tôi có thể nói rằng việc hiểu mã của nó sẽ là chìa khóa giúp các nhà phát triển làm việc hiệu quả. Ví dụ, nếu không có chú thích kiểu, có thể khó tìm ra đối số nào cần truyền cho hàm hoặc kiểu hàm có thể trả về. Dưới đây là những câu hỏi điển hình thường khó trả lời nếu không sử dụng chú thích loại:

  • chức năng này có thể trở lại None?
  • Lập luận này nên là gì? items?
  • loại thuộc tính là gì id: int Là nó, strhoặc có thể là một số loại tùy chỉnh?
  • Đối số này có nên là một danh sách không? Có thể truyền một tuple cho nó không?

Nếu bạn xem đoạn mã được chú thích kiểu sau và cố gắng trả lời các câu hỏi tương tự, thì hóa ra đây là nhiệm vụ đơn giản nhất:

class Resource:
    id: bytes
    ...
    def read_metadata(self, 
                      items: Sequence[str]) -> Dict[str, MetadataItem]:
        ...

  • read_metadata không trở lại None, vì kiểu trả về không phải là Optional[…].
  • Tranh luận items là một chuỗi các dòng. Nó không thể được lặp đi lặp lại một cách ngẫu nhiên.
  • Thuộc tính id là một chuỗi byte.

Trong một thế giới lý tưởng, người ta mong đợi rằng tất cả những điều tinh tế như vậy sẽ được mô tả trong tài liệu tích hợp sẵn (docstring). Nhưng kinh nghiệm đưa ra rất nhiều ví dụ về thực tế là tài liệu như vậy thường không được quan sát thấy trong mã mà bạn phải làm việc. Ngay cả khi tài liệu như vậy có trong mã, người ta không thể tin tưởng vào tính chính xác tuyệt đối của nó. Tài liệu này có thể mơ hồ, không chính xác và dẫn đến hiểu lầm. Trong các nhóm lớn hoặc các dự án lớn, vấn đề này có thể trở nên cực kỳ nghiêm trọng.

Mặc dù Python vượt trội trong giai đoạn đầu hoặc giai đoạn trung gian của dự án, nhưng tại một số thời điểm, các dự án và công ty thành công sử dụng Python có thể phải đối mặt với câu hỏi quan trọng: “Chúng ta có nên viết lại mọi thứ bằng ngôn ngữ được gõ tĩnh không?”.

Các hệ thống kiểm tra kiểu như mypy giải quyết vấn đề trên bằng cách cung cấp cho nhà phát triển một ngôn ngữ chính thức để mô tả các kiểu và bằng cách kiểm tra xem các khai báo kiểu đó có khớp với việc triển khai chương trình hay không (và, tùy chọn, bằng cách kiểm tra sự tồn tại của chúng). Nói chung, chúng tôi có thể nói rằng các hệ thống này cung cấp cho chúng tôi một số thứ như tài liệu được kiểm tra cẩn thận.

Việc sử dụng các hệ thống như vậy có những ưu điểm khác và chúng đã hoàn toàn không tầm thường:

  • Hệ thống kiểm tra loại có thể phát hiện một số lỗi nhỏ (và không quá nhỏ). Một ví dụ điển hình là khi họ quên xử lý một giá trị None hoặc một số điều kiện đặc biệt khác.
  • Việc tái cấu trúc mã được đơn giản hóa rất nhiều vì hệ thống kiểm tra kiểu thường rất chính xác về mã nào cần được thay đổi. Đồng thời, chúng tôi không cần phải hy vọng phạm vi bảo hiểm mã 100% với các thử nghiệm, điều này thường không khả thi trong mọi trường hợp. Chúng ta không cần đào sâu vào vết tích ngăn xếp để tìm ra nguyên nhân của vấn đề.
  • Ngay cả trong các dự án lớn, mypy thường có thể thực hiện kiểm tra toàn bộ chỉ trong tích tắc. Và việc thực hiện các bài kiểm tra thường mất hàng chục giây hoặc thậm chí vài phút. Hệ thống kiểm tra loại cung cấp phản hồi tức thời cho lập trình viên và cho phép anh ta thực hiện công việc của mình nhanh hơn. Anh ấy không còn cần phải viết các bài kiểm tra đơn vị mỏng manh và khó bảo trì, thay thế các thực thể thực bằng các bản mô phỏng và bản vá lỗi chỉ để nhận được kết quả kiểm tra mã nhanh hơn.

Các IDE và trình chỉnh sửa chẳng hạn như PyCharm hoặc Visual Studio Code sử dụng sức mạnh của chú thích loại để cung cấp cho nhà phát triển khả năng hoàn thiện mã, đánh dấu lỗi và hỗ trợ cho các cấu trúc ngôn ngữ thường được sử dụng. Và đây chỉ là một số lợi ích của việc đánh máy. Đối với một số lập trình viên, tất cả điều này là lý lẽ chính ủng hộ việc gõ. Đây là một cái gì đó có lợi ngay lập tức sau khi thực hiện. Trường hợp sử dụng này cho các loại không yêu cầu một hệ thống kiểm tra loại riêng biệt như mypy, mặc dù cần lưu ý rằng mypy giúp giữ cho các chú thích loại nhất quán với mã.

Bối cảnh của mypy

Lịch sử của mypy bắt đầu ở Vương quốc Anh, ở Cambridge, vài năm trước khi tôi gia nhập Dropbox. Tôi đã tham gia, như một phần trong nghiên cứu tiến sĩ của mình, trong việc hợp nhất các ngôn ngữ động và được gõ tĩnh. Tôi đã được truyền cảm hứng từ một bài báo về cách gõ tăng dần của Jeremy Siek và Walid Taha, và bởi dự án Vợt đánh máy. Tôi đã cố gắng tìm cách sử dụng cùng một ngôn ngữ lập trình cho nhiều dự án khác nhau - từ các tập lệnh nhỏ đến cơ sở mã bao gồm nhiều triệu dòng. Đồng thời, tôi muốn đảm bảo rằng trong một dự án ở bất kỳ quy mô nào, người ta sẽ không phải thực hiện những thỏa hiệp quá lớn. Một phần quan trọng của tất cả những điều này là ý tưởng chuyển dần từ một dự án nguyên mẫu chưa được nhập thành một sản phẩm hoàn chỉnh được nhập tĩnh được kiểm tra toàn diện. Ngày nay, những ý tưởng này phần lớn được coi là đương nhiên, nhưng vào năm 2010, đó là một vấn đề vẫn đang được tích cực khám phá.

Công việc kiểm tra kiểu ban đầu của tôi không nhằm vào Python. Thay vào đó, tôi đã sử dụng một ngôn ngữ nhỏ "tự chế" Alore. Đây là một ví dụ sẽ giúp bạn hiểu những gì chúng tôi đang nói đến (loại chú thích là tùy chọn ở đây):

def Fib(n as Int) as Int
  if n <= 1
    return n
  else
    return Fib(n - 1) + Fib(n - 2)
  end
end

Sử dụng một ngôn ngữ bản địa đơn giản hóa là một cách tiếp cận phổ biến được sử dụng trong nghiên cứu khoa học. Điều này là như vậy, nhất là vì nó cho phép bạn nhanh chóng tiến hành các thí nghiệm, và cũng do thực tế là những gì không liên quan đến nghiên cứu có thể dễ dàng bị bỏ qua. Các ngôn ngữ lập trình trong thế giới thực có xu hướng trở thành hiện tượng quy mô lớn với cách triển khai phức tạp và điều này làm chậm quá trình thử nghiệm. Tuy nhiên, bất kỳ kết quả nào dựa trên ngôn ngữ đơn giản hóa đều có vẻ hơi đáng ngờ, vì để có được những kết quả này, nhà nghiên cứu có thể đã hy sinh những cân nhắc quan trọng đối với việc sử dụng ngôn ngữ trong thực tế.

Trình kiểm tra kiểu của tôi cho Alore trông rất hứa hẹn, nhưng tôi muốn kiểm tra nó bằng cách thử nghiệm với mã thực, mà bạn có thể nói là không được viết bằng Alore. May mắn cho tôi, ngôn ngữ Alore chủ yếu dựa trên những ý tưởng giống như Python. Thật dễ dàng để làm lại máy đánh chữ để nó có thể hoạt động với cú pháp và ngữ nghĩa của Python. Điều này cho phép chúng tôi thử kiểm tra kiểu trong mã nguồn mở Python. Ngoài ra, tôi đã viết một bộ chuyển mã để chuyển mã được viết bằng Alore sang mã Python và sử dụng nó để dịch mã máy đánh máy của tôi. Bây giờ tôi đã có một hệ thống kiểm tra kiểu được viết bằng Python hỗ trợ một tập hợp con Python, một số loại ngôn ngữ đó! (Một số quyết định kiến ​​trúc có ý nghĩa đối với Alore lại không phù hợp với Python và điều này vẫn đáng chú ý trong các phần của cơ sở mã mypy.)

Trên thực tế, ngôn ngữ được hỗ trợ bởi hệ thống kiểu của tôi không thể được gọi là Python vào thời điểm này: nó là một biến thể của Python do một số hạn chế của cú pháp chú thích kiểu Python 3.

Nó trông giống như một hỗn hợp của Java và Python:

int fib(int n):
    if n <= 1:
        return n
    else:
        return fib(n - 1) + fib(n - 2)

Một trong những ý tưởng của tôi vào thời điểm đó là sử dụng các chú thích kiểu để cải thiện hiệu suất bằng cách biên dịch loại Python này thành C, hoặc có lẽ là mã byte JVM. Tôi đã đến giai đoạn viết một nguyên mẫu trình biên dịch, nhưng tôi đã từ bỏ ý tưởng này, vì bản thân việc kiểm tra kiểu có vẻ khá hữu ích.

Tôi đã kết thúc việc trình bày dự án của mình tại PyCon 2013 ở Santa Clara. Tôi cũng đã nói về điều này với Guido van Rossum, nhà độc tài Python nhân từ suốt đời. Anh ấy đã thuyết phục tôi bỏ cú pháp của riêng mình và sử dụng cú pháp tiêu chuẩn của Python 3. Python 3 hỗ trợ các chú thích hàm, vì vậy ví dụ của tôi có thể được viết lại như hình bên dưới, dẫn đến một chương trình Python bình thường:

def fib(n: int) -> int:
    if n <= 1:
        return n
    else:
        return fib(n - 1) + fib(n - 2)

Tôi đã phải thực hiện một số thỏa hiệp (trước hết, tôi muốn lưu ý rằng tôi đã phát minh ra cú pháp của riêng mình vì lý do này). Đặc biệt, Python 3.3, phiên bản mới nhất của ngôn ngữ này vào thời điểm đó, không hỗ trợ chú thích biến. Tôi đã thảo luận với Guido qua e-mail các khả năng khác nhau cho thiết kế cú pháp của các chú thích như vậy. Chúng tôi quyết định sử dụng nhận xét loại cho các biến. Điều này phục vụ mục đích đã định, nhưng hơi cồng kềnh (Python 3.6 cung cấp cho chúng tôi một cú pháp đẹp hơn):

products = []  # type: List[str]  # Eww

Nhận xét loại cũng có ích để hỗ trợ Python 2, vốn không có hỗ trợ tích hợp cho chú thích loại:

f fib(n):
    # type: (int) -> int
    if n <= 1:
        return n
    else:
        return fib(n - 1) + fib(n - 2)

Hóa ra những sự đánh đổi này (và những thứ khác) không thực sự quan trọng - lợi ích của việc gõ tĩnh có nghĩa là người dùng sẽ sớm quên đi cú pháp kém lý tưởng. Vì không có cấu trúc cú pháp đặc biệt nào được sử dụng trong mã Python được kiểm tra kiểu, nên các công cụ Python và quy trình xử lý mã hiện có tiếp tục hoạt động bình thường, giúp các nhà phát triển tìm hiểu công cụ mới dễ dàng hơn nhiều.

Guido cũng thuyết phục tôi tham gia Dropbox sau khi tôi hoàn thành luận văn tốt nghiệp. Đây là nơi phần thú vị nhất của câu chuyện mypy bắt đầu.

Để được tiếp tục ...

Gởi bạn đọc! Nếu bạn sử dụng Python, vui lòng cho chúng tôi biết quy mô các dự án bạn phát triển bằng ngôn ngữ này.

Đường dẫn để đánh máy 4 triệu dòng mã Python. Phần 1
Đường dẫn để đánh máy 4 triệu dòng mã Python. Phần 1

Nguồn: www.habr.com

Thêm một lời nhận xét