พาธไปยังการพิมพ์โค้ด Python 4 ล้านบรรทัด ส่วนที่ 1

วันนี้เราขอนำเสนอส่วนแรกของการแปลเนื้อหาเกี่ยวกับวิธีที่ Dropbox จัดการกับการควบคุมประเภทของโค้ด Python

พาธไปยังการพิมพ์โค้ด Python 4 ล้านบรรทัด ส่วนที่ 1

Dropbox เขียนอะไรมากมายใน Python เป็นภาษาที่เราใช้กันอย่างแพร่หลายมาก ทั้งสำหรับบริการส่วนหลังและแอปพลิเคชันไคลเอนต์เดสก์ท็อป นอกจากนี้เรายังใช้ Go, TypeScript และ Rust เป็นจำนวนมาก แต่ Python เป็นภาษาหลักของเรา เมื่อพิจารณาขนาดของเราและเรากำลังพูดถึงโค้ด Python หลายล้านบรรทัด ปรากฎว่าการพิมพ์แบบไดนามิกของโค้ดดังกล่าวทำให้ความเข้าใจซับซ้อนโดยไม่จำเป็นและเริ่มส่งผลกระทบต่อผลิตภาพแรงงานอย่างจริงจัง เพื่อลดปัญหานี้ เราได้เริ่มเปลี่ยนโค้ดของเราเป็นการตรวจสอบประเภทคงที่โดยใช้ mypy อย่างค่อยเป็นค่อยไป นี่อาจเป็นระบบตรวจสอบประเภทแบบสแตนด์อโลนที่ได้รับความนิยมมากที่สุดสำหรับ Python Mypy เป็นโครงการโอเพ่นซอร์ส นักพัฒนาหลักทำงานใน Dropbox

Dropbox เป็นหนึ่งในบริษัทแรกๆ ที่ใช้การตรวจสอบประเภทแบบสแตติกในโค้ด Python ในระดับนี้ Mypy ถูกใช้ในโครงการนับพันในปัจจุบัน เครื่องมือนี้นับครั้งไม่ถ้วนตามที่พวกเขากล่าวว่า "ทดสอบในสนามรบ" เรามาไกลเพื่อมาถึงจุดที่เราอยู่ตอนนี้ ระหว่างทาง มีการทดลองที่ไม่สำเร็จและล้มเหลวมากมาย โพสต์นี้ครอบคลุมประวัติของการตรวจสอบประเภทแบบสแตติกใน Python ตั้งแต่จุดเริ่มต้นที่ยากลำบากซึ่งเป็นส่วนหนึ่งของโครงการวิจัยของฉันจนถึงปัจจุบัน เมื่อการตรวจสอบประเภทและการบอกใบ้ประเภทกลายเป็นเรื่องธรรมดาสำหรับนักพัฒนาจำนวนนับไม่ถ้วนที่เขียนด้วย Python ขณะนี้กลไกเหล่านี้ได้รับการสนับสนุนโดยเครื่องมือมากมาย เช่น IDE และตัววิเคราะห์โค้ด

อ่านตอนที่สอง

ทำไมการตรวจสอบประเภทจึงจำเป็น

หากคุณเคยใช้ Python ที่พิมพ์แบบไดนามิก คุณอาจมีความสับสนว่าเหตุใดจึงมีความยุ่งยากเกี่ยวกับการพิมพ์แบบคงที่และ mypy เมื่อเร็ว ๆ นี้ หรือบางทีคุณอาจชอบ Python เพราะการพิมพ์แบบไดนามิก และสิ่งที่เกิดขึ้นก็ทำให้คุณอารมณ์เสีย กุญแจสำคัญของค่าของการพิมพ์แบบคงที่คือขนาดของโซลูชัน ยิ่งโปรเจ็กต์ของคุณมีขนาดใหญ่เท่าใด คุณก็ยิ่งเอนเอียงไปทางการพิมพ์แบบคงที่มากขึ้นเท่านั้น และท้ายที่สุด คุณก็ยิ่งต้องการมันมากเท่านั้น

