Có phải việc sử dụng tên tham số khác với tên loại chỉ bằng cách đặt vỏ bọc được coi là một thực tiễn xấu trong C #?


43

Tôi thấy các câu hỏi tương tự như thế này liên quan đến tên tham số khớp với các thuộc tính trên lớp, nhưng tôi không thể tìm thấy bất cứ điều gì liên quan đến việc sử dụng tên tham số giống với tên loại tham số ngoại trừ vỏ trong C #. Nó dường như không phải là một vi phạm mà tôi có thể tìm thấy, nhưng nó có bị coi là hành vi xấu không? Ví dụ, tôi có phương pháp sau

public Range PadRange(Range range) {}

Phương pháp này lấy một phạm vi và trả về một phạm vi mới đã áp dụng một số phần đệm. Vì vậy, với bối cảnh chung, tôi không thể nghĩ ra một tên mô tả nào hơn cho tham số. Tuy nhiên, tôi nhớ về một mẹo tôi đã chọn khi đọc Code Complete về "khoảng cách tâm lý". Nó nói rằng

Khoảng cách tâm lý có thể được định nghĩa là sự dễ dàng trong đó hai mục có thể được phân biệt ... Khi bạn gỡ lỗi, hãy sẵn sàng cho các vấn đề gây ra bởi khoảng cách tâm lý không đủ giữa các tên biến tương tự và giữa các tên thường trình tương tự. Khi bạn xây dựng mã, chọn tên có sự khác biệt lớn để bạn có thể tránh được vấn đề.

Chữ ký phương thức của tôi có rất nhiều "Phạm vi" đang diễn ra, vì vậy cảm giác như đó có thể là một vấn đề liên quan đến khoảng cách tâm lý này. Bây giờ, tôi thấy nhiều nhà phát triển làm như sau

public Range PadRange(Range myRange) {}

Cá nhân tôi có một sự chán ghét mạnh mẽ cho hội nghị này. Thêm tiền tố "của tôi" vào tên biến không cung cấp thêm ngữ cảnh.

Tôi cũng thấy như sau

public Range PadRange(Range rangeToPad) {}

Tôi thích điều này tốt hơn tiền tố "của tôi", nhưng vẫn không quan tâm đến nó. Nó chỉ cảm thấy quá dài dòng đối với tôi, và đọc lúng túng như một tên biến. Đối với tôi, nó hiểu rằng phạm vi sẽ được đệm vì tên phương thức.

Vì vậy, với tất cả điều này được đặt ra, ruột của tôi là đi với chữ ký đầu tiên. Đối với tôi, nó sạch sẽ. Không cần phải ép buộc bối cảnh khi không cần thiết. Nhưng tôi đang làm cho chính mình hoặc các nhà phát triển trong tương lai một sự bất đồng với quy ước này? Tôi có vi phạm một thực hành tốt nhất?


35
Nếu bạn không thể đưa ra một tham số mô tả nhiều hơn, Range rangethì tốt thôi.
Robert Harvey

21
Trong tình huống này, IDE sẽ tô màu 'Phạm vi' và 'phạm vi' khác nhau để thực sự dễ dàng thấy rằng một là loại và một là tên biến.
17 của 26 tháng

10
Vâng, tôi nhớ lại rằng. Đó là trong hướng dẫn thiết kế của họ như là một xem xét. "NGƯỜI TIÊU DÙNG cho một tài sản cùng tên với loại của nó." docs.microsoft.com/en-us/dotnet/stiteria/design-guferences/ory
Jason Tyler

11
Cũng tốt: Range r(ít nhất là cho một cơ thể phương pháp ngắn) và Range toPad.
Bergi

19
Tôi luôn coi tiền tố "của tôi" là một cái gì đó được thực hiện trong mã ví dụ, trong đó nó được sử dụng để chỉ cho người đọc rằng một tên nên được thay đổi thành một tên khác tùy thuộc vào ngữ cảnh. Nó không phải là một cái gì đó thực sự nên kết thúc trong bất kỳ mã thực sự.
kapex

Câu trả lời:


100

Đừng lật đổ điều này, Range rangelà tốt. Tôi sử dụng kiểu đặt tên như vậy trong hơn 15 năm trong C #, và có lẽ lâu hơn trong C ++, và chưa bao giờ gặp bất kỳ nhược điểm thực sự nào từ nó, hoàn toàn ngược lại.

