Liệu nó có ý nghĩa để tạo ra các khối chỉ để giảm phạm vi của một biến?


38

Tôi đang viết một chương trình bằng Java, tại một thời điểm tôi cần tải mật khẩu cho kho khóa của mình. Để giải trí, tôi đã cố gắng giữ mật khẩu của mình trong Java càng ngắn càng tốt bằng cách làm điều này:

//Some code
....

KeyManagerFactory keyManager = KeyManagerFactory.getInstance("SunX509");
Keystore keyStore = KeyStore.getInstance("JKS");

{
    char[] password = getPassword();
    keyStore.load(new FileInputStream(keyStoreLocation), password);
    keyManager.init(keyStore, password);
}

...
//Some more code

Bây giờ, tôi biết trong trường hợp này là hơi ngu ngốc. Có một loạt những thứ khác tôi có thể làm, hầu hết chúng thực sự tốt hơn (tôi hoàn toàn không thể sử dụng một biến).

Tuy nhiên, tôi đã tò mò liệu có trường hợp nào làm việc này không quá ngu ngốc. Điều khác duy nhất tôi có thể nghĩ đến là nếu bạn muốn sử dụng lại các tên biến phổ biến như count, hoặc temp, nhưng các quy ước đặt tên tốt và các phương thức ngắn làm cho nó không hữu ích.

Có trường hợp sử dụng các khối chỉ để giảm phạm vi biến có ý nghĩa?


13
@gnat .... cái gì? Tôi thậm chí không biết làm thế nào để chỉnh sửa câu hỏi của mình để ít giống với mục tiêu lừa bịp đó. Phần nào của điều này khiến bạn tin rằng những câu hỏi này giống nhau?
Lord Farquaad

22
Câu trả lời thông thường cho điều này là khối ẩn danh của bạn phải là một phương thức có tên quan trọng thay vào đó, cung cấp cho bạn tài liệu tự động phạm vi giảm mà bạn muốn. Trực tiếp, tôi không thể nghĩ đến một tình huống mà trích xuất một phương thức không tốt hơn là chèn một khối trần trụi.
Kilian Foth

4
Có một vài câu hỏi tương tự đặt ra cho C ++: Có phải thực tế không tốt khi tạo các khối mã? Mã cấu trúc với dấu ngoặc nhọn . Có thể một trong số đó có câu trả lời cho câu hỏi này, mặc dù C ++ và Java rất khác nhau về mặt này.
amon


4
@gnat Tại sao câu hỏi đó thậm chí còn mở? Đó là một trong những điều rộng nhất tôi từng thấy. Đây có phải là một nỗ lực tại một câu hỏi kinh điển?
jpmc26

Câu trả lời:


34

Đầu tiên, nói chuyện với các cơ chế cơ bản:

Trong phạm vi C ++ == bộ hủy b / c trọn đời được gọi khi thoát khỏi phạm vi. Hơn nữa, một sự khác biệt quan trọng trong C / C ++, chúng ta có thể khai báo các đối tượng cục bộ. Trong thời gian chạy cho C / C ++, trình biên dịch thường sẽ phân bổ khung ngăn xếp cho phương thức lớn nhất có thể, trước đó, thay vì phân bổ nhiều không gian ngăn xếp trên mục nhập cho mỗi phạm vi (khai báo các biến). Vì vậy, trình biên dịch đang thu gọn hoặc làm phẳng các phạm vi.

Trình biên dịch C / C ++ có thể sử dụng lại không gian lưu trữ ngăn xếp cho các địa phương không xung đột trong thời gian sử dụng (thông thường sẽ sử dụng phân tích mã thực tế để xác định điều này thay vì phạm vi, b / c chính xác hơn phạm vi!).

Tôi đề cập đến cú pháp Java của C / C ++, nghĩa là các dấu ngoặc nhọn và phạm vi, ít nhất là một phần có nguồn gốc từ gia đình đó. Và cũng bởi vì C ++ đã đưa ra ý kiến ​​câu hỏi.

