//go:fix nội tuyến và nội tuyến cấp nguồn
Backend·Hacker News·1 lượt xem

//go:fix nội tuyến và nội tuyến cấp nguồn

//go:fix inline and the source-level inliner

Go 1.26 chứa lệnh triển khai go fix hoàn toàn mới, được thiết kế để giúp bạn giữ cho mã Go của mình luôn cập nhật và hiện đại. Để giới thiệu, hãy bắt đầu bằng cách đọc bài đăng gần đây của chúng tôi về chủ đề này.

Go 1.26 chứa lệnh triển khai hoàn toàn mới của lệnh con go fix, được thiết kế để giúp bạn giữ cho mã Go của mình luôn cập nhật và hiện đại. Đối với một phần giới thiệu, hãy bắt đầu bằng cách đọc bài đăng gần đây của chúng tôi về chủ đề này. Trong bài đăng này, chúng ta sẽ xem xét một tính năng cụ thể, cấp độ nguồn nội tuyến.

Mặc dù go fix có một số công cụ hiện đại hóa dành riêng cho các tính năng mới cụ thể tính năng ngôn ngữ và thư viện, nội tuyến cấp nguồn là thành quả đầu tiên trong nỗ lực của chúng tôi nhằm cung cấp “ máy phân tích và hiện đại hóa tự phục vụ”. Nó cho phép bất kỳ tác giả gói nào thể hiện việc di chuyển API đơn giản và cập nhật một cách đơn giản và an toàn. Trước tiên, chúng tôi sẽ giải thích nội tuyến cấp nguồn là gì và cách bạn có thể sử dụng nó, sau đó chúng ta sẽ đi sâu vào một số khía cạnh của vấn đề và công nghệ đằng sau vấn đề đó.

Nội tuyến cấp nguồn

Vào năm 2023, chúng tôi đã xây dựng một thuật toán để nội tuyến cấp nguồn các lệnh gọi hàm trong Go. Để “nội tuyến” một cuộc gọi có nghĩa là thay thế cuộc gọi bằng một bản sao phần thân của hàm được gọi, thay thế các đối số cho các tham số. Chúng tôi gọi nó là nội tuyến “cấp nguồn” vì nó sửa đổi mã nguồn một cách lâu dài. Ngược lại, thuật toán nội tuyến được tìm thấy trong một trình biên dịch thông thường, bao gồm cả Go, áp dụng một phép biến đổi tương tự, nhưng đối với biểu diễn trung gian tạm thời của trình biên dịch, để tạo mã hiệu quả hơn.

Nếu bạn đã từng gọi gopls ’ Tái cấu trúc tương tác “Gọi nội tuyến”, bạn đã sử dụng nội tuyến cấp nguồn. (Trong Mã VS, bạn có thể tìm thấy hành động mã này trên menu “Hành động nguồn…”.) Ảnh chụp màn hình trước và sau bên dưới cho thấy tác dụng của việc nội tuyến lệnh gọi tới sum từ hàm có tên six.

Nội tuyến là một khối xây dựng quan trọng cho một số công cụ chuyển đổi nguồn. Ví dụ: gopls sử dụng nó cho các phép tái cấu trúc “Thay đổi chữ ký” và “Xóa tham số không được sử dụng” bởi vì, như chúng ta sẽ thấy bên dưới, nó xử lý nhiều vấn đề về độ chính xác tinh tế phát sinh khi tái cấu trúc các lệnh gọi hàm.

Bộ nội tuyến này cũng là một trong những bộ phân tích trong lệnh go fix hoàn toàn mới. Vào đi sửa , nó cho phép di chuyển và nâng cấp API tự phục vụ bằng cách sử dụng nhận xét chỉ thị //go:fix inline mới. Hãy cùng xem một số ví dụ về cách hoạt động của tính năng này và mục đích sử dụng.

Ví dụ: đổi tên ioutil.ReadFile

Trong phiên bản Go 1.16, hàm ioutil.ReadFile dùng để đọc nội dung của tệp đã không được dùng nữa mà thay vào đó là os.ReadFile mới chức năng. Trên thực tế, hàm này đã được đổi tên, mặc dù tất nhiên lời hứa về khả năng tương thích của Go ngăn cản chúng tôi xóa tên cũ.

gói ioutil
nhập "os"
// ReadFile đọc file được đặt tên theo tên file…
// Không được dùng nữa: Kể từ phiên bản Go 1.16, hàm này chỉ gọi [os.ReadFile].
func ReadFile(chuỗi tên tệp) ([]byte, lỗi) {
    trả về os.ReadFile(tên tệp)
}
 

