Làm thế nào tôi có thể quản lý cơ sở mã của phần mềm phức tạp đáng kể?


8

Tôi thường tạo các chương trình cho cả bản thân và những người khác bằng các ngôn ngữ lập trình hướng đối tượng khác nhau. Khi làm như vậy, chúng thường tương đối nhỏ (nhiều nhất là vài nghìn dòng). Tuy nhiên, gần đây, tôi đã cố gắng tham gia vào các dự án lớn hơn, chẳng hạn như các công cụ trò chơi hoàn chỉnh. Khi làm như vậy, tôi dường như thường chạy vào một khối đường: sự phức tạp.

Trong các dự án nhỏ hơn của tôi, thật dễ dàng để nhớ một bản đồ tinh thần về cách mọi phần của chương trình hoạt động. Làm điều này, tôi có thể nhận thức đầy đủ về bất kỳ thay đổi nào sẽ ảnh hưởng đến phần còn lại của chương trình và tránh các lỗi rất hiệu quả cũng như xem chính xác cách một tính năng mới phù hợp với cơ sở mã. Tuy nhiên, khi tôi cố gắng tạo ra các dự án lớn hơn, tôi thấy không thể giữ một bản đồ tinh thần tốt dẫn đến mã rất lộn xộn và nhiều lỗi không lường trước được.

Ngoài vấn đề "bản đồ tinh thần" này, tôi thấy khó có thể giữ mã của mình tách rời khỏi các phần khác của chính nó. Ví dụ: nếu trong trò chơi nhiều người chơi có một lớp để xử lý vật lý chuyển động của người chơi và một lớp khác để xử lý kết nối mạng, thì tôi thấy không có cách nào để một trong các lớp này không dựa vào lớp kia để đưa dữ liệu chuyển động của người chơi vào hệ thống mạng để gửi nó qua mạng. Sự ghép nối này là một nguồn đáng kể của sự phức tạp can thiệp vào một bản đồ tinh thần tốt.

Cuối cùng, tôi thường thấy mình đến với một hoặc nhiều lớp "người quản lý" điều phối các lớp khác. Ví dụ, trong một trò chơi, một lớp sẽ xử lý vòng lặp đánh dấu chính và sẽ gọi các phương thức cập nhật trong các lớp người chơi và mạng. Điều này đi ngược lại triết lý về những gì tôi đã tìm thấy trong nghiên cứu của mình rằng mỗi lớp nên có thể kiểm tra đơn vị và có thể sử dụng độc lập với các lớp khác, vì bất kỳ lớp quản lý nào như vậy bởi mục đích của nó phụ thuộc vào hầu hết các lớp khác trong dự án. Ngoài ra, một lớp quản lý phối hợp các phần còn lại của chương trình là một nguồn đáng kể về độ phức tạp không thể ánh xạ.

Được kết hợp với nhau, điều này ngăn tôi viết phần mềm không có lỗi chất lượng cao với kích thước đáng kể. Các nhà phát triển chuyên nghiệp làm gì để đối phó hiệu quả với vấn đề này? Tôi đặc biệt quan tâm đến mục tiêu câu trả lời OOP tại Java và C ++, nhưng loại lời khuyên này có lẽ rất chung chung.

Ghi chú:

  • Tôi đã thử sử dụng các sơ đồ UML, nhưng điều đó dường như chỉ giúp ích cho vấn đề đầu tiên, và thậm chí chỉ khi nó liên quan đến cấu trúc lớp chứ không phải (ví dụ) sắp xếp các lệnh gọi phương thức liên quan đến những gì được khởi tạo trước.

7
"Khi giao dịch cổ phiếu, làm thế nào để tôi kiếm được nhiều tiền nhất có thể?" - cùng một câu hỏi, nghề nghiệp khác nhau.
MaxB

1
Bạn dành bao nhiêu thời gian cho thiết kế và người mẫu? Nhiều rắc rối chúng ta vấp phải trong quá trình phát triển xuất phát từ quan điểm rò rỉ. Các loại phối cảnh chúng tôi tập trung vào trong quá trình mô hình hóa. Sau đó, chúng tôi cố gắng giải quyết rò rỉ này bằng mã. Mã cũng bị rò rỉ của phối cảnh (sự trừu tượng thích hợp). Các dự án quy mô lớn cần quy hoạch hóa, để có bức tranh toàn cầu rõ ràng. Từ cấp độ của codebase sẽ khó có tầm nhìn. " Đừng để cây ngăn bạn nhìn thấy rừng ".
Laiv

4
Mô đun hóa và cách ly. Nhưng có vẻ như bạn đã làm điều đó, nhưng thất bại ở nó. Trong trường hợp đó, nó thực sự phụ thuộc vào loại dự án và loại công nghệ được sử dụng.
Euphoric

1
Tái cấu trúc liên tục là một phần cần thiết của phát triển phần mềm - hãy coi đó là một dấu hiệu cho thấy bạn đang làm điều gì đó đúng. Bất kỳ hệ thống không tầm thường nào cũng luôn chứa "những ẩn số chưa biết" - tức là những thứ mà bạn không thể lường trước cũng như không tính đến trong thiết kế ban đầu của bạn. Tái cấu trúc là thực tế của sự phát triển lặp lại và phát triển dần dần theo hướng giải pháp 'tốt'. Đó là lý do tại sao các nhà phát triển có xu hướng thích viết mã dễ tái cấu trúc, thường liên quan đến các nguyên tắc RẮN
Ben Cottrell

2
IMML UML thiếu loại loại sơ đồ hữu ích nhất cho việc này: sơ đồ luồng dữ liệu. Tuy nhiên, đó không phải là viên đạn bạc, mô đun hóa và cách ly không tự động đến bằng cách sử dụng sơ đồ luồng dữ liệu.
Doc Brown

Câu trả lời:


8

Tôi dường như thường chạy vào một khối đường: sự phức tạp.

Có toàn bộ sách viết về chủ đề này. Dưới đây là trích dẫn từ một trong những cuốn sách quan trọng nhất từng được viết về phát triển phần mềm, Hoàn thành mã của Steve McConnell :

Quản lý sự phức tạp là chủ đề kỹ thuật quan trọng nhất trong phát triển phần mềm. Theo quan điểm của tôi, điều quan trọng là mệnh lệnh kỹ thuật chính của Phần mềm phải quản lý sự phức tạp.

