Tại sao Nhà nước toàn cầu lại xấu xa như vậy?


328

Trước khi chúng tôi bắt đầu điều này, hãy để tôi nói rằng tôi nhận thức rõ về các khái niệm của Trừu tượng hóa và Tiêm phụ thuộc. Tôi không cần mở mắt ra đây.

Chà, hầu hết chúng ta đều nói, (quá) nhiều lần mà không thực sự hiểu, "Đừng sử dụng các biến toàn cầu", hoặc "Singletons là xấu xa vì chúng là toàn cầu". Nhưng những gì thực sự xấu như vậy về tình trạng toàn cầu đáng ngại?

Giả sử tôi cần một cấu hình toàn cầu cho ứng dụng của mình, ví dụ như đường dẫn thư mục hệ thống hoặc thông tin cơ sở dữ liệu trên toàn ứng dụng.

Trong trường hợp đó, tôi không thấy bất kỳ giải pháp tốt nào ngoài việc cung cấp các cài đặt này trong một số không gian toàn cầu, vốn sẽ có sẵn cho toàn bộ ứng dụng.

Tôi biết đó là xấu để lạm dụng nó, nhưng là không gian toàn cầu thực sự RẰNG ác? Và nếu có, những lựa chọn thay thế tốt là gì?


5
bạn có thể sử dụng một lớp cấu hình xử lý quyền truy cập vào các cài đặt đó. Tôi nghĩ rằng thực tế tốt của nó là có một và chỉ một nơi mà các cài đặt cấu hình được đọc từ các tệp cấu hình và / hoặc cơ sở dữ liệu trong khi các đối tượng khác lấy chúng từ điều cấu hình đó. nó làm cho thiết kế của bạn sạch sẽ hơn và có thể dự đoán được.
Hajo

25
Đâu là sự khác biệt khi sử dụng toàn cầu? "Một và chỉ một nơi" của bạn nghe rất đáng ngờ giống như một Singleton.
Madara Uchiha

130
Cái ác thực sự duy nhất là giáo điều.
Pieter B

34
Sử dụng trạng thái toàn cầu với các lập trình viên đồng nghiệp của bạn cũng giống như sử dụng cùng một bàn chải đánh răng với bạn bè của bạn - bạn có thể nhưng bạn không bao giờ biết khi nào ai đó sẽ quyết định đẩy nó lên.
ziGi

5
Ma quỷ là ác quỷ. Nhà nước toàn cầu không xấu xa hay tốt đẹp. Nó có thể rất hữu ích hoặc có hại tùy thuộc vào cách bạn sử dụng nó. Đừng lắng nghe người khác và chỉ học cách lập trình đúng.
annoying_squid

Câu trả lời:


282

Rất ngắn gọn, nó làm cho nhà nước chương trình không thể đoán trước.

Để giải thích, hãy tưởng tượng bạn có một vài đối tượng mà cả hai sử dụng cùng một biến toàn cục. Giả sử bạn không sử dụng nguồn ngẫu nhiên ở bất kỳ đâu trong một trong hai mô-đun, thì đầu ra của một phương thức cụ thể có thể được dự đoán (và do đó được kiểm tra) nếu biết trạng thái của hệ thống trước khi bạn thực hiện phương thức.

Tuy nhiên, nếu một phương thức trong một trong các đối tượng kích hoạt hiệu ứng phụ làm thay đổi giá trị của trạng thái chung được chia sẻ, thì bạn không còn biết trạng thái bắt đầu là gì khi bạn thực hiện một phương thức trong đối tượng kia. Bây giờ bạn không còn có thể dự đoán đầu ra nào bạn sẽ nhận được khi thực hiện phương thức và do đó bạn không thể kiểm tra nó.

Ở cấp độ học vấn, điều này có vẻ không nghiêm trọng lắm, nhưng có thể đơn vị kiểm tra mã là một bước quan trọng trong quá trình chứng minh tính đúng đắn của nó (hoặc ít nhất là phù hợp với mục đích).

Trong thế giới thực, điều này có thể có một số hậu quả rất nghiêm trọng. Giả sử bạn có một lớp cư trú cấu trúc dữ liệu toàn cầu và một lớp khác tiêu thụ dữ liệu trong cấu trúc dữ liệu đó, thay đổi trạng thái của nó hoặc phá hủy nó trong quy trình. Nếu lớp bộ xử lý thực thi một phương thức trước khi lớp trình giả lập được thực hiện, kết quả là lớp bộ xử lý có thể sẽ có dữ liệu không đầy đủ để xử lý và cấu trúc dữ liệu mà lớp trình giả lập đang làm việc có thể bị hỏng hoặc bị phá hủy. Hành vi của chương trình trong những trường hợp này trở nên hoàn toàn không thể đoán trước, và có thể sẽ dẫn đến mất mát sử thi.

Hơn nữa, nhà nước toàn cầu làm tổn thương khả năng đọc mã của bạn. Nếu mã của bạn có một phụ thuộc bên ngoài không được giới thiệu rõ ràng vào mã thì bất cứ ai có công việc duy trì mã của bạn sẽ phải tìm kiếm nó để tìm ra nó đến từ đâu.

Đối với những gì thay thế tồn tại, cũng không thể không có trạng thái toàn cầu, nhưng trong thực tế, thường có thể hạn chế trạng thái toàn cầu đối với một đối tượng duy nhất bao bọc tất cả các đối tượng khác và không bao giờ được tham chiếu bằng cách dựa vào các quy tắc phạm vi của ngôn ngữ bạn đang sử dụng. Nếu một đối tượng cụ thể cần một trạng thái cụ thể, thì nó nên yêu cầu rõ ràng bằng cách chuyển nó làm đối số cho hàm tạo của nó hoặc bằng phương thức setter. Điều này được gọi là tiêm phụ thuộc.