Lý tưởng nhất là chúng tôi muốn thay đổi mọi chương trình Go trên thế giới ngừng sử dụng ioutil.ReadFile và thay vào đó gọi os.ReadFile. Nội tuyến có thể giúp chúng tôi làm điều đó. Đầu tiên chúng ta chú thích hàm cũ bằng //go:fix inline. Nhận xét này cho công cụ biết rằng bất cứ khi nào nó nhìn thấy lệnh gọi đến hàm này, nó sẽ thực hiện lệnh gọi nội tuyến.

gói ioutil
nhập "os"
// ReadFile đọc file được đặt tên theo tên file…
// Không được dùng nữa: Kể từ phiên bản Go 1.16, hàm này chỉ gọi [os.ReadFile].
//go:sửa nội tuyến
func ReadFile(chuỗi tên tệp) ([]byte, lỗi) {
    trả về os.ReadFile(tên tệp)
}

Khi chúng tôi chạy go fix trên một tệp chứa lệnh gọi tới ioutil.ReadFile, nó áp dụng thay thế:

$ đi sửa -diff ./...
-nhập "io/ioutil"
+nhập "os"
- dữ liệu, lỗi := ioutil.ReadFile("hello.txt")
+ dữ liệu, lỗi := os.ReadFile("hello.txt")

Cuộc gọi đã được nội tuyến, thực tế là thay thế lệnh gọi đến một chức năng bằng lệnh gọi đến chức năng khác.

Bởi vì nội tuyến thay thế lệnh gọi hàm bằng một bản sao phần thân của về nguyên tắc, hàm được gọi, không phải bằng một biểu thức tùy ý nào đó việc chuyển đổi không được thay đổi hành vi của chương trình (tất nhiên là mã chặn kiểm tra ngăn xếp cuộc gọi). Điều này khác với các công cụ khác cho phép viết lại tùy ý, chẳng hạn như gofmt -r, rất mạnh nhưng cần được theo dõi chặt chẽ.

Trong nhiều năm nay, các đồng nghiệp Google của chúng tôi trong nhóm hỗ trợ Java, Kotlin và C++ đã và đang sử dụng các công cụ nội tuyến cấp nguồn như thế này. Cho đến nay, những công cụ này đã loại bỏ hàng triệu cuộc gọi đến các thiết bị không được dùng nữa hoạt động trong cơ sở mã của Google. Người dùng chỉ cần thêm các chỉ thị và chờ đợi. Trong đêm, robot lặng lẽ chuẩn bị, kiểm tra và gửi các lô hàng mã thay đổi trên một monorepo gồm hàng tỷ dòng mã. Nếu mọi việc suôn sẻ thì đến sáng mã cũ không còn được sử dụng nữa và có thể được xóa một cách an toàn. Inliner của Go là một công cụ tương đối mới nhưng nó đã được sử dụng chuẩn bị hơn 18.000 danh sách thay đổi cho monorepo của Google.

Ví dụ: sửa lỗi thiết kế API

Với một chút sáng tạo, nhiều kiểu di chuyển khác nhau có thể được thể hiện dưới dạng nội tuyến. Hãy xem xét gói oldmath giả định này:

// Gói oldmath là gói toán cũ tệ. gói oldmath // Sub trả về x - y. func Sub(y, x int) int // Inf trả về giá trị dương vô cực. func Inf() float64 // Phủ định trả về -x. func Neg(x int) int

Nó có một số lỗi thiết kế: hàm Sub khai báo các tham số của nó theo sai thứ tự; hàm Inf ngầm ưu tiên một trong hai giá trị vô hạn; và Âm chức năng dư thừa với Sub. May mắn thay, chúng tôi có gói newmath giúp tránh những lỗi này và chúng tôi muốn người dùng chuyển sang gói này. Bước đầu tiên là triển khai API cũ theo gói mới và loại bỏ các chức năng cũ. Sau đó, chúng tôi thêm các chỉ thị nội tuyến:

// Gói oldmath là gói toán cũ tệ.
gói oldmath
nhập "newmath"
// Sub trả về x - y.
// Không được dùng nữa: thứ tự tham số khó hiểu.
//go:sửa nội tuyến
func Sub(y, x int) int {
    trả về newmath.Sub(x, y)
}
// Inf trả về giá trị dương vô cực.
// Không dùng nữa: có hai giá trị vô hạn; hãy rõ ràng.
//go:sửa nội tuyến
func Inf() float64 {
    trả về newmath.Inf(+1)
}
// Trả về giá trị âm -x.
// Không dùng nữa: chức năng này không cần thiết.
//go:sửa nội tuyến
func Neg(x int) int {
    trả về newmath.Sub(0, x)
}