Bên cạnh đó, tôi rất khuyên bạn nên đọc cuốn sách nếu bạn có hứng thú với việc phát triển phần mềm (mà tôi cho rằng bạn làm, vì bạn đã hỏi câu hỏi này). Ít nhất, nhấp vào liên kết ở trên và đọc đoạn trích về các khái niệm thiết kế.


Ví dụ: nếu trong trò chơi nhiều người chơi có một lớp để xử lý vật lý chuyển động của người chơi và một lớp khác để xử lý kết nối mạng, thì tôi thấy không có cách nào để một trong các lớp này không dựa vào lớp kia để đưa dữ liệu chuyển động của người chơi vào hệ thống mạng để gửi nó qua mạng.

Trong trường hợp cụ thể này, tôi sẽ coi PlayerMovementCalculatorlớp của bạn và NetworkSystemlớp của bạn hoàn toàn không liên quan đến nhau; một lớp chịu trách nhiệm tính toán chuyển động của người chơi và lớp kia chịu trách nhiệm cho I / O mạng. Có lẽ ngay cả trong các mô-đun độc lập riêng biệt.

Tuy nhiên, tôi chắc chắn sẽ mong đợi có ít nhất một chút dây hoặc keo dán ở đâu đó bên ngoài các mô-đun làm trung gian dữ liệu và / hoặc sự kiện / tin nhắn giữa chúng. Ví dụ: bạn có thể viết một PlayerNetworkMediatorlớp bằng Mẫu hòa giải .

Một cách tiếp cận khả thi khác có thể là ghép đôi các mô-đun của bạn bằng Bộ tổng hợp sự kiện .

Trong trường hợp lập trình không đồng bộ, chẳng hạn như loại logic liên quan đến các ổ cắm mạng, bạn có thể sử dụng các Đài quan sát phơi bày để thu dọn mã 'nghe' các thông báo đó.

Lập trình không đồng bộ không nhất thiết có nghĩa là đa luồng; thông tin thêm về cấu trúc chương trình và điều khiển luồng (mặc dù đa luồng là trường hợp sử dụng rõ ràng cho tính không đồng bộ). Các đài quan sát có thể hữu ích trong một hoặc cả hai mô-đun đó để cho phép các lớp không liên quan đăng ký thay đổi thông báo.

Ví dụ:

  • NetworkMessageReceuredEvent
  • PlayerPocationChangedEvent
  • PlayerDisconnectedEvent

Vân vân.


Cuối cùng, tôi thường thấy mình đến với một hoặc nhiều lớp "người quản lý" điều phối các lớp khác. Ví dụ, trong một trò chơi, một lớp sẽ xử lý vòng lặp đánh dấu chính và sẽ gọi các phương thức cập nhật trong các lớp người chơi và mạng. Điều này đi ngược lại triết lý về những gì tôi đã tìm thấy trong nghiên cứu của mình rằng mỗi lớp nên có thể kiểm tra đơn vị và có thể sử dụng độc lập với các lớp khác, vì bất kỳ lớp quản lý nào như vậy bởi mục đích của nó phụ thuộc vào hầu hết các lớp khác trong dự án. Ngoài ra, một lớp quản lý phối hợp các phần còn lại của chương trình là một nguồn đáng kể về độ phức tạp không thể ánh xạ.

Trong khi một số điều này chắc chắn đi xuống để trải nghiệm; tên Managertrong một lớp thường chỉ ra một mùi thiết kế .

Khi đặt tên lớp, hãy xem xét chức năng mà lớp chịu trách nhiệm và cho phép tên lớp của bạn phản ánh những gì nó làm .

Vấn đề với Người quản lý trong mã, hơi giống vấn đề với Người quản lý tại nơi làm việc. Mục đích của họ có xu hướng mơ hồ và kém hiểu biết ngay cả chính họ; hầu hết thời gian chúng ta chỉ tốt hơn khi không có chúng hoàn toàn.

Lập trình hướng đối tượng chủ yếu là về hành vi . Một lớp không phải là một thực thể dữ liệu, mà là một đại diện của một số yêu cầu chức năng trong mã của bạn.

Nếu bạn có thể đặt tên cho một lớp dựa trên yêu cầu chức năng mà nó đáp ứng, bạn sẽ giảm cơ hội kết thúc với một loại Đối tượng Thiên Chúa cồng kềnh và có nhiều khả năng có một lớp có danh tính và mục đích trong chương trình của bạn.

Hơn nữa, sẽ rõ ràng hơn khi các phương thức và hành vi bổ sung bắt đầu len vào khi nó thực sự không thuộc về, vì tên sẽ bắt đầu sai - tức là bạn sẽ có một lớp đang làm cả đống thứ phát sinh ' t phản ánh bởi tên của nó

Cuối cùng, tránh sự cám dỗ của các lớp viết có tên trông giống như chúng thuộc về một mô hình quan hệ thực thể. Vấn đề với tên lớp như Player, Monster, Car, Dog, vv là rằng hàm ý gì về hành vi của họ, và dường như chỉ để mô tả một tập hợp các dữ liệu hoặc các thuộc tính liên quan một cách logic. Thiết kế hướng đối tượng không phải là mô hình dữ liệu, mô hình hành vi của nó.

Ví dụ, hãy xem xét hai cách khác nhau để mô hình hóa a MonsterPlayertính toán thiệt hại:

class Monster : GameEntity {
    dealDamage(...);
}

class Player : GameEntity {
    dealDamage(...);
}

Vấn đề ở đây là bạn có thể mong đợi một cách hợp lý PlayerMonstercó cả đống phương pháp khác có lẽ hoàn toàn không liên quan đến mức độ thiệt hại mà các thực thể này có thể gây ra (ví dụ Chuyển động); bạn đang trên đường đến Thiên Chúa được đề cập ở trên.

Một cách tiếp cận hướng đối tượng tự nhiên hơn là xác định tên của lớp dựa trên hành vi của nó, ví dụ:

class MonsterDamageDealer : IDamageDealer {
    dealDamage(...) { }
}

class PlayerDamageDealer : IDamageDealer {
    dealDamage(...) { }
}

Với kiểu thiết kế này, bạn PlayerMonstercác đối tượng có thể không có bất kỳ phương thức nào liên quan đến chúng bởi vì các đối tượng đó chứa dữ liệu cần thiết cho toàn bộ ứng dụng của bạn; chúng có lẽ chỉ là các thực thể dữ liệu đơn giản sống trong kho lưu trữ và chỉ chứa các trường / thuộc tính.