Có vẻ ngớ ngẩn khi vượt qua trong một trạng thái mà bạn đã có thể truy cập do các quy tắc phạm vi của bất kỳ ngôn ngữ nào bạn đang sử dụng, nhưng những lợi thế là rất lớn. Bây giờ nếu ai đó nhìn vào mã một cách cô lập, thì rõ ràng nó cần trạng thái gì và nó đến từ đâu. Nó cũng có những lợi ích to lớn liên quan đến tính linh hoạt của mô-đun mã của bạn và do đó cơ hội sử dụng lại nó trong các bối cảnh khác nhau. Nếu trạng thái được truyền vào và các thay đổi về trạng thái là cục bộ đối với khối mã, thì bạn có thể chuyển qua bất kỳ trạng thái nào bạn muốn (nếu đó là loại dữ liệu chính xác) và yêu cầu mã của bạn xử lý nó. Mã được viết theo phong cách này có xu hướng có sự xuất hiện của một tập hợp các thành phần liên kết lỏng lẻo có thể dễ dàng thay thế cho nhau. Mã của một mô-đun không nên quan tâm đến trạng thái đến từ đâu, chỉ là cách xử lý nó.

Có rất nhiều lý do khác khiến việc chuyển trạng thái xung quanh vượt trội hơn rất nhiều so với việc dựa vào trạng thái toàn cầu. Câu trả lời này không có nghĩa là toàn diện. Bạn có thể có thể viết cả một cuốn sách về lý do tại sao nhà nước toàn cầu là xấu.


61
Về cơ bản, bởi vì bất cứ ai cũng có thể thay đổi trạng thái, bạn không thể dựa vào nó.
Oded

21
@Truth Cách thay thế? Phụ thuộc tiêm .
rdlowrey

12
Đối số chính của bạn không thực sự áp dụng cho một cái gì đó chỉ đọc hiệu quả, chẳng hạn như một đối tượng đại diện cho một cấu hình.
frankc

53
Không ai trong số đó giải thích tại sao các biến toàn cục chỉ đọc là xấu mà OP rõ ràng đã hỏi về. Đó là một câu trả lời hay, tôi chỉ ngạc nhiên khi OP đánh dấu nó là đã chấp nhận và khi anh ấy giải quyết một cách rõ ràng một điểm khác.
Konrad Rudolph

20
being able to unit test code is a major step in the process of proving its correctness (or at least fitness for purpose). Không, không. "Bây giờ là hai thập kỷ kể từ khi chỉ ra rằng thử nghiệm chương trình có thể chứng minh một cách thuyết phục sự hiện diện của các lỗi, nhưng không bao giờ có thể chứng minh sự vắng mặt của chúng. để hoàn thiện các chiến lược thử nghiệm của mình, giống như nhà giả kim của ngày xưa, người tiếp tục tinh chỉnh các quá trình thanh lọc chrysocosmic của mình. " - Djikstra, 1988. (Điều đó làm cho 4,5 thập kỷ nay ...)
Mason Wheeler

135

Nhà nước toàn cầu có thể thay đổi là xấu xa vì nhiều lý do:

  • Lỗi từ trạng thái toàn cầu có thể thay đổi - rất nhiều lỗi khó khăn là do tính đột biến. Lỗi có thể do đột biến từ bất cứ nơi nào trong chương trình thậm chí còn khó hơn, vì thường khó theo dõi nguyên nhân chính xác
  • Khả năng kiểm tra kém - nếu bạn có trạng thái toàn cầu có thể thay đổi, bạn sẽ cần định cấu hình nó cho bất kỳ bài kiểm tra nào bạn viết. Điều này làm cho việc kiểm tra khó khăn hơn (và mọi người là người ít có khả năng thực hiện nó!). ví dụ: trong trường hợp thông tin cơ sở dữ liệu trên toàn ứng dụng, điều gì xảy ra nếu một bài kiểm tra cần truy cập vào cơ sở dữ liệu kiểm tra cụ thể khác với mọi thứ khác?
  • Tính không linh hoạt - điều gì xảy ra nếu một phần của mã yêu cầu một giá trị ở trạng thái toàn cầu, nhưng một phần khác yêu cầu một giá trị khác (ví dụ: giá trị tạm thời trong khi giao dịch)? Bạn đột nhiên có một chút khó chịu khi tái cấu trúc trên tay
  • Tạp chất chức năng - Các hàm "thuần túy" (nghĩa là các hàm mà kết quả chỉ phụ thuộc vào các tham số đầu vào và không có tác dụng phụ) sẽ dễ dàng hơn nhiều để lý giải và soạn thảo để xây dựng các chương trình lớn hơn. Các chức năng đọc hoặc thao tác trạng thái toàn cầu có thể thay đổi là không tinh khiết.
  • Mã hiểu - hành vi mã mà phụ thuộc vào rất nhiều các biến toàn cục có thể thay đổi là phức tạp hơn nhiều để hiểu - bạn cần phải hiểu một loạt các tương tác tốt với các biến toàn cầu trước khi bạn có thể suy luận về hành vi của các mã. Trong một số tình huống, vấn đề này có thể trở nên khó hiểu.
  • Các vấn đề tương tranh - trạng thái toàn cầu có thể thay đổi thường yêu cầu một số hình thức khóa khi được sử dụng trong tình huống đồng thời. Điều này rất khó để có được đúng (là nguyên nhân gây ra lỗi) và làm tăng thêm độ phức tạp đáng kể cho mã của bạn (khó / tốn kém để duy trì).
  • Hiệu suất - nhiều luồng liên tục bị lỗi trên cùng một trạng thái chung gây ra tranh chấp bộ đệm và sẽ làm chậm toàn bộ hệ thống của bạn.

