Phương pháp trích xuất so với các giả định cơ bản


27

Khi tôi chia các phương thức (hoặc thủ tục hoặc hàm) lớn - câu hỏi này không dành riêng cho OOP, nhưng vì tôi làm việc trong các ngôn ngữ OOP 99% thời gian, đó là thuật ngữ mà tôi thấy thoải mái nhất) thành nhiều thuật ngữ nhỏ , Tôi thường thấy mình không hài lòng với kết quả. Lý do về các phương thức nhỏ này trở nên khó khăn hơn so với khi chúng chỉ là các khối mã trong một phương thức lớn, bởi vì khi tôi trích xuất chúng, tôi mất rất nhiều giả định cơ bản xuất phát từ ngữ cảnh của người gọi.

Sau này, khi tôi xem mã này và thấy các phương thức riêng lẻ, tôi không biết ngay chúng được gọi từ đâu và nghĩ về chúng như các phương thức riêng tư thông thường có thể được gọi từ bất kỳ đâu trong tệp. Ví dụ, hãy tưởng tượng một phương thức khởi tạo (hàm tạo hoặc cách khác) chia thành một loạt các phương thức nhỏ: trong bối cảnh của chính phương thức đó, bạn biết rõ trạng thái của đối tượng vẫn không hợp lệ, nhưng trong một phương thức riêng tư thông thường bạn có thể đi từ giả định đối tượng đó đã được khởi tạo và ở trạng thái hợp lệ.

Giải pháp duy nhất tôi thấy cho điều này là wheremệnh đề trong Haskell, cho phép bạn xác định các hàm nhỏ chỉ được sử dụng trong hàm "cha". Về cơ bản, nó trông như thế này:

len x y = sqrt $ (sq x) + (sq y)
    where sq a = a * a

Nhưng các ngôn ngữ khác tôi sử dụng không có bất cứ thứ gì như thế này - điều gần nhất là định nghĩa lambda trong phạm vi địa phương, điều này có lẽ còn khó hiểu hơn.

Vì vậy, câu hỏi của tôi là - bạn có gặp phải điều này không, và bạn thậm chí có thấy đây là một vấn đề không? Nếu bạn làm như vậy, làm thế nào để bạn thường giải quyết nó, đặc biệt là trong các ngôn ngữ OOP "chính thống", như Java / C # / C ++?

Chỉnh sửa về các bản sao: Như những người khác nhận thấy, đã có những câu hỏi thảo luận về các phương pháp phân tách và các câu hỏi nhỏ là một lớp. Tôi đọc chúng và họ không thảo luận về vấn đề giả định cơ bản có thể xuất phát từ ngữ cảnh của người gọi (ví dụ ở trên, đối tượng được khởi tạo). Đó là điểm của câu hỏi của tôi và đó là lý do tại sao câu hỏi của tôi khác.

Cập nhật: Nếu bạn theo dõi câu hỏi và thảo luận bên dưới, bạn có thể thích bài viết này của John Carmack về vấn đề này , đặc biệt:

Bên cạnh nhận thức về mã thực tế đang được thực thi, các hàm nội tuyến cũng có lợi ích là không thể gọi hàm từ các nơi khác. Nghe có vẻ vô lý, nhưng có một điểm cho nó. Khi một codebase phát triển qua nhiều năm sử dụng, sẽ có rất nhiều cơ hội để thực hiện một phím tắt và chỉ cần gọi một chức năng chỉ thực hiện công việc mà bạn nghĩ cần phải hoàn thành. Có thể có một hàm FullUpdate () gọi PartialUpdateA () và PartialUpdateB (), nhưng trong một số trường hợp cụ thể, bạn có thể nhận ra (hoặc nghĩ) rằng bạn chỉ cần thực hiện PartialUpdateB () và bạn đang làm việc hiệu quả bằng cách tránh công việc. Rất nhiều và rất nhiều lỗi bắt nguồn từ điều này. Hầu hết các lỗi là kết quả của trạng thái thực thi không chính xác như bạn nghĩ.




@gnat câu hỏi bạn liên kết để thảo luận về việc có nên trích xuất các hàm hay không, trong khi tôi không hỏi nó. Thay vào đó, tôi đặt câu hỏi về phương pháp tối ưu nhất để làm điều đó.
Max Yankov