Cách tiếp cận này thường được gọi là Mô hình miền thiếu máu , được coi là mô hình chống đối với Thiết kế hướng tên miền (DDD) , nhưng các nguyên tắc RẮN tự nhiên dẫn bạn đến một sự tách biệt rõ ràng giữa các thực thể dữ liệu 'được chia sẻ' (có thể trong kho lưu trữ) và các lớp hành vi mô đun (tốt nhất là không trạng thái) trong biểu đồ đối tượng của ứng dụng của bạn.

RẮN và DDD là hai cách tiếp cận khác nhau đối với thiết kế OO; trong khi chúng giao thoa theo nhiều cách, chúng có xu hướng kéo theo các hướng đối nghịch liên quan đến bản sắc giai cấp và phân tách dữ liệu và hành vi.


Quay trở lại với trích dẫn của McConnell ở trên - quản lý sự phức tạp là lý do tại sao phát triển phần mềm là một nghề lành nghề hơn là một công việc văn thư trần tục. Trước khi McConnell viết cuốn sách của mình, Fred Brooks đã viết một bài báo về chủ đề tóm tắt gọn gàng câu trả lời cho câu hỏi của bạn - Không có viên đạn bạc nào để quản lý sự phức tạp.

Vì vậy, trong khi không có câu trả lời duy nhất, bạn có thể làm cho cuộc sống của mình dễ dàng hơn hoặc khó khăn hơn tùy thuộc vào cách bạn tiếp cận nó:

  • Hãy nhớ KISS , DRYYAGNI .
  • Hiểu cách áp dụng các Nguyên tắc RẮN của Phát triển Phần mềm / Thiết kế OO
  • Cũng hiểu Thiết kế hướng tên miền ngay cả khi có những nơi mà cách tiếp cận mâu thuẫn với các nguyên tắc RẮN; RẮN và DDD có xu hướng đồng ý với nhau nhiều hơn là họ không đồng ý.
  • Yêu cầu mã của bạn thay đổi - viết các bài kiểm tra tự động để nắm bắt được những thay đổi đó (Bạn không phải tuân theo TDD để viết các bài kiểm tra tự động hữu ích - thực sự, một số bài kiểm tra đó có thể là bài kiểm tra tích hợp sử dụng ứng dụng bảng điều khiển "vứt bỏ" hoặc ứng dụng khai thác thử nghiệm)
  • Quan trọng nhất - hãy thực dụng. Đừng nô lệ làm theo bất kỳ hướng dẫn nào; mặt trái của sự phức tạp là sự đơn giản, vì vậy nếu nghi ngờ (một lần nữa) - KISS

Câu trả lời chắc chắn. Pun dự định.
ipaul

4
Tôi không đồng ý rằng DDD và RẮN là bất đồng với nhau. Tôi nghĩ rằng họ là khá bổ sung trong thực tế. Một phần lý do tại sao chúng có vẻ mâu thuẫn, hoặc có thể trực giao, là chúng mô tả những thứ khác nhau ở một mức độ trừu tượng khác nhau.
RibaldEddie

1
@RibaldEddie Tôi đồng ý rằng họ là phần bổ sung; vấn đề chính mà tôi thấy là RẮN dường như tích cực khuyến khích mô hình miền được gọi là "Thiếu máu" là kết quả tự nhiên của việc đưa SRP đến kết luận hợp lý của nó về một trách nhiệm đơn lẻ thực sự, trong khi DDD gọi đó là phản đối mẫu. Hoặc có lẽ có điều gì đó về mô tả của Fowler về Mô hình miền thiếu máu mà tôi đã hiểu sai?
Ben Cottrell

1

Tôi nghĩ đó là về việc quản lý sự phức tạp, vốn luôn giảm xuống khi những thứ xung quanh bạn trở nên quá phức tạp để vẫn xoay sở với những gì bạn hiện có và có thể xảy ra một vài lần khi phát triển từ một công ty 1 người thành nửa triệu doanh nghiệp toàn cầu .

Trong sự phức tạp nói chung phát triển khi dự án của bạn phát triển. Và nói chung tại thời điểm đó, sự phức tạp trở nên nhiều (độc lập nếu là CNTT hoặc quản lý chương trình, chương trình hoặc doanh nghiệp hoàn chỉnh), bạn cần nhiều công cụ hơn hoặc thay thế các công cụ bằng các công cụ xử lý sự phức tạp tốt hơn đó là điều mà nhân loại đã làm mãi mãi. Và các công cụ đi đôi với các quy trình phức tạp hơn, vì ai đó cần phải xuất ra thứ gì đó mà người khác cần để làm việc. Và những người song hành với "những người thừa" có vai trò cụ thể, ví dụ như Kiến trúc sư doanh nghiệp không cần thiết với dự án 1 người tại nhà.

Đồng thời, phân loại của bạn đằng sau nó phải phát triển đằng sau sự phức tạp của bạn cho đến khi thời điểm phân loại quá phức tạp và bạn chuyển sang dữ liệu phi cấu trúc. Ví dụ: bạn có thể lập danh sách các trang web và phân loại chúng nhưng nếu bạn cần có danh sách 1 tỷ trang web thì không có cách nào để làm điều này trong một thời gian hợp lý để bạn đi đến các thuật toán thông minh hơn hoặc chỉ tìm kiếm thông qua dữ liệu.

Trong một dự án nhỏ, bạn có thể làm việc trên một ổ đĩa chung. Trên một dự án hơi lớn, bạn cài đặt .git hoặc .svn hoặc bất cứ điều gì. Trên một chương trình phức tạp với nhiều dự án, tất cả đều có các bản phát hành khác nhau, tất cả đều có bản phát hành khác nhau và tất cả đều có ngày phát hành khác nhau và tất cả đều phụ thuộc vào các hệ thống khác mà bạn có thể cần phải chuyển sang sử dụng nếu bạn chịu trách nhiệm quản lý cấu hình của tất cả thay vì chỉ là phiên bản một số chi nhánh của một dự án.

Vì vậy, tôi nghĩ rằng nỗ lực tốt nhất của con người cho đến bây giờ là sự kết hợp của các công cụ, vai trò cụ thể, quy trình để quản lý sự phức tạp ngày càng tăng của mọi thứ.

