Làm thế nào để tránh javascript trở thành mã spaghetti?


8

Tôi đã thực hiện khá nhiều javascript trong những năm qua và tôi đang sử dụng một cách tiếp cận hướng đối tượng hơn, đặc biệt là với mô hình mô-đun. Bạn sử dụng cách tiếp cận nào để tránh cơ sở mã lớn hơn để trở thành spaghetti. Có "cách tiếp cận tốt nhất" nào không?


1
Làm thế nào bạn biết nếu bạn đã viết mã dễ đọc và dễ bảo trì? - Đồng nghiệp của bạn cho bạn biết sau khi xem lại mã.
gnat

Nhưng nếu một nhà phát triển nghĩ rằng công cụ trở nên phức tạp sau 3 dòng mã? Làm gì sau đó?
marko

2
Bạn không thể tự xác định điều này bởi vì bạn biết nhiều hơn với tư cách là tác giả. Một máy tính không thể cho bạn biết, vì những lý do tương tự mà nó không thể biết liệu một bức tranh có phải là nghệ thuật hay không. Do đó, bạn cần một người khác - có khả năng duy trì phần mềm - để xem xét những gì bạn đã viết và đưa ra ý kiến ​​của người đó. Tên chính thức của quy trình nói trên là "Đánh giá ngang hàng" ( nguồn trích dẫn - câu trả lời được bình chọn hàng đầu cho một câu hỏi giống với câu hỏi của bạn)
gnat

Điều đó sẽ thật tuyệt vời nếu chúng ta làm việc.
marko

Tái cấu trúc liên tục. Những quyết định tồi tệ luôn được đưa ra. Chúng trở thành nợ kỹ thuật một khi bạn ngừng quan tâm đến chúng.
Pithikos

Câu trả lời:


7

Dan Wahlin đưa ra hướng dẫn cụ thể về việc tránh mã spaghetti chức năng trong JavaScript.

Hầu hết mọi người (bao gồm cả tôi) bắt đầu viết mã JavaScript bằng cách thêm hàm sau hàm vào tệp .js hoặc HTML. Mặc dù chắc chắn không có gì sai với cách tiếp cận đó vì nó hoàn thành công việc, nó có thể nhanh chóng vượt khỏi tầm kiểm soát khi làm việc với rất nhiều mã. Khi gộp các hàm vào một tệp, việc tìm mã có thể khó khăn, việc tái cấu trúc mã là một việc rất lớn (trừ khi bạn có một công cụ hay như Resharper 6.0), phạm vi biến có thể trở thành một vấn đề và việc thực hiện bảo trì mã có thể là một cơn ác mộng đặc biệt là ban đầu bạn không viết nó.

Ông mô tả một vài mẫu thiết kế JavaScript cung cấp cấu trúc cho mã.

Tôi thích các mẫu nguyên mẫu tiết lộ . Yêu thích thứ hai của tôi là mẫu mô-đun tiết lộ , khác một chút so với mẫu mô-đun chuẩn ở chỗ bạn có thể khai báo phạm vi công khai / riêng tư.


Vâng, đó là những gì tôi đang làm, khi xây dựng những thứ lớn hơn.
marko

1
Nhưng nơi tôi nhìn thấy mẫu được đặt tên lần đầu tiên, phải có trong Mẫu Javascript - ( amazon.com/JavaScript-Potypes-Stoyan-Stefanov/dp/0596806752/ trộm ).
marko

Tôi ghét trở thành anh chàng nói "chỉ sử dụng một khung" ... nhưng đối với tôi, tôi luôn nghĩ về anh chàng đến sau tôi, và có một khuôn khổ được cộng đồng kiểm tra, ghi chép và tài liệu tốt ... nó không phải là một trí tuệ đối với tôi. Yêu thích của tôi là Javascript MVC (sắp có CanJS). Nó có các kịch bản giàn giáo và trình quản lý phụ thuộc rất trực quan ( steal()) cho phép bạn có một ứng dụng có cấu trúc rất tốt trong khi biên dịch các tập lệnh xuống còn 1 hoặc 2 tệp được rút gọn.
Ryan Wheale

4