Tất nhiên, khi bạn có các biến cục bộ khác nhau trong cùng một phạm vi, tất cả cùng loại, có thể sẽ giúp đầu tư một số nỗ lực tinh thần để phân biệt chúng đúng cách.


6
Một ngoại lệ khác tôi muốn thêm là khi cá thể đang có một hành động cụ thể được thực hiện trên nó. Có một chút chỗ để mô tả nhiều hơn về lý do tại sao tham số là cần thiết chẳng hạn. Wack (Bozo bozoToBeWacked) Thậm chí nhiều hơn nếu tham số là tùy chọn và mô tả đối tượng trong kịch bản đó có nghĩa là gì / nó sẽ tác động. Quy tắc ngón tay cái của tôi nếu nó không rõ ràng mà không nhìn vào mã những gì tham số làm thì nó cần một tên và / hoặc bình luận tốt hơn.
Mike

17
Tôi sẽ coi Wack(Bozo bozoToBeWacked)như một ví dụ về sự dư thừa trong tên biến được amply thể hiện bằng chính phương thức đó (và do đó không mong muốn theo câu hỏi ban đầu). Wack(Person bozo)là có giá trị hơn về mặt ngữ nghĩa, tuy nhiên, vì nó ngụ ý rằng nó chỉ được mong đợi cho các bozos bị đánh cắp.
Miral

2
Trong trường hợp bạn đề cập trong đoạn thứ hai, tôi sẽ thường đổi tên tham số thành một cái gì đó như initialRange. Nó vẫn rất chung chung nhưng không có mức độ khó chịu gần như tương tự myRange.
Jaquez

@Mirus: Nó trở nên phù hợp hơn trong các trường hợp như Punch(Bozo punchingBozo, Bozo bozoToBePunched). Đặt tên chi tiết có liên quan hơn khi bạn phải phân biệt giữa nhiều thứ (được mô tả theo ngữ nghĩa với khá nhiều từ giống nhau). Đề xuất của OP Range rangeToPadlà không cần thiết trong ví dụ về một phương thức tham số của anh ấy, nhưng có thể cực kỳ cần thiết cho một phương thức lấy trong một số Rangeđối tượng.
Flater

7
@Flater Punch(Bozo source, Bozo target)sẽ thực hiện công việc
Thomas Ayoub

17

Tôi làm điều này mọi lúc, nó mang lại cho tôi tâm trí tuyệt vời. Nếu đó là một đối số được truyền cho một hàm tạo cần được gán cho một thành viên, thì thành viên của tôi cũng sẽ được đặt tên là phạm vi và phép gán sẽ là

this.range = range;

Và tôi thường có một tài sản tên là Range.

Tất cả đều giống nhau, chỉ khác nhau về ngữ cảnh nên việc duy trì một tên duy nhất và bạn sẽ chỉ phải nhớ một tên. Đó là một điều, sự khác biệt hoàn toàn là kỹ thuật.

Bạn nên nghiêm khắc với các thành viên đủ điều kiện với "này." Mặc dù, nhưng đó là những gì StyleCop dành cho.

Ghi chú bên StyleCop

Tranh cãi được đảm bảo!

Đối với những người ủng hộ việc sử dụng "này.": Tôi đã thấy _, m, m_ và không có gì. Bản thân ngôn ngữ đang cung cấp cho chúng ta một cách hoàn toàn rõ ràng, rõ ràng, dễ nhận biết trên toàn cầu cho thấy chúng ta đang làm việc với một thành viên trong lớp. Tại sao trên trái đất bạn muốn tạo ra theo cách riêng của bạn mà cắt xén tên đã hoàn hảo?

Lý do duy nhất tôi có thể nghĩ về nó rằng đó là một thói quen di sản từ thời C, khi nó thực sự có ý nghĩa để làm điều đó bởi vì không có cách nào khác.

"Đó là nhiều nhân vật hơn!" Nghiêm túc? "Thời gian biên dịch sẽ tăng vọt!" Nghiêm túc? "Tôi sẽ phải nâng hồng hào của mình khi gõ nó!". Như thể thời gian gõ có bất kỳ ý nghĩa trong tổng thời gian phát triển.

