Tại sao Java có lỗi trình biên dịch "câu lệnh không thể truy cập"?


83

Tôi thường thấy khi gỡ lỗi một chương trình, việc chèn một câu lệnh trả về bên trong một khối mã sẽ rất tiện lợi (mặc dù được cho là một phương pháp không tốt). Tôi có thể thử một cái gì đó như thế này trong Java ....

class Test {
        public static void main(String args[]) {
                System.out.println("hello world");
                return;
                System.out.println("i think this line might cause a problem");
        }
}

tất nhiên, điều này sẽ dẫn đến lỗi trình biên dịch.

Test.java:7: câu lệnh không thể truy cập

Tôi có thể hiểu tại sao cảnh báo có thể được biện minh là có mã không được sử dụng là hành vi xấu. Nhưng tôi không hiểu tại sao điều này cần phải tạo ra một lỗi.

Đây chỉ là Java đang cố gắng trở thành một Nanny, hay có lý do chính đáng để khiến đây là lỗi trình biên dịch?


11
Java cũng không hoàn toàn nhất quán về điều này. Đối với một số luồng điều khiển gây ra mã chết, nhưng Java không phàn nàn. Đối với những người khác thì có. Xác định mã nào đã chết là một vấn đề không thể tính toán được. Tôi không biết tại sao Java quyết định bắt đầu một cái gì đó mà nó không thể kết thúc.
Paul Draper

Câu trả lời:


63

Bởi vì mã không thể truy cập được là vô nghĩa đối với trình biên dịch. Trong khi việc làm cho mã có ý nghĩa đối với mọi người là điều tối quan trọng và khó hơn là làm cho nó có ý nghĩa đối với một trình biên dịch, trình biên dịch là người tiêu dùng thiết yếu của mã. Các nhà thiết kế của Java có quan điểm rằng mã không có ý nghĩa đối với trình biên dịch là một lỗi. Lập trường của họ là nếu bạn có một số mã không thể truy cập được, bạn đã mắc một lỗi cần được sửa.

Có một câu hỏi tương tự ở đây: Mã không thể truy cập: lỗi hay cảnh báo? , trong đó tác giả nói "Cá nhân tôi thực sự cảm thấy nó phải là một lỗi: nếu lập trình viên viết một đoạn mã, nó phải luôn với ý định thực sự chạy nó trong một số tình huống." Rõ ràng là các nhà thiết kế ngôn ngữ của Java đồng ý.

Liệu mã không thể truy cập có ngăn cản quá trình biên dịch hay không là một câu hỏi mà sẽ không bao giờ có sự đồng thuận. Nhưng đây là lý do tại sao các nhà thiết kế Java đã làm điều đó.


Một số người trong các bình luận chỉ ra rằng có nhiều lớp mã không thể truy cập được Java không ngăn cản việc biên dịch. Nếu tôi hiểu đúng hậu quả của Gödel, không trình biên dịch nào có thể bắt được tất cả các lớp mã không thể truy cập được.

Bài kiểm tra đơn vị không thể bắt mọi lỗi đơn lẻ. Chúng tôi không sử dụng điều này như một lý lẽ chống lại giá trị của chúng. Tương tự như vậy, một trình biên dịch không thể bắt tất cả các mã có vấn đề, nhưng nó vẫn có giá trị để ngăn việc biên dịch mã xấu khi có thể.

Các nhà thiết kế ngôn ngữ Java coi mã không thể truy cập được là một lỗi. Vì vậy, ngăn chặn nó biên dịch khi có thể là hợp lý.


(Trước khi bạn phản đối: câu hỏi không phải là liệu Java có nên gặp lỗi trình biên dịch câu lệnh không thể truy cập hay không. Câu hỏi đặt ra là tại sao Java có lỗi trình biên dịch câu lệnh không thể truy cập. Đừng phản đối tôi chỉ vì bạn nghĩ rằng Java đã đưa ra quyết định thiết kế sai).


22
Rõ ràng các nhà thiết kế ngôn ngữ của Java đồng ý rằng "Các nhà thiết kế ngôn ngữ của Java" là con người và cũng dễ mắc sai lầm. Cá nhân tôi cảm thấy rằng phía khác trong cuộc thảo luận mà bạn giới thiệu có lập luận mạnh mẽ hơn nhiều.
Nikita Rybak

