2010-11-13 14 views
9

Tôi đang làm việc trong C++ môi trường và:
a) Chúng tôi bị cấm sử dụng ngoại lệ
b) Đó là application/dữ liệu mã máy chủ mà đánh giá rất nhiều yêu cầu của các loại khác nhauC++ thuần khiết đang

Tôi có kết quả đóng gói lớp đơn giản của hoạt động của máy chủ cũng được sử dụng nội bộ cho rất nhiều hàm ở đó.

class OpResult 
{ 
    ..... 
    bool succeeded(); 
    bool failed(); .... 
    ... data error/result message ... 
}; 

Như tôi đã cố gắng có tất cả các chức năng nhỏ và đơn giản, rất nhiều khối như thế này được phát sinh:

.... 
OpResult result = some_(mostly check)function(....); 
if (result.failed()) 
    return result; 
... 

Câu hỏi đặt ra là, là nó xấu thực hành để làm vĩ mô tìm kiếm như thế này và sử dụng nó mọi nơi?

#define RETURN_IF_FAILED(call) \ 
    {       \ 
    OpResult result = call; \ 
    if (result.failed())  \ 
     return result;   \ 
    } 

Tôi hiểu rằng ai đó có thể gọi nó là khó chịu, nhưng có cách nào tốt hơn không? Cách nào khác để xử lý kết quả và tránh được nhiều mã bloat bạn sẽ đề xuất?

+0

Điều gì sẽ xảy ra nếu cần dọn dẹp? –

+0

Vâng, câu trả lời được chấp nhận của tôi bị một người kiểm duyệt ngẫu nhiên xóa một cách đơn phương, người không thích nó. –

Trả lời

9

Đó là giao dịch. Bạn đang giao dịch mã kích thước cho obfuscation của logic. Tôi thích bảo toàn logic như hiển thị.

Tôi không thích các macro thuộc loại này vì chúng phá vỡ Intellisense (trên Windows) và gỡ lỗi logic chương trình. Thử đặt điểm ngắt trên tất cả các câu lệnh 10 return trong chức năng của bạn - chứ không phải séc, chỉ return. Hãy thử bước qua mã trong macro. Điều tồi tệ nhất về điều này là khi bạn chấp nhận điều này thật khó để tranh luận chống lại các macro quái vật 30 dòng mà một số lập trình viên thích sử dụng cho các nhiệm vụ nhỏ thường thấy bởi vì họ 'làm rõ mọi thứ'. Tôi đã nhìn thấy mã nơi các loại ngoại lệ khác nhau đã được xử lý theo cách này bởi bốn macro xếp tầng, dẫn đến 4 dòng trong tệp nguồn, với các macro thực sự mở rộng đến> 100 dòng thực. Bây giờ, bạn đang giảm bloat mã? Không. Không thể nói dễ dàng với các macro.

Một đối số chung khác về macro, ngay cả khi không được áp dụng rõ ràng ở đây, là khả năng lồng chúng với kết quả khó giải mã hoặc để vượt qua các đối số dẫn đến các đối số kỳ lạ nhưng có thể biên dịch được. việc sử dụng ++x trong một macro sử dụng đối số hai lần. Tôi luôn luôn biết nơi tôi đứng với mã, và tôi không thể nói rằng về một vĩ mô.

EDIT: Một nhận xét tôi nên thêm là nếu bạn thực sự lặp lại logic kiểm tra lỗi này nhiều lần, có lẽ có cơ hội tái cấu trúc trong mã. Không phải là một đảm bảo nhưng một cách tốt hơn của mã bloat giảm nếu nó áp dụng. Hãy tìm các chuỗi cuộc gọi lặp lại và đóng gói các chuỗi chung trong chức năng của riêng chúng, thay vì giải quyết cách thức xử lý từng cuộc gọi.

+0

Tôi đồng ý rằng macro phải luôn luôn là điều cuối cùng để sử dụng, khi tất cả các cách khác để 'làm rõ mọi thứ' đã được thử, và nó phải đơn giản, nhưng nó thực sự tốt hơn để copypasta macro thay vì sử dụng nó? – kovarex