Tôi nhận ra rằng bất kỳ phong cách nào khác với những gì bạn đã sử dụng sẽ gây ra một số sự phản đối. Nhưng sử dụng điều này một cách nhất quán là khó để tranh luận. Đây là cách nó hoạt động với tôi: Trước khi tôi đẩy một tệp mã mới, tôi chạy StyleCop và nó sẽ tìm thấy một số thành viên thiếu vòng loại "này". Tôi đặt "cái này." trên bảng tạm, chạy bởi các thành viên và chèn. Không có nỗ lực nào cả.

StyleCop làm được nhiều hơn thế (haha). Có rất nhiều cách mà một nhà phát triển có thể (chỉ xem xét định dạng mã) làm nản lòng công việc bảo trì của người kế nhiệm. StyleCop ngăn chặn hầu hết trong số họ. Nó là vô giá.

Nếu bạn chưa quen với nó: nó thường khiến bạn càu nhàu trong một hoặc hai tuần và sau đó bạn sẽ thích nó.


19
" Bạn nên nghiêm khắc với các thành viên đủ điều kiện với" điều này. ", " -1. Không sử dụng thistrừ khi cần thiết. Đó chỉ là tiếng ồn. Sử dụng _rangecho trường và thiskhông bao giờ cần thiết ngoại trừ trong các phương thức mở rộng.
David Arno

12
Tôi đồng tình với @DavidArno. "Đây" chỉ là tiếng ồn thuần túy. 'Phạm vi' là một thuộc tính, '_range' là một trường và 'phạm vi' là một tham số.
17 của 26 tháng

8
@David Nếu biến là một thành viên của lớp, nó là thiết yếu và đánh bại bất kỳ tiền tố tùy ý nào để báo hiệu bạn đang nhìn vào một thành viên của lớp chứ không phải là một đối số hoặc một biến cục bộ.
Martin Maat

6
IDE của tôi sẽ bắn những quả cầu lửa thứ hai tôi sẽ gán một biến cho chính nó.
Koekje

21
@DavidArno Muốn giải thích tại sao "tiếng ồn" lại _thích hợp hơn tiếng ồn this.? Tôi sẽ tranh luận một ý nghĩa được thực thi bởi ngôn ngữ (và thường có tô sáng cú pháp) trong khi ngôn ngữ kia thì không.
Clint

9

Tự hướng dẫn của tôi về phương pháp đặt tên, tham số và biến là khá đơn giản:

  1. Nếu tên chứa loại được truyền vào hoặc trả lại, bạn đã làm sai.
  2. Đặt tên cho những thứ theo những gì chúng được dự định, không phải những gì chúng là.
  3. Luôn nhớ rằng mã được đọc nhiều hơn nó được viết.

Do đó, chữ ký phương thức tối ưu, theo tôi, sẽ là:

Range Pad(Range toPad)

Rút ngắn tên phương thức là tự giải thích.

Tên tham số toPadngay lập tức cho người đọc biết rằng tham số đó có thể sẽ được sửa đổi tại chỗ bằng cách được đệm, sau đó trả lại. Ngược lại, không có giả định nào có thể được thực hiện về một biến có tên range.

Hơn nữa, trong cơ thể thực tế của phương thức, bất kỳ Rangebiến nào khác được giới thiệu sẽ (nên) được đặt tên theo ý định của chúng, vì vậy bạn có thể có paddedunpadded... toPadtuân theo các quy ước đặt tên đó, nhưng rangechỉ đưa ra và không tạo gel.


3
padded/ unpaddedâm thanh tốt cho các biến bất biến cục bộ. Nhưng nếu có một toPadtham số có thể thay đổi , thì sau khi đệm xong, tên toPadkhông còn phù hợp nữa (trừ khi kết quả được trả về ngay lập tức). Tôi thà dính Range rangevào những trường hợp đó.
kapex

@Kapep Tôi chỉ gọi nó là result. Nó được sửa đổitrả lại . Cái sau rõ ràng từ cái tên và cái trước theo sau, vì trả lại một đối số không được sửa đổi không có ý nghĩa.
maaartinus

Từ chữ ký, Padphương thức trả về a Range. Điều đó, với tôi, ngụ ý rằng cái tôi đã qua sẽ được bảo quản, không được sửa đổi tại chỗ và một miếng đệm mới, Rangesẽ được trả lại. .
Aaron M. Eshbach