6
@Gabe: Bởi vì nó không phải là vô hại - nó gần như chắc chắn là một sai lầm. Hoặc bạn đã đặt mã của mình sai chỗ hoặc hiểu nhầm rằng bạn đã viết câu lệnh của mình theo cách mà một số trong đó không thể liên lạc được. Việc tạo lỗi này sẽ ngăn không cho viết sai mã và nếu bạn muốn một thứ gì đó ở một nơi không thể truy cập được trong mã của mình để các nhà phát triển khác đọc (đối tượng duy nhất cho mã không thể truy cập), hãy sử dụng nhận xét thay thế.
SamStephens

10
SamStephens: Xem như cách các phương thức không được gọi và các biến không sử dụng về cơ bản là tất cả các dạng mã không thể truy cập. Tại sao lại cho phép một số biểu mẫu mà không phải những biểu mẫu khác? Đặc biệt, tại sao lại không cho phép một cơ chế gỡ lỗi hữu ích như vậy?
Gabe

5
Không phải bình luận cũng không thể truy cập được? Theo suy nghĩ của tôi, mã không thể truy cập thực sự là một dạng nhận xét (đây là những gì tôi đã cố gắng làm trước đó, v.v.). Nhưng xem xét ý kiến "thật" không phải là "có thể truy cập" hoặc là ... có lẽ họ cũng nên nâng lỗi này;)
Brady Moritz

10
Vấn đề là họ (các nhà thiết kế ngôn ngữ java) không nhất quán với điều này. Nếu có phân tích luồng thực tế, thật nhỏ khi nhận ra rằng cả hai return; System.out.println();if(true){ return; } System.out.println();đều được đảm bảo có cùng một hành vi, nhưng một là lỗi biên dịch, còn một là không. Vì vậy, khi tôi đang gỡ lỗi và tôi sử dụng phương pháp này để "bình luận" mã tạm thời, đó là cách tôi thực hiện. Vấn đề với việc viết mã bình luận là bạn phải tìm một nơi thích hợp để dừng bình luận của mình, điều này có thể mất nhiều thời gian hơn là return;thực hiện nhanh chóng.
LadyCailin 19/09/12

47

Không có lý do chính xác tại sao không cho phép các câu lệnh không thể truy cập được; các ngôn ngữ khác cho phép chúng mà không gặp vấn đề gì. Đối với nhu cầu cụ thể của bạn, đây là mẹo thông thường:

if (true) return;

Nó trông có vẻ vô lý, bất cứ ai đọc mã sẽ đoán rằng nó phải được thực hiện một cách có chủ ý, không phải là một sai lầm bất cẩn khi để các câu lệnh còn lại không thể truy cập được.

Java có một chút hỗ trợ cho "biên dịch có điều kiện"

http://java.sun.com/docs/books/jls/third_edition/html/statements.html#14.21

if (false) { x=3; }

không dẫn đến lỗi thời gian biên dịch. Một trình biên dịch tối ưu hóa có thể nhận ra rằng câu lệnh x = 3; sẽ không bao giờ được thực thi và có thể chọn bỏ mã cho câu lệnh đó khỏi tệp lớp đã tạo, nhưng câu lệnh x = 3; không được coi là "không thể truy cập" theo nghĩa kỹ thuật được quy định ở đây.

Cơ sở lý luận cho cách xử lý khác biệt này là cho phép các lập trình viên xác định "các biến cờ" như:

static final boolean DEBUG = false;

và sau đó viết mã như:

if (DEBUG) { x=3; }

Ý tưởng là có thể thay đổi giá trị của DEBUG từ false thành true hoặc từ true thành false và sau đó biên dịch mã một cách chính xác mà không có thay đổi nào khác đối với văn bản chương trình.


2
Vẫn không hiểu tại sao bạn lại bận tâm với thủ thuật mà bạn bày ra. Mã không thể truy cập là vô nghĩa đối với trình biên dịch. Vì vậy, khán giả duy nhất của nó là các nhà phát triển. Sử dụng một bình luận. Mặc dù tôi đoán nếu bạn tạm thời thêm lợi nhuận, thì việc sử dụng giải pháp thay thế của bạn có thể nhanh hơn một chút so với việc chỉ nhập một khoản lợi nhuận và nhận xét đoạn mã sau.
SamStephens

8
@SamStephens Nhưng mỗi khi bạn muốn chuyển đổi, bạn sẽ phải nhớ tất cả những nơi bạn phải nhận xét mã và nơi bạn phải làm ngược lại. Bạn sẽ phải đọc toàn bộ tệp, thay đổi mã nguồn, có thể thỉnh thoảng mắc một số lỗi nhỏ thay vì chỉ thay thế "true" bằng "false" tại một nơi. Tôi nghĩ tốt hơn là nên viết mã logic chuyển đổi một lần và không chạm vào nó trừ khi cần thiết.
Robert

