Làm thế nào cần thiết để tuân theo các thực tiễn lập trình phòng thủ cho mã sẽ không bao giờ được công khai?


45

Tôi đang viết một triển khai Java của một trò chơi bài, vì vậy tôi đã tạo ra một loại Bộ sưu tập đặc biệt mà tôi đang gọi là Khu vực. Tất cả các phương thức sửa đổi của Bộ sưu tập của Java đều không được hỗ trợ, nhưng có một phương thức trong API khu vực move(Zone, Card), giúp di chuyển Thẻ từ Vùng đã cho sang chính nó (được thực hiện bằng các kỹ thuật riêng tư gói). Bằng cách này, tôi có thể đảm bảo rằng không có thẻ nào được đưa ra khỏi một khu vực và chỉ đơn giản là biến mất; họ chỉ có thể được chuyển đến khu vực khác.

Câu hỏi của tôi là, loại mã hóa phòng thủ này cần thiết như thế nào? Đó là "chính xác", và nó cảm thấy như một cách thực hành đúng, nhưng nó không giống như API khu vực sẽ trở thành một phần của một số thư viện công cộng. Nó chỉ dành cho tôi, vì vậy nó giống như tôi đang bảo vệ mã của mình khỏi chính mình khi tôi có thể hiệu quả hơn bằng cách chỉ sử dụng Bộ sưu tập tiêu chuẩn.

Tôi nên lấy ý tưởng Khu vực này bao xa? Bất cứ ai cũng có thể cho tôi một lời khuyên về việc tôi nên suy nghĩ bao nhiêu về việc giữ các hợp đồng trong các lớp tôi viết, đặc biệt là cho những người không thực sự được công khai?


4
= ~ s / cần thiết / được đề xuất / gi
GrandmasterB

2
Các kiểu dữ liệu phải chính xác bằng cách xây dựng, hoặc nếu không thì bạn đang xây dựng cái gì? Chúng nên được gói gọn theo cách mà, có thể thay đổi hay không, chúng chỉ có thể ở trạng thái hợp lệ. Chỉ khi không thể thực thi điều này một cách tĩnh (hoặc khó khăn một cách vô lý) thì bạn mới có thể gây ra lỗi thời gian chạy.
Jon Purdy

1
Không bao giờ nói không bao giờ. Trừ khi mã của bạn không bao giờ được sử dụng, bạn không bao giờ có thể biết chắc chắn mã của bạn sẽ ở đâu. ;)
Izkata

1
@codebreaker Nhận xét của GrandmasterB là biểu thức thay thế. Nó có nghĩa là: thay thế "cần thiết" bằng "khuyến nghị".
Ricardo Souza

1
Bộ luật mã số # 116 Tin tưởng Không ai có khả năng đặc biệt thích hợp ở đây.

Câu trả lời:


72

Tôi sẽ không giải quyết vấn đề thiết kế - chỉ là câu hỏi liệu có nên làm mọi thứ "chính xác" trong API không công khai hay không.

nó chỉ dành cho tôi, vì vậy nó giống như tôi đang bảo vệ mã của chính mình khỏi chính tôi

Đó chính xác là vấn đề. Có lẽ có những lập trình viên ngoài kia, những người nhớ các sắc thái của mọi lớp và phương pháp họ từng viết và không bao giờ gọi nhầm họ với hợp đồng sai. Tôi không phải là một trong số họ. Tôi thường quên cách mã mà tôi đã viết được cho là hoạt động trong vài giờ sau khi viết nó. Sau khi bạn nghĩ rằng bạn đã hiểu đúng một lần, tâm trí của bạn sẽ có xu hướng chuyển đổi sang vấn đề bạn đang làm việc hiện tại .

Bạn có công cụ để chống lại điều đó. Những công cụ này bao gồm các quy ước (không theo thứ tự cụ thể), kiểm tra đơn vị và kiểm tra tự động khác, kiểm tra tiền điều kiện và tài liệu. Bản thân tôi đã tìm thấy các bài kiểm tra đơn vị là vô giá vì cả hai đều buộc bạn phải suy nghĩ về cách sử dụng hợp đồng của bạn và cung cấp tài liệu sau này về cách giao diện được thiết kế.