2
@gnat có những câu hỏi liên quan khác được liên kết từ đó, nhưng không có câu hỏi nào trong số này thảo luận về thực tế rằng mã này có thể dựa trên các giả định cụ thể chỉ có giá trị trong ngữ cảnh của người gọi.
Max Yankov

1
@Doval theo kinh nghiệm của tôi, nó thực sự làm. Khi có các phương thức trợ giúp rắc rối treo xung quanh như bạn mô tả, trích xuất một lớp gắn kết mới sẽ giải quyết vấn đề này
gnat

Câu trả lời:


29

Ví dụ, hãy tưởng tượng một phương thức khởi tạo được chia thành một loạt các phương thức nhỏ: trong bối cảnh của chính phương thức đó, bạn biết rõ trạng thái của đối tượng đó vẫn không hợp lệ, nhưng trong một phương thức riêng tư thông thường, bạn có thể đi từ giả định rằng đối tượng đã được khởi tạo và ở trạng thái hợp lệ. Giải pháp duy nhất tôi thấy cho việc này là ...

Mối quan tâm của bạn là có cơ sở. Có một giải pháp khác.

Lùi lại một bước. Những gì về cơ bản là mục đích của một phương pháp? Phương thức chỉ làm một trong hai điều sau:

  • Tạo ra một giá trị
  • Gây hiệu ứng

Hoặc, không may, cả hai. Tôi cố gắng tránh các phương pháp làm cả hai, nhưng nhiều làm. Hãy nói rằng hiệu ứng được tạo ra hoặc giá trị được tạo ra là "kết quả" của phương thức.

Bạn lưu ý rằng các phương thức được gọi trong một "bối cảnh". Bối cảnh đó là gì?

  • Các giá trị của các đối số
  • Trạng thái của chương trình ngoài phương thức

Về cơ bản những gì bạn đang chỉ ra là: tính chính xác của kết quả của phương pháp phụ thuộc vào bối cảnh mà nó được gọi .

Chúng ta gọi các điều kiện cần thiết trước khi phần thân phương thức bắt đầu để phương thức tạo ra kết quả chính xác các điều kiện tiên quyết của nó và chúng ta gọi các điều kiện sẽ được tạo sau khi phần thân phương thức trả về các điều kiện hậu của nó .

Vì vậy, về cơ bản những gì bạn đang chỉ ra là: khi tôi trích xuất một khối mã vào phương thức riêng của nó, tôi đang mất thông tin theo ngữ cảnh về các điều kiện tiên quyết và hậu điều kiện .

Giải pháp cho vấn đề này là làm cho các điều kiện tiên quyết và hậu điều kiện rõ ràng trong chương trình . Ví dụ, trong C #, bạn có thể sử dụng Debug.Asserthoặc Hợp đồng mã để thể hiện các điều kiện tiên quyết và hậu điều kiện.

Ví dụ: Tôi đã từng làm việc trên một trình biên dịch di chuyển qua một số "giai đoạn" biên dịch. Đầu tiên mã sẽ được lexed, sau đó phân tích cú pháp, sau đó các loại sẽ được giải quyết, sau đó hệ thống phân cấp thừa kế sẽ được kiểm tra theo chu kỳ, v.v. Mỗi bit của mã rất nhạy cảm với bối cảnh của nó; chẳng hạn, sẽ là thảm họa nếu hỏi "loại này có chuyển đổi được với loại đó không?" nếu đồ thị của các loại cơ sở chưa được biết là theo chu kỳ! Vì vậy, mỗi bit của mã ghi lại rõ ràng các điều kiện tiên quyết của nó. Chúng tôi sẽ asserttheo phương pháp mà kiểm tra cho loại hình chuyển đổi mà chúng ta đã vượt qua được "loại cơ sở acylic" kiểm tra, và sau đó nó trở nên rõ ràng để người đọc biết phương pháp này có thể được gọi và nơi mà nó có thể không được gọi.

Tất nhiên, có rất nhiều cách để thiết kế phương pháp tốt giảm thiểu vấn đề bạn đã xác định:

  • tạo các phương thức hữu ích cho các hiệu ứng hoặc giá trị của chúng nhưng không phải cả hai
  • làm cho các phương thức "tinh khiết" nhất có thể; một phương thức "thuần túy" tạo ra một giá trị chỉ phụ thuộc vào các đối số của nó và không tạo ra hiệu ứng. Đây là những phương pháp dễ nhất để lý giải vì "bối cảnh" họ cần rất cục bộ.
  • giảm thiểu số lượng đột biến xảy ra trong trạng thái chương trình; đột biến là những điểm mà mã trở nên khó hơn để lý do