สมมติว่าโครงการหนึ่งมีขนาดถึงหมื่นบรรทัดและปรากฎว่ามีโปรแกรมเมอร์หลายคนกำลังทำงานอยู่ เมื่อพิจารณาจากโครงการที่คล้ายกัน จากประสบการณ์ของเรา เราสามารถพูดได้ว่าการทำความเข้าใจโค้ดจะเป็นกุญแจสำคัญที่ทำให้นักพัฒนามีประสิทธิผล หากไม่มีคำอธิบายประกอบประเภท อาจเป็นเรื่องยากที่จะเข้าใจ ตัวอย่างเช่น อาร์กิวเมนต์ใดที่จะส่งไปยังฟังก์ชัน หรือประเภทใดที่ฟังก์ชันสามารถส่งคืนได้ ต่อไปนี้คือคำถามทั่วไปที่มักจะตอบได้ยากหากไม่ใช้คำอธิบายประกอบประเภท:

  • ฟังก์ชันนี้คืนค่าได้ไหม None?
  • ข้อโต้แย้งนี้ควรเป็นอย่างไร items?
  • ประเภทแอตทริบิวต์คืออะไร id: int ใช่ไหม, strหรืออาจเป็นประเภทที่กำหนดเอง
  • อาร์กิวเมนต์นี้ควรเป็นรายการหรือไม่ เป็นไปได้ไหมที่จะส่งทูเพิลไป?

หากคุณดูข้อมูลโค้ดที่มีคำอธิบายประกอบประเภทต่อไปนี้และพยายามตอบคำถามที่คล้ายกัน ปรากฎว่านี่เป็นงานที่ง่ายที่สุด:

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

  • read_metadata ไม่กลับมา Noneเนื่องจากประเภทการส่งคืนไม่ใช่ Optional[…].
  • อาร์กิวเมนต์ items เป็นลำดับของบรรทัด ไม่สามารถทำซ้ำแบบสุ่มได้
  • คุณลักษณะ id เป็นสตริงของไบต์

ในโลกอุดมคติ เราคาดหวังว่ารายละเอียดปลีกย่อยดังกล่าวทั้งหมดจะได้รับการอธิบายไว้ในเอกสารประกอบ (docstring) ที่มีมาให้ภายใน แต่ประสบการณ์ให้ตัวอย่างมากมายเกี่ยวกับความจริงที่ว่าเอกสารดังกล่าวมักไม่ได้รับการสังเกตในรหัสที่คุณต้องทำงาน แม้ว่าเอกสารดังกล่าวจะมีอยู่ในรหัส แต่ก็ไม่สามารถไว้วางใจในความถูกต้องสมบูรณ์ได้ เอกสารนี้อาจคลุมเครือ ไม่ถูกต้อง และเปิดโอกาสให้เกิดความเข้าใจผิดได้ ในทีมขนาดใหญ่หรือโครงการขนาดใหญ่ ปัญหานี้อาจรุนแรงมาก

แม้ว่า Python จะเก่งในขั้นเริ่มต้นหรือขั้นกลางของโครงการ แต่ในบางจุดโครงการที่ประสบความสำเร็จและบริษัทที่ใช้ Python อาจเผชิญกับคำถามที่สำคัญ: "เราควรเขียนทุกอย่างใหม่ด้วยภาษาที่พิมพ์แบบคงที่หรือไม่"

ระบบการตรวจสอบประเภทเช่น mypy แก้ปัญหาข้างต้นโดยการจัดหาภาษาที่เป็นทางการสำหรับนักพัฒนาเพื่ออธิบายประเภท และโดยการตรวจสอบว่าการประกาศประเภทนั้นตรงกับการใช้งานโปรแกรม (และอาจเป็นทางเลือกโดยการตรวจสอบการมีอยู่) โดยทั่วไป เราสามารถพูดได้ว่าระบบเหล่านี้มีบางอย่างเช่นการตรวจสอบเอกสารอย่างละเอียดถี่ถ้วน

