2010-01-29 48 views
28

Một cuộc thảo luận ở số another question khiến tôi băn khoăn: hệ thống ngoại lệ của các ngôn ngữ lập trình khác có những thiếu sót của Perl?Điều gì đã xảy ra với ngoại lệ trong Perl?

trường hợp ngoại lệ được xây dựng trong Perl là một chút ad-hoc ở chỗ họ, giống như hệ thống đối tượng Perl 5, sắp xếp của bolted trên là một suy nghĩ, và họ quá tải các từ khóa khác (evaldie) là không dành riêng cho trường hợp ngoại lệ.

Cú pháp có thể hơi xấu xí, so với ngôn ngữ có cú pháp kiểu try/throw/catch được tạo sẵn. Tôi thường làm như sau:

eval { 
    do_something_that_might_barf(); 
}; 

if (my $err = [email protected]) { 
    # handle $err here 
} 

Có một số mô-đun CPAN cung cấp cú pháp đường để thêm từ khóa cố gắng/nắm bắt và cho phép dễ dàng khai báo phân cấp lớp ngoại lệ và điều gì.

Vấn đề chính mà tôi thấy với hệ thống ngoại lệ của Perl là sử dụng đặc biệt [email protected] toàn cầu để giữ lỗi hiện tại, chứ không phải là một cơ chế kiểu catch chuyên dụng có thể an toàn hơn. cá nhân gặp phải bất kỳ vấn đề nào với việc [email protected] bị hất.

+14

Có lẽ những người như ngôn ngữ lập trình khác thực hiện nhiều sai lầm. – mob

+6

Ôi trời, tôi vừa nói to như thế? Chỉ đùa thôi! – mob

+4

Vâng, các ngôn ngữ khác có ngoại lệ và Perl thì không. Đó là sự khác biệt. Rằng chúng tôi giả mạo họ không làm cho Perl thực sự có ngoại lệ. –

Trả lời

13

Một số lớp ngoại lệ, ví dụ: Error, không thể xử lý điều khiển luồng từ trong các khối try/catch. Điều này dẫn đến sai sót tinh tế:

use strict; use warnings; 
use Error qw(:try); 

foreach my $blah (@somelist) 
{ 
    try 
    { 
     somemethod($blah); 
    } 
    catch Error with 
    { 
     my $exception = shift; 
     warn "error while processing $blah: " . $exception->stacktrace(); 
     next; # bzzt, this will not do what you want it to!!! 
    }; 

    # do more stuff... 
} 

Cách giải quyết là sử dụng một biến trạng thái và kiểm tra bên ngoài các khối try/catch, mà với tôi trông khủng khiếp như mã n00b hôi thối.

Hai khác "gotchas" trong Lỗi (cả hai đều đã gây cho tôi đau buồn vì họ là khủng khiếp để gỡ lỗi nếu bạn chưa chạy vào trong này trước đó):

use strict; use warnings; 
try 
{ 
    # do something 
} 
catch Error with 
{ 
    # handle the exception 
} 

Trông hợp lý, phải không? Mã này biên dịch, nhưng dẫn đến các lỗi kỳ lạ và không thể đoán trước. Những vấn đề là:

  1. use Error qw(:try) được bỏ qua, vì vậy khối try {}... sẽ được misparsed (bạn có thể hoặc có thể không thấy một lời cảnh báo, tùy thuộc vào phần còn lại của mã của bạn)
  2. thiếu dấu chấm phẩy sau khối catch!Không trực quan dưới dạng khối điều khiển không sử dụng dấu chấm phẩy, nhưng thực tế, try là một phương thức gọi là kiểu mẫu.

Ồ phải, điều đó cũng nhắc tôi rằng vì try, catch vv là các cuộc gọi phương thức, điều đó có nghĩa là ngăn xếp cuộc gọi trong các khối đó sẽ không phải là những gì bạn mong đợi. (Có thực sự hai cấp độ ngăn xếp thêm vì một cuộc gọi nội bộ bên trong Error.pm.) Do đó, tôi có một vài mô-đun đầy đủ các mã boilerplate như thế này, mà chỉ cho biết thêm lộn xộn:

