Tôi muốn một số lời khuyên về một kỹ thuật tôi va vào. Nó có thể dễ dàng được hiểu bằng cách nhìn vào các đoạn mã, nhưng tôi ghi lại nó một chút trong các đoạn sau đây.lời khuyên về lồng nhau Java cố gắng/cuối cùng mã bánh sandwich
Sử dụng thành ngữ "Mã Sandwich" là phổ biến để đối phó với quản lý tài nguyên. Được sử dụng cho thành ngữ RAII của C++, tôi chuyển sang Java và thấy quản lý tài nguyên ngoại lệ an toàn của mình dẫn đến mã lồng nhau sâu sắc, trong đó tôi có một thời gian thực sự khó khăn trong việc kiểm soát luồng thông thường thông thường.
Rõ ràng (java data access: is this good style of java data access code, or is it too much try finally?, Java io ugly try-finally block và nhiều thứ khác) Tôi không đơn độc.
tôi đã cố gắng giải pháp khác nhau để đối phó với điều này:
duy trì trạng thái chương trình một cách rõ ràng:
resource1aquired
,fileopened
..., và dọn dẹp có điều kiện:if (resource1acquired) resource1.cleanup()
... Nhưng tôi Shun sao chép chương trình nhà nước trong rõ ràng biến - thời gian chạy biết trạng thái và tôi không muốn quan tâm đến nó.quấn mỗi khối lồng nhau trong chức năng - kết quả trong thậm chí khó khăn hơn để theo dòng điều khiển, và làm cho tên hàm thực sự lúng túng:
runResource1Acquired(r1)
,runFileOpened(r1, file)
...
Và cuối cùng tôi đến một thành ngữ cũng (theo lý thuyết) được hỗ trợ bởi một số research paper on code sandwiches:
Thay vì điều này:
// (pseudocode)
try {
connection = DBusConnection.SessionBus(); // may throw, needs cleanup
try {
exported = false;
connection.export("/MyObject", myObject); // may throw, needs cleanup
exported = true;
//... more try{}finally{} nested blocks
} finally {
if(exported) connection.unExport("/MyObject");
}
} finally {
if (connection != null) connection.disconnect();
}
Sử dụng công cụ trợ giúp, bạn có thể đến một cấu trúc tuyến tính hơn, trong đó mã bù trừ nằm ngay bên cạnh người khởi tạo.
class Compensation {
public void compensate(){};
}
compensations = new Stack<Compensation>();
Và mã lồng nhau trở thành tuyến tính:
try {
connection = DBusConnection.SessionBus(); // may throw, needs cleanup
compensations.push(new Compensation(){ public void compensate() {
connection.disconnect();
});
connection.export("/MyObject", myObject); // may throw, needs cleanup
compensations.push(new Compensation(){ public void compensate() {
connection.unExport("/MyObject");
});
// unfolded try{}finally{} code
} finally {
while(!compensations.empty())
compensations.pop().compensate();
}
Tôi rất vui mừng: dù có bao nhiêu con đường đặc biệt, các dòng điều khiển vẫn tuyến tính, và các mã ngẫu nhiên là trực tiếp vào mã nguồn gốc. Trên hết, nó không cần phương thức closeQuietly
bị hạn chế giả tạo, khiến cho nó linh hoạt hơn (tức là không chỉ đối tượng Closeable
, mà còn là Disconnectable
, Rollbackable
và bất kỳ thứ gì khác).
Nhưng ...
Tôi không đề cập đến kỹ thuật này ở nơi khác. Vì vậy, đây là câu hỏi:
Kỹ thuật này có hợp lệ không? Bạn thấy gì trong đó?
Cảm ơn rất nhiều.
Bạn dường như không được xử lý trường hợp một ngoại lệ có thể được ném từ bên trong một phương thức bù(). Điều này sẽ ngăn chặn việc đền bù tiếp theo khi chạy. –
@Kevin: thực sự - quá nhiều thành phần C++ ở đây: destructors không được ném, và tôi dính vào thành ngữ đó ở đây. Nó phụ thuộc vào việc thực thi 'bù đắp' để xử lý các ngoại lệ. – xtofl
@xtofl - điều đó có vẻ hơi mâu thuẫn với bạn bằng cách sử dụng 'try-finally' ở tất cả - bạn chỉ có thể chấp nhận thành ngữ đó với chính mã đó, và không bận tâm đến một khối' cuối cùng'. Bên cạnh đó, nhiều 'Compensators' tự nhiên sẽ ném ngoại lệ (ví dụ:' SQLException' để đóng tài nguyên DB); mỗi lần thực hiện ** có ** để bắt và nuốt tất cả các ngoại lệ riêng lẻ, thay vì khối 'cuối cùng' xử lý nó một cách nhất quán ở một nơi. Nó không phải là tùy chọn cho họ để làm điều này (một ngoại lệ thời gian chạy sẽ vi phạm đặc điểm kỹ thuật của họ, và điều đó có thể xảy ra bất cứ lúc nào) vì vậy bạn chỉ cần được khuyến khích sao chép-dán. –