การใช้ระบบดังกล่าวมีข้อดีอื่น ๆ และไม่ใช่เรื่องเล็กน้อย:

  • ระบบตรวจสอบประเภทสามารถตรวจจับข้อผิดพลาดเล็กๆ น้อยๆ (และไม่เล็กมาก) ได้ ตัวอย่างทั่วไปคือเมื่อพวกเขาลืมประมวลผลค่า None หรือเงื่อนไขพิเศษอื่นๆ
  • การปรับโครงสร้างโค้ดทำได้ง่ายมากเนื่องจากระบบตรวจสอบประเภทมักจะแม่นยำมากเกี่ยวกับโค้ดที่ต้องเปลี่ยน ในขณะเดียวกัน เราไม่จำเป็นต้องหวังว่าจะครอบคลุมการทดสอบโค้ด 100% ซึ่งโดยปกติแล้วจะไม่สามารถทำได้ไม่ว่าในกรณีใด เราไม่จำเป็นต้องเจาะลึกลงไปในการติดตามสแต็กเพื่อค้นหาสาเหตุของปัญหา
  • แม้แต่ในโครงการขนาดใหญ่ mypy มักจะทำการตรวจสอบแบบเต็มได้ในเสี้ยววินาที และการดำเนินการทดสอบมักใช้เวลาหลายสิบวินาทีหรือแม้แต่นาที ระบบตรวจสอบประเภทให้ข้อเสนอแนะแก่โปรแกรมเมอร์ทันทีและช่วยให้เขาทำงานได้เร็วขึ้น เขาไม่จำเป็นต้องเขียนการทดสอบหน่วยที่เปราะบางและยากอีกต่อไป ซึ่งแทนที่เอนทิตีจริงด้วยการจำลองและแพตช์เพียงเพื่อให้ได้ผลการทดสอบโค้ดเร็วขึ้น

IDE และเอดิเตอร์ เช่น PyCharm หรือ Visual Studio Code ใช้พลังของคำอธิบายประกอบประเภทเพื่อให้นักพัฒนาโค้ดสมบูรณ์ การเน้นข้อผิดพลาด และการสนับสนุนสำหรับโครงสร้างภาษาที่ใช้กันทั่วไป และนี่เป็นเพียงประโยชน์บางประการของการพิมพ์ สำหรับโปรแกรมเมอร์บางคน ทั้งหมดนี้เป็นข้อโต้แย้งหลักที่สนับสนุนการพิมพ์ นี่คือสิ่งที่ได้ประโยชน์ทันทีหลังการนำไปใช้ กรณีการใช้งานสำหรับประเภทนี้ไม่ต้องการระบบตรวจสอบประเภทแยกต่างหากเช่น mypy แม้ว่าควรสังเกตว่า mypy ช่วยให้คำอธิบายประกอบประเภทสอดคล้องกับรหัส

ภูมิหลังของ mypy

ประวัติของ mypy เริ่มต้นขึ้นในสหราชอาณาจักร ในเคมบริดจ์ ไม่กี่ปีก่อนที่ฉันจะเข้าร่วม Dropbox ฉันได้มีส่วนร่วม ซึ่งเป็นส่วนหนึ่งของการวิจัยระดับปริญญาเอกของฉัน ในการรวมภาษาแบบสแตติกและไดนามิกเข้าด้วยกัน ฉันได้รับแรงบันดาลใจจากบทความเกี่ยวกับการพิมพ์ที่เพิ่มขึ้นโดย Jeremy Siek และ Walid Taha และจากโครงการ Typed Racket ฉันพยายามค้นหาวิธีใช้ภาษาโปรแกรมเดียวกันสำหรับโปรเจ็กต์ต่างๆ ตั้งแต่สคริปต์ขนาดเล็กไปจนถึงโค้ดเบสที่ประกอบด้วยหลายล้านบรรทัด ในเวลาเดียวกัน ฉันต้องการให้แน่ใจว่าในโครงการขนาดใดก็ตาม จะไม่มีการประนีประนอมมากเกินไป ส่วนสำคัญของทั้งหมดนี้คือแนวคิดในการค่อยๆ ย้ายจากโครงการต้นแบบที่ไม่ได้พิมพ์ไปเป็นผลิตภัณฑ์สำเร็จรูปที่พิมพ์แบบคงที่ที่ผ่านการทดสอบอย่างครอบคลุม ทุกวันนี้ แนวคิดเหล่านี้มักถูกมองข้ามไป แต่ในปี 2010 ปัญหานี้ยังคงได้รับการสำรวจอย่างต่อเนื่อง