Ngược lại, không thể có các đối tượng cục bộ trong Java: tất cả các đối tượng là các đối tượng heap và tất cả các địa phương / định dạng là các biến tham chiếu hoặc các kiểu nguyên thủy (btw, điều này cũng đúng với các thống kê).

Hơn nữa, trong phạm vi Java và thời gian tồn tại không được đánh đồng chính xác: ở trong / ngoài phạm vi chủ yếu là một khái niệm thời gian biên dịch đi đến khả năng truy cập và xung đột tên; không có gì thực sự xảy ra trong Java khi thoát khỏi phạm vi liên quan đến việc dọn sạch các biến. Bộ sưu tập rác của Java xác định (điểm kết thúc của) thời gian tồn tại của các đối tượng.

Ngoài ra cơ chế mã byte của Java (đầu ra của trình biên dịch Java) có xu hướng thúc đẩy các biến người dùng được khai báo trong phạm vi giới hạn lên mức cao nhất của phương thức, bởi vì không có tính năng phạm vi ở cấp độ mã byte (điều này tương tự như xử lý C / C ++ của cây rơm). Tốt nhất là trình biên dịch có thể sử dụng lại các vị trí biến cục bộ, nhưng (không giống như C / C ++) chỉ khi loại của chúng giống nhau.

(Mặc dù, để chắc chắn trình biên dịch JIT cơ bản trong thời gian chạy có thể sử dụng lại cùng một không gian lưu trữ ngăn xếp cho hai địa phương được gõ khác nhau (và có rãnh khác nhau), với phân tích đầy đủ về mã byte.)

Nói về lợi thế của lập trình viên, tôi có xu hướng đồng ý với những người khác rằng phương thức (riêng tư) là một cấu trúc tốt hơn ngay cả khi chỉ được sử dụng một lần.

Tuy nhiên, không có gì thực sự sai khi sử dụng nó để giải mã tên.

Tôi đã làm điều này trong những trường hợp hiếm hoi khi thực hiện các phương pháp riêng lẻ là một gánh nặng. Ví dụ, khi viết một trình thông dịch sử dụng một switchcâu lệnh lớn trong một vòng lặp, tôi có thể (tùy thuộc vào các yếu tố) giới thiệu một khối riêng cho mỗi casetrường hợp để tách riêng các trường hợp riêng biệt với nhau, thay vì đặt mỗi trường hợp thành một phương thức khác nhau (mỗi trường hợp mà chỉ được gọi một lần).

(Lưu ý rằng dưới dạng mã trong các khối, các trường hợp riêng lẻ có quyền truy cập vào các câu lệnh "break;" và "continue;" liên quan đến vòng lặp kèm theo, trong khi các phương thức sẽ yêu cầu trả về booleans và sử dụng các điều kiện của người gọi để có quyền truy cập vào luồng điều khiển này các câu lệnh.)


Câu trả lời rất thú vị (+1)! Tuy nhiên, một bổ sung cho phần C ++. Nếu mật khẩu là một chuỗi trong một khối, đối tượng chuỗi sẽ phân bổ không gian ở đâu đó trên cửa hàng miễn phí cho phần kích thước thay đổi. Khi rời khỏi khối, chuỗi sẽ bị phá hủy, dẫn đến sự sắp xếp của phần biến. Nhưng vì nó không nằm trong stack, không có gì đảm bảo rằng nó sẽ bị ghi đè sớm. Vì vậy, quản lý tốt hơn tính bảo mật bằng cách ghi đè mỗi nhân vật của nó trước khi chuỗi bị phá hủy ;-)
Christophe

1
@ErikEidt Tôi đã cố gắng thu hút sự chú ý đến vấn đề nói về "C / C ++" như thể nó thực sự là một "thứ", nhưng không phải là: "Trong thời gian chạy cho C / C ++, trình biên dịch thường sẽ phân bổ một khung stack đối với phương thức lớn nhất có thể cần thiết " nhưng C hoàn toàn không có phương thức. Nó có chức năng. Đây là khác nhau, đặc biệt là trong C ++.
tchrist

