2012-11-15 17 views
10

trong một thời gian khá tôi đã làm việc trên một ứng dụng. Khi lập trình chỉ là một sở thích, dự án này đã mất quá nhiều thời gian, nhưng đó là ngoài điểm. Bây giờ tôi đang ở một điểm mà mọi "vấn đề" trở nên khó giải quyết một cách khủng khiếp. Và tôi đang nghĩ đến việc tái cấu trúc mã, tuy nhiên điều đó sẽ dẫn đến việc viết lại "hoàn chỉnh".Mô hình lập trình; tự hỏi nếu viết lại/refactoring là cần thiết

Hãy để tôi giải thích sự cố và cách tôi giải quyết sự cố hiện tại. Về cơ bản tôi có dữ liệu và tôi để mọi thứ xảy ra trên dữ liệu này (tôi đã mô tả về mọi chương trình không phải là tôi?). Chuyện gì xảy ra là:

dữ liệu -> hỏi xem để hiển thị -> hiển thị xem dữ liệu dựa trên dữ liệu thực tế xem trả về đầu vào sử dụng -> Xóa dữ liệu -> yêu cầu "người thi hành" để thực hiện nó -> Xóa dữ liệu mới

enter image description here

Bây giờ điều này đã từng làm việc rất tốt, và tôi đã suy nghĩ ban đầu "hey tôi có thể ví dụ như thay đổi dấu nhắc lệnh bằng cách qt, hoặc cửa sổ - hoặc thậm chí mất rằng bên ngoài (C#) và chỉ cần gọi chương trình này" .

Tuy nhiên, khi chương trình phát triển, nó ngày càng trở nên mệt mỏi hơn. Vì điều quan trọng nhất là dữ liệu được hiển thị theo các cách khác nhau tùy thuộc vào dữ liệu và quan trọng hơn - nơi dữ liệu được định vị. Vì vậy, tôi đã trở lại cây & thêm vào một cách nào đó để "theo dõi" những gì các dòng phụ huynh là ". Sau đó, người xem chung sẽ tìm kiếm các widget thực tế cụ thể nhất.

Sự cố bắt đầu khi cập nhật cho "dữ liệu" mới - Tôi phải xem xét tất cả nội dung - trình xem, tiết kiệm, v.v ... Cập nhật cơ chế kiểm tra đã cho tôi nhiều lỗi .. Mọi thứ giống như "hey tại sao nó lại hiển thị tiện ích sai?"

Bây giờ tôi hoàn toàn có thể hoán đổi nó và thay vào đó là cơ sở hạ tầng cây gọi cho người xem chung, tôi sẽ sử dụng khả năng của cây OO "nội bộ". các nút sẽ là childs (& whe n một người xem mới hoặc cơ chế lưu là cần thiết một đứa trẻ mới được hình thành).

Điều này sẽ loại bỏ cơ chế kiểm tra khó khăn, nơi tôi kiểm tra vị trí trong cây. Tuy nhiên, nó có thể mở ra một toàn bộ các giun khác. Và tôi muốn nhận xét về điều này? Tôi có nên giữ người xem hoàn toàn riêng biệt - gặp khó khăn khi kiểm tra dữ liệu không? Hoặc là cách tiếp cận mới tốt hơn, nhưng nó kết hợp dữ liệu thực hiện & thành một nút duy nhất. (Vì vậy, nếu tôi muốn thay đổi từ qt nói cli/C# nó trở nên gần như bất khả thi)

enter image description here

phương pháp Những gì tôi nên theo đuổi cuối cùng? Ngoài ra có cái gì khác tôi có thể làm gì? Để giữ người xem tách biệt, nhưng vẫn không phải kiểm tra để xem tiện ích nào sẽ được hiển thị?

CHỈNH SỬA, chỉ để hiển thị một số "mã" và cách chương trình của tôi hoạt động. Không chắc chắn nếu điều này là tốt như tôi đã nói nó đã trở thành khá một clusterfuck của phương pháp luận.

Nó có nghĩa là hợp nhất một số "dự án gamemaker" với nhau (như GM: studio kỳ lạ thiếu tính năng đó). Các tệp dự án Gamemaker chỉ đơn giản là tập hợp các tệp xml. (Tệp xml chính chỉ có liên kết đến các tệp xml khác và tệp xml cho từng tài nguyên -object, sprite, sound, room etc-).Tuy nhiên có một số 'quirks' mà làm cho nó không thực sự có thể đọc với một cái gì đó như cây tăng tài sản hoặc qt: 1) thứ tự của các thuộc tính/nút con là rất quan trọng tại một số phần của các tập tin. và 2) không gian trắng thường bị bỏ qua tuy nhiên tại các điểm khác, điều rất quan trọng là phải bảo tồn nó.