Các lựa chọn thay thế cho nhà nước toàn cầu đột biến:

  • Các tham số chức năng - thường bị bỏ qua, nhưng tham số hóa các chức năng của bạn tốt hơn thường là cách tốt nhất để tránh trạng thái toàn cầu. Nó buộc bạn phải giải quyết câu hỏi khái niệm quan trọng: chức năng này yêu cầu thông tin gì để thực hiện công việc của nó? Đôi khi, có một cấu trúc dữ liệu gọi là "Bối cảnh" có thể được truyền qua một chuỗi các hàm bao bọc tất cả các thông tin liên quan.
  • Tiêm phụ thuộc - giống như đối với các tham số chức năng, chỉ cần thực hiện sớm hơn một chút (khi xây dựng đối tượng thay vì gọi hàm). Hãy cẩn thận nếu phụ thuộc của bạn là các đối tượng có thể thay đổi, điều này có thể nhanh chóng gây ra các vấn đề tương tự như trạng thái toàn cầu có thể thay đổi .....
  • Nhà nước toàn cầu bất biến hầu hết là vô hại - nó thực sự là một hằng số. Nhưng hãy chắc chắn rằng đó thực sự là một hằng số, và bạn sẽ không bị cám dỗ biến nó thành trạng thái toàn cầu có thể thay đổi vào thời điểm sau!
  • Singletons bất biến - gần giống như trạng thái toàn cầu bất biến, ngoại trừ việc bạn có thể trì hoãn việc khởi tạo cho đến khi chúng cần thiết. Hữu ích cho các cấu trúc dữ liệu cố định lớn cần tính toán trước một lần đắt tiền. Tất nhiên các singletons tương đương với trạng thái toàn cầu có thể thay đổi và do đó là xấu xa :-)
  • Liên kết động - chỉ khả dụng trong một số ngôn ngữ như Common Lisp / Clojure, nhưng điều này cho phép bạn liên kết một giá trị trong phạm vi được kiểm soát (thường là trên cơ sở luồng cục bộ) mà không ảnh hưởng đến các luồng khác. Ở một mức độ nào đó, đây là một cách "an toàn" để có được hiệu ứng tương tự như một biến toàn cục, vì bạn biết rằng chỉ có luồng xử lý hiện tại sẽ bị ảnh hưởng. Điều này đặc biệt hữu ích trong trường hợp bạn có nhiều luồng xử lý mỗi giao dịch độc lập.

11
Tôi nghĩ rằng việc truyền một đối tượng bối cảnh bằng tham số hàm hoặc hàm phụ thuộc sẽ gây ra vấn đề nếu bối cảnh có thể thay đổi, vấn đề tương tự như sử dụng trạng thái toàn cầu có thể thay đổi.
Alfredo Osorio

1
Tất cả những thứ tốt, và amen! Nhưng câu hỏi là về nhà nước toàn cầu bất biến
MarkJ

@Alfredo - rất đúng. Mặc dù nó không tệ như vậy vì ít nhất phạm vi có thể được kiểm soát phần nào. Nhưng nói chung, việc giải quyết vấn đề dễ dàng hơn bằng cách làm cho bối cảnh và sự phụ thuộc trở nên bất biến.
mikera

2
+1 cho đột biến / bất biến. Toàn cầu bất biến là ok. Ngay cả những người lười tải nhưng không bao giờ thay đổi. Tất nhiên, không để lộ các biến toàn cục, nhưng giao diện toàn cầu hoặc API.
Jess

1
@giorgio Câu hỏi làm rõ rằng các biến trong câu hỏi nhận được giá trị của chúng khi khởi động và không bao giờ thay đổi sau khi thực hiện chương trình (thư mục hệ thống, thông tin cơ sở dữ liệu). Tức là bất biến, nó không thay đổi một khi nó đã được đưa ra một giá trị. Cá nhân tôi cũng sử dụng từ "trạng thái" bởi vì nó có thể khác nhau từ thực thi này sang thực thi khác hoặc trên một máy khác. Có thể có những từ tốt hơn.
MarkJ

62
  1. Vì toàn bộ ứng dụng chết tiệt của bạn có thể đang sử dụng nó, nên cực kỳ khó để đưa chúng trở lại. Nếu bạn thay đổi bất cứ điều gì để làm với toàn cầu của bạn, tất cả mã của bạn cần thay đổi. Đây là một vấn đề đau đầu về bảo trì - không chỉ đơn giản là có thể grepcho tên loại để tìm ra chức năng nào sử dụng nó.
  2. Chúng rất tệ bởi vì chúng giới thiệu các phụ thuộc ẩn, phá vỡ đa luồng, ngày càng quan trọng đối với ngày càng nhiều ứng dụng.
  3. Trạng thái của biến toàn cục luôn hoàn toàn không đáng tin cậy, bởi vì tất cả các mã của bạn có thể làm bất cứ điều gì với nó.
  4. Chúng rất khó kiểm tra.
  5. Họ làm cho việc gọi API khó khăn. "Bạn phải nhớ gọi SET_MAGIC_VARIABLE () trước khi gọi API" chỉ đang cầu xin ai đó quên gọi nó. Nó làm cho việc sử dụng API dễ bị lỗi, gây ra các lỗi khó tìm. Bằng cách sử dụng nó như một tham số thông thường, bạn buộc người gọi cung cấp đúng giá trị.

Chỉ cần chuyển một tham chiếu vào các chức năng cần nó. Nó không khó lắm đâu.


3
Chà, bạn có thể có một lớp cấu hình toàn cầu, đóng gói khóa và IS được thiết kế để thay đổi trạng thái bất cứ lúc nào có thể. Tôi sẽ chọn cách tiếp cận này trên các trình đọc cấu hình khởi tạo từ 1000x vị trí trong mã. Nhưng vâng, không thể đoán trước là điều hoàn toàn tồi tệ nhất về họ.
Coder

6
@Coder: Lưu ý rằng thay thế lành mạnh cho toàn cầu không phải là "trình đọc cấu hình từ 1000x vị trí trong mã", mà là một trình đọc cấu hình, tạo ra một đối tượng cấu hình, phương thức có thể chấp nhận làm tham số (-> Tiêm phụ thuộc).
sleske

2
Nitpicking: Tại sao grep cho một loại dễ dàng hơn so với toàn cầu? Và câu hỏi là về toàn cầu chỉ đọc, vì vậy điểm 2 và 3 không liên quan
MarkJ

2
@MarkJ: Tôi hy vọng rằng không ai từng nói, phủ bóng tên.
DeadMG