7
" Ngược lại, không thể có các đối tượng cục bộ trong Java: tất cả các đối tượng là các đối tượng heap và tất cả các địa phương / định dạng là các biến tham chiếu hoặc các kiểu nguyên thủy (btw, điều này cũng đúng với các thống kê). " - Lưu ý rằng điều này là đúng không hoàn toàn đúng, đặc biệt là không cho các biến cục bộ phương thức. Phân tích thoát có thể phân bổ các đối tượng để ngăn xếp.
nhện của

1
Tốt nhất các trình biên dịch có thể tái sử dụng khe biến địa phương, nhưng (không giống như C / C ++) chỉ nếu loại của họ là như nhau. Đơn giản là sai. Ở cấp độ mã byte, các biến cục bộ có thể được gán và gán lại với bất cứ điều gì bạn muốn, hạn chế duy nhất là việc sử dụng tiếp theo tương thích với loại mục được gán mới nhất. Sử dụng lại các vị trí biến cục bộ là định mức, ví dụ với { int x = 42; String s="blah"; } { long y = 0; }, longbiến sẽ sử dụng lại các vị trí của cả hai biến xsvới tất cả các trình biên dịch được sử dụng phổ biến ( javacecj).
Holger

1
@Boris the Spider: đó là một tuyên bố thông tục thường lặp đi lặp lại không thực sự phù hợp với những gì đang diễn ra. Phân tích thoát có thể cho phép mở rộng quy mô, đó là sự phân tách các trường của đối tượng thành các biến, sau đó sẽ được tối ưu hóa hơn nữa. Một số trong số chúng có thể được loại bỏ là không sử dụng, một số khác có thể được gấp lại với các biến nguồn được sử dụng để khởi tạo chúng, một số khác được ánh xạ tới các thanh ghi CPU. Kết quả hiếm khi phù hợp với những gì mọi người có thể tưởng tượng khi nói rằng được phân bổ trên stack stack.
Holger

27

Theo tôi, sẽ rõ ràng hơn khi kéo khối ra thành phương thức riêng của nó. Nếu bạn để khối này vào, tôi hy vọng sẽ thấy một bình luận làm rõ lý do tại sao bạn đặt khối đó ở vị trí đầu tiên. Tại thời điểm đó, nó chỉ cảm thấy sạch hơn khi có phương thức gọi để initializeKeyManager()giữ biến mật khẩu giới hạn trong phạm vi của phương thức và hiển thị ý định của bạn với khối mã.


2
Tôi nghĩ câu trả lời của bạn bằng một usecase chính, đó là một bước trung gian trong quá trình tái cấu trúc. Nó có thể hữu ích để giới hạn phạm vi và biết mã của bạn vẫn hoạt động ngay trước khi trích xuất một phương thức.
RubberDuck

16
@RubberDuck đúng, nhưng trong trường hợp đó, phần còn lại của chúng ta sẽ không bao giờ nhìn thấy nó vì vậy nó không quan trọng những gì chúng ta nghĩ. Những gì một lập trình viên trưởng thành và một trình biên dịch đồng ý nhận được trong sự riêng tư của tủ riêng của họ không phải là mối quan tâm của ai khác.
candied_orange