my $errorString; 
try 
{ 
    $x->do_something(); 
    if ($x->failure()) 
    { 
     $errorString = 'some diagnostic string'; 
     return;  # break out of try block 
    } 

    do_more_stuff(); 
} 
catch Error with 
{ 
    my $exception = shift; 
    $errorString = $exception->text(); 
} 
finally 
{ 
    local $Carp::CarpLevel += 2; 
    croak "Could not perform action blah on " . $x->name() . ": " . $errorString if $errorString; 
}; 
+0

Tất cả các điểm xuất sắc. Đặc biệt là mục kiểm soát lưu lượng. Tôi đã không xem xét điều đó trước đây. – friedo

+3

Để người downvoter: Tôi sẽ hoan nghênh các đề xuất để cải thiện mã xấu xí này, nó cũng xúc phạm đến tôi. Tôi đã chuyển sang TryCatch trong các dự án tiếp theo, nhưng bài đăng này được dự định để chứng minh những cạm bẫy với một lớp ngoại lệ cụ thể (và nó có thể mở rộng cho những người khác; Tôi chưa thử tất cả). – Ether

1

Trong C++ và C#, bạn có thể xác định các loại có thể được ném, với các khối catch riêng biệt quản lý từng loại. Hệ thống kiểu Perl có một số vấn đề niggling liên quan đến RTTI và thừa kế, theo những gì tôi đọc trên blog của chomatic.

Tôi không chắc chắn các ngôn ngữ động khác quản lý ngoại lệ như thế nào; cả C++ và C# là các ngôn ngữ tĩnh và nó mang theo nó một sức mạnh nhất định trong hệ thống kiểu.

triết lý vấn đề là ngoại lệ Perl 5 được bắt vít; chúng không được xây dựng từ khi bắt đầu thiết kế ngôn ngữ như một cái gì đó không thể thiếu trong cách Perl được viết.

+0

Ràng buộc loại không phải là loại. Perl không có bất kỳ vấn đề với các loại, bởi vì nó không có bất kỳ loại nào. – jrockway

1

Đã lâu rồi tôi sử dụng Perl, vì vậy bộ nhớ của tôi có thể bị mờ và/hoặc Perl có thể cải thiện, nhưng từ những gì tôi nhớ lại (so với Python, tôi sử dụng hàng ngày):

  1. kể từ trường hợp ngoại lệ là một bổ sung muộn, họ không liên tục được hỗ trợ trong các thư viện lõi

    (không đúng, họ không liên tục được hỗ trợ trong các thư viện lõi bởi vì các lập trình viên mà viết những thư viện không thích ngoại lệ.)

  2. không có hệ thống phân cấp ngoại lệ được xác định trước - bạn không thể bắt một nhóm ngoại lệ có liên quan bằng cách bắt lớp cơ sở

  3. không tương đương với thử: ... cuối cùng: ... để xác định mã sẽ được gọi bất kể một ngoại lệ có được nâng lên hay không, ví dụ để giải phóng tài nguyên.

    (finally trong Perl phần lớn là không cần thiết - các trình phá hủy của đối tượng chạy ngay sau khi thoát khỏi phạm vi, không phải bất cứ khi nào xảy ra áp suất bộ nhớ. một cách an toàn.)

  4. (như xa như tôi có thể nói), bạn chỉ có thể ném dây -.. Bạn không thể ném các đối tượng có thêm thông tin

    (Hoàn toàn sai die $object hoạt động giống cũng như die $string)

  5. bạn không thể có được một stack trace hiển thị cho bạn nơi ngoại lệ được ném - trong python bạn nhận được thông tin chi tiết bao gồm cả mã nguồn cho mỗi dòng trong các cuộc gọi stack

    (Sai. perl -MCarp::Always và tận hưởng.)

  6. nó là một kludge mông xấu xí.

    (. Chủ quan Đó là thực hiện theo cùng một cách trong Perl vì nó là ở khắp mọi nơi khác Nó chỉ sử dụng các từ khóa khác nhau được đặt tên..)