2
@DeadMG, Re ".. luôn luôn hoàn toàn không đáng tin cậy ..", tương tự như vậy đối với tiêm phụ thuộc. Chỉ vì bạn đã biến nó thành một tham số không có nghĩa là obj được đảm bảo có trạng thái cố định. Ở đâu đó, bạn có thể thực hiện cuộc gọi đến một chức năng khác để sửa đổi trạng thái của biến được chèn ở trên cùng.
Pacerier

34

Nếu bạn nói "trạng thái", điều đó thường được hiểu là "trạng thái có thể thay đổi". Và trạng thái đột biến toàn cầu là hoàn toàn xấu xa, bởi vì điều đó có nghĩa là bất kỳ phần nào của chương trình có thể ảnh hưởng đến bất kỳ phần nào khác (bằng cách thay đổi trạng thái toàn cầu).

Tưởng tượng gỡ lỗi một chương trình không xác định: Bạn thấy hàm A hành xử theo một cách nhất định cho các tham số đầu vào nhất định, nhưng đôi khi nó hoạt động khác nhau cho cùng một tham số. Bạn thấy rằng nó sử dụng biến toàn cầu x .

Bạn tìm những nơi sửa đổi x và thấy rằng có năm địa điểm sửa đổi nó. Bây giờ chúc may mắn tìm ra trong trường hợp chức năng A làm gì ...


Vì vậy, nhà nước toàn cầu bất biến không phải là xấu xa?
Thất vọngWithFormsDesigner

50
Tôi muốn nói rằng nhà nước toàn cầu bất biến là thực tiễn tốt được biết đến như là "hằng số".
Telastyn

6
Nhà nước toàn cầu bất biến không phải là xấu, nó chỉ là xấu :-). Nó vẫn có vấn đề vì khớp nối mà nó giới thiệu (làm cho thay đổi, tái sử dụng và kiểm tra đơn vị khó hơn), nhưng tạo ra ít vấn đề hơn, vì vậy trong những trường hợp đơn giản, nó thường được chấp nhận.
sleske

2
IFF không sử dụng các biến toàn cục thì chỉ một đoạn mã sẽ sửa đổi nó. Phần còn lại là miễn phí để đọc nó. Các vấn đề của người khác thay đổi nó không biến mất với chức năng đóng gói và truy cập. Đây không phải là những gì các cấu trúc đó là dành cho.
phkahler

1
@Pacerier: Có, thay đổi giao diện được sử dụng rộng rãi là khó khăn, bất kể nó được sử dụng như một biến toàn cục hay biến cục bộ. Tuy nhiên, điều đó độc lập với quan điểm của tôi, đó là sự tương tác của các đoạn mã khác nhau rất khó hiểu với các vars toàn cầu.
sleske

9

Bạn sắp xếp trả lời câu hỏi của riêng bạn. Chúng khó quản lý khi 'bị lạm dụng', nhưng có thể hữu ích và [phần nào] có thể dự đoán được khi được sử dụng đúng cách, bởi một người biết cách chứa chúng. Bảo trì và thay đổi / trên toàn cầu thường là một cơn ác mộng, trở nên tồi tệ hơn khi kích thước của ứng dụng tăng lên.

Các lập trình viên có kinh nghiệm, những người có thể cho biết sự khác biệt giữa các thế giới là lựa chọn duy nhất và họ là bản sửa lỗi dễ dàng, có thể có các vấn đề tối thiểu khi sử dụng chúng. Nhưng những vấn đề bất tận có thể xảy ra với việc sử dụng chúng đòi hỏi phải có lời khuyên chống lại việc sử dụng chúng.

chỉnh sửa: Để làm rõ những gì tôi muốn nói, bản chất toàn cầu là không thể đoán trước. Như với bất cứ điều gì không thể đoán trước, bạn có thể thực hiện các bước để ngăn chặn sự không thể đoán trước, nhưng luôn có giới hạn cho những gì có thể được thực hiện. Thêm vào đó, rắc rối của các nhà phát triển mới tham gia dự án phải đối phó với các biến tương đối không rõ, các khuyến nghị chống lại việc sử dụng toàn cầu là điều dễ hiểu.


9

Có nhiều vấn đề với Singletons - đây là hai vấn đề lớn nhất trong tâm trí tôi.

  • Nó làm cho thử nghiệm đơn vị có vấn đề. Nhà nước toàn cầu có thể bị ô nhiễm từ thử nghiệm này sang thử nghiệm tiếp theo

  • Nó thi hành quy tắc cứng "Một và duy nhất", mặc dù nó không thể thay đổi, đột nhiên có. Một loạt các mã tiện ích sử dụng đối tượng có thể truy cập toàn cầu sau đó cần phải được thay đổi.

Phải nói rằng, hầu hết các hệ thống đều có một số nhu cầu đối với Big Global Object. Đây là những mục lớn và đắt tiền (ví dụ: Trình quản lý kết nối cơ sở dữ liệu) hoặc giữ thông tin trạng thái phổ biến (ví dụ: khóa thông tin).

Thay thế cho Singleton là tạo các Đối tượng toàn cầu lớn này khi khởi động và được chuyển dưới dạng tham số cho tất cả các lớp hoặc phương thức cần truy cập vào đối tượng này.

Vấn đề ở đây là bạn kết thúc với một trò chơi lớn "vượt qua bưu kiện". Bạn có một biểu đồ các thành phần và các phụ thuộc của chúng, và một số lớp tạo các lớp khác và mỗi lớp phải chứa một loạt các thành phần phụ thuộc chỉ vì các thành phần được sinh ra của chúng (hoặc các thành phần được sinh ra) cần chúng.

Bạn gặp vấn đề bảo trì mới. Một ví dụ: Đột nhiên "WidgetFactory" của bạn, thành phần nằm sâu trong biểu đồ cần một đối tượng hẹn giờ mà bạn muốn mô phỏng. Tuy nhiên, "WidgetFactory" được tạo bởi "WidgetBuilder", một phần của "WidgetCreationManager" và bạn cần có ba lớp biết về đối tượng hẹn giờ này mặc dù chỉ có một người thực sự sử dụng nó. Bạn thấy mình muốn từ bỏ và trở lại Singletons, và chỉ cần làm cho đối tượng hẹn giờ này có thể truy cập được trên toàn cầu.