Bây giờ, khi người dùng oldmath chạy lệnh go fix trên mã của họ, lệnh này sẽ thay thế tất cả các lệnh gọi đến hàm cũ bằng đối tác mới của họ. Nhân tiện, gopls đã đưa inline vào bộ phân tích của nó một thời gian, vì vậy nếu trình soạn thảo của bạn sử dụng gopls, ngay khi bạn thêm lệnh //go:fix inline, bạn sẽ bắt đầu thấy chẩn đoán tại mỗi trang web cuộc gọi, chẳng hạn như "cuộc gọi của oldmath.Sub phải được nội tuyến", cùng với bản sửa lỗi được đề xuất sẽ thực hiện cuộc gọi cụ thể đó.

Ví dụ: mã cũ này:

nhập khẩu "oldmath" var nine = oldmath.Sub(1, 10) // chẩn đoán: "gọi tới oldmath.Sub nên được nội tuyến"

sẽ được chuyển thành:

nhập "newmath"
var nine = newmath.Sub(10, 1)

Lưu ý rằng sau khi sửa lỗi, các đối số của Sub theo thứ tự hợp lý. Đây là sự tiến bộ! Nếu bạn may mắn, nội tuyến sẽ thành công trong việc loại bỏ mọi lệnh gọi đến các hàm trong oldmath, có thể cho phép bạn xóa nó dưới dạng phần phụ thuộc.

Bộ phân tích nội tuyến cũng hoạt động trên các loại và hằng số. Nếu gói oldmath của chúng tôi ban đầu đã khai báo kiểu dữ liệu cho số hữu tỉ và hằng số cho số π, thì chúng tôi có thể sử dụng các khai báo chuyển tiếp sau để di chuyển chúng sang gói newmath trong khi vẫn giữ nguyên hành vi của mã hiện có:

gói oldmath
//go:sửa nội tuyến
gõ Rational = newmath.Rational
//go:sửa nội tuyến
const Pi = newmath.Pi

Mỗi lần nội tuyến máy phân tích gặp một tham chiếu đến oldmath.Rational hoặc oldmath.Pi, nó sẽ cập nhật chúng để tham chiếu đến newmath.

Dưới mui xe nội tuyến

Nhìn thoáng qua, nội tuyến nguồn có vẻ đơn giản: chỉ cần thay thế gọi bằng phần thân của hàm callee, giới thiệu các biến cho tham số hàm và liên kết các đối số cuộc gọi với các biến đó. Nhưng xử lý tất cả các trường hợp phức tạp và góc cạnh một cách chính xác trong khi việc tạo ra những kết quả có thể chấp nhận được lại là một thách thức kỹ thuật không hề nhỏ: phần nội tuyến có khoảng 7.000 dòng logic dày đặc, giống như trình biên dịch. Chúng ta hãy xem xét sáu khía cạnh của vấn đề làm cho nó trở nên phức tạp.

1. Loại bỏ tham số

Một trong những nhiệm vụ quan trọng nhất của nội tuyến là cố gắng thay thế mỗi lần xuất hiện của một tham số trong lệnh gọi bằng đối số tương ứng của nó trong lệnh gọi. Trong trường hợp đơn giản nhất, đối số là một chữ tầm thường chẳng hạn như 0 hoặc "", do đó việc thay thế rất đơn giản và có thể loại bỏ tham số.

//go:sửa nội tuyến
func show(tiền tố, chuỗi mục) {
    fmt.Println(tiền tố, mục)
}
show("", "xin chào")
fmt.Println("", "xin chào")

Đối với những chữ ít tầm thường hơn như 404 hoặc "go.dev" , việc thay thế cũng đơn giản như vậy, miễn là tham số xuất hiện trong callee nhiều nhất một lần. Nhưng nếu nó xuất hiện nhiều lần, sẽ là một phong cách tồi nếu rải các bản sao của các giá trị ma thuật này trong toàn bộ mã vì nó sẽ che khuất mối quan hệ giữa chúng; sau này chỉ thay đổi một trong số chúng có thể tạo ra sự không nhất quán.