Nếu bạn đang sử dụng OOP trong một ngôn ngữ hiện đại, mối nguy hiểm lớn nhất thường không phải là " mã spaghetti " mà là " mã ravioli". Bạn có thể kết thúc việc phân chia và chinh phục các vấn đề về nơi mà cơ sở mã của bạn bao gồm các phần của nó, các chức năng và đối tượng tuổi teen, tất cả được ghép lỏng lẻo và thực hiện các trách nhiệm đơn lẻ nhưng thiếu niên, tất cả đều được thử nghiệm với các bài kiểm tra đơn vị, với một mạng nhện tương tác trừu tượng đang diễn ra khiến cho rất khó để suy luận về những gì đang xảy ra liên quan đến những thứ như tác dụng phụ. Và thật dễ dàng để nghĩ rằng bạn đã thiết kế nó đẹp như vậy, vì các mảnh riêng lẻ thực sự có thể đẹp và tuân thủ RẮN, trong khi vẫn tìm thấy bộ não trên bờ vực bùng nổ từ sự phức tạp của tất cả các tương tác khi cố gắng hiểu toàn bộ hệ thống.

Và mặc dù rất dễ để suy luận về bất kỳ một trong những đối tượng hoặc chức năng này làm gì, vì chúng thực hiện một trách nhiệm đơn lẻ và đơn giản và thậm chí có thể đẹp trong khi thể hiện ít nhất sự phụ thuộc trừu tượng của chúng thông qua DI, vấn đề là khi bạn muốn để phân tích bức tranh lớn, thật khó để tìm ra hàng ngàn thứ tuổi teen với một mạng nhện tương tác cuối cùng sẽ làm gì. Tất nhiên mọi người nói, chỉ cần nhìn vào các vật thể lớn và các chức năng lớn được ghi lại và không đi sâu vào những người trẻ tuổi, và tất nhiên điều đó sẽ giúp ít nhất hiểu được những gì sẽ xảy ra theo cách cấp cao. ..

Tuy nhiên, điều đó không giúp được gì nhiều khi bạn cần thực sự thay đổi hoặc gỡ lỗi mã, tại thời điểm đó, bạn phải có khả năng tìm ra những gì tất cả những điều này cộng lại để thực hiện các ý tưởng cấp thấp hơn như tác dụng phụ và thay đổi trạng thái liên tục và làm thế nào để duy trì bất biến trên toàn hệ thống. Và thật khó để ghép các tác dụng phụ xảy ra giữa các tương tác của hàng ngàn thứ tuổi teen cho dù họ có sử dụng giao diện trừu tượng để giao tiếp với nhau hay không.

ECS

Vì vậy, điều cuối cùng mà tôi tìm thấy để giảm thiểu vấn đề này thực sự là các hệ thống thành phần thực thể, nhưng điều đó có thể là quá mức cần thiết cho nhiều dự án. Tôi đã yêu ECS đến mức bây giờ, ngay cả khi tôi viết các dự án nhỏ, tôi sử dụng công cụ ECS của mình (mặc dù dự án nhỏ đó có thể chỉ có một hoặc hai hệ thống). Tuy nhiên, đối với những người không quan tâm đến ECS, tôi đã cố gắng tìm hiểu tại sao ECS đơn giản hóa khả năng hiểu hệ thống rất nhiều và tôi nghĩ rằng tôi nên áp dụng một số điều nên áp dụng cho nhiều dự án ngay cả khi họ không sử dụng kiến ​​trúc ECS.

Vòng lặp đồng nhất

Một khởi đầu cơ bản là ưu tiên các vòng lặp đồng nhất hơn, có xu hướng ngụ ý nhiều lượt truyền hơn trên cùng một dữ liệu, nhưng các đường chuyền đồng đều hơn. Ví dụ, thay vì làm điều này:

for each entity:
    apply physics to entity
    apply AI to entity
    apply animation to entity
    update entity textures
    render entity

... bằng cách nào đó dường như sẽ giúp rất nhiều nếu bạn làm điều này thay vào đó:

for each entity:
    apply physics to entity

for each entity:
    apply AI to entity

etc.

Và điều đó có vẻ lãng phí khi lặp đi lặp lại trên cùng một dữ liệu nhiều lần, nhưng bây giờ mỗi lần vượt qua đều rất đồng nhất. Nó cho phép bạn nghĩ, "Được rồi, trong giai đoạn này của hệ thống, không có gì xảy ra với những vật thể này ngoại trừ vật lý. Nếu có những thứ bị thay đổi và tác dụng phụ xảy ra, tất cả chúng đều được thay đổi một cách rất thống nhất. " Và bằng cách nào đó tôi thấy điều đó giúp lý luận về codebase rất nhiều.

