Làm thế nào để đơn giản hóa các lớp trạng thái phức tạp của tôi và thử nghiệm của chúng?


9

Tôi đang ở trong một dự án hệ thống phân tán được viết bằng java nơi chúng tôi có một số lớp tương ứng với các đối tượng kinh doanh trong thế giới thực rất phức tạp. Các đối tượng này có rất nhiều phương thức tương ứng với các hành động mà người dùng (hoặc một số tác nhân khác) có thể áp dụng cho các đối tượng đó. Kết quả là, các lớp này trở nên rất phức tạp.

Cách tiếp cận kiến ​​trúc chung của hệ thống đã dẫn đến rất nhiều hành vi tập trung vào một vài lớp và rất nhiều tình huống tương tác có thể xảy ra.

Để làm ví dụ và để giữ mọi thứ dễ dàng và rõ ràng, hãy nói rằng Robot và Xe hơi là các lớp trong dự án của tôi.

Vì vậy, trong lớp Robot tôi sẽ có rất nhiều phương thức theo mẫu sau:

  • ngủ(); isS ngủAvaliable ();
  • thức giấc(); isAwakeAvaliable ();
  • đi bộ (Hướng); isWalkAvaliable ();
  • bắn (Hướng); isShootAvaliable ();
  • TurnOnAlert (); isTurnOnAlertAv Available ();
  • lần lượt TắtAlert (); isTurn OfferAlertAv Available ();
  • nạp điện(); isRechargAv Available ();
  • tắt nguồn(); isPowerPackAv Available ();
  • stepInCar (Xe hơi); isStepInCarAv Available ();
  • stepOutCar (Xe hơi); isStepOutCarAv Available ();
  • tự hủy(); isSelfDesturationAv Available ();
  • chết(); isDieAv Available ();
  • isAlive (); la thưc tỉnh(); isAlertOn (); getBatteryLevel (); getCienRidingCar (); getAmmo ();
  • ...

Trong lớp Xe hơi, nó sẽ tương tự:

  • bật(); isTurnOnAvaliable ();
  • tắt(); isTurn OfferAvaliable ();
  • đi bộ (Hướng); isWalkAvaliable ();
  • tiếp nhiên liệu (); isRefuelAv Available ();
  • tự hủy(); isSelfDesturationAv Available ();
  • tai nạn(); isCrashAv Available ();
  • isOperational (); isOn (); getFuelLevel (); getCienPasbah ();
  • ...

Mỗi trong số này (Robot và Xe hơi) được triển khai như một cỗ máy trạng thái, trong đó một số hành động có thể xảy ra ở một số tiểu bang và một số thì không. Các hành động thay đổi trạng thái của đối tượng. Các phương thức hành động ném IllegalStateExceptionkhi được gọi ở trạng thái không hợp lệ và các isXXXAvailable()phương thức cho biết liệu hành động đó có khả thi tại thời điểm đó hay không. Mặc dù một số người dễ dàng suy luận từ trạng thái (ví dụ, ở trạng thái ngủ, tỉnh táo là có sẵn), một số không (để bắn, nó phải tỉnh táo, còn sống, có đạn và không đi xe hơi).

Hơn nữa, các tương tác giữa các đối tượng cũng phức tạp. Ví dụ, Xe chỉ có thể chứa một hành khách Robot, vì vậy nếu một người khác cố gắng vào, một ngoại lệ sẽ bị ném; Nếu xe gặp nạn, hành khách sẽ chết; Nếu robot chết trong xe, anh ta không thể bước ra ngoài, ngay cả khi chính Xe vẫn ổn; Nếu Robot ở trong Xe, anh ta không thể vào xe khác trước khi bước ra ngoài; Vân vân.

Kết quả của điều này, như tôi đã nói, các lớp này trở nên thực sự phức tạp. Để làm cho mọi thứ tồi tệ hơn, có hàng trăm tình huống có thể xảy ra khi Robot và Xe tương tác. Hơn nữa, phần lớn logic đó cần phải truy cập dữ liệu từ xa trong các hệ thống khác. Kết quả là việc kiểm tra đơn vị trở nên rất khó khăn và chúng tôi có rất nhiều vấn đề kiểm thử, một vấn đề gây ra vấn đề khác trong một vòng luẩn quẩn:

  • Các thiết lập testcase rất phức tạp, bởi vì chúng cần tạo ra một thế giới phức tạp đáng kể để thực hiện.
  • Số lượng bài kiểm tra là rất lớn.
  • Bộ thử nghiệm mất vài giờ để chạy.
  • Phạm vi kiểm tra của chúng tôi là rất thấp.
  • Mã kiểm tra có xu hướng được viết vài tuần hoặc vài tháng so với mã mà họ kiểm tra hoặc hoàn toàn không bao giờ.
  • Rất nhiều thử nghiệm cũng bị hỏng, chủ yếu là do các yêu cầu của mã thử nghiệm đã thay đổi.
  • Một số tình huống rất phức tạp, đến nỗi chúng không hết thời gian chờ trong quá trình thiết lập (chúng tôi đã định cấu hình thời gian chờ trong mỗi thử nghiệm, trong trường hợp xấu nhất là 2 phút và thậm chí thời gian này chúng hết thời gian, chúng tôi đảm bảo rằng đó không phải là một vòng lặp vô hạn).
  • Lỗi thường xuyên trượt vào môi trường sản xuất.