1
Tôi không biết phải nghĩ gì về lời khuyên của bạn. Pad(toPad)cảm thấy hơi sai IMHO. Bạn thực hiện một quan điểm tốt để bảo vệ quan điểm của bạn mặc dù. Bạn nhận được +0 từ tôi! :)
Eric Duminil

Trong câu hỏi, nó nói rằng nó "trả về một phạm vi mới đã áp dụng một số phần đệm". Tôi đồng ý với cách đặt tên của bạn cho những gì bạn nghĩ là kịch bản, nhưng đối với câu hỏi thực tế tôi sẽ tìm một cái gì đó như Range CreatRangeWithPadding (Nguồn phạm vi)
Richard Willis

3

Để đặt tên cho các thành phần mã (loại, biến, hàm, bất cứ thứ gì), câu hỏi chính để tự hỏi mình là

Nếu tôi mắc lỗi đánh máy, trình biên dịch có tìm thấy nó cho tôi không?

Loại lỗi tồi tệ nhất dựa trên lỗi đánh máy là một lỗi mà mã biên dịch và chạy, nhưng đưa ra hành vi khác với những gì bạn mong đợi, vì những lý do rất tinh vi. Và vì đó là do lỗi đánh máy, nên thường rất khó nhìn thấy khi bạn kiểm tra mã. Nếu một lỗi đánh máy sẽ dừng biên dịch mã, thì trình biên dịch sẽ gắn cờ dòng gây ra sự cố và bạn có thể dễ dàng tìm và sửa nó.

Đối với tình huống của bạn khi loại và biến chỉ khác nhau về viết hoa, điều này sẽ luôn luôn như vậy. (Hoặc gần như luôn luôn - với nỗ lực đầy đủ, tôi chắc chắn bạn có thể làm cho nó hoạt động, nhưng bạn phải thực sự cố gắng.) Vì vậy, tôi nghĩ rằng bạn ổn ở đó.

Nơi bạn cần quan tâm sẽ là nếu có hai biến, phương thức, hàm hoặc thuộc tính trong phạm vi hiện tại được gọi rangeRange. Trong trường hợp đó, trình biên dịch có thể sẽ cho phép nó đi qua và bạn sẽ có hành vi bất ngờ vào thời gian chạy. Lưu ý rằng đó là hai trong số bất kỳ của những loại nguyên tố mã, không chỉ là "hai biến" hay "hai chức năng" - tất cả những người có thể được ngầm đúc với nhau, với kết quả là tàn sát khi nó chạy. Bạn có thể nhận được cảnh báo, nhưng bạn không thể đảm bảo nhiều hơn thế. Bạn có vấn đề tương tự nếu bạn có hai loại được gọi là rangeRange.

Cũng lưu ý điều tương tự áp dụng cho kiểu ký hiệu Hungary , trong đó các tên được thêm tiền tố với một hoặc nhiều ký tự để nói thêm về bất cứ điều gì. Nếu bạn có một biến được gọi Rangevà một con trỏ tới nó được gọi PRange, chẳng hạn, rất dễ vô tình bỏ lỡ P. C # nên nắm bắt điều này, nhưng C và C ++ sẽ chỉ đưa ra cảnh báo cho bạn nhiều nhất. Hoặc đáng lo ngại hơn, giả sử bạn có một phiên bản kép được gọi DRangevà bạn chuyển mẫu đó thành phiên bản nổi được gọi FRange. Sử dụng một phao một cách tình cờ (đó là dễ dàng vì các phím liền kề trên bàn phím) và mã của bạn sẽ loại công việc, nhưng nó sẽ đổ theo những cách kỳ lạ và không thể đoán trước khi quá trình này hết độ phân giải và underflows.

Chúng ta không còn ở những ngày mà chúng ta có giới hạn đặt tên là 8 ký tự, hoặc 16 ký tự, hoặc bất kỳ giới hạn tùy ý nào. Đôi khi tôi đã nghe người mới phàn nàn về các tên biến dài hơn khiến việc mã hóa mất nhiều thời gian hơn. Chỉ có những người mới phàn nàn về điều này mặc dù. Các lập trình viên nghiêm túc biết rằng những gì thực sự cần thời gian là tìm ra các lỗi tối nghĩa - và lựa chọn đặt tên không tốt là một cách cổ điển để thả mình vào lỗ hổng cụ thể đó.