+1 vì là câu trả lời giải thích vấn đề theo các điều kiện tiên quyết / hậu điều kiện.
Câu hỏi

5
Tôi sẽ nói thêm rằng thường xuyên có thể (và một ý tưởng hay!) Để ủy quyền kiểm tra các điều kiện trước và sau cho hệ thống loại. Nếu bạn có một chức năng lấy stringvà lưu nó vào cơ sở dữ liệu, bạn có nguy cơ bị tiêm SQL nếu bạn quên làm sạch nó. Mặt khác, nếu chức năng của bạn có một SanitisedStringvà cách duy nhất để có được một SantisiedStringbằng cách gọi Sanitise, thì bạn đã loại trừ các lỗi tiêm SQL bằng cách xây dựng. Tôi ngày càng thấy mình tìm cách để trình biên dịch từ chối mã không chính xác.
Benjamin Hodgson

+1 Một điều quan trọng cần lưu ý là có một chi phí để chia một phương pháp lớn thành các phần nhỏ hơn: nó thường không hữu ích trừ khi các điều kiện tiên quyết và hậu điều kiện thoải mái hơn so với ban đầu và cuối cùng bạn có thể phải trả chi phí bằng cách thực hiện lại các kiểm tra mà bạn có thể đã thực hiện. Đây không phải là một quá trình tái cấu trúc hoàn toàn "miễn phí".
Mehrdad

"Bối cảnh đó là gì?" chỉ để làm rõ, tôi chủ yếu có nghĩa là trạng thái riêng tư của đối tượng mà phương thức này được gọi. Tôi đoán nó được bao gồm trong thể loại thứ hai.
Max Yankov

Đây là một câu trả lời xuất sắc và kích thích tư duy, cảm ơn bạn. (Tất nhiên, không nói rằng các câu trả lời khác là xấu theo cách nào). Tôi sẽ không đánh dấu câu hỏi là đã trả lời, vì tôi thực sự thích cuộc thảo luận ở đây (và nó có xu hướng dừng lại khi câu trả lời được đánh dấu là đã trả lời) và cần thời gian để xử lý và suy nghĩ về nó.
Max Yankov

13

Tôi thường thấy điều này, và đồng ý rằng đó là một vấn đề. Thông thường tôi giải quyết nó bằng cách tạo một đối tượng phương thức : một lớp chuyên biệt mới có các thành viên là các biến cục bộ từ phương thức ban đầu, quá lớn.

Lớp mới có xu hướng có một cái tên như 'Nhà xuất khẩu' hoặc 'Tabulation' và nó được thông qua bất kỳ thông tin nào là cần thiết để thực hiện nhiệm vụ cụ thể đó từ bối cảnh lớn hơn. Sau đó, có thể tự do định nghĩa các đoạn mã trình trợ giúp nhỏ hơn mà không có nguy cơ bị sử dụng cho bất cứ điều gì ngoại trừ lập bảng hoặc xuất.


Tôi thực sự thích ý tưởng này khi tôi nghĩ về nó. Nó có thể là một lớp riêng trong lớp công khai hoặc nội bộ. Bạn không làm lộn xộn không gian tên của mình với các lớp mà bạn chỉ quan tâm rất cục bộ và đó là một cách để đánh dấu rằng đây là "trình trợ giúp xây dựng" hoặc "trình trợ giúp phân tích cú pháp" hoặc bất cứ điều gì.
Mike hỗ trợ Monica

Gần đây tôi chỉ ở trong một tình huống sẽ là lý tưởng cho điều này từ quan điểm kiến ​​trúc. Tôi đã viết một trình kết xuất phần mềm với lớp trình kết xuất và phương thức kết xuất công khai, có rất nhiều ngữ cảnh mà nó sử dụng để gọi các phương thức khác. Tôi đã dự tính tạo ra một lớp RenderContext riêng cho việc này, tuy nhiên, việc phân bổ và phân bổ dự án này mỗi khung hình dường như rất lãng phí. github.com/golergka/tinyrenderer/blob/master/src/renderer.h
Max Yankov

6