@candied_orange Tôi sợ tôi không hiểu :( Bạn có muốn giải thích không?
doubleOrt

@doubleOrt Ý tôi là các bước tái cấu trúc trung gian là có thật và cần thiết nhưng chắc chắn không cần phải được lưu giữ trong kiểm soát nguồn nơi mọi người có thể nhìn vào nó. Chỉ cần hoàn thành tái cấu trúc trước khi bạn đăng ký.
candied_orange

@candied_orange Ồ, nghĩ rằng bạn đang tranh luận và không mở rộng đối số của RubberDuck.
doubleOrt

17

Chúng có thể hữu ích trong Rust, với các quy tắc vay nghiêm ngặt. Và nói chung, trong các ngôn ngữ chức năng, nơi khối đó trả về một giá trị. Nhưng trong các ngôn ngữ như Java? Mặc dù ý tưởng có vẻ thanh lịch, nhưng trong thực tế, nó hiếm khi được sử dụng vì vậy sự nhầm lẫn khi nhìn thấy nó sẽ làm giảm khả năng rõ ràng của việc giới hạn phạm vi của các biến.

Có một trường hợp mặc dù tôi thấy điều này hữu ích - trong một switchtuyên bố! Đôi khi bạn muốn khai báo một biến trong một case:

switch (op) {
    case ADD:
        int result = a + b;
        System.out.printf("%s + %s = %s\n", a, b, result);
        break;
    case SUB:
        int result = a - b;
        System.out.printf("%s - %s = %s\n", a, b, result);
        break;
}

Điều này, tuy nhiên, sẽ thất bại:

error: variable result is already defined in method main(String[])
            int result = a - b;

switch báo cáo là lạ - chúng là báo cáo kiểm soát, nhưng do sự sai lầm của chúng, chúng không giới thiệu phạm vi.

Vì vậy - bạn chỉ có thể sử dụng các khối để tự tạo phạm vi:

switch (op) {
    case ADD: {
        int result = a + b;
        System.out.printf("%s + %s = %s\n", a, b, result);
    } break;
    case SUB: {
        int result = a - b;
        System.out.printf("%s - %s = %s\n", a, b, result);
    } break;
}

6
  • Trường hợp chung:

Có trường hợp sử dụng các khối chỉ để giảm phạm vi biến có ý nghĩa?

Nó thường được coi là thực hành tốt để giữ cho các phương pháp ngắn. Việc giảm phạm vi của một số biến có thể là một dấu hiệu cho thấy phương thức của bạn khá dài, đủ để bạn nghĩ rằng phạm vi của các biến cần phải được giảm. Trong trường hợp này, có lẽ đáng để tạo một phương thức riêng cho phần mã đó.

Các trường hợp mà tôi sẽ thấy có vấn đề là khi phần mã đó sử dụng nhiều hơn một số đối số. Có những tình huống mà bạn có thể kết thúc bằng các phương thức nhỏ mà mỗi phương thức cần rất nhiều tham số (hoặc cần giải pháp để mô phỏng trả về nhiều giá trị). Giải pháp cho vấn đề cụ thể đó là tạo một lớp khác giữ các biến khác nhau mà bạn sử dụng và thực hiện tất cả các bước bạn có trong các phương thức tương đối ngắn của nó: đây là trường hợp sử dụng điển hình để sử dụng mẫu Builder .

Điều đó nói rằng, tất cả các hướng dẫn mã hóa đôi khi có thể được áp dụng một cách lỏng lẻo. Đôi khi có những trường hợp kỳ lạ, trong đó mã có thể dễ đọc hơn nếu bạn giữ nó trong một phương thức dài hơn một chút, thay vì chia nó thành các phương thức phụ nhỏ (hoặc các mẫu xây dựng). Thông thường, điều này hoạt động cho các vấn đề có kích thước trung bình, khá tuyến tính và việc chia tách quá nhiều thực sự cản trở khả năng đọc (đặc biệt là nếu bạn cần chuyển qua quá nhiều phương thức để hiểu mã làm gì).

Nó có thể có ý nghĩa, nhưng nó rất hiếm.

  • Trường hợp sử dụng của bạn:

Đây là mã của bạn:

KeyManagerFactory keyManager = KeyManagerFactory.getInstance("SunX509");
Keystore keyStore = KeyStore.getInstance("JKS");

{
    char[] password = getPassword();
    keyStore.load(new FileInputStream(keyStoreLocation), password);
    keyManager.init(keyStore, password);
}

Bạn đang tạo một FileInputStream, nhưng bạn không bao giờ đóng nó.

Một cách để giải quyết điều này là sử dụng câu lệnh try-with-resource . Nó phạm vi InputStreamvà bạn cũng có thể sử dụng khối này để giảm phạm vi của các biến khác nếu bạn muốn (trong lý do, vì các dòng này có liên quan):

KeyManagerFactory keyManager = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
Keystore keyStore = KeyStore.getInstance("JKS");

try (InputStream fis = new FileInputStream(keyStoreLocation)) {
    char[] password = getPassword();
    keyStore.load(fis, password);
    keyManager.init(keyStore, password);
}

(Nhân tiện, nó cũng thường tốt hơn để sử dụng getDefaultAlgorithm()thay vì SunX509, nhân tiện.)

Ngoài ra, trong khi lời khuyên để tách các đoạn mã thành các phương thức riêng biệt thường là âm thanh. Nó hiếm khi đáng giá nếu chỉ có 3 dòng (nếu có, đó là trường hợp tạo một phương thức riêng biệt cản trở khả năng đọc).


"Giảm phạm vi của một số biến có thể là dấu hiệu cho thấy phương thức của bạn khá dài" - Nó có thể là một dấu hiệu nhưng cũng có thể và IMHO nên được sử dụng để ghi lại thời gian sử dụng giá trị của biến cục bộ. Trong thực tế, đối với các ngôn ngữ không hỗ trợ phạm vi lồng nhau (ví dụ: Python) tôi sẽ đánh giá cao nếu một nhận xét mã tương ứng nêu "thời gian kết thúc" của một biến. Nếu cùng một biến được sử dụng lại sau này, sẽ dễ dàng tìm ra nếu nó phải (hoặc nên) đã được khởi tạo với một giá trị mới ở đâu đó trước đó (nhưng sau nhận xét "kết thúc cuộc đời").
Jonny Dee

2

Theo ý kiến ​​khiêm tốn của tôi, nói chung, đó là điều cần tránh phần lớn cho mã sản xuất bởi vì bạn thường không muốn làm điều đó ngoại trừ trong các chức năng lẻ tẻ thực hiện các nhiệm vụ khác nhau. Tôi có xu hướng làm điều đó trong một số mã phế liệu được sử dụng để kiểm tra mọi thứ, nhưng không tìm thấy sự cám dỗ nào để làm điều đó trong mã sản xuất, nơi tôi đã suy nghĩ trước về việc mỗi chức năng nên làm gì, từ đó chức năng sẽ tự nhiên có phạm vi hạn chế đối với nhà nước địa phương của nó.

Tôi chưa bao giờ thực sự thấy các ví dụ về các khối ẩn danh được sử dụng như thế này (không liên quan đến các điều kiện, một trykhối cho giao dịch, v.v.) để giảm phạm vi theo cách có ý nghĩa trong một chức năng không đặt ra câu hỏi tại sao nó không thể được chia thành các chức năng đơn giản hơn với phạm vi giảm nếu nó thực sự được hưởng lợi thực tế từ quan điểm SE chính hãng từ các khối ẩn danh. Đó thường là mã chiết trung đang thực hiện một loạt những điều liên quan lỏng lẻo hoặc rất không liên quan, nơi chúng ta mong muốn nhất để đạt được điều này.

Ví dụ, nếu bạn đang cố gắng làm điều này để sử dụng lại một biến có tên count, thì nó gợi ý bạn đang đếm hai thứ khác nhau. Nếu tên biến sẽ ngắn như vậy count, thì nó có ý nghĩa với tôi để buộc nó vào ngữ cảnh của hàm, có khả năng chỉ là đếm một loại điều. Sau đó, bạn có thể ngay lập tức nhìn vào tên và / hoặc tài liệu của hàm, xem countvà biết ngay ý nghĩa của nó trong ngữ cảnh của hàm đang làm mà không cần phân tích tất cả mã. Tôi thường không tìm thấy một đối số tốt cho một hàm để đếm hai thứ khác nhau sử dụng cùng một tên biến theo cách làm cho phạm vi / khối ẩn danh trở nên hấp dẫn so với các lựa chọn thay thế. Điều đó không có nghĩa là tất cả các chức năng chỉ phải tính một điều. TÔI'kỹ thuật có lợi cho một hàm sử dụng lại cùng một tên biến để đếm hai hoặc nhiều thứ và sử dụng các khối ẩn danh để giới hạn phạm vi của từng số riêng lẻ. Nếu hàm này đơn giản và rõ ràng, thì đó không phải là ngày tận thế có hai biến đếm được đặt tên khác nhau với biến đầu tiên có thể có một vài dòng khả năng hiển thị phạm vi hơn mức yêu cầu lý tưởng. Các hàm như vậy thường không phải là nguồn gốc của các lỗi thiếu các khối ẩn danh như vậy để giảm phạm vi tối thiểu của các biến cục bộ của nó hơn nữa.

Không phải là một gợi ý cho các phương pháp không cần thiết

Điều này không có nghĩa là bạn mạnh mẽ tạo ra các phương thức hoặc chỉ để giảm phạm vi. Điều đó được cho là xấu hoặc tệ hơn, và những gì tôi đề nghị không nên kêu gọi cần một phương pháp "người trợ giúp" riêng tư vụng về hơn bất kỳ nhu cầu nào về phạm vi ẩn danh. Đó là suy nghĩ quá nhiều về mã như hiện tại và làm thế nào để giảm phạm vi của các biến hơn là suy nghĩ về cách giải quyết khái niệm vấn đề ở cấp độ giao diện theo cách mang lại khả năng hiển thị ngắn, rõ ràng của các trạng thái chức năng cục bộ mà không cần lồng sâu vào các khối và hơn 6 cấp độ thụt. Tôi đồng ý với Bruno rằng bạn có thể cản trở khả năng đọc mã bằng cách đẩy mạnh 3 dòng mã vào một chức năng, nhưng điều đó bắt đầu với giả định rằng bạn đang phát triển các chức năng bạn tạo dựa trên triển khai hiện có của bạn, thay vì thiết kế các chức năng mà không bị rối trong việc triển khai. Nếu bạn làm theo cách thứ hai, tôi đã tìm thấy rất ít nhu cầu đối với các khối ẩn danh không phục vụ mục đích nào ngoài việc giảm phạm vi biến trong một phương thức nhất định trừ khi bạn cố gắng hết sức để giảm phạm vi của biến chỉ bằng một vài dòng mã vô hại nơi mà sự giới thiệu kỳ lạ của các khối ẩn danh này được cho là đóng góp nhiều chi phí trí tuệ khi chúng loại bỏ.

Cố gắng giảm phạm vi tối thiểu hơn nữa

Nếu việc giảm phạm vi biến cục bộ xuống mức tối thiểu tuyệt đối là đáng giá, thì cần phải có sự chấp nhận rộng rãi mã như thế này:

ImageIO.write(new Robot("borg").createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize())), "png", new File(Db.getUserId(User.handle()).toString()));