งานเดิมของฉันในการตรวจสอบประเภทไม่ได้มุ่งเป้าไปที่ Python แต่ฉันใช้ภาษา "โฮมเมด" เล็กน้อยแทน อลเวง. นี่คือตัวอย่างที่จะช่วยให้คุณเข้าใจสิ่งที่เรากำลังพูดถึง (คำอธิบายประกอบประเภทเป็นตัวเลือกที่นี่):

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

การใช้ภาษาพื้นเมืองที่เรียบง่ายเป็นวิธีการทั่วไปที่ใช้ในการวิจัยทางวิทยาศาสตร์ ไม่น้อยเพราะมันช่วยให้คุณทำการทดลองได้อย่างรวดเร็วและเนื่องจากความจริงที่ว่าสิ่งที่ไม่เกี่ยวข้องกับการศึกษาสามารถถูกเพิกเฉยได้ง่าย ภาษาการเขียนโปรแกรมในโลกแห่งความเป็นจริงมักจะเป็นปรากฏการณ์ขนาดใหญ่ที่มีการใช้งานที่ซับซ้อน และทำให้การทดลองช้าลง อย่างไรก็ตาม ผลลัพธ์ใด ๆ ที่อิงจากภาษาที่เรียบง่ายนั้นดูน่าสงสัยเล็กน้อย เนื่องจากในการได้รับผลลัพธ์เหล่านี้ ผู้วิจัยอาจเสียสละการพิจารณาที่สำคัญสำหรับการใช้ภาษาจริง

ตัวตรวจสอบประเภทของฉันสำหรับ Alore ดูดีมาก แต่ฉันต้องการทดสอบโดยการทดลองกับโค้ดจริง ซึ่งคุณอาจพูดได้ว่าไม่ได้เขียนด้วยภาษา Alore โชคดีสำหรับฉัน ภาษา Alore ส่วนใหญ่มาจากแนวคิดเดียวกันกับ Python การสร้างตัวตรวจสอบตัวพิมพ์ใหม่นั้นง่ายพอที่จะทำงานร่วมกับไวยากรณ์และความหมายของ Python ได้ สิ่งนี้ทำให้เราลองตรวจสอบประเภทในโค้ด Python แบบโอเพ่นซอร์ส นอกจากนี้ ฉันได้เขียน transpiler เพื่อแปลงโค้ดที่เขียนด้วย Alore เป็นโค้ด Python และใช้มันเพื่อแปลโค้ดตัวตรวจสอบตัวพิมพ์ของฉัน ตอนนี้ฉันมีระบบตรวจสอบประเภทที่เขียนด้วย Python ที่รองรับเซ็ตย่อยของ Python ซึ่งเป็นภาษาประเภทนั้น! (การตัดสินใจทางสถาปัตยกรรมบางอย่างที่สมเหตุสมผลสำหรับ Alore นั้นไม่เหมาะสมสำหรับ Python และสิ่งนี้ยังคงสังเกตเห็นได้ในส่วนของ mypy codebase)

อันที่จริง ภาษาที่รองรับโดยระบบประเภทของฉันไม่สามารถเรียกว่า Python ได้ในตอนนี้: มันเป็นตัวแปรของ Python เนื่องจากข้อจำกัดบางประการของไวยากรณ์คำอธิบายประกอบประเภท Python 3

ดูเหมือนว่าเป็นส่วนผสมของ Java และ Python:

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