+2

Cho dù nó tăng bloat mã hay không phụ thuộc vào việc có hay không có một thay thế cho các macro. Nếu thay thế cho các macro là viết 100 dòng theo cách thủ công thì các macro không tăng mã bloat. Nếu có một thay thế thì có, các macro có vấn đề. –

+1

@Peter - trân trọng không đồng ý. Tôi sẽ KHÔNG BAO GIỜ đặt nhiều dòng mã trong một macro cho mã sản xuất. Mã thử nghiệm là một điều khác. Đó là nghĩa đen không thể gỡ lỗi mã như vậy, và khi bản chất thực sự của mã được ẩn đến một mức độ như vậy, đó là một mã mùi bất cứ điều gì cơ chế obfuscatory. –

4

Thực ra, tôi thích giải pháp hơi khác. Vấn đề là kết quả của cuộc gọi bên trong không nhất thiết là một kết quả hợp lệ của một cuộc gọi bên ngoài. Ví dụ, thất bại bên trong có thể là "tập tin không tìm thấy", nhưng bên ngoài "cấu hình không có sẵn". Do đó đề xuất của tôi là tạo lại OpResult (có khả năng đóng gói "bên trong" OpResult vào nó để gỡ lỗi tốt hơn). Điều này tất cả đi theo hướng "InnerException" trong .NET.

về mặt kỹ thuật, trong trường hợp của tôi vĩ mô trông giống như

#define RETURN_IF_FAILED(call, outerresult) \ 
    {           \ 
    OpResult innerresult = call;   \ 
    if (innerresult.failed())    \ 
    {          \ 
     outerresult.setInner(innerresult); \ 
     return outerresult;     \ 
    }          \ 
    } 

Giải pháp này đòi hỏi tuy nhiên một số quản lý bộ nhớ, vv

Một số người theo chủ nghĩa thuần túy cho rằng không có lợi tức rõ ràng cản trở khả năng đọc của mã. Theo ý kiến ​​của tôi tuy nhiên có rõ ràng RETURN như là một phần của tên vĩ mô là đủ để ngăn chặn sự nhầm lẫn cho bất kỳ nhà phát triển có kỹ năng và chu đáo.


Ý kiến ​​của tôi là các macro này không làm xáo trộn logic chương trình, nhưng ngược lại làm cho nó sạch hơn. Với macro như vậy, bạn khai báo ý định của mình một cách rõ ràng và ngắn gọn, trong khi cách khác có vẻ quá chi tiết và do đó dễ xảy ra lỗi. Làm cho các nhà bảo trì phân tích cú pháp trong tâm trí cùng một cấu trúc OpResult r = call(); if (r.failed) return r là lãng phí thời gian của họ.

Phương pháp thay thế không có lợi nhuận sớm được áp dụng cho mỗi dòng mã mẫu như CHECKEDCALL(r, call) với #define CHECKEDCALL(r, call) do { if (r.succeeded) r = call; } while(false). Điều này là trong mắt của tôi tồi tệ hơn nhiều và chắc chắn là dễ bị lỗi, vì mọi người có xu hướng quên thêm CHECKEDCALL() khi thêm mã khác.

Có một số phổ biến cần thực hiện kiểm tra trả về (hoặc mọi thứ) với macro có vẻ là dấu hiệu thiếu tính năng ngôn ngữ bị thiếu đối với tôi.

+0

Điều gì đó tương tự cũng có thể hữu ích. – kovarex

+1

Nếu bạn cần điều này, hãy đặt các cuộc gọi bên trong đó vào các hàm riêng (được nội tuyến) của chúng với một hàm bên ngoài diễn giải kết quả của chúng. Sau đó, không cần phải hack macro này. – sbi

+0

@sbi: mã của tôi trong câu trả lời chỉ là một gợi ý. mã sản xuất thực tế sử dụng một số hàm trợ giúp thích hợp. – Vlad

2

Miễn là định nghĩa macro nằm trong tệp triển khai và không được xác định ngay khi không cần thiết, tôi sẽ không bị sợ hãi.