May mắn thay, có những công ty bán khung kinh doanh, khung quy trình, phân loại, mô hình dữ liệu, v.v., tùy thuộc vào ngành bạn có thể mua, khi độ phức tạp tăng lên đến mức mọi người đều thừa nhận rằng không có cách nào khác, dữ liệu- mô hình và xử lý ra khỏi ví dụ như một công ty bảo hiểm toàn cầu, từ đó bạn tìm cách tái cấu trúc các ứng dụng hiện có của mình để đưa mọi thứ trở lại phù hợp với khung đã được chứng minh chung bao gồm mô hình và quy trình đã được chứng minh.

Nếu không có thị trường cho ngành công nghiệp của bạn có lẽ có cơ hội :)


Có vẻ như về cơ bản, bạn đang đề xuất một số kết hợp "thuê người" hoặc "mua thứ gì đó sẽ khiến vấn đề của bạn biến mất ™" Tôi quan tâm đến sự phức tạp trong quản lý như một nhà phát triển cá nhân không có ngân sách làm việc cho các dự án sở thích (hiện tại).
john01dav

Tôi nghĩ ở cấp độ đó có lẽ câu hỏi của bạn là một cái gì đó như "tên 100 thực hành tốt nhất mà một kiến ​​trúc sư ứng dụng tốt áp dụng"? Bởi vì tôi nghĩ về bản chất anh ta đang quản lý sự phức tạp đằng sau mọi thứ anh ta làm. anh ta đặt ra các tiêu chuẩn để chiến đấu phức tạp.
edelwater

Đó là những gì tôi đang hỏi, nhưng tôi quan tâm đến một triết lý chung hơn là một danh sách.
john01dav

Đó là một câu hỏi lớn vì có hơn 21.000 cuốn sách về thực hành / mẫu / thiết kế kiến ​​trúc ứng dụng, v.v: safaribooksonline.com/search/?query=application%20arch
architecture

Cuốn sách chỉ có thể cho thấy sự phổ biến của một câu hỏi, không phải là câu trả lời hay. Nếu bạn nghĩ rằng cần một cuốn sách để trả lời câu hỏi này, thì bằng mọi cách hãy đưa ra khuyến nghị.
john01dav

0

Dừng viết mọi thứ OOP và thêm một số dịch vụ thay thế.

Ví dụ, gần đây tôi đã viết một GOAP cho một trò chơi. Bạn sẽ thấy các ví dụ trên internet hành động mạnh mẽ hơn tới công cụ trò chơi với phong cách OOP:

Action.Process();

Trong trường hợp của tôi, tôi cần xử lý các hành động cả trong động cơ và cả bên ngoài động cơ, mô phỏng các hành động theo cách ít chi tiết hơn mà người chơi không có mặt. Vì vậy, thay vì tôi đã sử dụng:

Interface IActionProcessor<TAction>
{
    void ProcessAction(TAction action);
}

điều này cho phép tôi tách rời logic và có hai lớp.

ActionProcesser_InEngine.ProcessAction()


ActionProcesser_BackEnd.ProcessAction()

Nó không phải là OOP, nhưng điều đó có nghĩa là các hành động hiện không có liên kết đến động cơ. Do đó, mô-đun GOAP của tôi hoàn toàn tách biệt với phần còn lại của trò chơi.

Sau khi hoàn thành, tôi chỉ sử dụng dll và không nghĩ về việc thực hiện nội bộ.


1
Làm thế nào là bộ xử lý hành động của bạn không hướng đối tượng?
Greg Burghardt

2
Tôi nghĩ rằng quan điểm của bạn là: Không Đừng cố gắng tạo ra một thiết kế OOP, bởi vì OOP chỉ là một trong nhiều kỹ thuật để giải quyết sự phức tạp - một cái gì đó khác (ví dụ như một cách tiếp cận thủ tục) có thể hoạt động tốt hơn trong trường hợp của bạn. Không quan trọng là bạn đến đó bằng cách nào, miễn là bạn có ranh giới mô-đun rõ ràng trừu tượng hơn các chi tiết triển khai.
amon

1
Tôi nghĩ vấn đề không nằm ở cấp mã. Đó là ở cấp độ cao hơn. Các vấn đề với codebase là triệu chứng không phải là bệnh. Giải pháp sẽ không đến từ mã. Tôi nghĩ nó sẽ đến từ thiết kế và mô hình.
Laiv

2
Nếu tôi đọc điều này tưởng tượng mình là OP, tôi thấy câu trả lời này thiếu chi tiết và không cung cấp bất kỳ thông tin hữu ích nào, vì nó không mô tả cách IActionProcessor thực sự cung cấp giá trị thay vì chỉ thêm một lớp cảm ứng khác.
whatsisname

sử dụng các dịch vụ như thế này là thủ tục chứ không phải OO. Điều này mang lại sự phân tách rõ ràng hơn giữa các trách nhiệm so với OO, mà OP đã tìm thấy có thể nhanh chóng trở nên quá ghép đôi
Ewan

0

Thật là một câu hỏi mẹ! Tôi có thể lúng túng khi thử cái này với những suy nghĩ kỳ quặc của mình (và tôi rất thích nghe những lời đề nghị nếu tôi thực sự tắt). Nhưng đối với tôi, điều hữu ích nhất tôi đã học được gần đây trong miền của mình (bao gồm cả chơi game trước đây, bây giờ là VFX) là thay thế các tương tác giữa các giao diện trừu tượng với dữ liệu như một cơ chế tách rời (và cuối cùng giảm lượng thông tin cần thiết giữa mọi thứ và về nhau đến mức tối thiểu tuyệt đối). Điều này nghe có vẻ hoàn toàn điên rồ (và tôi có thể đang sử dụng tất cả các loại thuật ngữ kém).

Tuy nhiên, hãy nói rằng tôi cung cấp cho bạn một công việc hợp lý có thể quản lý. Bạn có tập tin này chứa dữ liệu cảnh và hoạt hình để kết xuất. Có tài liệu bao gồm định dạng tập tin. Công việc duy nhất của bạn là tải tệp, hiển thị hình ảnh đẹp cho hoạt hình bằng cách sử dụng theo dõi đường dẫn và xuất kết quả ra tệp hình ảnh. Đó là một ứng dụng quy mô khá nhỏ mà có lẽ sẽ không vượt quá hàng chục nghìn LỘC ngay cả đối với một trình kết xuất khá tinh vi (chắc chắn không phải hàng triệu).