Tốt để biết. Trước đây tôi thường chỉ lập trình hiệu quả nhất có thể, vì vậy đôi khi tôi gặp khó khăn khi làm quen với những ý tưởng như thế này. Tôi rất vui vì tôi đã đi đúng hướng.
codebreaker

15
"Hiệu quả" có thể có nghĩa là nhiều thứ khác nhau! Theo kinh nghiệm của tôi, người mới (không phải tôi đang nói bạn là một người) thường bỏ qua việc họ có thể hỗ trợ chương trình hiệu quả như thế nào. Mã thường dành nhiều thời gian hơn trong giai đoạn hỗ trợ của vòng đời sản phẩm so với giai đoạn "viết mã mới", vì vậy tôi nghĩ rằng đó là một hiệu quả cần được xem xét cẩn thận.
Charlie Kilian

2
Tôi chắc chắn đồng ý. Quay lại trường đại học, tôi chưa bao giờ phải nghĩ về điều đó.
codebreaker

25

Tôi thường tuân theo một số quy tắc đơn giản:

  • Cố gắng luôn luôn lập trình bằng hợp đồng .
  • Nếu một phương thức có sẵn công khai hoặc nhận đầu vào từ thế giới bên ngoài , hãy thực thi một số biện pháp phòng thủ (ví dụ IllegalArgumentException).
  • Đối với mọi thứ khác chỉ có thể truy cập được trong nội bộ, hãy sử dụng các xác nhận (ví dụ assert input != null).

Nếu một khách hàng thực sự thích nó, họ sẽ luôn tìm cách làm cho mã của bạn hoạt động sai. Họ luôn có thể làm điều đó thông qua sự phản ánh, ít nhất. Nhưng đó là vẻ đẹp của thiết kế theo hợp đồng . Bạn không chấp nhận việc sử dụng mã như vậy và vì vậy bạn không thể đảm bảo rằng nó sẽ hoạt động trong các tình huống như vậy.

Đối với trường hợp cụ thể của bạn, nếu người ngoài Zonekhông nên sử dụng và / hoặc truy cập, hãy đặt gói lớp ở chế độ riêng tư (và có thể final), hoặc tốt nhất là sử dụng các bộ sưu tập mà Java đã cung cấp cho bạn. Họ đã được thử nghiệm và bạn không phải phát minh lại bánh xe. Lưu ý rằng điều này không ngăn bạn sử dụng các xác nhận trong toàn bộ mã của mình để đảm bảo mọi thứ hoạt động như mong đợi.


1
+1 để đề cập đến Thiết kế theo Hợp đồng. Nếu bạn không thể hoàn toàn cấm hành vi (và điều đó khó thực hiện) thì ít nhất bạn cũng nói rõ rằng không có gì đảm bảo cho hành vi xấu. Tôi cũng thích ném IllegalStateException hoặc UnsupportedOperationException.
dùng949300

@ user949300 Chắc chắn. Tôi muốn tin rằng những ngoại lệ như vậy đã được giới thiệu với một mục đích có ý nghĩa. Tôn vinh hợp đồng dường như phù hợp với vai trò như vậy.
afsantos

16

Lập trình phòng thủ là một điều rất tốt.
Cho đến khi nó bắt đầu nhận được trong cách viết mã. Sau đó, nó không phải là một điều tốt.


Nói một cách thực tế hơn một chút ...

Có vẻ như bạn đang ở rìa của việc đưa mọi thứ đi quá xa. Thách thức (và câu trả lời cho câu hỏi của bạn) nằm ở việc hiểu các quy tắc hoặc yêu cầu kinh doanh của chương trình là gì.

Sử dụng API trò chơi thẻ của bạn làm ví dụ, có một số môi trường nơi mọi thứ có thể được thực hiện để ngăn chặn gian lận là rất quan trọng. Một lượng lớn tiền thật có thể được tham gia, do đó, thật hợp lý khi đặt một số lượng lớn séc để đảm bảo rằng gian lận không thể xảy ra.