// something.cpp 

#define RETURN_IF_FAILED() /* ... */ 

void f1() { /* ... */ } 
void f2() { /* ... */ } 

#undef RETURN_IF_FAILED 

Tuy nhiên, tôi sẽ chỉ sử dụng điều này sau khi loại bỏ tất cả các giải pháp phi vĩ mô.

0

Tôi đồng ý với Steve's POV.

đầu tiên tôi nghĩ, ít nhất là giảm macro để

#define RETURN_IF_FAILED(result) if(result.failed()) return result; 

nhưng sau đó nó xảy ra với tôi đây đã một lớp lót, do đó thực sự rất ít lợi ích trong việc vĩ mô.


Tôi nghĩ, về cơ bản, bạn phải thực hiện giao dịch giữa khả năng viết và khả năng đọc. Macro chắc chắn dễ dàng hơn để viết. Đó là, tuy nhiên, một câu hỏi mở cho dù nó cũng là dễ dàng hơn để đọc. Sau này là một bản án chủ quan để thực hiện. Tuy nhiên, việc sử dụng macro một cách khách quan là không mã obfuscate.


Cuối cùng, vấn đề cơ bản là bạn không được sử dụng ngoại lệ. Bạn đã không nói những lý do cho quyết định đó là gì, nhưng tôi chắc chắn hy vọng họ có giá trị những vấn đề này gây ra.

+0

Bằng cách này, bạn phải gọi hàm và sau đó là macro và bạn cũng phải sử dụng khối ở mọi nơi hoặc thận trọng để không tạo các biến kết quả trùng lặp. – kovarex

+0

macro trong ví dụ của bạn không phải là tối ưu, vì nó sẽ mang lại một lỗi trong mã như 'if (cond) RETURN_IF_FAILED (...) else do_something_else;'. Bạn có lẽ muốn thêm thông thường 'do {...} trong khi (0)' xung quanh. – Vlad

+2

@Vlad: _I đã cãi nhau ** đối với ** _đó vĩ mô, _ đó là lý do tại sao tôi không đưa bất kỳ suy nghĩ nào vào đó. – sbi

0

Có thể thực hiện với C++ 0x lambdas.

template<typename F> inline OpResult if_failed(OpResult a, F f) { 
    if (a.failed()) 
     return a; 
    else 
     return f(); 
}; 

OpResult something() { 
    int mah_var = 0; 
    OpResult x = do_something(); 
    return if_failed(x, [&]() -> OpResult { 
     std::cout << mah_var; 
     return f; 
    }); 
}; 

Nếu bạn thông minh và tuyệt vọng, bạn có thể làm cùng một loại mẹo hoạt động với các đối tượng thông thường.

0

Theo ý kiến ​​của tôi, việc ẩn câu lệnh trả về trong macro là một ý tưởng tồi. Các 'mã obfucation' (tôi thích thuật ngữ đó ..!) Đạt đến mức cao nhất có thể.giải pháp thông thường của tôi đến các vấn đề như vậy là để tổng hợp việc thực hiện chức năng tại một nơi và kiểm soát kết quả theo cách thức sau (giả sử bạn có 5 chức năng nullary):

std::array<std::function<OpResult()>, 5> tFunctions = { 
f1, f2, f3, f4, f5 
}; 

auto tFirstFailed = std::find_if(tFunctions.begin(), tFunctions.end(), 
    [] (std::function<OpResult()>& pFunc) -> bool { 
     return pFunc().failed(); 
    }); 

if (tFirstFailed != tFunctions.end()) { 
// tFirstFailed is the first function which failed... 
} 
+0

Thật khó để gọi lệnh này * ẩn * một câu lệnh trả về, khi tên macro bắt đầu bằng 'RETURN_IF _...' – slacker

0

Có bất kỳ thông tin trong kết quả mà thực sự là hữu ích nếu cuộc gọi không thành công?

Nếu không, sau đó

static const error_result = something; 

if (call().failed()) return error_result; 

sẽ đủ.

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