nhập mô tả hình ảnh ở đây

Bạn có thế giới riêng biệt nhỏ bé của riêng mình cho trình kết xuất này. Nó không bị ảnh hưởng bởi thế giới bên ngoài. Nó cô lập sự phức tạp của chính nó. Ngoài những lo ngại về việc đọc tệp cảnh này và xuất kết quả của bạn thành tệp hình ảnh, bạn có thể tập trung hoàn toàn vào chỉ hiển thị. Nếu có lỗi xảy ra trong quá trình, bạn biết đó là trong trình kết xuất và không có gì khác, vì không có gì khác liên quan đến bức ảnh này.

Trong khi đó, giả sử thay vào đó bạn phải làm cho trình kết xuất của mình hoạt động trong bối cảnh của một phần mềm hoạt hình lớn thực sự có hàng triệu LỘC. Thay vì chỉ đọc một định dạng tệp được sắp xếp hợp lý, tài liệu để lấy dữ liệu cần thiết để kết xuất, bạn phải trải qua tất cả các loại giao diện trừu tượng để lấy tất cả dữ liệu bạn cần để thực hiện:

nhập mô tả hình ảnh ở đây

Đột nhiên trình kết xuất của bạn không còn trong thế giới biệt lập nhỏ bé của riêng nó. Điều này cảm thấy như vậy, phức tạp hơn nhiều. Bạn phải hiểu thiết kế tổng thể của toàn bộ phần mềm như một tổng thể hữu cơ với nhiều bộ phận chuyển động và thậm chí đôi khi phải suy nghĩ về việc triển khai những thứ như mắt lưới hoặc máy ảnh nếu bạn gặp phải nút cổ chai hoặc lỗi trong một chức năng.

Chức năng so với dữ liệu được sắp xếp hợp lý

Và một trong những lý do là vì chức năng phức tạp hơn nhiều so với dữ liệu tĩnh. Cũng có rất nhiều cách mà một lệnh gọi hàm có thể sai theo cách mà việc đọc dữ liệu tĩnh không thể. Có rất nhiều tác dụng phụ tiềm ẩn có thể xảy ra khi gọi các chức năng đó mặc dù về mặt khái niệm nó chỉ lấy dữ liệu chỉ đọc để kết xuất. Nó cũng có thể có rất nhiều lý do để thay đổi. Vài tháng sau, bạn có thể thấy giao diện lưới hoặc kết cấu thay đổi hoặc không dùng các bộ phận theo cách yêu cầu bạn viết lại các phần lớn của trình kết xuất và theo kịp các thay đổi đó, mặc dù bạn đang tìm nạp cùng một dữ liệu đầu vào dữ liệu cho trình kết xuất của bạn không thay đổi bất cứ điều gì (chỉ có chức năng cần thiết để cuối cùng truy cập tất cả).

Vì vậy, khi có thể, tôi đã thấy rằng dữ liệu được sắp xếp hợp lý là một cơ chế tách riêng rất tốt, thực sự cho phép bạn tránh phải suy nghĩ về toàn bộ hệ thống và cho phép bạn chỉ tập trung vào một phần rất cụ thể của hệ thống để thực hiện cải tiến, thêm các tính năng mới, sửa chữa mọi thứ, v.v ... Đó là một suy nghĩ rất I / O cho các phần cồng kềnh tạo nên phần mềm của bạn. Nhập cái này, làm việc của bạn, xuất cái đó, và không trải qua hàng tá giao diện trừu tượng kết thúc các cuộc gọi chức năng vô tận trên đường đi. Và nó bắt đầu giống với một mức độ nào đó, lập trình chức năng.

Vì vậy, đây chỉ là một chiến lược và nó có thể không áp dụng cho tất cả mọi người. Và tất nhiên nếu bạn đang bay một mình, bạn vẫn phải duy trì mọi thứ (bao gồm cả định dạng của dữ liệu), nhưng điều khác biệt là khi bạn ngồi xuống để cải thiện trình kết xuất đó, bạn thực sự có thể chỉ tập trung vào trình kết xuất phần lớn và không có gì khác Nó trở nên quá cô lập trong thế giới nhỏ bé của chính nó - về sự cô lập nhất có thể với dữ liệu mà nó yêu cầu cho đầu vào được sắp xếp hợp lý.

Và tôi đã sử dụng ví dụ về định dạng tệp nhưng nó không phải là tệp cung cấp dữ liệu quan tâm hợp lý cho đầu vào. Nó có thể là một cơ sở dữ liệu trong bộ nhớ. Trong trường hợp của tôi, đó là một hệ thống thành phần thực thể với các thành phần lưu trữ dữ liệu quan tâm. Tuy nhiên, tôi đã tìm thấy nguyên tắc cơ bản này để tách rời dữ liệu được sắp xếp hợp lý (tuy nhiên bạn làm điều đó) nên đánh thuế vào khả năng tinh thần của tôi ít hơn nhiều so với các hệ thống trước đây mà tôi đã làm việc xoay quanh sự trừu tượng và rất nhiều và rất nhiều tương tác đang diễn ra giữa tất cả những điều này giao diện trừu tượng khiến bạn không thể chỉ ngồi xuống với một thứ và chỉ nghĩ về điều đó và những thứ khác. Não tôi tràn ngập bờ vực với những loại hệ thống trước đó và muốn bùng nổ vì có quá nhiều tương tác đang diễn ra giữa rất nhiều thứ,

Tách

Nếu bạn muốn giảm thiểu mức độ mã hóa lớn hơn cho bộ não của mình, thì hãy làm cho nó thành từng phần lớn của phần mềm (toàn bộ hệ thống kết xuất, toàn bộ hệ thống vật lý, v.v.) sống trong thế giới biệt lập nhất có thể. Giảm thiểu lượng giao tiếp và tương tác đi đến mức tối thiểu thông qua dữ liệu được sắp xếp hợp lý nhất. Bạn thậm chí có thể chấp nhận một số dự phòng (một số công việc dư thừa cho bộ xử lý hoặc thậm chí cho chính mình) nếu trao đổi là một hệ thống tách biệt hơn nhiều mà không phải nói chuyện với hàng tá thứ khác trước khi nó có thể thực hiện công việc của mình.