Điều đó đang được nói cũng có rất nhiều điểm mà nút chính xác giống nhau .. Giống như cách nền có thể có <width>200</width> và một phòng cũng có thể có. Tuy nhiên, đối với người dùng nó là khá quan trọng mà chiều rộng ông đang nói về.

Anyways, do đó, "nói chung xem" (AskGUIFn) có typedefs sau đây để xử lý này:

typedef int (AskGUIFn::*MemberFn)(const GMProject::pTree& tOut, const GMProject::pTree& tIn, int) const; 
    typedef std::vector<std::pair<boost::regex, MemberFn> > DisplaySubMap_Ty; 
    typedef std::map<RESOURCE_TYPES, std::pair<DisplaySubMap_Ty, MemberFn> > DisplayMap_Ty; 

đâu "GMProject :: pTree" là một nút cây, RESOURCE_TYPES là một hằng số để theo dõi trong loại tài nguyên nào tôi đang ở thời điểm này (sprite, object etc). Các "memberFn" sẽ ở đây chỉ đơn giản là một cái gì đó mà tải một widget. (Mặc dù AskGUIFn không phải là trình xem tổng quát duy nhất tất nhiên, trình xem này chỉ được mở nếu các trình xử lý ghi đè, bỏ qua, đổi tên khác "tự động" không thành công).

Bây giờ để hiển thị như thế nào các bản đồ này được khởi tạo (tất cả mọi thứ trong không gian tên "MW" là một widget qt):