Nhiều ngôn ngữ cho phép bạn lồng các hàm như Haskell. Java / C # / C ++ thực sự là những ngoại lệ tương đối trong vấn đề đó. Thật không may, họ đang rất phổ biến mà mọi người đến để suy nghĩ, "Đó phải là một ý tưởng tồi, nếu không ngôn ngữ 'chính' yêu thích của tôi sẽ cho phép điều đó."

Về cơ bản, Java / C # / C ++ nghĩ rằng một lớp nên là nhóm phương thức duy nhất bạn cần. Nếu bạn có rất nhiều phương pháp mà bạn không thể xác định bối cảnh của chúng, có hai cách tiếp cận chung cần thực hiện: sắp xếp chúng theo ngữ cảnh hoặc phân chia chúng theo ngữ cảnh.

Sắp xếp theo ngữ cảnh là một khuyến nghị được thực hiện trong Clean Code , trong đó tác giả mô tả một mẫu "TO đoạn văn". Điều này về cơ bản là đặt các hàm trợ giúp của bạn ngay lập tức sau hàm gọi chúng, vì vậy bạn có thể đọc chúng như các đoạn trong một bài báo, để biết thêm chi tiết khi bạn đọc thêm. Tôi nghĩ trong video của mình, anh ấy thậm chí còn thụt lề chúng.

Cách tiếp cận khác là chia lớp của bạn. Điều này không thể được thực hiện rất xa, vì nhu cầu khó chịu để khởi tạo các đối tượng trước khi bạn có thể gọi bất kỳ phương thức nào trên chúng và các vấn đề cố hữu với việc quyết định một trong số các lớp nhỏ sẽ sở hữu mỗi phần dữ liệu. Tuy nhiên, nếu bạn đã xác định được một số phương thức thực sự chỉ phù hợp trong một bối cảnh, thì có lẽ chúng là một ứng cử viên tốt để xem xét đưa vào lớp học của riêng họ. Ví dụ, khởi tạo phức tạp có thể được thực hiện theo mô hình sáng tạo như trình xây dựng.


Các hàm lồng nhau ... không phải là những gì các hàm lambda đạt được trong C # (và Java 8)?
Arturo Torres Sánchez

Tôi đã suy nghĩ giống như một bao đóng được định nghĩa bằng một cái tên, giống như những ví dụ về con trăn này . Lambdas không phải là cách rõ ràng nhất để làm điều gì đó như thế. Chúng nhiều hơn cho các biểu thức ngắn như một vị từ bộ lọc.
Karl Bielefeldt

Những ví dụ Python đó chắc chắn có thể có trong C #. Ví dụ, giai thừa . Họ có thể dài dòng hơn, nhưng họ có thể 100%.
Arturo Torres Sánchez

2
Không ai nói điều đó là không thể. OP thậm chí đã đề cập đến việc sử dụng lambdas trong câu hỏi của mình. Chỉ là nếu bạn trích xuất một phương thức vì mục đích dễ đọc, sẽ tốt hơn nếu nó dễ đọc hơn.
Karl Bielefeldt

Đoạn đầu tiên của bạn dường như ngụ ý điều đó là không thể, đặc biệt là với câu trích dẫn của bạn: "Đó phải là một ý tưởng tồi, nếu không, ngôn ngữ 'chính thống' yêu thích của tôi sẽ cho phép nó."
Arturo Torres Sánchez

4

Tôi nghĩ rằng câu trả lời trong hầu hết các trường hợp là bối cảnh. Là một nhà phát triển viết mã, bạn nên cho rằng mã của bạn sẽ được thay đổi trong tương lai. Một lớp có thể được tích hợp với một lớp khác, có thể thay thế thuật toán bên trong của nó hoặc có thể được tách ra thành một vài lớp để tạo ra sự trừu tượng hóa. Đó là những điều mà các nhà phát triển mới bắt đầu thường không xem xét, gây ra sự cần thiết cho các cách giải quyết lộn xộn hoặc đại tu hoàn chỉnh sau này.