Và khi bạn bắt đầu làm điều đó, có cảm giác như bạn đang duy trì hàng tá ứng dụng quy mô nhỏ thay vì một ứng dụng khổng lồ. Và tôi thấy điều đó cũng vui hơn rất nhiều. Bạn có thể ngồi xuống và chỉ làm việc trên một hệ thống theo nội dung trái tim của bạn mà không liên quan đến bản thân với thế giới bên ngoài. Nó chỉ trở thành nhập dữ liệu đúng và xuất dữ liệu đúng ở cuối đến một nơi mà các hệ thống khác có thể lấy được (lúc đó một số hệ thống khác có thể nhập dữ liệu đó và thực hiện việc đó, nhưng bạn không cần phải quan tâm đến điều đó khi làm việc trên hệ thống của bạn). Tất nhiên, bạn vẫn phải suy nghĩ về cách mọi thứ tích hợp trong giao diện người dùng, chẳng hạn (tôi vẫn thấy mình phải suy nghĩ về toàn bộ thiết kế cho GUI), nhưng ít nhất là không phải khi bạn ngồi xuống và làm việc trên hệ thống hiện có đó hoặc quyết định thêm một cái mới.

Có lẽ tôi đang mô tả một cái gì đó rõ ràng cho những người luôn cập nhật các phương pháp kỹ thuật mới nhất. Tôi không biết. Nhưng nó không rõ ràng đối với tôi. Tôi muốn tiếp cận thiết kế phần mềm xung quanh các đối tượng tương tác với nhau và các chức năng được gọi cho phần mềm quy mô lớn. Và những cuốn sách tôi đọc ban đầu về thiết kế phần mềm quy mô lớn tập trung vào thiết kế giao diện bên trên những thứ như triển khai và dữ liệu (câu thần chú hồi đó là việc triển khai không quan trọng lắm, chỉ giao diện, bởi vì trước đây có thể dễ dàng thay thế hoặc thay thế ). Ban đầu, tôi không nghĩ đến việc tương tác với phần mềm khi sôi sục chỉ nhập và xuất dữ liệu giữa các hệ thống con lớn hầu như không nói chuyện với nhau ngoại trừ thông qua dữ liệu được sắp xếp hợp lý này. Tuy nhiên, khi tôi bắt đầu chuyển trọng tâm sang thiết kế xung quanh khái niệm đó, nó khiến mọi thứ trở nên dễ dàng hơn nhiều. Tôi có thể thêm rất nhiều mã mà không cần nổ não. Cảm giác như tôi đang xây dựng một trung tâm mua sắm thay vì một tòa tháp có thể đổ xuống nếu tôi thêm quá nhiều hoặc nếu có một vết nứt ở bất kỳ phần nào.

Triển khai phức tạp so với tương tác phức tạp

Đây là một điều khác tôi nên đề cập bởi vì tôi đã dành một phần tốt trong phần đầu của sự nghiệp để tìm kiếm những triển khai đơn giản nhất. Vì vậy, tôi đã phân tách mọi thứ thành các bit và mảnh nhỏ nhất và đơn giản nhất, nghĩ rằng tôi đang cải thiện khả năng bảo trì.

Nhìn lại, tôi không nhận ra mình đang trao đổi một loại phức tạp khác. Để giảm tất cả mọi thứ xuống các bit và phần đơn giản nhất, các tương tác diễn ra giữa các phần thiếu niên đó đã biến thành một mạng lưới tương tác phức tạp nhất với các lệnh gọi chức năng đôi khi đi sâu 30 bậc vào bảng gọi. Và tất nhiên, nếu bạn nhìn vào bất kỳ một chức năng nào, thì thật đơn giản và dễ dàng để biết nó làm gì. Nhưng bạn không nhận được nhiều thông tin hữu ích tại thời điểm đó vì mỗi chức năng hoạt động rất ít. Sau đó, bạn cuối cùng phải theo dõi tất cả các loại chức năng và nhảy qua tất cả các loại vòng để thực sự tìm ra những gì tất cả chúng làm theo cách có thể khiến não bạn muốn nổ tung nhiều hơn một,

Đó không phải là đề nghị các đối tượng thần hoặc bất cứ điều gì như thế. Nhưng có lẽ chúng ta không cần phải xé các vật thể lưới của chúng ta thành những thứ nhỏ nhất như một vật thể đỉnh, vật thể cạnh, vật thể mặt. Có lẽ chúng ta chỉ có thể giữ nó ở "lưới" với việc triển khai phức tạp hơn vừa phải đằng sau nó để đổi lấy các tương tác mã ít hơn. Tôi có thể xử lý một triển khai phức tạp vừa phải ở đây và đó. Tôi không thể xử lý một lượng lớn các tương tác với các tác dụng phụ xảy ra với những người hiểu biết ở đâu và theo thứ tự nào.

Ít nhất tôi thấy rằng việc đánh thuế vào não rất nhiều, ít hơn nhiều, bởi vì đó là những tương tác khiến não tôi bị tổn thương trong một cơ sở mã lớn. Không phải bất kỳ một điều cụ thể.

Tính tổng quát so với tính đặc hiệu

Có lẽ gắn liền với những điều trên, tôi đã từng yêu thích việc sử dụng lại tính tổng quát và mã, và từng nghĩ rằng thách thức lớn nhất của việc thiết kế một giao diện tốt là đáp ứng nhiều nhu cầu nhất vì giao diện sẽ được sử dụng bởi tất cả các loại khác nhau với các nhu cầu khác nhau. Và khi bạn làm điều đó, chắc chắn bạn phải nghĩ về hàng trăm thứ cùng một lúc, bởi vì bạn đang cố gắng cân bằng nhu cầu của một trăm thứ cùng một lúc.

Tổng quát hóa mọi thứ mất rất nhiều thời gian. Chỉ cần nhìn vào các thư viện tiêu chuẩn đi kèm với ngôn ngữ của chúng tôi. Thư viện chuẩn C ++ chứa rất ít chức năng, nhưng nó đòi hỏi các nhóm người phải duy trì và điều chỉnh với toàn bộ ủy ban của mọi người tranh luận và đưa ra các đề xuất về thiết kế của nó. Đó là bởi vì một chút chức năng nhỏ bé đó đang cố gắng xử lý toàn bộ nhu cầu của thế giới.