Chỉ cần một lưu ý cho độc giả - đối với C #, mọi người nên thực sự tránh Ký hiệu Hungary. Mặc dù nó rất hữu ích trong thời đại cũ khi việc tô sáng cú pháp không phải là một thứ hoặc bị hạn chế, ngày nay nó thực sự không có ích.
T. Sar - Tái lập Monica

@ T.Sar Ngay cả khi trở lại trong ngày, tôi ghét nó với một niềm đam mê! Như bạn nói, các IDE tốt hơn đã giải quyết các vấn đề mà nó nhắm đến, nhưng nó vẫn xuất hiện thường xuyên. Chẳng hạn, bạn vẫn sẽ thấy nó nếu bạn cần nói chuyện với API Windows, vì những lý do lịch sử. Tôi không muốn hoàn toàn bash phong cách ưa thích của mọi người nếu họ thực sự thích nó, nhưng tôi đã muốn sử dụng nó như một cái móc để cho thấy rằng chữ hoa / chữ thường không phải là cách duy nhất bạn có thể làm hỏng việc đặt tên.
Graham

Tôi biết rằng bạn không đề xuất việc sử dụng Ký hiệu Hungary, nhưng tôi cũng nhận thấy rằng các nhà phát triển trẻ có một điểm yếu rất lớn đối với những thứ có tên hay. Có thể nói "Mã của tôi được viết theo phong cách mã hóa ninja siêu bí mật này - Ký hiệu Hungary!" nghe có vẻ mát mẻ không thể cưỡng lại đối với tâm trí trẻ. Tôi đã thấy quá nhiều nhà phát triển trở thành con mồi của những từ ngữ lạnh lùng ... Ký hiệu Hungary là nỗi đau đưa ra hình thức mã nguồn xX
T. Sar - Tái lập Monica

@ T.Sar Đúng vậy. "Những người không nhớ về quá khứ sẽ cam chịu lặp lại nó" và tất cả điều đó. : /
Graham

1
Có phải chúng ta đang nói về ký hiệu tiếng Hungary gốc hoặc cách Microsoft bị hiểu sai một cách thô thiển (gần "các tác giả tài liệu trong nhóm Windows đã vô tình phát minh ra cái gọi là Hệ thống Hungary"trong cuộc phỏng vấn của Joel Spolsky bởi Leo Laporte trên tập Triangulation 277, 2016-12-12, 04 phút 00 giây - 06 phút 35 giây )?
Peter Mortensen

1

Một giai thoại tôi muốn thêm vào, trong khi Range rangeđiều này là hợp pháp về mặt cú pháp, nó có thể khiến mọi thứ trở nên khó khăn hơn để gỡ lỗi hoặc tái cấu trúc. Tìm kiếm một biến có tên "phạm vi" trong một tệp có nhiều biến loại Phạm vi? Bạn có thể sẽ làm nhiều công việc sau này do lựa chọn đặt tên này.

Điều này phần lớn phụ thuộc vào bối cảnh mặc dù. Nếu đó là một tệp 30 dòng, các tuyên bố của tôi không thực sự phát huy tác dụng.


7
Hầu hết mọi người sử dụng các công cụ phân biệt giữa các loại và tham số hoặc biến, để phát triển Windows. Những gì bạn mô tả nhắc nhở tôi về những ngày grep cũ tốt trên Unix.
Frank Hileman

1
@FrankHileman Nó có thể làm bạn ngạc nhiên, nhưng Unix vẫn còn sống và grep vẫn đang được sử dụng (: D). Vì grep mặc định là phân biệt chữ hoa chữ thường, nên vấn đề được đề cập không tồn tại. Tuy nhiên, ngay cả chúng tôi, những người ghét cửa sổ nghiện thiết bị đầu cuối, hiếm khi sử dụng grep cho mã nguồn vì IDE tốt hơn nhiều trong các tìm kiếm thông thường.
maaartinus

Tôi nghĩ rằng chúng tôi đồng ý rằng nền tảng không phải là vấn đề. greplà một công cụ tuyệt vời, nhưng nó không phải là một công cụ tái cấu trúc. Và các công cụ tái cấu trúc tồn tại trên mọi nền tảng phát triển mà tôi đã sử dụng. (Hệ điều hành X, Ubuntu, Windows).
Eric Wilson