Mặc dù nó có vẻ lãng phí, nhưng nó cũng có thể giúp bạn tìm thấy nhiều cơ hội hơn để song song mã khi các tác vụ thống nhất đang được áp dụng trên tất cả mọi thứ trong mỗi vòng lặp. Và nó cũng có xu hướng để khuyến khíchmột mức độ tách rời lớn hơn. Tự nhiên khi bạn có những đường chuyền ly dị này mà không cố gắng làm mọi thứ với một đối tượng trong một lượt, bạn có xu hướng tìm nhiều cơ hội hơn để dễ dàng tách mã và giữ cho nó tách rời. Trong ECS, các hệ thống thường được tách rời hoàn toàn với nhau và không có "lớp" hoặc "chức năng" bên ngoài nào tự phối hợp chúng với nhau. ECS cũng không bị lỗi bộ nhớ cache lặp đi lặp lại vì nó không nhất thiết phải lặp trên cùng một dữ liệu nhiều lần (mỗi vòng lặp có thể truy cập các thành phần khác nhau nằm hoàn toàn ở nơi khác trong bộ nhớ, nhưng được liên kết với cùng một thực thể). Các hệ thống không phải được phối hợp thủ công vì chúng tự trị và chịu trách nhiệm cho việc lặp. Họ chỉ cần truy cập vào cùng một dữ liệu trung tâm.

Vì vậy, đó là một cách để bắt đầu có thể giúp bạn thiết lập một loại luồng kiểm soát đơn giản và thống nhất hơn trên hệ thống của bạn.

Làm phẳng xử lý sự kiện

Một cách khác là giảm sự phụ thuộc vào xử lý sự kiện. Xử lý sự kiện thường là cần thiết để tìm ra những điều bên ngoài đã xảy ra mà không bỏ phiếu, nhưng thường có nhiều cách để tránh các sự kiện đẩy tầng dẫn đến các luồng điều khiển và tác dụng phụ rất khó dự đoán. Xử lý sự kiện, về bản chất, có xu hướng xử lý những điều phức tạp xảy ra với một đối tượng nhỏ tại một thời điểm, khi chúng ta muốn tập trung vào những điều đơn giản và thống nhất xảy ra với nhiều đối tượng tại một thời điểm.

Vì vậy, ví dụ, thay vì thay đổi kích thước hệ điều hành thay đổi kích thước điều khiển cha mẹ, sau đó bắt đầu thay đổi kích thước và vẽ các sự kiện cho mỗi đứa trẻ có thể xếp nhiều sự kiện hơn cho ai biết, bạn chỉ có thể kích hoạt thay đổi kích thước sự kiện và đánh dấu cha mẹ và con cái như dirtyvà cần phải được sơn lại. Bạn thậm chí có thể đánh dấu tất cả các điều khiển là cần thay đổi kích thước tại điểm mà một điểm LayoutSystemcó thể chọn và thay đổi kích thước mọi thứ và kích hoạt thay đổi kích thước sự kiện cho tất cả các điều khiển có liên quan.

Sau đó, hệ thống kết xuất GUI của bạn có thể được đánh thức bằng một biến điều kiện và lặp qua các điều khiển bẩn và sơn lại chúng bằng một đường chuyền rộng (không phải hàng đợi sự kiện) và toàn bộ đường chuyền đó được tập trung vào không có gì ngoại trừ vẽ UI. Nếu có sự phụ thuộc thứ tự phân cấp để sơn lại, thì hãy tìm ra các vùng hoặc hình chữ nhật bẩn và vẽ lại mọi thứ trong các vùng đó theo thứ tự z thích hợp để bạn không phải thực hiện chuyển đổi cây và chỉ có thể lặp lại dữ liệu trong một thời trang đơn giản và "phẳng", không phải là thời trang đệ quy và "sâu sắc".