... vì điều đó gây ra khả năng hiển thị tối thiểu của trạng thái bằng cách thậm chí không tạo các biến để tham chiếu đến chúng ở vị trí đầu tiên. Tôi không muốn trở thành giáo điều nhưng tôi thực sự nghĩ rằng giải pháp thực dụng là tránh các khối ẩn danh khi có thể giống như để tránh dòng mã khủng khiếp ở trên, và nếu chúng có vẻ rất cần thiết trong bối cảnh sản xuất từ quan điểm về tính đúng đắn và duy trì các bất biến trong một hàm, sau đó tôi chắc chắn nghĩ rằng cách bạn tổ chức mã của mình thành các hàm và thiết kế giao diện của bạn đáng để kiểm tra lại. Đương nhiên, nếu phương thức của bạn dài 400 dòng và phạm vi của biến có thể nhìn thấy hơn 300 dòng mã, đó có thể là một vấn đề kỹ thuật chính hãng, nhưng đó không nhất thiết là vấn đề cần giải quyết với các khối ẩn danh.

Nếu không có gì khác, sử dụng các khối ẩn danh ở khắp mọi nơi là kỳ lạ, không phải là thành ngữ và mã kỳ lạ có nguy cơ bị người khác ghét, nếu không phải là chính bạn, nhiều năm sau đó.