@EricWilson Tại một thời điểm, grep và sed là những công cụ tái cấu trúc duy nhất trên unix.
Frank Hileman

@FrankHileman Chỉ vì một cái gì đó được sử dụng như một công cụ tái cấu trúc không có nghĩa nó là một công cụ. Tôi có thể cấu trúc lại trong notepad, nhưng điều đó không làm cho nó nhiều hơn một trình soạn thảo văn bản.
Eric Wilson

1

Tôi nghĩ bạn có thể sử dụng ngày Range rangenay vì một lý do: tô sáng cú pháp. IDE hiện đại thường làm nổi bật tên loại và tên tham số trong các màu khác nhau . Ngoài ra một loại và một biến có "khoảng cách logic" đáng kể để không dễ bị nhầm lẫn.

Nếu đây không phải là trường hợp tôi sẽ xem xét một tên khác hoặc cố gắng kích hoạt một plugin / tiện ích mở rộng có thể làm nổi bật cú pháp này.


0

Khi một hàm là chung, lý do là các tham số sẽ là chung và do đó nên có tên chung.

Không phải những gì bạn đang nói, nhưng tôi đã thấy các hàm thực hiện một hàm chung có các tên tham số cụ thể gây hiểu nhầm. Như

public String removeNonDigits(String phoneNumber)

Tên hàm nghe có vẻ rất chung chung, như thế này có thể được áp dụng cho nhiều chuỗi trong nhiều tình huống. Nhưng tên tham số cụ thể kỳ lạ, khiến tôi tự hỏi liệu tên hàm có bị sai lệch không, hay ... gì?

Vì vậy, chắc chắn, thay vì nói Phạm vi phạm vi, bạn có thể nói Phạm vi phạm vi. Nhưng những thông tin này thêm gì? Tất nhiên đó là phạm vi để pad. Nó sẽ là gì khác?

Thêm một số tiền tố tùy ý, "của tôi" hoặc "m_" hoặc bất cứ điều gì, truyền đạt không thông tin bổ sung cho người đọc. Khi tôi đã sử dụng các ngôn ngữ mà trình biên dịch không cho phép tên biến giống như tên loại - có hoặc không có phân biệt chữ hoa chữ thường - đôi khi tôi đã đặt tiền tố hoặc hậu tố, chỉ để biên dịch nó . Nhưng đó chỉ là để đáp ứng trình biên dịch. Người ta có thể lập luận rằng ngay cả khi trình biên dịch có thể phân biệt, điều này giúp người đọc dễ dàng phân biệt hơn. Nhưng wow, trong Java tôi đã viết các câu như "Khách hàng của khách hàng = Khách hàng mới ();" một tỷ lần và tôi không bao giờ thấy nó khó hiểu. (Tôi luôn thấy nó hơi dư thừa và tôi thích điều đó hơn trong VB, bạn chỉ có thể nói "khách hàng mờ là khách hàng mới" và bạn không phải đặt tên lớp hai lần.)

Trường hợp tôi DO phản đối mạnh mẽ các tên chung là khi có hai hoặc nhiều phiên bản cùng loại trong cùng một chức năng. Thông số ĐẶC BIỆT. Như:

public Range pad(Range range1, Range range2)

Sự khác biệt giữa phạm vi1 và phạm vi 2 là gì? Như thế nào anh có thể biết? Nếu đó là thứ mà chúng thực sự là hai giá trị chung và có thể hoán đổi cho nhau, thì được, như

public boolean overlap(Range range1, Range range2)

Tôi hy vọng điều đó sẽ trả về true nếu các phạm vi trùng nhau và sai nếu chúng không có, vì vậy chúng là chung và có thể hoán đổi cho nhau.

Nhưng nếu chúng khác nhau, hãy cho tôi manh mối chúng khác nhau như thế nào! Gần đây tôi mới làm việc trên một chương trình có lớp "Địa điểm" để chứa dữ liệu về các vị trí địa lý và với các biến loại này có tên là "p", "place", "place2", "myPlace", v.v. tên thực sự giúp tôi xác định đó là cái nào.

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.