Tôi đã xem bài nói " Suy nghĩ về dữ liệu " của Stuart Sierra và lấy một trong những ý tưởng từ đó làm nguyên tắc thiết kế trong trò chơi này. Sự khác biệt là anh ấy làm việc ở Clojure và tôi đang làm việc với JavaScript. Tôi thấy một số khác biệt chính giữa các ngôn ngữ của chúng tôi ở chỗ:
- Clojure là lập trình chức năng thành ngữ
- Hầu hết nhà nước là bất biến
Tôi lấy ý tưởng từ slide "Mọi thứ là bản đồ" (Từ 11 phút, 6 giây đến> 29 phút). Một số điều ông nói là:
- Bất cứ khi nào bạn thấy một hàm có 2-3 đối số, bạn có thể tạo một trường hợp để biến nó thành bản đồ và chỉ cần truyền bản đồ vào. Có rất nhiều lợi thế cho điều đó:
- Bạn không phải lo lắng về thứ tự tranh luận
- Bạn không phải lo lắng về bất kỳ thông tin bổ sung. Nếu có thêm chìa khóa, đó không thực sự là mối quan tâm của chúng tôi. Họ chỉ chảy qua, họ không can thiệp.
- Bạn không phải xác định một lược đồ
- Trái ngược với việc truyền vào Object không có dữ liệu nào che giấu. Tuy nhiên, anh ta đưa ra trường hợp rằng việc ẩn dữ liệu có thể gây ra vấn đề và bị đánh giá quá cao:
- Hiệu suất
- Dễ thực hiện
- Ngay khi bạn liên lạc qua mạng hoặc qua các quy trình, bạn phải có cả hai bên đồng ý về việc trình bày dữ liệu. Đó là công việc làm thêm bạn có thể bỏ qua nếu bạn chỉ làm việc trên dữ liệu.
Liên quan nhất đến câu hỏi của tôi. Đây là 29 phút trong: "Làm cho các chức năng của bạn có thể ghép lại được". Đây là mẫu mã anh ta sử dụng để giải thích khái niệm:
;; Bad (defn complex-process [] (let [a (get-component @global-state) b (subprocess-one a) c (subprocess-two a b) d (subprocess-three a b c)] (reset! global-state d))) ;; Good (defn complex-process [state] (-> state subprocess-one subprocess-two subprocess-three))
Tôi hiểu phần lớn các lập trình viên không quen thuộc với Clojure, vì vậy tôi sẽ viết lại điều này theo phong cách bắt buộc:
;; Good def complex-process(State state) state = subprocess-one(state) state = subprocess-two(state) state = subprocess-three(state) return state
Dưới đây là những ưu điểm:
- Dễ kiểm tra
- Dễ dàng nhìn vào các chức năng đó trong sự cô lập
- Dễ dàng nhận xét một dòng về điều này và xem kết quả là gì bằng cách loại bỏ một bước duy nhất
- Mỗi quy trình con có thể thêm thông tin vào trạng thái. Nếu một quy trình con cần truyền đạt một cái gì đó đến quy trình ba, thì đơn giản như thêm khóa / giá trị.
- Không có bản tóm tắt để trích xuất dữ liệu bạn cần ra khỏi trạng thái chỉ để bạn có thể lưu lại. Chỉ cần chuyển qua toàn bộ trạng thái và để quy trình con chỉ định những gì nó cần.
Bây giờ, trở lại tình huống của tôi: tôi đã học bài học này và áp dụng nó vào trò chơi của mình. Đó là, gần như tất cả các hàm cấp cao của tôi lấy và trả về một gameState
đối tượng. Đối tượng này chứa tất cả dữ liệu của trò chơi. EG: Danh sách badGuys, danh sách các menu, loot trên mặt đất, v.v ... Đây là một ví dụ về chức năng cập nhật của tôi:
update(gameState)
...
gameState = handleUnitCollision(gameState)
...
gameState = handleLoot(gameState)
...
Điều tôi ở đây để hỏi là, tôi đã tạo ra một số gớm ghiếc làm sai lệch một ý tưởng chỉ thực tế trong ngôn ngữ lập trình chức năng chưa? JavaScript không có chức năng thành ngữ (mặc dù nó có thể được viết theo cách đó) và thực sự rất khó khăn để viết các cấu trúc dữ liệu bất biến. Một điều khiến tôi lo lắng là anh ta giả định rằng mỗi quy trình con đó là thuần túy. Tại sao giả định đó cần phải được thực hiện? Thật hiếm khi bất kỳ chức năng nào của tôi là thuần túy (điều đó, ý tôi là chúng thường sửa đổi gameState
. Tôi không có bất kỳ tác dụng phụ phức tạp nào khác ngoài điều đó). Những ý tưởng này có sụp đổ nếu bạn không có dữ liệu bất biến?
Tôi lo lắng rằng một ngày nào đó tôi sẽ thức dậy và nhận ra toàn bộ thiết kế này là một sự giả tạo và tôi thực sự vừa mới thực hiện mô hình chống bóng Big Ball Of Mud .
Thành thật mà nói, tôi đã làm việc với mã này trong nhiều tháng và nó thật tuyệt. Tôi cảm thấy như mình đang nhận được tất cả những lợi thế mà anh ấy đã tuyên bố. Mã của tôi là siêu dễ dàng cho tôi lý do về. Nhưng tôi là một đội một người nên tôi có lời nguyền về kiến thức.
Cập nhật
Tôi đã mã hóa hơn 6 tháng với mẫu này. Thông thường đến lúc này tôi quên những gì tôi đã làm và đó là nơi "tôi đã viết điều này một cách sạch sẽ?" vào trong chơi. Nếu tôi không, tôi thực sự đấu tranh. Cho đến nay, tôi không phải vật lộn chút nào.
Tôi hiểu làm thế nào một bộ mắt khác sẽ cần thiết để xác nhận khả năng duy trì của nó. Tất cả những gì tôi có thể nói là tôi quan tâm đến khả năng bảo trì trước hết. Tôi luôn là người truyền giáo lớn nhất cho mã sạch cho dù tôi làm việc ở đâu.
Tôi muốn trả lời trực tiếp cho những người đã có trải nghiệm cá nhân tồi tệ với cách mã hóa này. Lúc đó tôi không biết, nhưng tôi nghĩ chúng ta thực sự đang nói về hai cách viết mã khác nhau. Cách tôi đã làm dường như có cấu trúc hơn những gì người khác đã trải qua. Khi ai đó có trải nghiệm cá nhân tồi tệ với "Mọi thứ là bản đồ", họ nói về việc duy trì nó khó đến mức nào bởi vì:
- Bạn không bao giờ biết cấu trúc của bản đồ mà hàm yêu cầu
- Bất kỳ chức năng nào cũng có thể làm thay đổi đầu vào theo những cách bạn không bao giờ mong đợi. Bạn phải xem xét tất cả các cơ sở mã để tìm hiểu làm thế nào một khóa cụ thể đã vào bản đồ hoặc tại sao nó biến mất.
Đối với những người có trải nghiệm như vậy, có lẽ cơ sở mã là "Mọi thứ cần 1 trong N loại bản đồ". Của tôi là, "Mọi thứ cần 1 trên 1 loại bản đồ". Nếu bạn biết cấu trúc của 1 loại đó, bạn sẽ biết cấu trúc của mọi thứ. Tất nhiên, cấu trúc đó thường phát triển theo thời gian. Đó là lý do ...
Có một nơi để tìm kiếm triển khai tham chiếu (ví dụ: lược đồ). Việc triển khai tham chiếu này là mã mà trò chơi sử dụng để nó không bị lỗi thời.
Đối với điểm thứ hai, tôi không thêm / xóa các khóa vào bản đồ bên ngoài triển khai tham chiếu, tôi chỉ thay đổi những gì đã có. Tôi cũng có một bộ lớn các bài kiểm tra tự động.
Nếu kiến trúc này cuối cùng sụp đổ dưới sức nặng của chính nó, tôi sẽ thêm một bản cập nhật thứ hai. Nếu không, giả sử mọi thứ đang diễn ra tốt đẹp :)