Trong những trường hợp như vậy, phần tử nội tuyến phải hoạt động cẩn thận và tạo ra kết quả thận trọng hơn. Bất cứ khi nào một hoặc nhiều tham số không thể được thay thế hoàn toàn vì bất kỳ lý do gì, nội tuyến sẽ chèn một khai báo "ràng buộc tham số" rõ ràng:

//go:sửa nội tuyến
func printPair(trước, x, y, sau chuỗi) {
    fmt.Println(trước, x, sau)
    fmt.Println(trước, y, sau)
}
printPair("[", "một", "hai", "]")
// khai báo "ràng buộc tham số" var trước, sau = "[", "]" fmt.Println(trước, "một", sau) fmt.Println(trước, "hai", sau)

2. Tác dụng phụ

Trong Go, cũng như trong tất cả các ngôn ngữ lập trình mệnh lệnh, việc gọi một hàm có thể có tác dụng phụ là cập nhật các biến, do đó có thể ảnh hưởng đến hoạt động của các hàm khác. Hãy xem xét lệnh gọi add bên dưới:

func add(x, y int) int { return y + x }
z = cộng(f(), g())

Một nội tuyến đơn giản của cuộc gọi sẽ thay thế x bằng f()y bằng g(), với kết quả này:

z = g() + f()

Nhưng kết quả này không chính xác vì việc đánh giá g() hiện diễn ra trước f() ; nếu hai hàm có tác dụng phụ thì lúc này các tác dụng đó sẽ được quan sát theo thứ tự khác và có thể ảnh hưởng đến kết quả của biểu thức. Tất nhiên, việc viết mã dựa vào thứ tự hiệu ứng giữa các đối số lệnh gọi là một hình thức không tốt, nhưng điều đó không có nghĩa là mọi người không làm điều đó và các công cụ của chúng tôi phải làm đúng.

Vì vậy, phần nội tuyến phải cố gắng chứng minh rằng f()g() không có tác dụng phụ với nhau. Khi thành công, nó có thể tiến hành một cách an toàn với kết quả trên. Nếu không, nó phải quay trở lại ràng buộc tham số rõ ràng:

var x = f()
z = g() + x

Khi xem xét các tác dụng phụ, không chỉ các biểu thức đối số mới quan trọng. Điều quan trọng nữa là thứ tự các tham số được đánh giá liên quan đến mã khác trong callee. Hãy xem xét lệnh gọi tới add2:

//go:sửa nội tuyến
func add2(x, y int) int {
    trả về x + other() + y
}
add2(f(), g())

Lần này, các tham số xy được sử dụng theo đúng thứ tự mà chúng được khai báo, do đó, việc thay thế f() + other() + g() sẽ không thay đổi thứ tự tác dụng của f()g()—nhưng nó sẽ thay đổi thứ tự của bất kỳ hiệu ứng nào của other()g(). Hơn nữa, nếu phần thân hàm sử dụng một tham số trong vòng lặp, thì việc thay thế có thể thay đổi số lượng của các hiệu ứng.

Bộ nội tuyến sử dụng phân tích mối nguy mới để lập mô hình thứ tự tác dụng trong từng hàm callee. Tuy nhiên, khả năng xây dựng các bằng chứng an toàn cần thiết của nó khá hạn chế. Ví dụ: nếu các lệnh gọi f()g() là các trình truy cập đơn giản, thì việc gọi chúng theo một trong hai thứ tự là hoàn toàn an toàn. Thật vậy, một trình biên dịch tối ưu hóa có thể sử dụng kiến ​​thức về phần bên trong của fg để sắp xếp lại hai lệnh gọi một cách an toàn. Nhưng không giống như trình biên dịch, tạo ra mã đối tượng phản ánh nguồn tại một thời điểm cụ thể, mục đích của nội tuyến là thực hiện các thay đổi vĩnh viễn đối với nguồn, do đó, nó không thể tận dụng các chi tiết phù du. Để làm một ví dụ điển hình, hãy xem xét hàm start này:

func start() { /* TODO: triển khai */ }

Trình biên dịch tối ưu hóa có thể tự do xóa từng lệnh gọi tới start() vì nó không có tác dụng hôm nay, nhưng nội tuyến thì không, vì nó có thể trở nên quan trọng vào ngày mai.

Tóm lại, nội tuyến có thể tạo ra những kết quả mà—đối với con mắt sáng suốt của người bảo trì dự án—rõ ràng là quá thận trọng. Trong những trường hợp như vậy, mã cố định sẽ được hưởng lợi về mặt phong cách nếu được dọn dẹp thủ công một chút.

3. Biểu thức hằng số “có thể sai”