+3

# 5 là không đúng - xem 'Carp :: cluck()' và 'Carp :: confess()', và nó không quá nhiều rắc rối để đặt '$ SIG {__ DIE __}' và '$ SIG {__ WARN __}' xử lý với cá chép để có được dấu vết ngăn xếp theo mặc định. Nhưng # 7 là rất đúng, vì vậy nó phát triển. – mob

+2

Re: # 4, bạn có thể ném các đối tượng kể từ Perl 5.6 ít nhất (hầu hết các hệ thống ngoại lệ trên CPAN đều dựa trên điều này). Tôi đồng ý rằng 'cuối cùng' ngữ nghĩa có thể nhận được lông – friedo

+0

# 3 - chỉ cần đặt mã này sau khi eval, hoặc sử dụng một cái gì đó như Error.pm hoặc Try :: Tiny. –

2

Với Perl, ngôn ngữ và ngoại lệ người dùng viết được kết hợp: cả hai thiết lập [email protected]. Trong các ngôn ngữ khác, ngoại lệ ngôn ngữ riêng biệt với ngoại lệ do người dùng viết và tạo ra một luồng hoàn toàn riêng biệt.

Bạn có thể nắm bắt cơ sở của ngoại lệ người dùng viết.

Nếu có My::Exception::oneMy::Exception::two

if ([email protected] and [email protected]>isa('My::Exception')) 

sẽ bắt cả hai.

Hãy nhớ bắt mọi ngoại lệ không phải của người dùng với else.

elsif ([email protected]) 
    { 
    print "Other Error [email protected]\n"; 
    exit; 
    } 

Bạn cũng nên bao gồm ngoại lệ trong cuộc gọi phụ phụ để ném nó.

+1

Xin chào, bạn có vẻ mới ở đây. Câu trả lời của bạn dường như không phải là câu trả lời cho OP (những gì bị hỏng về Perl ngoại lệ), nhưng một bình luận về câu trả lời của người khác (có thể là của Dave Kirby)? Như vậy, mặc dù thông tin bạn trình bày có ích nói chung, vì nó không phải là câu trả lời cho OP, do đó, nó cần phải được đăng dưới dạng bình luận hoặc ở nơi khác. –

+2

@Adam Bellaire: OK. Tôi đã chỉnh sửa câu trả lời của mình. – bitbucket

23

Phương pháp điển hình hầu hết mọi người đã học được cách xử lý trường hợp ngoại lệ là dễ bị thiếu ngoại lệ bị mắc kẹt:

eval { some code here }; 
if([email protected]) { handle exception here }; 

Bạn có thể làm:

eval { some code here; 1 } or do { handle exception here }; 

này bảo vệ từ thiếu ngoại lệ do [email protected] được clobbered , nhưng vẫn dễ bị mất giá trị [email protected].

Để chắc chắn bạn không che giấu một ngoại lệ, khi bạn thực hiện eval của mình, bạn phải bản địa hóa [email protected];

eval { local [email protected]; some code here; 1 } or do { handle exception here }; 

Đây là tất cả vỡ tinh tế và phòng ngừa đòi hỏi nhiều bản mẫu bí truyền.

Trong hầu hết các trường hợp, đây không phải là vấn đề. Nhưng tôi đã bị đốt cháy bởi những kẻ phá hủy đối tượng ăn uống ngoại lệ trong mã thực. Gỡ lỗi vấn đề là khủng khiếp.

Tình huống rõ ràng là xấu. Nhìn vào tất cả các mô-đun trên CPAN được xây dựng cung cấp xử lý ngoại lệ khá.