Sự hữu ích thiết thực của việc giảm phạm vi

Tính hữu dụng cuối cùng của việc giảm phạm vi của các biến là cho phép bạn quản lý trạng thái chính xác và giữ đúng và cho phép bạn dễ dàng suy luận về bất kỳ phần nào của một cơ sở mã hóa - để có thể duy trì các bất biến khái niệm. Nếu quản lý trạng thái cục bộ của một hàm phức tạp đến mức bạn phải giảm mạnh phạm vi với một khối ẩn danh trong mã không có nghĩa là hoàn thành và tốt, thì một lần nữa, đó là một dấu hiệu cho tôi rằng chính hàm đó cần phải được kiểm tra lại . Nếu bạn gặp khó khăn trong việc suy luận về việc quản lý trạng thái của các biến trong phạm vi hàm cục bộ, hãy tưởng tượng khó khăn về lý luận về các biến riêng có thể truy cập được đối với mọi phương thức của toàn bộ một lớp. Chúng tôi không thể sử dụng các khối ẩn danh để giảm khả năng hiển thị của chúng. Đối với tôi, điều đó giúp bắt đầu với việc chấp nhận rằng các biến sẽ có xu hướng có phạm vi rộng hơn một chút so với lý tưởng mà chúng cần có trong nhiều ngôn ngữ, miễn là nó không vượt quá tầm với khi bạn gặp khó khăn trong việc duy trì bất biến. Đó không phải là một cái gì đó để giải quyết rất nhiều với các khối ẩn danh như tôi thấy nó là chấp nhận từ quan điểm thực dụng.