Bạn có thể tưởng tượng (như tôi đã từng làm) rằng việc thay thế một biến tham số bằng một đối số không đổi cùng loại sẽ luôn an toàn. Đáng ngạc nhiên là điều này hóa ra không phải như vậy, bởi vì một số kiểm tra trước đây được thực hiện trong thời gian chạy giờ lại xảy ra—và không thành công—tại thời điểm biên dịch. Hãy xem xét lệnh gọi hàm index này:

//go:sửa nội tuyến
chỉ số func (chuỗi, i int) byte {
    trả lại s[i]
}
chỉ mục("", 0)

Một nội tuyến đơn giản có thể thay thế s bằng ""i bằng 0, dẫn đến ""[0], nhưng đây thực tế không phải là biểu thức Go hợp pháp vì chỉ mục cụ thể này nằm ngoài giới hạn cho chuỗi cụ thể này. Vì biểu thức ""[0] bao gồm các hằng số nên nó được đánh giá tại thời điểm biên dịch và chương trình chứa nó thậm chí sẽ không được xây dựng. Ngược lại, chương trình ban đầu sẽ chỉ thất bại nếu quá trình thực thi đạt đến lệnh gọi index này, điều này có lẽ không xảy ra trong một chương trình đang hoạt động.

Do đó, bộ nội tuyến phải theo dõi tất cả các biểu thức và toán hạng của chúng có thể trở thành không đổi trong quá trình thay thế tham số, kích hoạt các bước kiểm tra bổ sung trong thời gian biên dịch. Nó xây dựng một hệ thống ràng buộc và cố gắng giải quyết nó. Mỗi ràng buộc không được thỏa mãn sẽ được giải quyết bằng cách thêm một ràng buộc rõ ràng cho các tham số bị ràng buộc.

4. Đổ bóng

Các biểu thức đối số điển hình chứa một hoặc nhiều mã định danh tham chiếu đến các ký hiệu (biến, hàm, v.v.) trong tệp của trình gọi. Nội tuyến phải đảm bảo rằng mỗi tên trong biểu thức đối số sẽ đề cập đến cùng một ký hiệu sau khi thay thế tham số; nói cách khác, không có tên người gọi nào bị ẩn trong callee. Nếu điều này không thành công, nội tuyến phải chèn lại các liên kết tham số, như trong ví dụ này:

//go:sửa nội tuyến
func f(chuỗi val) {
    x := 123
    fmt.Println(val, x)
}
x := "xin chào"
f(x)
x := "xin chào"
{
    // khai báo "ràng buộc tham số" khác
    // để đọc x của người gọi trước khi ẩn nó
    var val chuỗi = x
    x := 123
    fmt.Println(val, x)
}

Ngược lại, inliner cũng phải kiểm tra xem mỗi tên trong nội dung hàm callee có tham chiếu đến cùng một thứ khi nó được ghép vào trang cuộc gọi hay không. Nói cách khác, không có tên nào của người được gọi bị ẩn hoặc thiếu trong người gọi. Đối với những tên bị thiếu, phần nội tuyến có thể cần phải nhập thêm.

5. Biến không được sử dụng

Khi một biểu thức đối số không có tác dụng và tham số tương ứng của nó không bao giờ được sử dụng thì biểu thức đó có thể bị loại bỏ. Tuy nhiên, nếu biểu thức chứa tham chiếu cuối cùng đến một biến cục bộ ở phương thức gọi, điều này có thể gây ra lỗi biên dịch vì biến đó hiện không được sử dụng.

//go:sửa nội tuyến
func f(_ int) { print("xin chào") }
x := 42
f(x)
x := 42 // lỗi: biến không được sử dụng: x
in ("xin chào")

Vì vậy, nội tuyến phải tính đến các tham chiếu đến các biến cục bộ và tránh loại bỏ biến cuối cùng. (Tất nhiên, vẫn có khả năng hai bản sửa lỗi nội tuyến khác nhau đều loại bỏ tham chiếu giây-đến-cuối cùng của một biến, do đó, hai bản sửa lỗi hợp lệ riêng biệt nhưng không cùng nhau; hãy xem phần thảo luận về xung đột ngữ nghĩa trong bài đăng trước. Thật không may, trong trường hợp này, chắc chắn cần phải dọn dẹp thủ công.)

6. Trì hoãn