Phương pháp trích xuất là tốt, nhưng ở một mức độ nào đó. Tôi luôn cố gắng tự hỏi mình những câu hỏi này khi kiểm tra hoặc trước khi viết mã:

  • Có phải mã này chỉ được sử dụng bởi lớp / chức năng này? nó sẽ giữ nguyên trong tương lai?
  • Nếu tôi cần phải thực hiện một số triển khai cụ thể, tôi có thể thực hiện dễ dàng không?
  • Các nhà phát triển khác trong nhóm của tôi có thể hiểu những gì được thực hiện trong chức năng này không?
  • Là cùng một mã được sử dụng ở một nơi khác trong lớp này? bạn nên tránh trùng lặp trong hầu hết các trường hợp.

Trong mọi trường hợp, luôn luôn nghĩ trách nhiệm duy nhất. Một lớp nên có một trách nhiệm, các chức năng của nó sẽ phục vụ một dịch vụ không đổi duy nhất và nếu chúng thực hiện một số hành động, các hành động đó sẽ có chức năng riêng của chúng, vì vậy thật dễ dàng để phân biệt hoặc thay đổi chúng sau này.


1

Lý do về các phương thức nhỏ này trở nên khó khăn hơn so với khi chúng chỉ là các khối mã trong một phương thức lớn, bởi vì khi tôi trích xuất chúng, tôi mất rất nhiều giả định cơ bản xuất phát từ ngữ cảnh của người gọi.

Tôi đã không nhận ra vấn đề này lớn đến mức nào cho đến khi tôi chấp nhận một ECS khuyến khích các chức năng hệ thống bản đồ lớn hơn (với các hệ thống là những người duy nhất có chức năng) và phụ thuộc vào dữ liệu thô , không trừu tượng hóa.

Điều đó, làm tôi ngạc nhiên, mang lại một cơ sở mã hóa dễ dàng hơn rất nhiều để lý giải và duy trì so với các cơ sở mã mà tôi đã làm việc trong quá khứ, trong khi gỡ lỗi, bạn phải theo dõi tất cả các loại chức năng nhỏ, thường thông qua các chức năng trừu tượng thông qua giao diện thuần túy dẫn đến ai biết được nơi nào cho đến khi bạn lần theo dấu vết của nó, chỉ để sinh ra một số sự kiện dẫn đến những nơi bạn không bao giờ nghĩ rằng mã sẽ dẫn đến.

Không giống như John Carmack, vấn đề lớn nhất của tôi với các cơ sở mã hóa đó là hiệu năng vì tôi chưa bao giờ có nhu cầu độ trễ cực kỳ chặt chẽ của các công cụ trò chơi AAA và hầu hết các vấn đề về hiệu suất của chúng tôi liên quan nhiều hơn đến thông lượng. Tất nhiên, bạn cũng có thể bắt đầu làm cho việc tối ưu hóa các điểm nóng ngày càng khó khăn hơn khi bạn làm việc trong phạm vi hẹp hơn và hẹp hơn về các chức năng và lớp học thiếu niên và thiếu niên hơn mà không có cấu trúc đó cản trở (yêu cầu bạn phải hợp nhất tất cả các phần tuổi teen này lại đến một cái gì đó lớn hơn trước khi bạn thậm chí có thể bắt đầu giải quyết nó một cách hiệu quả).

Tuy nhiên, vấn đề lớn nhất đối với tôi là không thể tự tin suy luận về tính đúng đắn của hệ thống mặc dù tất cả các bài kiểm tra đã qua. Có quá nhiều thứ để đưa vào não tôi và hiểu bởi vì loại hệ thống đó không cho phép bạn suy luận về nó mà không tính đến tất cả các chi tiết nhỏ này và sự tương tác vô tận giữa các chức năng và vật thể nhỏ bé đang diễn ra ở mọi nơi. Có quá nhiều "chuyện gì xảy ra?", Quá nhiều điều cần được gọi đúng lúc, quá nhiều câu hỏi về những gì sẽ xảy ra nếu chúng được gọi sai thời điểm (bắt đầu trở nên hoang tưởng khi bạn có một sự kiện kích hoạt một sự kiện khác kích hoạt một sự kiện khác dẫn bạn đến tất cả các địa điểm không thể đoán trước), v.v.