Như một cảnh báo cuối cùng, như tôi đã đề cập, thỉnh thoảng tôi vẫn sử dụng các khối ẩn danh, thường là trong các phương thức nhập điểm chính cho mã phế liệu. Một số người khác đã đề cập đến nó như là một công cụ tái cấu trúc và tôi nghĩ điều đó tốt, vì kết quả là trung gian và dự định sẽ được tái cấu trúc thêm, không phải là mã hoàn chỉnh. Tôi không hoàn toàn giảm giá tính hữu dụng của chúng, nhưng nếu bạn đang cố gắng viết mã được chấp nhận rộng rãi, được kiểm tra tốt và chính xác, tôi hầu như không thấy sự cần thiết của các khối ẩn danh. Bị ám ảnh về việc sử dụng chúng có thể chuyển sự tập trung khỏi việc thiết kế các chức năng rõ ràng hơn với các khối nhỏ tự nhiên.

2

Có trường hợp sử dụng các khối chỉ để giảm phạm vi biến có ý nghĩa?

Tôi nghĩ rằng động lực đó là đủ lý do để giới thiệu một khối, vâng.

Như Casey đã lưu ý, khối này là một "mùi mã" cho thấy bạn có thể tốt hơn khi trích xuất khối vào một hàm. Nhưng bạn có thể không.

John Carmark đã viết một bản ghi nhớ về mã nội tuyến vào năm 2007. Tóm tắt kết luận của ông

  • Nếu một chức năng chỉ được gọi từ một nơi duy nhất, hãy xem xét nội tuyến nó.
  • Nếu một chức năng được gọi từ nhiều nơi, hãy xem liệu có thể sắp xếp công việc được thực hiện ở một nơi duy nhất không, có thể có cờ và nội tuyến.
  • Nếu có nhiều phiên bản của một chức năng, hãy xem xét thực hiện một chức năng với nhiều tham số hơn, có thể được mặc định.
  • Nếu công việc gần với chức năng hoàn toàn, với một vài tài liệu tham khảo về trạng thái toàn cầu, hãy cố gắng làm cho nó hoàn toàn hoạt động.

Vì vậy, hãy suy nghĩ , đưa ra lựa chọn có mục đích và (tùy chọn) để lại bằng chứng mô tả động lực của bạn cho lựa chọn bạn đã thực hiện.


1

Ý kiến ​​của tôi có thể gây tranh cãi, nhưng vâng, tôi nghĩ rằng chắc chắn có những tình huống tôi sẽ sử dụng kiểu này. Tuy nhiên, "chỉ để giảm phạm vi của biến" không phải là một đối số hợp lệ, bởi vì đó là những gì bạn đang làm. Và làm một cái gì đó chỉ để làm nó không phải là một lý do hợp lệ. Cũng lưu ý rằng giải thích này không có ý nghĩa nếu nhóm của bạn đã giải quyết được loại cú pháp này có thông thường hay không.