May mắn thay, đây chính xác là vấn đề được giải quyết bằng khung Dependency Injection. Bạn có thể chỉ cần nói cho khung công tác những lớp mà nó cần tạo và nó sử dụng sự phản chiếu để tìm ra biểu đồ phụ thuộc cho bạn và tự động xây dựng từng đối tượng khi cần.

Vì vậy, tóm lại, Singletons rất tệ và phương án thay thế là sử dụng khung Dependency Injection.

Tôi tình cờ sử dụng Castle Windsor, nhưng bạn tha hồ lựa chọn. Xem trang này từ năm 2008 trở lại để biết danh sách các khung có sẵn.


8

Trước hết để tiêm phụ thuộc là "trạng thái", bạn sẽ cần sử dụng singletons, vì vậy mọi người nói rằng đây là một cách thay thế bị nhầm lẫn. Mọi người sử dụng các đối tượng bối cảnh toàn cầu mọi lúc ... Ví dụ, ngay cả trạng thái phiên cũng thực chất là một biến toàn cục. Vượt qua tất cả mọi thứ xung quanh cho dù bằng cách tiêm phụ thuộc hay không luôn không phải là giải pháp tốt nhất. Tôi làm việc trên một ứng dụng rất lớn hiện đang sử dụng rất nhiều đối tượng bối cảnh toàn cầu (singletons được tiêm thông qua một bộ chứa IoC) và nó chưa bao giờ là vấn đề để gỡ lỗi. Đặc biệt với một kiến ​​trúc hướng sự kiện, nó có thể được ưu tiên sử dụng các đối tượng bối cảnh toàn cầu hơn là vượt qua bất cứ điều gì thay đổi. Phụ thuộc vào người bạn hỏi.

Bất cứ điều gì có thể bị lạm dụng và nó cũng phụ thuộc vào loại ứng dụng. Ví dụ, sử dụng biến tĩnh trong ứng dụng web hoàn toàn khác với ứng dụng trên máy tính để bàn. Nếu bạn có thể tránh các biến toàn cục, thì hãy làm như vậy, nhưng đôi khi chúng có công dụng của chúng. Ít nhất hãy chắc chắn rằng dữ liệu toàn cầu của bạn nằm trong một đối tượng theo ngữ cảnh rõ ràng. Theo như gỡ lỗi, không có gì ngăn xếp cuộc gọi và một số điểm dừng không thể giải quyết.

Tôi muốn nhấn mạnh rằng sử dụng các biến toàn cầu một cách mù quáng là một ý tưởng tồi. Các hàm nên được sử dụng lại và không nên quan tâm dữ liệu đến từ đâu - tham khảo các biến toàn cục kết hợp chức năng với một dữ liệu đầu vào cụ thể. Đây là lý do tại sao nó nên được thông qua và tại sao tiêm phụ thuộc có thể hữu ích, mặc dù bạn vẫn đang xử lý một cửa hàng bối cảnh tập trung (thông qua singletons).

Btw ... Một số người nghĩ rằng tiêm phụ thuộc là xấu, bao gồm cả người tạo ra Linq, nhưng điều đó sẽ không ngăn mọi người sử dụng nó, bao gồm cả bản thân tôi. Cuối cùng kinh nghiệm sẽ là giáo viên tốt nhất của bạn. Có những lúc để tuân theo các quy tắc và thời gian để phá vỡ chúng.


4

Kể từ khi một số câu trả lời khác ở đây làm cho sự phân biệt giữa có thể thay đổikhông thể thay đổi trạng thái toàn cầu, tôi muốn tỏ bày rằng thậm chí không thể thay đổi các biến toàn cục / cài đặt thường một ít phiền toái .

Hãy xem xét câu hỏi:

... Giả sử tôi cần một cấu hình toàn cầu cho ứng dụng của mình, ví dụ như đường dẫn thư mục hệ thống hoặc thông tin cơ sở dữ liệu trên toàn ứng dụng. ...

Đúng, đối với một chương trình nhỏ, điều này có thể không phải là vấn đề, nhưng ngay khi bạn thực hiện việc này với các thành phần trong hệ thống lớn hơn một chút , việc viết các bài kiểm tra tự động đột nhiên trở nên khó khăn hơn vì tất cả các bài kiểm tra (~ chạy trong cùng một quy trình) phải hoạt động với cùng giá trị cấu hình toàn cầu.

Nếu tất cả dữ liệu cấu hình được truyền một cách rõ ràng, các thành phần trở nên dễ kiểm tra hơn nhiều và bạn không bao giờ phải lo lắng làm thế nào để khởi động lại giá trị cấu hình toàn cầu cho nhiều thử nghiệm, thậm chí có thể song song.


3

Vâng, đối với một, bạn có thể chạy vào chính xác cùng một vấn đề mà bạn có thể với singletons. Những gì ngày nay trông giống như một "thứ toàn cầu tôi chỉ cần một trong những thứ" sẽ đột nhiên chuyển sang thứ bạn cần nhiều hơn trong tương lai.

Ví dụ, hôm nay bạn tạo hệ thống cấu hình toàn cầu này vì bạn muốn một cấu hình toàn cầu cho toàn bộ hệ thống. Vài năm sau, bạn chuyển sang một hệ thống khác và ai đó nói "này, bạn biết đấy, điều này có thể hoạt động tốt hơn nếu có một cấu hình chung toàn cầu và một cấu hình cụ thể nền tảng." Đột nhiên bạn có tất cả công việc này để làm cho cấu trúc toàn cầu của bạn không phải là toàn cầu, vì vậy bạn có thể có nhiều cấu trúc.

(Đây không phải là một ví dụ ngẫu nhiên ... điều này đã xảy ra với hệ thống cấu hình của chúng tôi trong dự án tôi hiện đang làm.)

Xem xét rằng chi phí để làm một cái gì đó không toàn cầu thường là không đáng kể, thật ngớ ngẩn khi làm điều đó. Bạn chỉ đang tạo ra những vấn đề trong tương lai.