Bây giờ tôi thích các chức năng 80 dòng mông lớn của mình ở đây và ở đó, miễn là chúng vẫn thực hiện một trách nhiệm đơn lẻ và rõ ràng và không có 8 cấp độ của các khối lồng nhau. Chúng dẫn đến một cảm giác rằng có ít thứ trong hệ thống để kiểm tra và hiểu, ngay cả khi các phiên bản nhỏ hơn, được cắt nhỏ hơn của các chức năng lớn hơn này chỉ là chi tiết thực hiện riêng tư không thể được gọi bởi bất kỳ ai khác ... nó có xu hướng cảm thấy như có ít tương tác diễn ra trên toàn hệ thống. Tôi thậm chí thích một số sao chép mã rất khiêm tốn, miễn là nó không phức tạp logic (chỉ là 2-3 dòng mã), nếu nó có nghĩa là ít chức năng hơn. Tôi thích lý do của Carmack ở đó về nội tuyến khiến cho chức năng đó không thể gọi ở nơi khác trong tệp nguồn. Có

Sự đơn giản không phải lúc nào cũng làm giảm độ phức tạp ở mức hình ảnh lớn nếu tùy chọn nằm giữa một chức năng nhuần nhuyễn so với 12 chức năng đơn giản gọi nhau bằng một biểu đồ phụ thuộc phức tạp. Vào cuối ngày, bạn thường phải suy luận về những gì diễn ra ngoài chức năng, phải suy luận về những gì các chức năng này cuối cùng làm, và có thể khó nhìn thấy bức tranh lớn đó nếu bạn phải suy luận từ mảnh ghép nhỏ nhất.

Tất nhiên mã loại thư viện có mục đích rất chung được thử nghiệm tốt có thể được miễn trừ khỏi quy tắc này, vì mã mục đích chung như vậy thường hoạt động và tự đứng vững. Ngoài ra, nó có xu hướng tuổi teen so với mã gần hơn một chút với miền ứng dụng của bạn (hàng ngàn dòng mã chứ không phải hàng triệu) và được áp dụng rộng rãi đến mức nó bắt đầu trở thành một phần của từ vựng hàng ngày. Nhưng với một cái gì đó cụ thể hơn cho ứng dụng của bạn, nơi các bất biến trên toàn hệ thống mà bạn phải duy trì vượt xa một chức năng hoặc một lớp duy nhất, tôi có xu hướng thấy nó giúp có các hàm thịt hơn vì bất kỳ lý do gì. Tôi thấy dễ dàng hơn nhiều khi làm việc với những mảnh ghép lớn hơn trong việc cố gắng tìm hiểu những gì đang diễn ra với bức tranh lớn.


0

Tôi không nghĩ đó là một vấn đề lớn , nhưng tôi đồng ý rằng nó thật rắc rối. Thông thường tôi chỉ cần đặt người trợ giúp ngay sau người thụ hưởng của nó và thêm hậu tố "Người trợ giúp". Điều đó cộng với privatechỉ định truy cập sẽ làm rõ vai trò của nó. Nếu có một số bất biến không giữ được khi người trợ giúp được gọi, tôi sẽ thêm một nhận xét vào người trợ giúp.

Giải pháp này có nhược điểm đáng tiếc là không nắm bắt được phạm vi của chức năng mà nó giúp. Lý tưởng nhất là các chức năng của bạn nhỏ nên hy vọng điều này không dẫn đến quá nhiều tham số. Thông thường, bạn sẽ giải quyết điều này bằng cách xác định các cấu trúc hoặc lớp mới để gói các tham số, nhưng số lượng nồi hơi cần thiết có thể dễ dàng dài hơn chính trình trợ giúp, và sau đó bạn quay lại nơi bạn bắt đầu mà không có cách liên kết rõ ràng nào các cấu trúc với chức năng.

Bạn đã đề cập đến giải pháp khác - xác định người trợ giúp bên trong chức năng chính. Nó có thể là một thành ngữ hơi lạ trong một số ngôn ngữ, nhưng tôi không nghĩ nó sẽ gây nhầm lẫn (trừ khi các đồng nghiệp của bạn bị nhầm lẫn bởi lambdas nói chung). Điều này chỉ hoạt động nếu bạn có thể xác định các chức năng hoặc các đối tượng giống như chức năng một cách dễ dàng. Tôi sẽ không thử điều này trong Java 7, ví dụ, vì một lớp ẩn danh yêu cầu đưa ra 2 mức lồng nhau cho "hàm" nhỏ nhất. Điều này gần với một lethoặc một wheremệnh đề như bạn có thể nhận được; bạn có thể tham khảo các biến cục bộ trước khi định nghĩa và trình trợ giúp không thể được sử dụng ngoài phạm vi đó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.