Tôi chủ yếu là một lập trình viên C #, nhưng tôi đã nghe nói rằng trong Java, việc trả lại mật khẩu char[]là để cho phép bạn giảm thời gian mật khẩu sẽ ở lại trong bộ nhớ. Trong ví dụ này, nó sẽ không giúp ích gì, bởi vì trình thu gom rác được phép thu thập mảng ngay từ khi nó không được sử dụng, do đó, vấn đề không phải là nếu mật khẩu rời khỏi phạm vi. Tôi không tranh luận liệu có khả thi để xóa mảng sau khi bạn hoàn thành nó hay không, nhưng trong trường hợp này, nếu bạn muốn làm điều đó, hãy xác định biến có ý nghĩa:

{
    char[] password = getPassword();
    try{
        keyStore.load(new FileInputStream(keyStoreLocation), password);
        keyManager.init(keyStore, password);
    }finally{
        Arrays.fill(password, '\0');
    }
}

Điều này thực sự giống với câu lệnh try-with-resource , bởi vì nó quét phạm vi tài nguyên và thực hiện hoàn thiện nó sau khi bạn hoàn thành. Một lần nữa xin lưu ý rằng tôi không tranh luận về việc xử lý mật khẩu theo cách này, chỉ là nếu bạn quyết định làm như vậy, phong cách này là hợp lý.

Lý do cho điều này là biến không còn hiệu lực. Bạn đã tạo ra nó, sử dụng nó và vô hiệu hóa trạng thái của nó để không chứa thông tin có ý nghĩa. Không có ý nghĩa gì khi sử dụng biến sau khối này, vì vậy rất hợp lý để phạm vi nó.

Một ví dụ khác tôi có thể nghĩ đến là khi bạn có hai biến có cùng tên và nghĩa, nhưng bạn làm việc với một biến và sau đó với một biến khác và bạn muốn tách chúng ra. Tôi đã viết mã này bằng C #:

{
    MethodBuilder m_ToString = tb.DefineMethod("ToString", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final, typeofString, Type.EmptyTypes);
    var il = m_ToString.GetILGenerator();
    il.Emit(OpCodes.Ldstr, templateType.ToString()+":"+staticType.ToString());
    il.Emit(OpCodes.Ret);
    tb.DefineMethodOverride(m_ToString, m_Object_ToString);
}
{
    PropertyBuilder p_Class = tb.DefineProperty("Class", PropertyAttributes.None, typeofType, Type.EmptyTypes);
    MethodBuilder m_get_Class = tb.DefineMethod("get_Class", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final, typeofType, Type.EmptyTypes);
    var il = m_get_Class.GetILGenerator();
    il.Emit(OpCodes.Ldtoken, staticType);
    il.Emit(OpCodes.Call, m_Type_GetTypeFromHandle);
    il.Emit(OpCodes.Ret);
    p_Class.SetGetMethod(m_get_Class);
    tb.DefineMethodOverride(m_get_Class, m_IPattern_get_Class);
}

Bạn có thể lập luận rằng tôi có thể chỉ cần khai báo ILGenerator il;ở đầu phương thức, nhưng tôi cũng không muốn sử dụng lại biến cho các đối tượng khác nhau (cách tiếp cận chức năng). Trong trường hợp này, các khối giúp phân tách các công việc được thực hiện dễ dàng hơn, cả về mặt cú pháp và trực quan. Nó cũng nói rằng sau khi chặn, tôi đã hoàn thành ilvà không có gì nên truy cập nó.

Một đối số chống lại ví dụ này là sử dụng các phương thức. Có thể có, nhưng trong trường hợp này, mã không dài và việc tách nó thành các phương thức khác nhau cũng sẽ cần phải vượt qua tất cả các biến được sử dụng trong mã.

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.