AskGUIFn::DisplayMap_Ty AskGUIFn::DisplayFunctionMap_INIT() { 
    DisplayMap_Ty t; 
     DisplaySubMap_Ty tmp; 

     tmp.push_back(std::pair<boost::regex, AskGUIFn::MemberFn> (boost::regex("^instances "), &AskGUIFn::ExecuteFn<MW::RoomInstanceDialog>)); 
     tmp.push_back(std::pair<boost::regex, AskGUIFn::MemberFn> (boost::regex("^code $"), &AskGUIFn::ExecuteFn<MW::RoomStringDialog>)); 
     tmp.push_back(std::pair<boost::regex, AskGUIFn::MemberFn> (boost::regex("^(isometric|persistent|showcolour|enableViews|clearViewBackground) $"), &AskGUIFn::ExecuteFn<MW::ResourceBoolDialog>)); 
     //etc etc etc 
    t[RT_ROOM] = std::pair<DisplaySubMap_Ty, MemberFn> (tmp, &AskGUIFn::ExecuteFn<MW::RoomStdDialog>); 

     tmp.clear(); 
     //repeat above 
    t[RT_SPRITE] = std::pair<DisplaySubMap_Ty, MemberFn>(tmp, &AskGUIFn::ExecuteFn<MW::RoomStdDialog>); 
    //for each resource type. 

Sau đó, khi datastructure cây nói với người xem nói chung nó mong muốn được hiển thị cho người xem thực thi chức năng sau:

AskGUIFn::MemberFn AskGUIFn::FindFirstMatch() const { 
    auto map_loc(DisplayFunctionMap.find(res_type)); 
    if (map_loc != DisplayFunctionMap.end()) { 
     std::string stack(CallStackSerialize()); 
     for (auto iter(map_loc->second.first.begin()); iter != map_loc->second.first.end(); ++iter) { 
      if (boost::regex_search(stack, iter->first)) { 
       return iter->second; 
      } 
     } 
     return map_loc->second.second; 
    } 

    return BackupScreen; 
} 

Và đây là nơi vấn đề bắt đầu thẳng thắn. Chức năng CallStackSerialize() phụ thuộc vào ngăn xếp cuộc gọi .. Tuy nhiên, lệnh gọi_được lưu trữ bên trong "trình xử lý". Tôi đã lưu trữ nó ở đó vì mọi thứ bắt đầu từ một trình xử lý. Tôi không thực sự chắc chắn nơi tôi nên lưu trữ này "call_stack". Giới thiệu một đối tượng khác theo dõi những gì đang xảy ra? Tôi đã thử đi tuyến đường nơi tôi lưu trữ phụ huynh với chính nút đó. (Ngăn chặn sự cần thiết cho một cuộc gọi ngăn xếp). Tuy nhiên điều đó không diễn ra tốt đẹp như tôi mong muốn: mỗi nút chỉ đơn giản có một vectơ chứa các nút con của nó. Vì vậy, bằng cách sử dụng con trỏ là ra khỏi câu hỏi để trỏ đến lưu ý cha mẹ ... (PS: có lẽ tôi nên cải cách này trong một câu hỏi khác ..)

+6

+1, cho các số liệu. Nếu không đọc câu hỏi, nó đơn giản trông rất tuyệt. – iammilind

+2

Bạn có quen thuộc với "[Hiệu ứng nền tảng bên trong] (http://en.wikipedia.org/wiki/Inner_platform_effect)" không? Tôi không hiểu vấn đề của bạn đủ tốt để rút ra bất kỳ kết luận nào (và tôi chắc chắn không biết gì về ứng dụng của bạn!) Nhưng nếu bạn không quen thuộc với thuật ngữ thì nó đáng để nghiên cứu một chút. Đấu tranh để sửa đổi một hệ thống rất phức tạp nhằm mục đích là một khuôn khổ siêu mục đích nói chung giống như một triệu chứng của antipattern này ... – Rook

+0

không phải là các nút dữ liệu của bạn cần một loại hồ sơ khi chúng được tạo ra, hồ sơ mô tả cách thức để trình bày, hành động và cứu họ? – armel

Trả lời

2

Tái cấu trúc/viết lại cơ chế kiểm tra vị trí phức tạp này ra khỏi trình xem thành lớp chuyên dụng có ý nghĩa, vì vậy bạn có thể cải thiện giải pháp mà không ảnh hưởng đến phần còn lại của chương trình. Cho phép gọi số này NodeToWidgetMap.

Kiến trúc
vẻ tiêu đề của bạn hướng tới một kiến ​​trúc Model-View-Controller đó là một điều tốt IMO. Cấu trúc cây của bạn và các nút của nó là các mô hình, nơi mà người xem và "widget" là các khung nhìn, và việc chọn các widget logic tùy thuộc vào nút sẽ là một phần của bộ điều khiển.

Câu hỏi chính vẫn là khi nào và cách bạn chọn tiện ích w N cho nút N đã cho và cách lưu trữ lựa chọn này.

NodeToWidgetMap: Khi nào thì chọn
Nếu bạn có thể giả định rằng w N không thay đổi trong suốt cuộc đời của nó mặc dù các nút được di chuyển, bạn có thể chọn nó ngay khi tạo nút. Nếu không, bạn sẽ cần phải biết vị trí (hoặc đường dẫn thông qua XML) và, trong hậu quả, tìm cha mẹ của một nút khi yêu cầu nó.

Tìm Nodes Chánh
Giải pháp của tôi sẽ được lưu trữ con trỏ để thay vì các trường hợp nút tự, có lẽ sử dụng boost::shared_ptr. Điều này có nhược điểm, ví dụ như các nút sao chép buộc bạn phải thực hiện các trình tạo bản sao của riêng bạn sử dụng đệ quy để tạo một bản sao sâu của cây con của bạn. (Tuy nhiên, di chuyển sẽ không ảnh hưởng đến các nút con.)

Các lựa chọn thay thế tồn tại, chẳng hạn như giữ các nút con tăng lên bất cứ khi nào chạm vào nút cha tương ứng với vector của ông nội. Hoặc bạn có thể xác định hàm Node::findParentOf(node) khi biết rằng một số nút nhất định chỉ có thể (hoặc thường xuyên) được xem là con của các nút nhất định. Điều này là phù hợp nhưng sẽ hoạt động tốt cho những cây nhỏ, chỉ không quy mô rất tốt.

NodeToWidgetMap: Làm thế nào để chọn
Hãy thử viết xuống các quy tắc làm thế nào để chọn w N trên mảnh giấy, có lẽ chỉ là một phần. Sau đó thử dịch những quy tắc này thành C++. Điều này có thể dài hơn một chút về mặt mã nhưng sẽ dễ hiểu và dễ bảo trì hơn.

Cách tiếp cận hiện tại của bạn là sử dụng cụm từ thông dụng để khớp với đường dẫn XML (ngăn xếp).

Ý tưởng của tôi là tạo biểu đồ tra cứu có các cạnh được gắn nhãn bằng tên phần tử XML và các nút của nó cho biết tiện ích nào sẽ được sử dụng. Bằng cách này, đường dẫn XML của bạn (stack) mô tả một tuyến đường thông qua biểu đồ. Sau đó, câu hỏi sẽ trở thành mô hình hóa rõ ràng một biểu đồ hay liệu một nhóm các cuộc gọi hàm có thể được sử dụng để phản ánh đồ thị này hay không.

NodeToWidgetMap: Nơi lưu trữ lựa chọn
Gắn một độc đáo, số id cho mỗi nút, ghi lại các lựa chọn phụ tùng sử dụng một bản đồ từ id nút để phụ tùng bên trong NodeToWidgetMap.

Viết lại vs Refactoring
Nếu bạn viết lại bạn có thể nhận đòn bẩy tốt tieing đến một khuôn khổ hiện có như Qt để tập trung vào chương trình của bạn thay vì viết lại các bánh xe.Nó có thể được dễ dàng hơn để cổng một chương trình được viết tốt từ trên khuôn khổ khác hơn là trừu tượng xung quanh pecularities của mỗi nền tảng. Qt là một khung làm việc tốt để có được kinh nghiệm và hiểu biết tốt về các kiến ​​trúc MVC.

Viết lại hoàn chỉnh cho bạn cơ hội suy nghĩ lại mọi thứ nhưng ngụ ý rủi ro bạn bắt đầu từ đầu và sẽ không có phiên bản mới trong một khoảng thời gian tốt. Và ai biết liệu bạn sẽ có đủ thời gian để hoàn thành? Nếu bạn chọn cấu trúc lại các cấu trúc hiện có, bạn sẽ cải thiện từng bước một, có một phiên bản có thể sử dụng sau mỗi bước. Nhưng có nguy cơ nhỏ vẫn còn bị mắc kẹt trong những cách suy nghĩ cũ, khi viết lại gần như buộc bạn phải suy nghĩ lại mọi thứ. Vì vậy, cả hai cách tiếp cận đều có giá trị của chúng, nếu bạn thích lập trình tôi sẽ viết lại. Lập trình nhiều hơn, nhiều niềm vui hơn.

+1

Meh Tôi chắc chắn sẽ viết lại tôi đoán .. Dự án này thực sự cho thấy rất nhiều thay đổi. Trước đó tôi chỉ là "thành thạo với C++/stl", tuy nhiên tôi không bao giờ làm bất cứ điều gì lớn. Bây giờ tôi đã học được qt, và nhiều thứ khác tôi cần (C++ 11). Tôi thực sự có thể thấy sự khác biệt trong phong cách mã đến mức tôi xấu hổ về nó. – paul23

+0

meh, xin lỗi vì nhiều chỉnh sửa, có vẻ như bạn đã gặp phải "Luật Bảo tồn Khó khăn" –

+0

Rất tiếc, tôi thực sự có thể sử dụng phương pháp "nodekind"; các nút sẽ giữ kiểu của chúng (và chỉ được thêm vào các cây khác). Tuy nhiên điều này – paul23

1

Chào mừng đến với thế giới lập trình!
Những gì bạn mô tả là vòng đời điển hình của một ứng dụng, bắt đầu như một ứng dụng đơn giản nhỏ, sau đó nó nhận được nhiều tính năng hơn cho đến khi nó không còn có thể duy trì được nữa. Bạn không thể tưởng tượng có bao nhiêu dự án tôi đã nhìn thấy trong giai đoạn sụp đổ cuối cùng này!
Bạn có cần phải tái cấu trúc không? Tất nhiên rồi! Tất cả thời gian! Bạn có cần viết lại mọi thứ không? Không chắc lắm.
Trong thực tế giải pháp tốt là làm việc theo chu kỳ: bạn thiết kế những gì bạn cần để mã, bạn mã nó, bạn cần thêm chức năng, bạn thiết kế chức năng mới này, bạn cấu trúc lại mã để bạn có thể tích hợp mã mới, v.v. Nếu bạn không làm như thế này thì bạn sẽ đến điểm mà nó ít tốn kém để viết lại sau đó để tái cấu trúc. Nhận cuốn sách này: Tái cấu trúc - Martin Fowler. Nếu bạn thích nó thì hãy lấy cái này: Refactoring to Patterns.

0

Pedro NF cho biết, Martin Fowler "Refactoring" là nơi tốt để làm quen với nó.

0

Tôi khuyên bạn nên mua một bản sao của Robert Martins "Nguyên tắc nhanh nhẹn, mô hình và thực tiễn trong C#" Ông đi qua một số nghiên cứu trường hợp rất thực tế cho thấy cách vượt qua các vấn đề bảo trì như thế này.