Mặt khác, bạn cần lưu tâm đến các nguyên tắc RẮN, đặc biệt là trách nhiệm đơn lẻ. Yêu cầu lớp container kiểm toán hiệu quả nơi thẻ sẽ đến có thể hơi nhiều. Có thể tốt hơn khi có một lớp kiểm toán / bộ điều khiển giữa hộp chứa thẻ và chức năng nhận các yêu cầu di chuyển.


Liên quan đến những lo ngại đó, bạn cần hiểu những thành phần nào trong API của bạn được phơi bày công khai (và do đó dễ bị tổn thương) so với những gì riêng tư và ít bị lộ. Tôi không phải là người ủng hộ hoàn toàn cho "lớp phủ bên ngoài cứng với lớp bên trong mềm", nhưng sự trở lại tốt nhất của nỗ lực của bạn là làm cứng lớp ngoài của API.

Tôi không nghĩ rằng người dùng cuối dự định của một thư viện là rất quan trọng đối với quyết tâm về việc bạn đặt bao nhiêu chương trình phòng thủ. Ngay cả với các mô-đun mà tôi viết cho mục đích sử dụng của riêng mình, tôi vẫn đưa ra một biện pháp kiểm tra để đảm bảo rằng tương lai tôi không mắc phải một số lỗi vô ý khi gọi thư viện.


2
+1 cho "Cho đến khi nó bắt đầu cản trở việc viết mã." Đặc biệt đối với các dự án cá nhân ngắn hạn, mã hóa phòng thủ có thể mất nhiều thời gian hơn giá trị của nó.
Corey

2
Đồng ý, mặc dù tôi muốn nói thêm rằng đó là một điều tốt để có thể / có thể / lập trình phòng thủ, nhưng điều quan trọng là có thể lập trình theo kiểu nguyên mẫu. Khả năng thực hiện cả hai sẽ cho phép bạn chọn hành động phù hợp nhất, tốt hơn nhiều so với nhiều lập trình viên mà tôi biết, những người chỉ có thể lập trình (sắp xếp) phòng thủ.
David Mulder

13

Mã hóa phòng thủ không chỉ là một ý tưởng tốt cho mã công khai. Đó là một ý tưởng tuyệt vời cho bất kỳ mã nào không bị vứt bỏ ngay lập tức. Chắc chắn, bạn biết làm thế nào nó được gọi là bây giờ , nhưng bạn không biết bạn sẽ nhớ sáu tháng này như thế nào khi bạn quay lại dự án.

Cú pháp cơ bản của Java cung cấp cho bạn rất nhiều khả năng phòng thủ so với ngôn ngữ cấp thấp hơn hoặc được giải thích như C hoặc Javascript tương ứng. Giả sử rằng bạn đặt tên cho các phương thức của mình một cách rõ ràng và không có "trình tự phương thức" bên ngoài, bạn có thể thoát khỏi việc chỉ định các đối số là một kiểu dữ liệu chính xác và bao gồm cả hành vi hợp lý nếu dữ liệu được nhập đúng cách vẫn có thể không hợp lệ.

. mỗi thẻ. Nhưng vì tôi không biết Khu vực của bạn làm gì ngoài việc giữ thẻ, thật khó để biết liệu điều đó có phù hợp hay không.)


1
Tôi coi khu vực này là tài sản của thẻ, nhưng vì Thẻ của tôi hoạt động tốt hơn như những vật bất biến, tôi quyết định cách này là tốt nhất. Cảm ơn vì lời khuyên.
codebreaker

3
@codebreaker một điều có thể giúp trong trường hợp đó là gói thẻ trong một đối tượng khác. Một Ace of Spades là những gì nó là. Vị trí không xác định danh tính của nó và một thẻ có thể là bất biến. Có thể có một Vùng chứa thẻ: có thể có một CardDescriptorthẻ chứa thẻ, vị trí của nó, trạng thái úp / xuống hoặc thậm chí xoay cho các trò chơi quan tâm đến điều đó. Đó là tất cả các thuộc tính có thể thay đổi mà không làm thay đổi danh tính của thẻ.

1