Có lẽ chúng ta không cần phải mang mọi thứ cho đến nay. Có lẽ không sao khi chỉ có một chỉ số không gian chỉ được sử dụng để phát hiện va chạm giữa các mắt lưới được lập chỉ mục và không có gì khác. Có lẽ chúng ta có thể sử dụng một cái khác cho các loại bề mặt khác, và một cái khác để kết xuất. Tôi đã từng rất tập trung vào việc loại bỏ các loại dư thừa này, nhưng một phần lý do là vì tôi đang phải đối phó với các cấu trúc dữ liệu rất kém hiệu quả được thực hiện bởi một loạt người. Đương nhiên, nếu bạn có một octree chỉ mất 1 gigabyte cho một lưới tam giác 300k, bạn không muốn có thêm một bộ nhớ nữa.

Nhưng tại sao các octrees không hiệu quả ngay từ đầu? Tôi có thể tạo các quãng tám chỉ mất 4 byte cho mỗi nút và mất ít hơn một megabyte để làm điều tương tự như phiên bản gigabyte đó trong khi xây dựng trong một phần nhỏ thời gian và thực hiện các truy vấn tìm kiếm nhanh hơn. Tại thời điểm đó một số dư thừa là hoàn toàn chấp nhận được.

Hiệu quả

Vì vậy, điều này chỉ liên quan đến các lĩnh vực quan trọng về hiệu suất nhưng bạn càng có được những thứ như hiệu quả bộ nhớ, bạn càng có khả năng lãng phí nhiều hơn một chút (có thể chấp nhận dư thừa một chút để đổi lấy việc giảm tính tổng quát hoặc tách rời) theo năng suất . Và nó giúp tôi khá tốt và thoải mái với các trình biên dịch của bạn và tìm hiểu về kiến ​​trúc máy tính và hệ thống phân cấp bộ nhớ, bởi vì sau đó bạn có thể hy sinh nhiều hơn để đạt được hiệu quả để đổi lấy năng suất vì mã của bạn đã rất hiệu quả và đủ khả năng để có được kém hiệu quả hơn một chút ngay cả trong các lĩnh vực quan trọng trong khi vẫn vượt trội so với đối thủ. Tôi đã thấy rằng việc cải thiện trong lĩnh vực này cũng đã cho phép tôi thoát khỏi những triển khai đơn giản và đơn giản hơn,

độ tin cậy

Đây là loại hiển nhiên nhưng cũng có thể đề cập đến nó. Những điều đáng tin cậy nhất của bạn đòi hỏi chi phí trí tuệ tối thiểu. Bạn không cần phải suy nghĩ nhiều về họ. Họ chỉ làm việc. Kết quả là bạn càng phát triển danh sách các bộ phận cực kỳ đáng tin cậy cũng "ổn định" (không cần thay đổi) thông qua thử nghiệm kỹ lưỡng, bạn càng ít phải suy nghĩ.

Cụ thể

Vì vậy, tất cả những điều trên bao gồm một số điều chung có ích cho tôi, nhưng hãy chuyển sang các khía cạnh cụ thể hơn cho khu vực của bạn:

Trong các dự án nhỏ hơn của tôi, thật dễ dàng để nhớ một bản đồ tinh thần về cách mọi phần của chương trình hoạt động. Làm điều này, tôi có thể nhận thức đầy đủ về bất kỳ thay đổi nào sẽ ảnh hưởng đến phần còn lại của chương trình và tránh các lỗi rất hiệu quả cũng như xem chính xác cách một tính năng mới phù hợp với cơ sở mã. Tuy nhiên, khi tôi cố gắng tạo ra các dự án lớn hơn, tôi thấy không thể giữ một bản đồ tinh thần tốt dẫn đến mã rất lộn xộn và nhiều lỗi không lường trước được.

Đối với tôi điều này có xu hướng liên quan đến các tác dụng phụ phức tạp và các luồng điều khiển phức tạp. Đó là một cái nhìn khá thấp về mọi thứ nhưng tất cả các giao diện đẹp nhất và tất cả sự tách rời từ cụ thể đến trừu tượng không thể làm cho mọi lý do dễ dàng hơn về các tác dụng phụ phức tạp xảy ra trong các luồng điều khiển phức tạp.

Đơn giản hóa / giảm các tác dụng phụ và / hoặc đơn giản hóa các luồng điều khiển, lý tưởng là cả hai. và nói chung bạn sẽ thấy dễ dàng hơn nhiều để suy luận về những gì các hệ thống lớn hơn làm và cả những gì sẽ xảy ra để đáp ứng với những thay đổi của bạn.

Ngoài vấn đề "bản đồ tinh thần" này, tôi thấy khó có thể giữ mã của mình tách rời khỏi các phần khác của chính nó. Ví dụ: nếu trong trò chơi nhiều người chơi có một lớp để xử lý vật lý chuyển động của người chơi và một lớp khác để xử lý kết nối mạng, thì tôi thấy không có cách nào để một trong các lớp này không dựa vào lớp kia để đưa dữ liệu chuyển động của người chơi vào hệ thống mạng để gửi nó qua mạng. Sự ghép nối này là một nguồn đáng kể của sự phức tạp can thiệp vào một bản đồ tinh thần tốt.

Về mặt khái niệm bạn phải có một số khớp nối. Khi mọi người nói về việc tách rời, họ thường có nghĩa là thay thế một loại bằng một loại khác, mong muốn hơn (thường là hướng tới sự trừu tượng). Đối với tôi, với miền của tôi, cách thức hoạt động của bộ não, v.v. loại mong muốn nhất để giảm các yêu cầu "bản đồ tinh thần" xuống mức tối thiểu là dữ liệu được sắp xếp hợp lý ở trên. Một hộp đen phun ra dữ liệu được đưa vào một hộp đen khác và cả hai hoàn toàn không biết gì về sự tồn tại của nhau. Họ chỉ biết một số vị trí trung tâm nơi dữ liệu được lưu trữ (ví dụ: hệ thống tệp trung tâm hoặc cơ sở dữ liệu trung tâm) thông qua đó họ tìm nạp dữ liệu đầu vào, làm một cái gì đó và nhổ ra một đầu ra mới mà một số hộp đen khác có thể nhập vào.