"bất cứ ai đọc mã sẽ đoán rằng nó phải được thực hiện một cách có chủ ý". Theo tôi đây chính xác là vấn đề, mọi người không nên đoán mò mà nên hiểu. Ghi nhớ mã là giao tiếp với mọi người, không chỉ là hướng dẫn cho máy tính. Nếu đó là bất cứ điều gì khác hơn là cực kỳ tạm thời, bạn nên sử dụng cấu hình theo ý kiến ​​của tôi. Nhìn vào những gì bạn đang hiển thị ở trên, if (true) return;không chỉ ra TẠI SAO bạn đang bỏ qua logic, ngược lại thể hiện if (BEHAVIOURDISABLED) return;ý định.
SamStephens

5
Thủ thuật này thực sự hữu ích để gỡ lỗi. Tôi thường thêm trả về if (true) để loại bỏ "phần còn lại" của một phương thức đang cố gắng tìm ra nơi nó không thành công. Có nhiều cách khác, nhưng đây là sạch sẽ và nhanh chóng - nhưng nó không phải là một cái gì đó bạn đã bao giờ nên kiểm tra trong.
Bill K

18

Đó là Vú em. Tôi cảm thấy .Net đã làm đúng điều này - nó đưa ra cảnh báo cho mã không thể truy cập, nhưng không phải là lỗi. Thật tốt khi được cảnh báo về điều đó, nhưng tôi không thấy có lý do gì để ngăn chặn quá trình biên dịch (đặc biệt là trong các phiên gỡ lỗi, nơi thật tuyệt khi ném trả về để bỏ qua một số mã).


1
java đã được thiết kế theo cách trước đó, mỗi byte đếm được vào thời điểm đó, đĩa mềm vẫn là công nghệ cao.
chối cãi

1
Đối với việc trả lại sớm để gỡ lỗi, phải mất thêm năm giây nữa để nhận xét các dòng sau khi bạn quay lại sớm. Một cái giá nhỏ để chơi cho các lỗi được ngăn chặn bằng cách làm cho mã không thể truy cập được thành lỗi.
SamStephens

6
trình biên dịch cũng dễ dàng loại trừ mã không thể truy cập. không có lý do gì để buộc nhà phát triển phải làm như vậy.
Brady Moritz

2
@SamStephens không nếu bạn đã có nhận xét ở đó, vì các nhận xét không xếp chồng lên nhau, nên không cần thiết phải giải quyết vấn đề này với nhận xét.
enrey

@SamStephens Mã được nhận xét không cấu trúc lại tốt.
cowlinator

14

Tôi chỉ nhận thấy câu hỏi này và muốn thêm 0,02 đô la của mình vào câu hỏi này.

Trong trường hợp của Java, đây thực sự không phải là một tùy chọn. Lỗi "mã không thể truy cập" không xuất phát từ việc các nhà phát triển JVM nghĩ rằng sẽ bảo vệ các nhà phát triển khỏi bất cứ điều gì hoặc cần hết sức cảnh giác, mà là do các yêu cầu của đặc tả JVM.

Cả trình biên dịch Java và JVM, đều sử dụng cái được gọi là "bản đồ ngăn xếp" - một thông tin xác định về tất cả các mục trên ngăn xếp, như được cấp phát cho phương thức hiện tại. Loại của mỗi và mọi vị trí của ngăn xếp phải được biết, để một lệnh JVM không xử lý nhầm mục của một loại cho một loại khác. Điều này chủ yếu quan trọng để ngăn chặn việc có một giá trị số được sử dụng làm con trỏ. Có thể sử dụng Java assembly để cố gắng đẩy / lưu trữ một số, nhưng sau đó bật / tải một tham chiếu đối tượng. Tuy nhiên, JVM sẽ từ chối mã này trong quá trình xác thực lớp, - đó là khi các bản đồ ngăn xếp đang được tạo và kiểm tra tính nhất quán.

Để xác minh các bản đồ ngăn xếp, máy ảo phải đi qua tất cả các đường dẫn mã tồn tại trong một phương thức và đảm bảo rằng bất kể đường dẫn mã nào sẽ được thực thi, dữ liệu ngăn xếp cho mọi lệnh đồng ý với những gì mà bất kỳ mã nào trước đó đã đẩy / được lưu trữ trong ngăn xếp. Vì vậy, trong trường hợp đơn giản của:

Object a;
if (something) { a = new Object(); } else { a = new String(); }
System.out.println(a);