Ví dụ của bạn không phải là ý của OP. Bạn đang nói rõ ràng về việc khởi tạo các cấu hình trừu tượng (chỉ là cơ sở hạ tầng của trình quản lý cấu hình, không có bất kỳ khóa ưu tiên dành riêng cho ứng dụng nào) vì bạn muốn chia tất cả các khóa trong một phiên bản đang chạy của chương trình qua nhiều phiên bản của lớp trình quản lý cấu hình của bạn. (Giả sử, mỗi ví dụ như vậy có thể xử lý một tệp cấu hình chẳng hạn).
Jo So

3

Vấn đề khác là họ làm cho một ứng dụng khó mở rộng quy mô vì thay vào đó là không đủ "toàn cầu". Phạm vi của một biến toàn cầu là quá trình.

Nếu bạn muốn mở rộng ứng dụng của mình bằng cách sử dụng nhiều quy trình hoặc bằng cách chạy trên nhiều máy chủ, bạn không thể. Ít nhất là không cho đến khi bạn tính ra tất cả các quả cầu và thay thế chúng bằng một số cơ chế khác.


3

Tại sao Nhà nước toàn cầu lại xấu xa như vậy?

Có thể thay đổi trạng thái toàn cầu là ác vì nó rất khó khăn cho não của chúng ta để đưa vào tài khoản nhiều hơn một vài thông số tại một thời điểm và tìm ra cách họ kết hợp cả từ góc độ thời gian và góc độ giá trị ảnh hưởng đến một cái gì đó.

Do đó, chúng tôi rất tệ trong việc gỡ lỗi hoặc kiểm tra một đối tượng có hành vi có nhiều lý do bên ngoài bị thay đổi trong quá trình thực thi chương trình. Hãy để một mình khi chúng ta phải lý do về hàng tá các đối tượng này được thực hiện cùng nhau.


3

Các ngôn ngữ được thiết kế để thiết kế hệ thống an toàn và mạnh mẽ thường loại bỏ hoàn toàn trạng thái có thể thay đổi toàn cầu . (Có thể cho rằng điều này có nghĩa là không có toàn cầu, vì các đối tượng bất biến theo nghĩa không thực sự có trạng thái vì chúng không bao giờ có sự chuyển đổi trạng thái.)

Joe-E là một ví dụ và David Wagner giải thích quyết định như vậy:

Phân tích ai có quyền truy cập vào một đối tượng và nguyên tắc đặc quyền tối thiểu đều bị phá vỡ khi các khả năng được lưu trữ trong các biến toàn cục và do đó có thể đọc được bởi bất kỳ phần nào của chương trình. Khi một đối tượng có sẵn trên toàn cầu, không thể giới hạn phạm vi phân tích: quyền truy cập vào đối tượng là một đặc quyền không thể bị giữ lại từ bất kỳ mã nào trong chương trình. Joe-E tránh những vấn đề này bằng cách xác minh rằng phạm vi toàn cầu không chứa các khả năng, chỉ có dữ liệu bất biến.

Vì vậy, một cách để nghĩ về nó là

  1. Lập trình là một vấn đề lý luận phân tán. Các lập trình viên trong các dự án lớn cần chia chương trình thành các phần có thể được lý giải bởi các cá nhân.
  2. Phạm vi càng nhỏ, càng dễ lý do. Điều này đúng cả cá nhân và công cụ phân tích tĩnh cố gắng chứng minh các thuộc tính của hệ thống và các thử nghiệm cần kiểm tra các thuộc tính của hệ thống.
  3. Các nguồn thẩm quyền quan trọng có sẵn trên toàn cầu làm cho các thuộc tính của hệ thống khó có thể lý giải.

Do đó, trạng thái đột biến trên toàn cầu làm cho khó khăn hơn để

  1. thiết kế hệ thống mạnh mẽ,
  2. khó hơn để chứng minh tính chất của một hệ thống, và
  3. khó hơn để chắc chắn rằng các thử nghiệm của bạn đang thử nghiệm trong một phạm vi tương tự như môi trường sản xuất của bạn.

Trạng thái đột biến toàn cầu tương tự như địa ngục DLL . Theo thời gian, các phần khác nhau của một hệ thống lớn sẽ yêu cầu hành vi khác biệt tinh tế từ các phần được chia sẻ của trạng thái có thể thay đổi. Giải quyết địa ngục DLL và sự không nhất quán trạng thái có thể thay đổi được chia sẻ đòi hỏi sự phối hợp quy mô lớn giữa các nhóm khác nhau. Những vấn đề này sẽ không xảy ra nếu nhà nước toàn cầu được sắp xếp hợp lý để bắt đầu.


2

Globals không phải là xấu. Như đã nêu trong một số câu trả lời khác, vấn đề thực sự với họ là, ngày hôm nay, đường dẫn thư mục toàn cầu của bạn có thể, vào ngày mai, là một trong vài, hoặc thậm chí hàng trăm. Nếu bạn đang viết một chương trình nhanh, một lần, hãy sử dụng toàn cầu nếu dễ hơn. Nói chung, mặc dù, cho phép bội số ngay cả khi bạn chỉ nghĩ rằng bạn cần một là cách để đi. Thật không dễ chịu khi phải cấu trúc lại một chương trình phức tạp lớn đột nhiên cần nói chuyện với hai cơ sở dữ liệu.

Nhưng họ không làm tổn thương độ tin cậy. Bất kỳ dữ liệu nào được tham chiếu từ nhiều nơi trong chương trình của bạn đều có thể gây ra sự cố nếu nó thay đổi bất ngờ. Các điều tra viên nghẹt thở khi bộ sưu tập họ liệt kê được thay đổi trong bảng liệt kê. Sự kiện xếp hàng sự kiện có thể chơi các thủ thuật với nhau. Chủ đề luôn có thể tàn phá havok. Bất cứ điều gì không phải là một biến cục bộ hoặc trường không thể thay đổi là một vấn đề. Globals là loại vấn đề này, nhưng bạn sẽ không khắc phục điều đó bằng cách biến chúng thành phi toàn cầu.