Kịch bản Robot và Xe hơi đó là sự đơn giản hóa quá mức những gì chúng ta có trong thực tế. Rõ ràng, tình trạng này là không thể quản lý. Vì vậy, tôi đang yêu cầu trợ giúp và gợi ý để: 1, Giảm độ phức tạp của các lớp; 2. Đơn giản hóa các kịch bản tương tác giữa các đối tượng của tôi; 3. Giảm thời gian thử nghiệm và sửa đổi mã cần kiểm tra.

EDIT:
Tôi nghĩ rằng tôi đã không rõ ràng về các máy nhà nước. Robot tự nó là một cỗ máy trạng thái, với các trạng thái "ngủ", "thức", "sạc lại", "chết", v.v ... Xe là một cỗ máy trạng thái khác.

EDIT 2: Trong trường hợp bạn tò mò về hệ thống của tôi thực sự là gì, các lớp tương tác là những thứ như Server, IPAddress, Disk, Backup, User, SoftwareLicense, v.v. Kịch bản Robot và xe hơi chỉ là một trường hợp mà tôi tìm thấy Điều đó đủ đơn giản để giải thích vấn đề của tôi.


bạn đã cân nhắc việc hỏi tại Code Review.SE chưa? Ngoài ra, đối với thiết kế như của bạn, tôi bắt đầu nghĩ về tái cấu trúc loại Extract Class
gnat

Tôi đã xem xét Code Code, nhưng đó không phải là nơi thích hợp. Vấn đề chính không nằm ở bản thân mã, vấn đề nằm ở cách tiếp cận kiến ​​trúc chung của hệ thống dẫn đến rất nhiều hành vi tập trung vào một vài lớp và rất nhiều tình huống tương tác có thể xảy ra.
Victor Stafusa

@gnat Bạn có thể cung cấp một ví dụ về cách tôi sẽ triển khai Lớp trích xuất trong kịch bản Robot và Xe hơi đã cho không?
Victor Stafusa

Tôi sẽ trích xuất những thứ liên quan đến Xe hơi từ Robot vào một lớp riêng. Tôi cũng trích xuất tất cả các phương pháp liên quan đến giấc ngủ + thức dậy thành một lớp dành riêng. Các "ứng cử viên" khác có vẻ xứng đáng được trích xuất là sức mạnh + phương thức nạp tiền, công cụ liên quan đến chuyển động. V.v. Lưu ý vì đây là tái cấu trúc, API bên ngoài cho robot có lẽ nên ở lại; ở giai đoạn đầu tiên tôi chỉ sửa đổi nội bộ. BTDTGTTS
gnat

Đây không phải là một câu hỏi Đánh giá mã - kiến ​​trúc không có chủ đề ở đó.
Michael K

Câu trả lời:


8

Các nhà nước mẫu thiết kế có thể sử dụng, nếu bạn chưa sử dụng nó.

Ý tưởng cốt lõi là bạn tạo ra một lớp bên trong cho mỗi tiểu bang khác nhau - như vậy để tiếp tục dụ, bạn SleepingRobot, AwakeRobot, RechargingRobotDeadRobottất cả sẽ là lớp học, thực hiện một giao diện chung.

Các phương thức trên Robotlớp (như sleep()isSleepAvaliable()) có các triển khai đơn giản ủy thác cho lớp bên trong hiện tại.

Thay đổi trạng thái được thực hiện bằng cách hoán đổi lớp bên trong hiện tại với một lớp khác.

Ưu điểm của phương pháp này là mỗi lớp trạng thái đơn giản hơn đáng kể (vì nó chỉ đại diện cho một trạng thái có thể) và có thể được kiểm tra độc lập. Tùy thuộc vào ngôn ngữ triển khai của bạn (không được chỉ định), bạn vẫn có thể bị hạn chế có mọi thứ trong cùng một tệp hoặc bạn có thể chia mọi thứ thành các tệp nguồn nhỏ hơn.


Tôi đang sử dụng java.
Victor Stafusa