Có vẻ như một sự khác biệt tinh tế như vậy nhưng tôi thấy điều này khá hữu ích từ quan điểm dòng kiểm soát vì một số lý do. Đó thực sự là về việc giảm số lượng những điều xảy ra với từng đối tượng riêng lẻ, cố gắng nhắm đến một thứ tương tự như SRP nhưng được áp dụng theo các vòng lặp và tác dụng phụ: " Nguyên tắc vòng lặp một nhiệm vụ ", " Loại đơn của một bên Nguyên tắc hiệu quả trên mỗi vòng lặp ".

Kiểu luồng điều khiển này cho phép bạn nghĩ về hệ thống nhiều hơn về các nhiệm vụ lớn, nặng nhưng cực kỳ đồng đều được áp dụng trong các vòng lặp, không phải tất cả các chức năng và tác dụng phụ có thể xảy ra với từng đối tượng riêng lẻ. Nhiều như điều này có vẻ không tạo ra sự khác biệt lớn, tôi thấy nó tạo ra tất cả sự khác biệt trên thế giới ít nhất là bằng khả năng của chính tôi để hiểu hành vi của cơ sở mã trong tất cả các lĩnh vực quan trọng khi thực hiện thay đổi hoặc gỡ lỗi (điều mà tôi cũng thấy ít cần phải làm hơn với phương pháp này).

Dòng chảy phụ thuộc vào dữ liệu

Đây có lẽ là phần gây tranh cãi nhất của ECS và thậm chí nó có thể là thảm họa đối với một số miền. Nó vi phạm trực tiếp Nguyên tắc đảo ngược phụ thuộc của RẮN, trong đó nêu rõ rằng các phụ thuộc phải chảy theo hướng trừu tượng, ngay cả đối với các mô-đun cấp thấp. Nó cũng vi phạm việc che giấu thông tin, nhưng ít nhất là đối với ECS, không nhiều như nó xuất hiện vì thông thường chỉ có một hoặc hai hệ thống sẽ truy cập vào bất kỳ dữ liệu nào của thành phần cụ thể.

Và tôi nghĩ rằng ý tưởng về sự phụ thuộc chảy vào trừu tượng hoạt động rất tốt nếu sự trừu tượng của bạn ổn định (như trong, không thay đổi). Phụ thuộc nên chảy theo hướng ổn định . Tuy nhiên, ít nhất theo kinh nghiệm của tôi, sự trừu tượng thường không ổn định. Các nhà phát triển sẽ không bao giờ làm cho họ hoàn toàn đúng và sẽ thấy cần phải thay đổi hoặc xóa các chức năng (thêm không quá tệ) cũng như không dùng một số giao diện một hoặc hai năm sau đó. Khách hàng sẽ thay đổi suy nghĩ của họ theo cách phá vỡ các khái niệm cẩn thận mà các nhà phát triển đã xây dựng, đưa xuống nhà máy trừu tượng cho ngôi nhà trừu tượng của các thẻ trừu tượng.

Trong khi đó, tôi thấy dữ liệu ổn định hơn nhiều. Ví dụ, dữ liệu nào mà một thành phần chuyển động cần trong trò chơi? Câu trả lời khá đơn giản. Nó cần một số loại ma trận biến đổi 4 x 4 và nó cần một tham chiếu / con trỏ tới cha mẹ để cho phép phân cấp chuyển động được tạo. Đó là nó. Quyết định thiết kế đó có thể kéo dài trọn đời của toàn bộ phần mềm.

Có thể có một số điểm tinh tế như liệu chúng ta nên sử dụng dấu phẩy động chính xác đơn hay điểm chính xác kép cho ma trận, nhưng cả hai đều là những quyết định đúng đắn. Nếu SPFP được sử dụng, thì độ chính xác là một thách thức. Nếu DPFP được sử dụng, thì tốc độ là một thách thức, nhưng cả hai đều là những lựa chọn tốt không cần phải thay đổi hoặc nhất thiết phải ẩn đằng sau một giao diện. Đại diện là một trong những chúng ta có thể cam kết và giữ ổn định.

Tuy nhiên, tất cả các chức năng cần thiết cho một IMotiongiao diện trừu tượng là gì , và quan trọng hơn, bộ chức năng tối thiểu lý tưởng mà nó sẽ cung cấp để làm những việc hiệu quả chống lại nhu cầu của tất cả các hệ thống con sẽ xử lý chuyển động là gì? Đó là như vậy, rất khó để trả lời mà không hiểu quá nhiều về toàn bộ thiết kế của ứng dụng cần trả trước. Và do đó, khi rất nhiều phần của codebase kết thúc tùy thuộc vào điều này IMotion, chúng ta có thể thấy mình phải viết lại rất nhiều với mỗi lần lặp thiết kế trừ khi chúng ta có thể làm điều này ngay lần đầu tiên.