Nếu bạn chuẩn bị ghi vào một tập tin và đường dẫn thư mục thay đổi, thay đổi và ghi cần phải được đồng bộ hóa. (Là một trong hàng ngàn điều có thể sai, giả sử bạn lấy đường dẫn, sau đó thư mục đó sẽ bị xóa, sau đó đường dẫn thư mục được thay đổi thành một thư mục tốt, sau đó bạn thử và ghi vào thư mục đã xóa.) đường dẫn thư mục là toàn cục hoặc là một trong một ngàn chương trình hiện đang sử dụng.

Có một vấn đề thực sự với các trường có thể được truy cập bởi các sự kiện khác nhau trên hàng đợi, mức đệ quy khác nhau hoặc các luồng khác nhau. Để làm cho nó đơn giản (và đơn giản): các biến cục bộ là tốt và các trường là xấu. Nhưng các cầu toàn cầu trước đây vẫn sẽ là các lĩnh vực, vì vậy vấn đề này (tuy rất quan trọng) không áp dụng cho trạng thái Tốt hoặc Ác của các trường Toàn cầu.

Ngoài ra: Các vấn đề đa luồng:

(Lưu ý rằng bạn có thể gặp vấn đề tương tự với hàng đợi sự kiện hoặc các cuộc gọi đệ quy, nhưng đa luồng là điều tồi tệ nhất.) Hãy xem xét đoạn mã sau:

if (filePath != null)  text = filePath.getName();

Nếu filePathlà một biến cục bộ hoặc một loại hằng số, chương trình của bạn sẽ không bị lỗi khi chạy vì không filePathcó giá trị. Việc kiểm tra luôn hoạt động. Không có chủ đề khác có thể thay đổi giá trị của nó. Nếu không , không có đảm bảo. Khi tôi bắt đầu viết các chương trình đa luồng bằng Java, tôi đã nhận được NullPulumExceptions trên các dòng như thế này mọi lúc. Bất kìchủ đề khác có thể thay đổi giá trị bất cứ lúc nào và họ thường làm. Như một số câu trả lời khác chỉ ra, điều này tạo ra các vấn đề nghiêm trọng để thử nghiệm. Tuyên bố trên có thể hoạt động hàng tỷ lần, thông qua thử nghiệm toàn diện và toàn diện, sau đó nổ tung một lần trong sản xuất. Người dùng sẽ không thể tái tạo vấn đề và nó sẽ không xảy ra lần nữa cho đến khi họ tự thuyết phục rằng họ đã nhìn thấy mọi thứ và quên nó đi.

Globals chắc chắn có vấn đề này, và nếu bạn có thể loại bỏ chúng hoàn toàn hoặc thay thế chúng bằng hằng số hoặc biến cục bộ, đó là một điều rất tốt. Nếu bạn có mã không trạng thái chạy trên máy chủ web, có lẽ bạn có thể. Thông thường, tất cả các vấn đề đa luồng của bạn có thể được cơ sở dữ liệu xử lý.

Nhưng nếu chương trình của bạn phải ghi nhớ mọi thứ từ một hành động của người dùng sang hành động tiếp theo, bạn sẽ có các trường có thể truy cập được bởi bất kỳ luồng nào đang chạy. Chuyển đổi toàn cầu sang một lĩnh vực phi toàn cầu như vậy sẽ không giúp độ tin cậy.


1
Bạn có thể làm rõ ý của bạn về vấn đề này không?
Andres F.

1
@AndresF.: Tôi mở rộng câu trả lời của mình. Tôi nghĩ rằng tôi đang thực hiện một cách tiếp cận máy tính để bàn trong đó hầu hết mọi người trên trang này có nhiều mã máy chủ hơn với cơ sở dữ liệu. "Toàn cầu" có thể có nghĩa là những điều khác nhau trong những trường hợp này.
RalphChapin

2

Nhà nước là không thể tránh khỏi trong bất kỳ ứng dụng thực tế. Bạn có thể gói nó theo bất kỳ cách nào bạn thích, nhưng một bảng tính phải chứa dữ liệu trong các ô. Bạn có thể tạo các đối tượng ô chỉ có chức năng như một giao diện, nhưng điều đó không hạn chế số lượng địa điểm có thể gọi một phương thức trên ô và thay đổi dữ liệu. Bạn xây dựng toàn bộ hệ thống phân cấp đối tượng để cố gắng ẩn giao diện để các phần khác của mã không thểthay đổi dữ liệu theo mặc định. Điều đó không ngăn cản một tham chiếu đến đối tượng chứa được truyền xung quanh một cách tùy tiện. Cũng không có bất kỳ điều đó loại bỏ các vấn đề tương tranh của chính nó. Nó làm cho việc tăng sinh truy cập dữ liệu trở nên khó khăn hơn, nhưng thực tế nó không loại bỏ được các vấn đề nhận thức với toàn cầu. Nếu ai đó muốn sửa đổi một phần trạng thái, họ sẽ thực hiện điều đó theo thời gian trên toàn cầu hoặc thông qua một API phức tạp (sau này sẽ chỉ làm nản lòng, không ngăn chặn).

Lý do thực sự không sử dụng lưu trữ toàn cầu là để tránh xung đột tên. Nếu bạn tải nhiều mô-đun khai báo cùng một tên toàn cầu, bạn có hành vi không xác định (rất khó gỡ lỗi vì các kiểm tra đơn vị sẽ vượt qua) hoặc lỗi liên kết (Tôi đang nghĩ C - Trình liên kết của bạn có cảnh báo hoặc thất bại về điều này không?).

Nếu bạn muốn sử dụng lại mã, bạn đã có thể lấy một mô-đun từ một nơi khác và không để nó vô tình bước lên toàn cầu của bạn vì họ đã sử dụng một mô-đun có cùng tên. Hoặc nếu bạn may mắn và gặp lỗi, bạn không muốn phải thay đổi tất cả các tham chiếu trong một phần của mã để ngăn ngừa xung đột.


1