Gợi ý tốt. Bằng cách này, mỗi triển khai có một trọng tâm rõ ràng có thể được kiểm tra riêng lẻ mà không cần phải kiểm tra đồng thời tất cả các trạng thái lớp 2.000 dòng.
OliverS

3

Tôi không biết mã của bạn, nhưng lấy ví dụ về phương pháp "ngủ", tôi sẽ cho rằng đó là một cái gì đó giống như mã "đơn giản" sau đây:

public void sleep() {
 if(!dead && awake) {
  sleeping = true;
  awake = false;
  this.updateState(SLEEPING);
 }
 throw new IllegalArgumentException("robot is either dead or not awake");
}

Tôi nghĩ bạn phải tạo ra sự khác biệt giữa kiểm tra tích hợpkiểm tra đơn vị . Viết một bài kiểm tra chạy qua toàn bộ trạng thái máy của bạn chắc chắn là một nhiệm vụ lớn. Viết các bài kiểm tra đơn vị nhỏ hơn để kiểm tra xem phương pháp ngủ của bạn có hoạt động tốt hay không dễ dàng hơn. Tại thời điểm này, bạn không cần phải biết liệu trạng thái máy đã được cập nhật đúng cách hay "xe" đã phản hồi đúng với thực tế là trạng thái máy đã được "robot" cập nhật ... v.v. bạn có nhận được không.

Với mã ở trên, tôi sẽ mô phỏng đối tượng "machineState" và thử nghiệm đầu tiên của tôi sẽ là:

testSleep_dead() {
 robot.dead = true;
 robot.awake = false;
 robot.setState(AWAKE);
 try {
  robot.sleep();
  fail("should have got an exception");
 } catch(Exception e) {
  assertTrue(e instanceof IllegalArgumentException);
  assertEquals("robot is either dead or not awake", e.getMessage());
 }
}

Ý kiến ​​cá nhân của tôi là viết các bài kiểm tra đơn vị nhỏ như vậy nên là điều đầu tiên cần làm. Bạn đã viết như vậy:

Các thiết lập testcase rất phức tạp, bởi vì chúng cần tạo ra một thế giới phức tạp đáng kể để thực hiện.

Chạy các thử nghiệm nhỏ này sẽ rất nhanh và bạn không nên có bất cứ điều gì để khởi tạo trước như "thế giới phức tạp" của bạn. Ví dụ: nếu đó là một ứng dụng dựa trên bộ chứa IOC (giả sử là Spring), bạn không cần phải khởi tạo ngữ cảnh trong các thử nghiệm đơn vị.

Sau khi bạn đã bao phủ một tỷ lệ phần trăm tốt cho mã phức tạp của mình bằng các bài kiểm tra đơn vị, bạn có thể bắt đầu xây dựng các bài kiểm tra tích hợp tốn nhiều thời gian hơn và phức tạp hơn.

Cuối cùng, điều này có thể được thực hiện cho dù mã của bạn phức tạp (như bạn đã nói bây giờ) hoặc sau khi bạn đã tái cấu trúc.


Tôi nghĩ rằng tôi đã không rõ ràng về các máy nhà nước. Robot tự nó là một cỗ máy trạng thái, với các trạng thái "ngủ", "thức", "sạc lại", "chết", v.v ... Xe là một cỗ máy trạng thái khác.
Victor Stafusa

@Victor OK, vui lòng sửa mã ví dụ của tôi nếu bạn muốn. Trừ khi bạn nói với tôi khác, tôi nghĩ rằng quan điểm của tôi về bài kiểm tra đơn vị vẫn còn hiệu lực, tôi hy vọng ít nhất là như vậy.
Jalayn

Tôi đã sửa lại ví dụ. Tôi không có đặc quyền để làm cho nó dễ nhìn thấy, vì vậy nó phải được xem xét trước. Nhận xét của bạn rất hữu ích.
Victor Stafusa

2

Tôi đã đọc phần "Nguồn gốc" của bài viết Wikipedia về Nguyên tắc phân chia giao diện và tôi đã được nhắc nhở về câu hỏi này.

Tôi sẽ trích dẫn bài viết. Vấn đề: "... một lớp Công việc chính .... một lớp chất béo với vô số phương thức dành riêng cho nhiều khách hàng khác nhau." Giải pháp: "... một lớp giao diện giữa lớp Công việc và tất cả các máy khách của nó ..."

Vấn đề của bạn có vẻ như hoán vị của một Xerox. Thay vì một lớp mỡ bạn có hai, và hai lớp mỡ này đang nói chuyện với nhau thay vì nhiều khách hàng.

Bạn có thể nhóm các phương thức theo loại tương tác và sau đó tạo một lớp giao diện cho từng loại không? Ví dụ: Các lớp RobotPowerInterface, RobotNavlationInterface, RobotAlarmInterface?

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.