หนึ่งในความคิดของฉันในเวลานั้นคือการใช้คำอธิบายประกอบประเภทเพื่อปรับปรุงประสิทธิภาพโดยการรวบรวม Python ประเภทนี้ไปยัง C หรืออาจเป็น JVM bytecode ฉันมาถึงขั้นตอนของการเขียนต้นแบบคอมไพเลอร์ แต่ฉันละทิ้งแนวคิดนี้ เนื่องจากการตรวจสอบประเภทนั้นดูมีประโยชน์ทีเดียว

ฉันจบลงด้วยการนำเสนอโครงการของฉันที่ PyCon 2013 ในซานตาคลารา ฉันได้พูดคุยเกี่ยวกับเรื่องนี้กับ Guido van Rossum เผด็จการ Python ที่ใจดีต่อชีวิต เขาโน้มน้าวให้ฉันเลิกใช้ไวยากรณ์ของตัวเองและใช้ไวยากรณ์ Python 3 มาตรฐานต่อไป Python 3 รองรับคำอธิบายประกอบของฟังก์ชัน ดังนั้น ตัวอย่างของฉันสามารถเขียนใหม่ได้ดังที่แสดงด้านล่าง ส่งผลให้โปรแกรม Python ปกติ:

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

ฉันต้องประนีประนอมบางอย่าง (ก่อนอื่น ฉันต้องการทราบว่าฉันคิดค้นไวยากรณ์ของตัวเองด้วยเหตุผลนี้เอง) โดยเฉพาะอย่างยิ่ง Python 3.3 ซึ่งเป็นเวอร์ชันล่าสุดของภาษาในขณะนั้น ไม่รองรับคำอธิบายประกอบแบบแปรผัน ฉันได้พูดคุยกับ Guido ทางอีเมลเกี่ยวกับความเป็นไปได้ต่างๆ สำหรับการออกแบบวากยสัมพันธ์ของคำอธิบายประกอบดังกล่าว เราตัดสินใจใช้ความคิดเห็นประเภทสำหรับตัวแปร สิ่งนี้เป็นไปตามจุดประสงค์ แต่ค่อนข้างยุ่งยาก (Python 3.6 ทำให้เรามีไวยากรณ์ที่ดีกว่า):

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

ความคิดเห็นประเภทยังมีประโยชน์ในการรองรับ Python 2 ซึ่งไม่รองรับคำอธิบายประกอบประเภทในตัว:

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

ปรากฎว่าการแลกเปลี่ยน (และอื่น ๆ ) เหล่านี้ไม่สำคัญ - ประโยชน์ของการพิมพ์แบบคงที่หมายความว่าในไม่ช้าผู้ใช้จะลืมเกี่ยวกับไวยากรณ์ที่น้อยกว่าอุดมคติ เนื่องจากไม่มีการใช้โครงสร้างวากยสัมพันธ์พิเศษในโค้ด Python ที่ตรวจสอบประเภท เครื่องมือ Python ที่มีอยู่และกระบวนการประมวลผลโค้ดจึงทำงานต่อไปได้ตามปกติ ทำให้นักพัฒนาสามารถเรียนรู้เครื่องมือใหม่ได้ง่ายขึ้นมาก

Guido ยังโน้มน้าวให้ฉันเข้าร่วม Dropbox หลังจากที่ฉันทำวิทยานิพนธ์ระดับบัณฑิตศึกษาเสร็จ นี่คือจุดเริ่มต้นของเรื่องราว Mypy ที่น่าสนใจที่สุด

จะยังคง ...

เรียนผู้อ่าน! หากคุณใช้ Python โปรดบอกเราเกี่ยวกับขนาดของโครงการที่คุณพัฒนาในภาษานี้

พาธไปยังการพิมพ์โค้ด Python 4 ล้านบรรทัด ส่วนที่ 1
พาธไปยังการพิมพ์โค้ด Python 4 ล้านบรรทัด ส่วนที่ 1

ที่มา: will.com

เพิ่มความคิดเห็น