Tất nhiên trong một số trường hợp, biểu diễn dữ liệu có thể rất không ổn định. Một cái gì đó có thể phụ thuộc vào cấu trúc dữ liệu phức tạp có thể cần thay thế trong tương lai do sự không phù hợp trong cấu trúc dữ liệu trong khi nhu cầu chức năng của hệ thống liên quan đến cấu trúc dữ liệu dễ dàng được dự đoán trước. Vì vậy, đáng để thực dụng và quyết định mọi thứ trong từng trường hợp cụ thể là liệu phụ thuộc có chảy vào trừu tượng hay dữ liệu hay không, nhưng đôi khi, dữ liệu dễ ổn định hơn trừu tượng, và cho đến khi tôi chấp nhận ECS mà tôi thậm chí đã cân nhắc việc tạo ra sự phụ thuộc chủ yếu vào dữ liệu (với các hiệu ứng ổn định và đơn giản hóa đáng kinh ngạc).

Vì vậy, trong khi điều này có vẻ kỳ lạ, trong những trường hợp như vậy dễ dàng hơn để đưa ra một thiết kế ổn định cho dữ liệu qua giao diện trừu tượng, tôi thực sự khuyên bạn nên hướng sự phụ thuộc vào dữ liệu cũ đơn giản. Điều này có thể giúp bạn tiết kiệm nhiều lần lặp lại các lần viết lại. Tuy nhiên, liên quan đến việc kiểm soát các luồng và mã spaghetti và ravioli, điều này cũng sẽ có xu hướng đơn giản hóa các luồng kiểm soát của bạn khi bạn không phải có các tương tác phức tạp như vậy trước khi cuối cùng bạn nhận được dữ liệu liên quan.


1
Tôi thực sự rất thích câu trả lời này, mặc dù nó có thể không trả lời trực tiếp câu hỏi mà nó rất sâu sắc. Cảm ơn bạn đã đóng góp, có lẽ bạn có thể viết một loạt bài viết trên blog về chủ đề này? Tôi sẽ đọc nó!
Bến

Chúc mừng! Đối với blog, tôi thích sử dụng nơi này để bỏ suy nghĩ của mình! :-D

2

Các tiêu chuẩn mã nói chung là hữu ích.

Điều đó có nghĩa là:

Các mô-đun là chắc chắn cần thiết. Bạn cũng cần sự nhất quán trong cách thực hiện "các lớp", tức là "các phương thức trên nguyên mẫu" so với "các phương thức trên ví dụ". Bạn cũng nên quyết định phiên bản nào của ECMAScript để nhắm mục tiêu và nếu bạn đang nhắm mục tiêu tức là ECMAScript 5 sử dụng các tính năng ngôn ngữ được cung cấp , (ví dụ: getters và setters).

Xem thêm: TypeScript, có thể giúp bạn chuẩn hóa các lớp, ví dụ như các lớp. Một chút mới vào lúc này, nhưng tôi không thấy bất kỳ nhược điểm nào khi sử dụng nó vì hầu như không có bất kỳ khóa nào (vì nó biên dịch thành JavaScript).


Tôi nghĩ rằng Typecript là không cần thiết.
marko

1
@marko: Không cần thiết cho ứng dụng gì? Bạn sẽ thực hiện một trình biên dịch 25 kloc mà không cần gõ tĩnh?
Janus Troelsen

2

Một sự tách biệt giữa mã làm việc và mã được triển khai là hữu ích. Tôi sử dụng một công cụ để Kết hợp và Nén các tệp javascript của mình. Vì vậy, tôi có thể có bất kỳ số lượng mô-đun nào trong một thư mục, tất cả dưới dạng các tệp riêng biệt, khi tôi đang làm việc trên mô-đun cụ thể đó. Nhưng đối với thời gian triển khai, các tệp đó được kết hợp thành một tệp nén.

Tôi sử dụng Chirpy http://chirpy.codeplex.com/ , cũng hỗ trợ SASS và coffeeScript.

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.