Trước tiên, hãy tạo một lớp giữ danh sách các Vùng để bạn không bị mất Vùng hoặc thẻ trong đó. Sau đó, bạn có thể kiểm tra xem chuyển khoản có trong ZoneList của bạn không. Lớp này có thể sẽ là một loại đơn, vì bạn sẽ chỉ cần một thể hiện, nhưng bạn có thể muốn có các Vùng sau, vì vậy hãy mở các tùy chọn của bạn.

Thứ hai, không có Bộ sưu tập Khu vực hoặc Khu vực thực hiện Bộ sưu tập hoặc bất cứ điều gì khác trừ khi bạn muốn có nó. Đó là, nếu một Vùng hoặc Danh sách Vùng sẽ được chuyển đến một thứ gì đó mong đợi Bộ sưu tập, thì hãy triển khai nó. Bạn có thể vô hiệu hóa một loạt các phương thức bằng cách yêu cầu chúng ném một ngoại lệ (Unim HiệnedException, hoặc một cái gì đó tương tự) hoặc bằng cách đơn giản là chúng không làm gì cả. (Hãy suy nghĩ thật kỹ trước khi sử dụng tùy chọn thứ hai. Nếu bạn làm điều đó bởi vì thật dễ dàng, bạn sẽ thấy mình thiếu các lỗi mà bạn có thể đã mắc phải sớm.)

Có những câu hỏi thực sự về những gì là "chính xác". Nhưng một khi bạn tìm ra những gì bạn sẽ muốn làm những điều đó theo cách đó. Trong hai năm, bạn sẽ quên tất cả những điều này và nếu bạn cố gắng sử dụng mã thì bạn sẽ thực sự khó chịu với anh chàng đã viết nó theo cách phản trực giác như vậy và không giải thích được gì.


2
Câu trả lời của bạn tập trung quá nhiều vào vấn đề trong tay thay vì những câu hỏi rộng hơn mà OP đang hỏi về lập trình phòng thủ nói chung.

Tôi thực sự đã chuyển các Vùng cho các phương thức thực hiện Bộ sưu tập, vì vậy việc triển khai là cần thiết. Một loại đăng ký của các khu vực trong trò chơi là một ý tưởng thú vị, mặc dù.
codebreaker

@ GlenH7: Tôi thấy làm việc với các ví dụ cụ thể thường giúp nhiều hơn lý thuyết trừu tượng. OP cung cấp một cái khá thú vị, vì vậy tôi đã đi với nó.
RalphChapin

1

Mã hóa phòng thủ trong thiết kế API nói chung là về việc xác thực đầu vào và cẩn thận chọn một cơ chế xử lý lỗi thích hợp. Những điều khác câu trả lời đề cập cũng đáng chú ý.

Đây thực sự không phải là những gì ví dụ của bạn là về. Bạn đang ở đó giới hạn bề mặt API của bạn, vì một lý do rất cụ thể. Như GlenH7 đã đề cập, khi bộ thẻ được sử dụng trong một trò chơi thực tế, với một cỗ bài ('đã sử dụng' và 'không sử dụng'), ví dụ như một bàn và bàn tay, bạn chắc chắn muốn đặt séc vào để đảm bảo mỗi thẻ Thẻ từ bộ có mặt một lần và chỉ một lần.

Rằng bạn thiết kế cái này với "vùng", là một lựa chọn tùy ý. Tùy thuộc vào việc thực hiện (một khu vực chỉ có thể là một bàn tay, một sàn hoặc một bảng trong ví dụ trên) nó rất có thể là một thiết kế kỹ lưỡng.

Tuy nhiên, việc triển khai đó có vẻ giống như một loại dẫn xuất của một Collection<Card>bộ thẻ giống hơn , với API ít hạn chế hơn. Ví dụ: khi bạn muốn xây dựng một máy tính giá trị tay hoặc AI, bạn chắc chắn muốn được tự do lựa chọn và số lượng mỗi thẻ bạn lặp đi lặp lại.

Vì vậy, thật tốt khi trưng ra một API hạn chế như vậy, nếu mục tiêu duy nhất của API đó là đảm bảo mỗi thẻ luôn nằm trong một vùng.

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.