Phản hồi áp đảo ủng hộ Try::Tiny kết hợp với thực tế là Try :: Tiny không quá "thông minh một nửa", đã thuyết phục tôi dùng thử. Những thứ như TryCatchException::Class::TryCatch, Error, và trên và quá phức tạp để tôi tin tưởng. Hãy thử :: Tiny là một bước đi đúng hướng, nhưng tôi vẫn không có một lớp ngoại lệ nhẹ để sử dụng.

24

Try::Tiny (hoặc mô-đun được xây dựng trên đầu trang) là cách duy nhất để xử lý ngoại lệ trong Perl 5. Các vấn đề liên quan là tinh tế, nhưng bài viết được liên kết giải thích chi tiết.

Dưới đây là làm thế nào để sử dụng nó:

use Try::Tiny; 

try { 
    my $code = 'goes here'; 
    succeed() or die 'with an error'; 
} 
catch { 
    say "OH NOES, YOUR PROGRAM HAZ ERROR: $_"; 
}; 

eval[email protected] là bộ phận chuyển động, bạn không cần phải quan tâm mình với.

Một số người nghĩ rằng đây là một kludge, nhưng có đọc các triển khai của các ngôn ngữ khác (cũng như Perl 5), nó không khác với bất kỳ khác. Chỉ có phần chuyển động [email protected] mà bạn có thể bắt tay vào ... nhưng cũng như với các phần máy móc khác có bộ phận chuyển động tiếp xúc ... nếu bạn không chạm vào nó, nó sẽ không xé ngón tay bạn. Vì vậy, sử dụng Try :: Tiny và giữ tốc độ đánh máy của bạn lên;)

+4

Lưu ý rằng các phiên bản của Perl> = 5.14 bây giờ cố gắng giữ cho trường hợp ngoại lệ lành mạnh, do đó, điều này không còn là "chỉ đúng cách". Nhưng nó vẫn khá tốt. – jrockway

8

Một vấn đề tôi gặp phải với cơ chế ngoại lệ eval phải làm với trình xử lý $SIG{__DIE__}. Tôi đã - sai - giả định rằng trình xử lý này chỉ được gọi khi trình thông dịch Perl được thoát ra thông qua die() và muốn sử dụng trình xử lý này để ghi lại các sự kiện gây tử vong. Sau đó hóa ra tôi đã đăng nhập ngoại lệ trong mã thư viện là lỗi nghiêm trọng mà rõ ràng là sai.

Giải pháp là để kiểm tra trạng thái của biến $^S hoặc $EXCEPTIONS_BEING_CAUGHT:

use English; 
$SIG{__DIE__} = sub { 
    if (!$EXCEPTION_BEING_CAUGHT) { 
     # fatal logging code here 
    } 
}; 

Vấn đề tôi thấy ở đây là xử lý __DIE__ được sử dụng trong hai tình huống tương tự nhưng khác nhau. Biến số $^S này trông rất giống một tiện ích bổ sung trễ cho tôi. Tôi không biết nếu điều này thực sự là trường hợp, mặc dù.

0

Không sử dụng ngoại lệ cho lỗi thường xuyên. Chỉ những vấn đề nghiêm trọng mà sẽ dừng việc thực hiện hiện tại sẽ chết. Tất cả các mục khác phải được xử lý mà không cần die.

Ví dụ: Xác thực tham số của phụ được gọi là: Không chết ở vấn đề đầu tiên. Kiểm tra tất cả các thông số khác và sau đó quyết định dừng lại bằng cách trả về một cái gì đó hoặc cảnh báo và sửa các tham số bị lỗi và tiến hành. Điều đó làm trong chế độ thử nghiệm hoặc phát triển. Nhưng có thể là die ở chế độ sản xuất. Hãy để ứng dụng quyết định điều này.

JPR (đăng nhập CPAN của tôi)

Chúc mừng từ SOGEL, Đức

Các vấn đề liên quan