Trong một số trường hợp, đơn giản là không thể chuyển cuộc gọi sang nội tuyến. Hãy xem xét lệnh gọi hàm sử dụng câu lệnh defer: nếu chúng ta loại bỏ cuộc gọi, hàm trì hoãn sẽ thực thi khi hàm caller trả về thì đã quá muộn. Tất cả những gì chúng ta có thể làm một cách an toàn khi người được gọi sử dụng defer là để đặt phần thân của callee theo nghĩa đen của hàm và gọi nó ngay lập tức. Hàm này có nghĩa đen là func() { … }(), phân định thời gian tồn tại của Câu lệnh defer, như trong ví dụ này:

//go:sửa nội tuyến
hàm callee() {
    trì hoãn f()
    …
}
callee()
func() {
    trì hoãn f()
    …
}()

Nếu bạn gọi nội tuyến trong gopls, bạn sẽ thấy rằng nó thực hiện thay đổi được hiển thị ở trên và giới thiệu nghĩa đen của hàm. Kết quả này có thể phù hợp trong cài đặt tương tác, vì bạn có thể chỉnh sửa mã ngay lập tức (hoặc hoàn tác việc sửa lỗi) theo ý muốn, nhưng điều này hiếm khi được mong muốn trong một công cụ hàng loạt, do đó, do vấn đề chính sách, trình phân tích trong go fix từ chối thực hiện các lệnh gọi "chữ" như vậy.

Trình biên dịch tối ưu hóa cho sự “ngăn nắp”

Bây giờ chúng ta đã thấy nửa tá ví dụ về cách nội tuyến xử lý chính xác các trường hợp phức tạp về ngữ nghĩa. (Xin cảm ơn Rob Findley, Jonathan Amsterdam và Olena Synenka vì những hiểu biết sâu sắc, thảo luận, đánh giá, tính năng và bản sửa lỗi.) Bằng cách đưa tất cả các tính năng thông minh vào nội tuyến, người dùng có thể chỉ cần áp dụng tái cấu trúc “Cuộc gọi nội tuyến” trong IDE của họ hoặc thêm //go:fix inline cho các hàm riêng của chúng và tin tưởng rằng các chuyển đổi mã kết quả có thể được áp dụng chỉ với một lần xem xét ngắn gọn nhất.

Mặc dù chúng ta đã đạt được tiến bộ tốt hướng tới mục tiêu đó nhưng chúng ta vẫn chưa đạt được nó một cách trọn vẹn và có khả năng là chúng ta sẽ không bao giờ đạt được mục tiêu đó. Hãy xem xét một trình biên dịch. Trình biên dịch âm thanh tạo ra đầu ra chính xác cho bất kỳ đầu vào nào và không bao giờ biên dịch sai mã của bạn; đây là kỳ vọng cơ bản mà mọi người dùng nên có đối với trình biên dịch của họ. Trình biên dịch tối ưu hóa tạo ra mã được lựa chọn cẩn thận để đảm bảo tốc độ mà không ảnh hưởng đến độ an toàn. Tương tự, trình nội tuyến hơi giống một trình biên dịch tối ưu hóa có mục tiêu không phải là tốc độ mà là sự gọn gàng: nội tuyến lệnh gọi không bao giờ được thay đổi hành vi của chương trình của bạn và lý tưởng nhất là nó tạo ra mã gọn gàng và ngăn nắp nhất. Thật không may, trình biên dịch tối ưu hóa có thể chứng minh được chưa bao giờ được thực hiện: việc chứng minh rằng hai chương trình khác nhau là tương đương nhau là một vấn đề không thể giải quyết được và sẽ luôn có những cải tiến mà chuyên gia biết là an toàn nhưng trình biên dịch không thể chứng minh. Điều tương tự cũng xảy ra với nội tuyến: sẽ luôn có trường hợp đầu ra của nội tuyến quá cầu kỳ hoặc kém hơn về mặt phong cách so với chuyên gia về con người và sẽ luôn có nhiều "tối ưu hóa gọn gàng" hơn để thêm vào.

Hãy dùng thử!

Chúng tôi hy vọng chuyến tham quan nội tuyến này sẽ giúp bạn hiểu được một số thách thức liên quan cũng như các ưu tiên và định hướng của chúng tôi trong việc cung cấp các công cụ chuyển đổi mã tự phục vụ hợp lý. Vui lòng dùng thử nội tuyến, tương tác trong IDE của bạn hoặc thông qua lệnh //go:fix inline và lệnh go fix, đồng thời chia sẻ với chúng tôi trải nghiệm cũng như bất kỳ ý tưởng nào bạn có để cải tiến thêm hoặc các công cụ mới.

Tác giả: commotionfever

#discussion