tại dòng 3, JVM sẽ kiểm tra xem cả hai nhánh của 'if' chỉ được lưu trữ vào một (chỉ là var # 0 cục bộ) thứ gì đó tương thích với Object (vì đó là cách mã từ dòng 3 trở đi sẽ xử lý var # 0 cục bộ ).

Khi trình biên dịch nhận được một mã không thể truy cập, nó không hoàn toàn biết trạng thái của ngăn xếp có thể ở thời điểm đó, vì vậy nó không thể xác minh trạng thái của nó. Nó không thể biên dịch mã nữa vào thời điểm đó, vì nó cũng không thể theo dõi các biến cục bộ, vì vậy thay vì để lại sự mơ hồ này trong tệp lớp, nó sẽ tạo ra một lỗi nghiêm trọng.

Tất nhiên một điều kiện đơn giản như if (1<2)sẽ đánh lừa nó, nhưng nó không thực sự đánh lừa - nó cung cấp cho nó một nhánh tiềm năng có thể dẫn đến mã và ít nhất cả trình biên dịch và máy ảo đều có thể xác định, cách các mục ngăn xếp có thể được sử dụng từ đó trên.

PS Tôi không biết .NET làm gì trong trường hợp này, nhưng tôi tin rằng nó cũng sẽ không biên dịch được. Điều này thường sẽ không phải là vấn đề đối với bất kỳ trình biên dịch mã máy nào (C, C ++, Obj-C, v.v.)


Hệ thống cảnh báo mã chết của Java mạnh đến mức nào? Nó có thể được diễn đạt như một phần của ngữ pháp (ví dụ { [flowingstatement;]* }=> flowingstatement, { [flowingstatement;]* stoppingstatement; }=> stoppingstatement, return=> stoppingstatement, if (condition) stoppingstatement; else stoppingstatement=>, stoppingstatement;v.v.?
supercat

Tôi không giỏi ngữ pháp, nhưng tôi tin là có. Tuy nhiên, "stop" có thể là cục bộ, ví dụ như câu lệnh "break" bên trong một vòng lặp. Ngoài ra, phần cuối của một phương thức là một câu lệnh dừng ngầm đối với mức phương thức, nếu phương thức đó là void. 'ném' cũng sẽ là một tuyên bố dừng rõ ràng.
Pawel Veselov

Tiêu chuẩn Java sẽ nói gì về điều gì đó giống như void blah() { try {return;} catch (RuntimeError ex) { } DoSomethingElse(); } Java sẽ phản đối rằng không có cách nào trykhối thực sự có thể thoát ra thông qua một ngoại lệ, hoặc nó sẽ cho rằng bất kỳ loại ngoại lệ không được kiểm tra nào có thể xảy ra trong bất kỳ trykhối nào?
supercat

Cách tốt nhất để tìm hiểu là thử và biên dịch nó. Nhưng trình biên dịch cho phép các câu lệnh try / catch thậm chí trống. Tuy nhiên, về mặt lý thuyết, bất kỳ lệnh JVM nào cũng có thể ném ra một ngoại lệ, vì vậy khối try có thể thoát như vậy. Nhưng tôi không chắc điều này có liên quan như thế nào đến câu hỏi này.
Pawel Veselov

Về cơ bản, câu hỏi đặt ra là liệu các câu lệnh "không thể truy cập" bị cấm duy nhất có thể được xác định là không thể truy cập được trên cơ sở phân tích cú pháp, mà không cần phải thực sự đánh giá bất kỳ biểu thức nào hay không, hay liệu tiêu chuẩn có thể cấm các câu lệnh không thể truy cập như thế nào if (constantThatEqualsFive < 3) {doSomething;};.
supercat

5

Một trong những mục tiêu của trình biên dịch là loại trừ các lớp lỗi. Một số mã không thể truy cập được một cách tình cờ, thật tuyệt khi javac loại bỏ lớp lỗi đó tại thời điểm biên dịch.

Đối với mỗi quy tắc bắt gặp mã sai, ai đó sẽ muốn trình biên dịch chấp nhận nó vì họ biết họ đang làm gì. Đó là hình phạt của việc kiểm tra trình biên dịch, và cân bằng chính xác là một trong những điểm khó của thiết kế ngôn ngữ. Ngay cả khi kiểm tra nghiêm ngặt nhất vẫn có vô số chương trình có thể được viết, vì vậy mọi thứ không thể tệ như vậy.


5

Mặc dù tôi nghĩ rằng lỗi trình biên dịch này là một điều tốt, nhưng có một cách bạn có thể khắc phục nó. Sử dụng một điều kiện bạn biết sẽ đúng:

public void myMethod(){

    someCodeHere();

    if(1 < 2) return; // compiler isn't smart enough to complain about this

    moreCodeHere();

}

Trình biên dịch không đủ thông minh để phàn nàn về điều đó.


6
Câu hỏi đặt ra ở đây là tại sao? Mã không thể truy cập là vô nghĩa đối với trình biên dịch. Vì vậy, khán giả duy nhất của nó là các nhà phát triển. Sử dụng một bình luận.
SamStephens 25/09/10

3
Vì vậy, tôi nhận được phiếu phản đối vì tôi đồng ý với cách mà những người java đã mô tả biên dịch của họ? Jeez ...
Sean Patrick Floyd

1
Bạn nhận được phiếu phản đối vì không trả lời câu hỏi thực tế. Xem bình luận của SamStephens.
BoltClock

2
javac chắc chắn có thể suy ra điều đó 1<2là đúng và phần còn lại của phương thức không cần phải đưa vào mã byte. các quy tắc của "câu lệnh không thể truy cập" phải rõ ràng, cố định và không phụ thuộc vào sự thông minh của trình biên dịch.
chối cãi

1
@Sam với thái độ đó, bạn sẽ phải từ chối 50% câu trả lời hay nhất của trang web này (và tôi không nói rằng câu trả lời của tôi nằm trong số đó). Một phần quan trọng của việc trả lời các câu hỏi trên SO, cũng như trong bất kỳ tình huống tư vấn CNTT nào, là xác định câu hỏi thực sự (hoặc các câu hỏi phụ có liên quan) từ một câu hỏi nhất định.
Sean Patrick Floyd

0

Chắc chắn là một điều tốt khi phàn nàn rằng trình biên dịch càng nghiêm ngặt càng tốt, chừng nào nó cho phép bạn làm những gì bạn cần. Thông thường, cái giá nhỏ phải trả là nhận xét mã ra, lợi ích là khi bạn biên dịch mã của bạn hoạt động. Một ví dụ chung là Haskell về việc mọi người hét lên cho đến khi họ nhận ra rằng thử nghiệm / gỡ lỗi của họ chỉ là thử nghiệm chính và là thử nghiệm ngắn. Cá nhân tôi trong Java hầu như không gỡ lỗi trong khi (thực tế là có chủ đích) không chú ý.


0

Nếu lý do cho phép if (aBooleanVariable) return; someMoreCode;là cho phép cờ, thì thực tế là if (true) return; someMoreCode;không tạo ra lỗi thời gian biên dịch có vẻ như không nhất quán trong chính sách tạo ngoại lệ CodeNotReachable, vì trình biên dịch 'biết' đó truekhông phải là cờ (không phải biến).

Hai cách khác có thể thú vị, nhưng không áp dụng cho việc tắt một phần mã của phương thức cũng như if (true) return:

Bây giờ, thay vì nói, if (true) return;bạn có thể muốn nói assert falsevà thêm -ea OR -ea package OR -ea classNamevào các đối số jvm. Điểm tốt là điều này cho phép một số chi tiết và yêu cầu thêm một tham số bổ sung vào lệnh gọi jvm, do đó không cần đặt cờ GỬI trong mã, nhưng bằng cách thêm đối số trong thời gian chạy, điều này hữu ích khi mục tiêu không phải là máy của nhà phát triển và việc biên dịch lại và chuyển bytecode mất nhiều thời gian.

Cũng có System.exit(0)cách, nhưng điều này có thể là quá mức cần thiết, nếu bạn đặt nó trong Java trong JSP thì nó sẽ kết thúc máy chủ.

Ngoài việc Java được thiết kế bằng ngôn ngữ 'bảo mẫu', tôi muốn sử dụng một thứ gì đó bản địa như C / C ++ để kiểm soát nhiều hơn.


Thay đổi một cái gì đó từ một static finalbiến được đặt trong khối khởi tạo tĩnh thành một static finalbiến được đặt trong khai báo của nó không phải là một thay đổi vi phạm. Hoàn toàn hợp lý khi một lớp được viết lần đầu tiên, nó có thể đôi khi không thể làm được điều gì đó (và không biết cho đến thời điểm chạy liệu nó có thể làm được hay không), nhưng các phiên bản sau của lớp này có thể thực hiện hành động đó luôn luôn. Thay đổi myClass.canFoothành một hằng số sẽ làm cho if (myClass.canFoo) myClass.Foo(); else doSomethingElse();hiệu quả hơn - không phá vỡ nó.
supercat
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.