Khi dễ dàng nhìn thấy và truy cập tất cả các trạng thái toàn cầu, các lập trình viên luôn luôn kết thúc việc đó. Những gì bạn nhận được là không nói và khó theo dõi các phụ thuộc (int blahblah có nghĩa là mảng foo có giá trị trong bất cứ điều gì). Về cơ bản, nó làm cho gần như không thể duy trì bất biến chương trình vì mọi thứ có thể được xoay vòng một cách độc lập. someInt có mối quan hệ giữa otherInt, điều đó khó quản lý và khó chứng minh hơn nếu bạn có thể trực tiếp thay đổi bất cứ lúc nào.

Điều đó nói rằng, nó có thể được thực hiện (cách trở lại khi đó là cách duy nhất trong một số hệ thống), nhưng những kỹ năng đó đã bị mất. Chúng chủ yếu xoay quanh các quy ước về mã hóa và đặt tên - lĩnh vực đã chuyển sang vì lý do chính đáng. Trình biên dịch và trình liên kết của bạn thực hiện công việc kiểm tra các bất biến trong dữ liệu được bảo vệ / riêng tư của các lớp / mô-đun tốt hơn là dựa vào con người để tuân theo kế hoạch tổng thể và đọc nguồn.


1
"Nhưng những kỹ năng đó đã mất" ... chưa hoàn toàn. Gần đây tôi đã làm việc tại một nhà phần mềm thề bằng "Clarion", một công cụ tạo mã có ngôn ngữ giống như cơ bản, nó thiếu các tính năng như truyền đối số cho các thói quen phụ ... Các nhà phát triển ngồi không hài lòng với bất kỳ đề xuất nào về "Thay đổi" hoặc "hiện đại hóa", cuối cùng đã chán ngấy với những nhận xét của tôi và miêu tả tôi là người thiếu sót và bất tài. Tôi phải rời đi ...
Louis Bolog

1

Tôi sẽ không nói liệu các biến toàn cầu là tốt hay xấu, nhưng điều tôi sẽ thêm vào cuộc thảo luận là nói thực tế rằng nếu bạn không sử dụng trạng thái toàn cầu, thì có lẽ bạn đang lãng phí rất nhiều bộ nhớ, đặc biệt là khi bạn sử dụng classess để lưu trữ các phụ thuộc của chúng trong các trường.

Đối với nhà nước toàn cầu, không có vấn đề như vậy, tất cả mọi thứ là toàn cầu.

Ví dụ: hãy tưởng tượng một kịch bản sau đây: Bạn có một lưới 10x10 được làm bằng "Bảng" và "Ngói".

Nếu bạn muốn làm theo cách OOP, bạn có thể sẽ chuyển đối tượng "Board" cho mỗi "Ngói". Bây giờ chúng ta hãy nói rằng "Ngói" có các trường loại 2 "byte" lưu trữ tọa độ của nó. Tổng bộ nhớ cần có trên máy 32 bit cho một ô sẽ là (1 + 1 + 4 = 6) byte: 1 cho tọa độ x, 1 cho tọa độ y và 4 cho con trỏ vào bảng. Điều này cung cấp tổng cộng 600 byte cho thiết lập gạch 10x10

Bây giờ đối với trường hợp Board nằm trong phạm vi toàn cục, một đối tượng duy nhất có thể truy cập được từ mỗi ô bạn chỉ phải lấy 2 byte bộ nhớ cho mỗi ô, đó là các byte phối hợp x và y. Điều này sẽ chỉ cung cấp 200 byte.

Vì vậy, trong trường hợp này, bạn nhận được 1/3 mức sử dụng bộ nhớ nếu bạn chỉ sử dụng trạng thái toàn cầu.

Điều này, bên cạnh những thứ khác, tôi đoán là một lý do tại sao phạm vi toàn cầu vẫn còn trong các ngôn ngữ cấp thấp (tương đối) như C ++


1
Điều đó phụ thuộc rất nhiều vào ngôn ngữ. Trong các ngôn ngữ nơi các chương trình chạy liên tục, có thể đúng là bạn có thể tiết kiệm bộ nhớ bằng cách sử dụng không gian và singletons toàn cầu thay vì khởi tạo nhiều lớp, nhưng ngay cả đối số đó cũng bị rung. Đó là tất cả về ưu tiên. Trong các ngôn ngữ như PHP (được chạy một lần cho mỗi yêu cầu và các đối tượng không tồn tại), ngay cả đối số đó cũng được đưa ra.
Madara Uchiha

1
@MadaraUchiha Không, lập luận này không bị lung lay theo bất kỳ cách nào. Đó là sự thật khách quan, trừ khi một số VM hoặc ngôn ngữ được biên dịch khác thực hiện một số tối ưu hóa mã cứng với loại vấn đề này. Nếu không thì nó vẫn còn liên quan. Ngay cả với các chương trình phía máy chủ được sử dụng là "một lần bắn", bộ nhớ vẫn được bảo lưu trong suốt thời gian thực hiện. Với một máy chủ tải cao, đây có thể là một điểm quan trọng.
luke1985

0

Có một số yếu tố cần xem xét với nhà nước toàn cầu:

  1. Không gian bộ nhớ lập trình viên
  2. Toàn cầu bất biến / viết một lần toàn cầu.
  3. Toàn cầu đột biến
  4. Phụ thuộc vào toàn cầu.

Bạn càng có nhiều quả cầu, cơ hội giới thiệu các bản sao càng lớn và do đó phá vỡ mọi thứ khi các bản sao không đồng bộ. Giữ tất cả các quả cầu trong bộ nhớ con người đáng sợ của bạn là điều cần thiết và đau đớn.

Bất biến / ghi một lần nói chung là OK, nhưng xem ra các lỗi trình tự khởi tạo.

Các thế giới có thể thay đổi thường bị nhầm lẫn với các thế giới bất biến

Một hàm sử dụng toàn cầu có hiệu quả có thêm các tham số "ẩn", khiến việc tái cấu trúc trở nên khó khăn hơn.

Nhà nước toàn cầu không xấu, nhưng nó có chi phí nhất định - sử dụng nó khi lợi ích vượt xa chi phí.

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.