Nếu bạn làm theo cách này, hệ thống vật lý sẽ phụ thuộc vào cơ sở dữ liệu trung tâm và hệ thống mạng sẽ phụ thuộc vào cơ sở dữ liệu trung tâm, nhưng họ sẽ không biết gì về nhau. Họ thậm chí sẽ không biết nhau tồn tại. Họ thậm chí sẽ không biết rằng các giao diện trừu tượng cho nhau tồn tại.

Cuối cùng, tôi thường thấy mình đến với một hoặc nhiều lớp "người quản lý" điều phối các lớp khác. Ví dụ, trong một trò chơi, một lớp sẽ xử lý vòng lặp đánh dấu chính và sẽ gọi các phương thức cập nhật trong các lớp người chơi và mạng. Điều này đi ngược lại triết lý về những gì tôi đã tìm thấy trong nghiên cứu của mình rằng mỗi lớp nên có thể kiểm tra đơn vị và có thể sử dụng độc lập với các lớp khác, vì bất kỳ lớp quản lý nào như vậy bởi mục đích của nó phụ thuộc vào hầu hết các lớp khác trong dự án. Ngoài ra, một lớp quản lý phối hợp các phần còn lại của chương trình là một nguồn đáng kể về độ phức tạp không thể ánh xạ.

Bạn có xu hướng cần một cái gì đó để phối hợp tất cả các hệ thống trong trò chơi của bạn. Trung tâm có thể ít nhất là ít phức tạp và dễ quản lý hơn giống như một hệ thống vật lý gọi một hệ thống kết xuất sau khi nó được thực hiện. Nhưng ở đây chắc chắn chúng ta cần một số chức năng được gọi, và tốt nhất là chúng trừu tượng.

Vì vậy, bạn có thể tạo một giao diện trừu tượng cho một hệ thống có updatechức năng trừu tượng . Sau đó, nó có thể tự đăng ký với công cụ trung tâm và hệ thống mạng của bạn có thể nói, "Này, tôi là một hệ thống và đây là chức năng cập nhật của tôi. Thỉnh thoảng hãy gọi cho tôi." Và sau đó công cụ của bạn có thể lặp qua tất cả các hệ thống như vậy và cập nhật chúng mà không cần gọi chức năng mã hóa cứng đến các hệ thống cụ thể.

Điều đó cho phép các hệ thống của bạn sống nhiều hơn trong thế giới biệt lập của riêng chúng. Công cụ trò chơi không cần phải biết về chúng một cách cụ thể (một cách cụ thể) nữa. Và sau đó, hệ thống vật lý của bạn có thể có chức năng cập nhật được gọi, tại thời điểm đó, nó nhập dữ liệu cần thiết từ cơ sở dữ liệu trung tâm cho mọi chuyển động, áp dụng vật lý, sau đó đưa ra chuyển động kết quả trở lại.

Sau đó, hệ thống mạng của bạn có thể có chức năng cập nhật được gọi, tại thời điểm đó, nó nhập dữ liệu cần thiết từ cơ sở dữ liệu trung tâm và đầu ra, giả sử, dữ liệu ổ cắm cho máy khách. Một lần nữa, mục tiêu mà tôi thấy là cô lập từng hệ thống càng nhiều càng tốt để nó có thể sống trong thế giới nhỏ bé của riêng mình với kiến ​​thức tối thiểu về thế giới bên ngoài. Về cơ bản, đó là cách tiếp cận được áp dụng trong ECS ​​phổ biến trong các công cụ trò chơi.

ECS

Tôi đoán rằng tôi nên đề cập đến ECS một chút vì rất nhiều suy nghĩ của tôi ở trên xoay quanh ECS và cố gắng hợp lý hóa lý do tại sao cách tiếp cận hướng dữ liệu này để bảo trì dễ dàng hơn nhiều so với các hệ thống dựa trên đối tượng và COM mà tôi duy trì trong quá khứ mặc dù vi phạm chỉ về tất cả mọi thứ tôi giữ thiêng liêng ban đầu mà tôi đã học về SE. Ngoài ra, nó có thể có ý nghĩa với bạn nếu bạn đang cố gắng xây dựng các trò chơi quy mô lớn hơn. Vì vậy, ECS hoạt động như thế này:

nhập mô tả hình ảnh ở đây

Và như trong sơ đồ trên, MovementSystemcó thể có updatechức năng của nó được gọi. Tại thời điểm này, nó có thể truy vấn cơ sở dữ liệu trung tâm cho PosAndVelocitycác thành phần dưới dạng dữ liệu cần nhập (các thành phần chỉ là dữ liệu, không có chức năng). Sau đó, nó có thể lặp qua các vị trí đó, sửa đổi vị trí / vận tốc và đưa ra kết quả mới một cách hiệu quả. Sau đó, RenderingSystemcó thể có chức năng cập nhật của nó được gọi, tại đó nó truy vấn cơ sở dữ liệu PosAndVelocitySpritecác thành phần, và xuất hình ảnh ra màn hình dựa trên dữ liệu đó.

Tất cả các hệ thống hoàn toàn không biết gì về sự tồn tại của nhau và thậm chí chúng không cần phải hiểu những gì CarLà. Họ chỉ cần biết các thành phần cụ thể về sở thích của mỗi hệ thống tạo nên dữ liệu cần thiết để đại diện cho một. Mỗi hệ thống giống như một hộp đen. Nó nhập dữ liệu và xuất dữ liệu với kiến ​​thức tối thiểu về thế giới bên ngoài và thế giới bên ngoài cũng có kiến ​​thức tối thiểu về nó. Có thể có một số sự kiện đẩy từ một hệ thống và xuất hiện từ một hệ thống khác, do đó, sự va chạm của hai thực thể trong hệ thống vật lý có thể khiến âm thanh nhìn thấy một sự kiện va chạm khiến nó phát ra âm thanh, nhưng các hệ thống vẫn có thể bị lãng quên về nhau. Và tôi đã tìm thấy những hệ thống như vậy dễ dàng hơn nhiều để lý do. Chúng không khiến não tôi muốn nổ tung ngay cả khi bạn có hàng tá hệ thống, bởi vì mỗi hệ thống đều bị cô lập. Bạn không' Không phải suy nghĩ về sự phức tạp của tất cả mọi thứ khi bạn phóng to và làm việc trên bất kỳ cái nào. Và do đó, rất dễ dàng để dự đoán kết quả của những thay đổi của bạn.

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.