Làm thế nào để tôi ngăn chặn mã trùng lặp không xác định?


33

Tôi làm việc trên một cơ sở mã khá lớn. Hàng trăm lớp, hàng tấn tệp khác nhau, rất nhiều chức năng, mất hơn 15 phút để kéo xuống một bản sao mới, v.v.

Một vấn đề lớn với cơ sở mã lớn như vậy là nó có khá nhiều phương thức tiện ích và nó cũng làm điều tương tự hoặc có mã không sử dụng các phương thức tiện ích này khi có thể. Và các phương thức tiện ích không chỉ có trong một lớp (vì nó sẽ là một mớ hỗn độn rất lớn).

Tôi còn khá mới mẻ với cơ sở mã, nhưng nhóm trưởng, người đã làm việc với nó trong nhiều năm dường như có cùng một vấn đề. Nó dẫn đến nhiều sự trùng lặp mã và công việc, và như vậy, khi một cái gì đó bị phá vỡ, nó thường bị phá vỡ trong 4 bản sao của cùng một mã

Làm thế nào chúng ta có thể kiềm chế mô hình này? Như với hầu hết các dự án lớn, không phải tất cả các mã đều được ghi lại (mặc dù một số là) và không phải tất cả các mã đều ... tốt, sạch sẽ. Nhưng về cơ bản, thật tuyệt nếu chúng ta có thể cải thiện chất lượng về mặt này để trong tương lai chúng ta có ít sự sao chép mã hơn và những thứ như các chức năng tiện ích dễ dàng khám phá hơn.

Ngoài ra, các hàm tiện ích thường là trong một số lớp trình trợ giúp tĩnh, trong một số lớp trình trợ giúp không tĩnh hoạt động trên một đối tượng hoặc là một phương thức tĩnh trên lớp mà nó chủ yếu "trợ giúp".

Tôi đã có một thử nghiệm trong việc thêm các chức năng tiện ích như các phương thức Tiện ích mở rộng (Tôi không cần bất kỳ nội bộ nào của lớp và nó chắc chắn chỉ được yêu cầu trong các tình huống rất cụ thể). Điều này có tác dụng ngăn chặn sự lộn xộn của lớp chính và như vậy, nhưng nó không thực sự được khám phá nữa trừ khi bạn đã biết về nó


1
Liên quan: lập trình
Bảo mật

Câu trả lời:


30

Câu trả lời đơn giản là bạn thực sự không thể ngăn chặn việc sao chép mã. Tuy nhiên, bạn có thể "sửa nó" thông qua một quá trình gia tăng lặp đi lặp lại liên tục khó khăn, rút ​​gọn thành hai bước:

Bước 1. Bắt đầu viết các bài kiểm tra về mã kế thừa (tốt nhất là sử dụng khung kiểm tra)

Bước 2. Viết lại / cấu trúc lại mã được sao chép bằng cách sử dụng những gì bạn đã học được từ các bài kiểm tra

Bạn có thể sử dụng các công cụ phân tích tĩnh để phát hiện mã trùng lặp và đối với C #, có vô số công cụ có thể làm điều này cho bạn:

Các công cụ như thế này sẽ giúp bạn tìm các điểm trong mã thực hiện những điều tương tự. Tiếp tục viết bài kiểm tra để xác định rằng họ thực sự làm; sử dụng các thử nghiệm tương tự để làm cho mã trùng lặp đơn giản hơn để sử dụng. Việc "tái cấu trúc" này có thể được thực hiện theo nhiều cách và bạn có thể sử dụng danh sách này để xác định chính xác:

Hơn nữa, cũng có cả một cuốn sách về chủ đề này của Michael C. Feathers, làm việc hiệu quả với Bộ luật kế thừa . Nó đi sâu vào các chiến lược khác nhau mà bạn có thể thực hiện để thay đổi mã thành tốt hơn. Anh ta có một "thuật toán thay đổi mã kế thừa" không quá xa so với quy trình hai bước ở trên:

  1. Xác định điểm thay đổi
  2. Tìm điểm kiểm tra
  3. Phá vỡ sự phụ thuộc
  4. Viết bài kiểm tra
  5. Thay đổi và tái cấu trúc

Cuốn sách rất đáng đọc nếu bạn đang xử lý sự phát triển của trường màu nâu, tức là mã kế thừa cần thay đổi.

Trong trường hợp này

Trong trường hợp của OP, tôi có thể tưởng tượng mã không thể kiểm tra được gây ra bởi một nồi mật ong cho "các phương pháp và thủ thuật tiện ích" có nhiều dạng:

Hãy lưu ý rằng không có gì sai với những điều này, nhưng mặt khác, chúng thường khó duy trì và thay đổi. Các phương thức mở rộng trong .NET là các phương thức tĩnh, nhưng cũng tương đối dễ kiểm tra.

Trước khi bạn trải qua các lần tái cấu trúc, hãy nói chuyện với nhóm của bạn về nó. Chúng cần được giữ trên cùng một trang với bạn trước khi bạn tiến hành bất cứ điều gì. Điều này là do nếu bạn tái cấu trúc một cái gì đó thì khả năng cao là bạn sẽ gây ra xung đột hợp nhất. Vì vậy, trước khi làm lại một cái gì đó, hãy điều tra nó, nói với nhóm của bạn để làm việc với những điểm mã đó một cách thận trọng trong một thời gian cho đến khi bạn hoàn thành.

Vì OP là mới đối với mã, có một số điều khác phải làm trước khi bạn nên làm bất cứ điều gì:

  • Dành thời gian để học hỏi từ codebase, tức là phá vỡ "mọi thứ", kiểm tra "mọi thứ", hoàn nguyên.
  • Yêu cầu ai đó trong nhóm xem xét mã của bạn trước khi cam kết. ;-)

Chúc may mắn!


Chúng tôi thực sự có khá nhiều thử nghiệm đơn vị và tích hợp. Không bảo hiểm 100%, nhưng một số điều chúng tôi làm gần như không thể kiểm tra đơn vị nếu không có thay đổi căn bản đối với cơ sở mã của chúng tôi. Tôi không bao giờ xem xét sử dụng phân tích tĩnh để tìm sự trùng lặp. Tôi sẽ phải thử điều đó tiếp theo.
Earlz

@Earlz: Phân tích mã tĩnh là tuyệt vời! ;-) Ngoài ra, bất cứ khi nào bạn cần thực hiện thay đổi, hãy nghĩ đến các giải pháp để thực hiện thay đổi dễ dàng hơn (kiểm tra danh mục tái cấu trúc cho mẫu này)
Spoike

+1 Tôi sẽ hiểu nếu ai đó đưa tiền thưởng vào Q này để trao giải cho anwser này là "hữu ích thêm". Danh mục Refactor to Forms là vàng, những thứ như thế này theo cách của GuidanceExplorer.codeplex.com là những công cụ hỗ trợ lập trình tuyệt vời.
Jeremy Thompson

2

Chúng tôi cũng có thể cố gắng nhìn vấn đề từ một góc độ khác. Thay vì nghĩ rằng vấn đề là sự sao chép mã, chúng tôi có thể xem xét nếu sự cố bắt nguồn từ việc thiếu chính sách để sử dụng lại mã.

Gần đây tôi đã đọc cuốn sách Kỹ thuật phần mềm với các thành phần có thể tái sử dụng và nó thực sự có một bộ các ý tưởng rất thú vị về cách thúc đẩy khả năng sử dụng lại mã ở cấp độ tổ chức.

Tác giả của cuốn sách này, Julian Sametinger, mô tả một loạt các rào cản đối với việc tái sử dụng mã, một số khái niệm về kỹ thuật. Ví dụ:

Khái niệm và kỹ thuật

  • Khó tìm phần mềm có thể tái sử dụng : phần mềm không thể được sử dụng lại trừ khi có thể tìm thấy phần mềm. Tái sử dụng không có khả năng xảy ra khi kho lưu trữ không có đủ thông tin về các thành phần hoặc khi các thành phần được phân loại kém.
  • Không thể truy cập được của phần mềm được tìm thấy : dễ dàng truy cập vào phần mềm hiện tại không nhất thiết phải tăng khả năng sử dụng lại phần mềm. Vô tình, phần mềm hiếm khi được viết theo cách để người khác có thể sử dụng lại nó. Sửa đổi và điều chỉnh phần mềm của người khác có thể trở nên đắt hơn so với lập trình các chức năng cần thiết từ đầu.
  • Các thành phần kế thừa không phù hợp để tái sử dụng : Việc tái sử dụng các thành phần là khó hoặc không thể trừ khi chúng được thiết kế và phát triển để tái sử dụng. Chỉ cần thu thập các thành phần hiện có từ các hệ thống phần mềm cũ và cố gắng sử dụng lại chúng cho các phát triển mới là không đủ để tái sử dụng có hệ thống. Tái cấu trúc có thể giúp trích xuất các thành phần có thể tái sử dụng, tuy nhiên nỗ lực này có thể là đáng kể.
  • Công nghệ hướng đối tượng : Người ta tin rằng công nghệ hướng đối tượng có tác động tích cực đến việc tái sử dụng phần mềm. Thật không may và sai, nhiều người cũng tin rằng việc tái sử dụng phụ thuộc vào công nghệ này hoặc việc áp dụng công nghệ hướng đối tượng đủ cho việc tái sử dụng phần mềm.
  • Sửa đổi : các thành phần sẽ không luôn luôn chính xác theo cách chúng ta muốn. Nếu sửa đổi là cần thiết, chúng tôi sẽ có thể xác định ảnh hưởng của chúng đối với thành phần và kết quả xác minh trước đó.
  • Tái sử dụng rác : Xác nhận các thành phần có thể tái sử dụng ở mức chất lượng nhất định giúp giảm thiểu các khuyết tật có thể. Kiểm soát chất lượng kém là một trong những rào cản lớn để tái sử dụng. Chúng ta cần một số phương tiện để đánh giá xem các hàm cần thiết có khớp với các hàm được cung cấp bởi một thành phần hay không.

Những khó khăn kỹ thuật cơ bản khác bao gồm

  • Đồng ý về những gì một thành phần có thể tái sử dụng cấu thành.
  • Hiểu những gì một thành phần làm và làm thế nào để sử dụng nó.
  • Hiểu cách giao diện các thành phần có thể tái sử dụng với phần còn lại của thiết kế.
  • Thiết kế các thành phần có thể tái sử dụng để chúng dễ dàng thích ứng và sửa đổi một cách có kiểm soát.
  • Tổ chức một kho lưu trữ để các lập trình viên có thể tìm và sử dụng những gì họ cần.

Theo tác giả, mức độ tái sử dụng khác nhau xảy ra tùy thuộc vào sự trưởng thành của một tổ chức.

  • Tái sử dụng đặc biệt giữa các nhóm ứng dụng : nếu không có cam kết rõ ràng về việc tái sử dụng, thì việc tái sử dụng có thể xảy ra theo cách không chính thức và không phù hợp nhất. Hầu hết việc tái sử dụng, nếu có, sẽ xảy ra trong các dự án. Điều này cũng dẫn đến việc quét mã và kết thúc trong quá trình sao chép mã.
  • Tái sử dụng dựa trên kho lưu trữ giữa các nhóm ứng dụng : tình hình cải thiện đôi chút khi kho lưu trữ thành phần được sử dụng và có thể được truy cập bởi các nhóm ứng dụng khác nhau. Tuy nhiên, không có cơ chế rõ ràng nào tồn tại để đưa các thành phần vào kho lưu trữ và không ai chịu trách nhiệm về chất lượng của các thành phần trong kho lưu trữ. Điều này có thể dẫn đến nhiều vấn đề và cản trở việc tái sử dụng phần mềm.
  • Tái sử dụng tập trung với một nhóm thành phần: Trong kịch bản này, một nhóm thành phần chịu trách nhiệm rõ ràng cho kho lưu trữ. Nhóm xác định thành phần nào sẽ được lưu trữ trong kho lưu trữ và đảm bảo chất lượng của các thành phần này và tính sẵn có của tài liệu cần thiết và giúp truy xuất các thành phần phù hợp trong kịch bản tái sử dụng cụ thể. Các nhóm ứng dụng được tách ra khỏi nhóm thành phần, hoạt động như một loại nhà thầu phụ cho mỗi nhóm ứng dụng. Một mục tiêu của nhóm thành phần là để giảm thiểu dư thừa. Trong một số mô hình, các thành viên của nhóm này cũng có thể làm việc trên các dự án cụ thể. Trong quá trình khởi động dự án, kiến ​​thức của họ rất có giá trị để thúc đẩy việc tái sử dụng và nhờ sự tham gia của họ vào một dự án cụ thể, họ có thể xác định các ứng cử viên có thể để đưa vào kho lưu trữ.
  • Tái sử dụng dựa trên tên miền : Việc chuyên môn hóa các nhóm thành phần tương đương với tái sử dụng dựa trên tên miền. Mỗi nhóm miền chịu trách nhiệm cho các thành phần trong miền của mình, ví dụ: các thành phần mạng, thành phần giao diện người dùng, thành phần cơ sở dữ liệu.

Vì vậy, có lẽ, bên cạnh tất cả các đề xuất được đưa ra trong các câu trả lời khác, bạn có thể thiết kế chương trình tái sử dụng, liên quan đến quản lý, tạo thành một nhóm thành phần chịu trách nhiệm xác định các thành phần có thể sử dụng lại bằng cách phân tích miền và xác định kho lưu trữ các thành phần có thể tái sử dụng mà các nhà phát triển khác có thể dễ dàng truy vấn và tìm kiếm các giải pháp nấu chín cho các vấn đề của họ.


1

Có 2 giải pháp khả thi:

Phòng ngừa - Cố gắng có tài liệu tốt nhất có thể. Làm cho mọi chức năng được ghi lại đúng cách và dễ dàng tìm kiếm thông qua toàn bộ tài liệu. Ngoài ra, khi viết mã, làm cho nó rõ ràng nơi mã nên đi, vì vậy rõ ràng là nơi để tìm. Giới hạn số lượng mã "tiện ích" là một trong những điểm chính của điều này. Mỗi lần tôi nghe "hãy tạo lớp tiện ích", tóc tôi bứt lên và máu đóng băng, vì đó rõ ràng là một vấn đề. Luôn có cách nhanh chóng và dễ dàng để yêu cầu mọi người biết về cơ sở mã bất cứ khi nào một số tính năng đã tồn tại.

Giải pháp - Nếu phòng ngừa thất bại, bạn sẽ có thể giải quyết nhanh chóng và hiệu quả đoạn mã có vấn đề. Quá trình phát triển của bạn sẽ cho phép sửa chữa nhanh chóng mã trùng lặp. Kiểm thử đơn vị là hoàn hảo cho việc này, bởi vì bạn có thể sửa đổi mã một cách hiệu quả mà không sợ phá vỡ mã. Vì vậy, nếu bạn tìm thấy 2 đoạn mã tương tự, thì việc trừu tượng hóa chúng thành một hàm hoặc lớp sẽ dễ dàng với một chút tái cấu trúc.

Cá nhân tôi không nghĩ rằng phòng ngừa là có thể. Bạn càng cố gắng, càng có nhiều vấn đề để tìm các tính năng đã có sẵn.


0

Tôi không nghĩ loại vấn đề này có giải pháp chung. Mã trùng lặp sẽ không được tạo nếu nhà phát triển có đủ khả năng tra cứu mã hiện có. Ngoài ra các nhà phát triển có thể khắc phục các sự cố tại chỗ nếu họ muốn.

Nếu ngôn ngữ là hợp nhất trùng lặp C / C ++ sẽ dễ dàng hơn vì tính linh hoạt của liên kết (người ta có thể gọi bất kỳ externchức năng nào mà không cần thông tin trước). Đối với Java hoặc .NET, bạn có thể cần phải tạo ra các lớp trình trợ giúp và / hoặc các thành phần tiện ích.

Tôi thường bắt đầu loại bỏ trùng lặp mã hiện có chỉ khi các lỗi chính phát sinh từ các phần trùng lặp.


0

Đây là một vấn đề điển hình của một dự án lớn hơn đã được xử lý bởi nhiều lập trình viên, đã được đóng góp dưới đôi khi rất nhiều áp lực ngang hàng. Sẽ rất rất hấp dẫn để tạo một bản sao của một lớp và điều chỉnh nó cho lớp cụ thể đó. Tuy nhiên, khi một vấn đề được tìm thấy trong lớp khởi tạo, nó cũng cần được giải quyết trong các phần tử thường bị lãng quên.

Có một giải pháp cho vấn đề này và nó được gọi là Generics đã được giới thiệu trong Java 6. Nó tương đương với C ++ được gọi là Mẫu. Mã trong đó lớp chính xác chưa được biết đến trong Lớp chung. Vui lòng kiểm tra Java Generics và bạn sẽ tìm thấy hàng tấn tài liệu cho nó.

Một cách tiếp cận tốt là viết lại mã dường như được sao chép / dán ở nhiều nơi bằng cách viết lại mã đầu tiên mà bạn cần phải sửa vì một lỗi nhất định. Viết lại để sử dụng Generics và cũng viết mã kiểm tra rất nghiêm ngặt.

Hãy chắc chắn rằng mọi phương thức của lớp Generic được gọi. Bạn cũng có thể giới thiệu các công cụ bao phủ mã: mã chung nên được bảo hiểm mã đầy đủ vì nó sẽ được sử dụng ở một số nơi.

Đồng thời viết mã kiểm tra tức là sử dụng JUnit hoặc tương tự cho lớp được chỉ định đầu tiên sẽ được sử dụng cùng với đoạn mã Chung.

Bắt đầu sử dụng mã Chung cho phiên bản sao chép thứ hai (hầu hết các lần) khi tất cả các mã trước đó hoạt động và được kiểm tra đầy đủ. Bạn sẽ thấy rằng có một số dòng mã dành riêng cho Lớp được chỉ định đó. Bạn có thể gọi các dòng mã hóa này trong một phương thức được bảo vệ trừu tượng cần được triển khai bởi lớp dẫn xuất sử dụng lớp cơ sở Chung.

Vâng, đó là một công việc tẻ nhạt, nhưng khi bạn thực hiện nó sẽ ngày càng tốt hơn để tách ra các lớp tương tự và thay thế nó bằng một cái gì đó rất rất sạch sẽ, được viết tốt và dễ bảo trì hơn nhiều.

Tôi đã có một tình huống tương tự khi cuối cùng trên lớp chung thay thế một cái gì đó giống như 6 hoặc 7 lớp khác gần như giống hệt nhau nhưng đã được sao chép và dán bởi các lập trình viên khác nhau trong một khoảng thời gian.

Và vâng, tôi rất ủng hộ việc kiểm tra mã tự động. Nó sẽ có giá cao hơn khi bắt đầu nhưng chắc chắn sẽ giúp bạn tiết kiệm rất nhiều thời gian. Và cố gắng đạt được phạm vi bảo hiểm mã tổng thể ít nhất 80% và 100% cho mã Chung.

Hy vọng điều này sẽ giúp và may mắn.


0

Tôi thực sự sẽ lặp lại ý kiến ​​ít phổ biến nhất ở đây và bên cạnh Gangnusvà đề xuất rằng sao chép mã không phải lúc nào cũng có hại và đôi khi có thể là điều xấu xa hơn.

Nếu, giả sử, bạn cho tôi tùy chọn sử dụng:

A) Một thư viện hình ảnh ổn định (không thay đổi) và nhỏ, được kiểm tra tốt , sao chép vài chục dòng mã toán học tầm thường cho toán học vectơ như các sản phẩm chấm và lerps và kẹp, nhưng hoàn toàn tách rời khỏi mọi thứ khác và xây dựng trong một phần của một giây

B) Một thư viện hình ảnh không ổn định (thay đổi nhanh) phụ thuộc vào thư viện toán sử thi để tránh vài chục dòng mã được đề cập ở trên, với thư viện toán học không ổn định và liên tục nhận được các cập nhật và thay đổi mới, và do đó thư viện hình ảnh cũng phải được xây dựng lại nếu không hoàn toàn thay đổi là tốt. Phải mất 15 phút để làm sạch xây dựng toàn bộ.

... thì rõ ràng là không có trí tuệ đối với hầu hết mọi người rằng A, và thực sự chính xác là do sự sao chép mã nhỏ của nó, là thích hợp hơn. Điểm nhấn quan trọng tôi cần thực hiện là phần được kiểm tra tốt . Rõ ràng không có gì tệ hơn là có mã trùng lặp thậm chí không hoạt động ngay từ đầu, tại thời điểm đó, nó trùng lặp lỗi.

Nhưng cũng có sự kết hợp và ổn định để suy nghĩ, và một số sao chép khiêm tốn ở đây và có thể đóng vai trò là một cơ chế tách rời cũng làm tăng tính ổn định (tính chất không thay đổi) của gói.

Vì vậy, đề nghị của tôi thực sự sẽ tập trung hơn vào thử nghiệm và cố gắng đưa ra một cái gì đó thực sự ổn định (như không thay đổi, tìm một vài lý do để thay đổi trong tương lai) và đáng tin cậy có sự phụ thuộc vào các nguồn bên ngoài, nếu có, rất ổn định, qua việc cố gắng dập tắt tất cả các hình thức sao chép trong cơ sở mã của bạn. Trong một môi trường nhóm lớn, cái sau có xu hướng là một mục tiêu không thực tế, chưa kể rằng nó có thể làm tăng sự ghép nối và số lượng mã không ổn định mà bạn có trong cơ sở mã của bạn.


-2

Đừng quên rằng sao chép mã không phải lúc nào cũng có hại. Hãy tưởng tượng: bây giờ bạn có một số nhiệm vụ cần giải quyết trong các mô-đun hoàn toàn khác nhau trong dự án của bạn. Bây giờ nó là nhiệm vụ tương tự.

Có thể có ba lý do cho nó:

  1. Một số chủ đề xung quanh nhiệm vụ này là giống nhau cho cả hai mô-đun. Trong trường hợp này, sao chép mã là xấu và nên được thanh lý. Sẽ là thông minh khi tạo một lớp hoặc một mô-đun để hỗ trợ chủ đề này và sử dụng các phương thức của nó trong cả hai mô-đun.

  2. Nhiệm vụ này là lý thuyết về dự án của bạn. Ví dụ, nó là từ vật lý hoặc toán học, vv Nhiệm vụ tồn tại độc lập trong dự án của bạn. Trong trường hợp này, sao chép mã là xấu và cũng nên được thanh lý. Tôi sẽ tạo một lớp đặc biệt cho các chức năng như vậy. Và sử dụng một chức năng như vậy trong bất kỳ mô-đun nơi bạn cần nó.

  3. Nhưng trong các trường hợp khác, sự trùng hợp ngẫu nhiên của các nhiệm vụ là sự trùng hợp tạm thời và không có gì hơn thế. Sẽ rất nguy hiểm khi tin rằng các nhiệm vụ này sẽ giữ nguyên trong quá trình thay đổi dự án do tái cấu trúc và thậm chí gỡ lỗi. Trong trường hợp này, tốt hơn là tạo hai hàm / đoạn mã giống nhau ở những nơi khác nhau. Và những thay đổi trong tương lai ở một trong số chúng sẽ không chạm vào cái khác.

Và trường hợp thứ 3 này xảy ra rất thường xuyên. Nếu bạn sao chép "không biết", chủ yếu là vì lý do này - đó không phải là một bản sao thực sự!

Vì vậy, hãy cố gắng giữ sạch sẽ khi thực sự cần thiết và đừng sợ trùng lặp nếu không cần thiết.


2
code duplication is not always harmfullà một lời khuyên tồi
Tulains Córdova

1
Tôi có nên cúi đầu trước thẩm quyền của bạn? Tôi đã đặt lý do của tôi ở đây. Nếu tôi nhầm, hãy chỉ ra lỗi ở đâu. Bây giờ nó có vẻ như là khả năng kém của bạn để tiếp tục thảo luận.
Gangnus

3
Sao chép mã là một trong những vấn đề cốt lõi trong phát triển phần mềm và nhiều nhà khoa học và nhà lý thuyết điện toán đã phát triển mô hình và phương pháp luận để tránh sao chép mã là nguồn chính của các vấn đề bảo trì trong phát triển phần mềm. Nó giống như nói "viết mã kém không phải lúc nào cũng xấu", theo cách đó, bất cứ điều gì cũng có thể được biện minh về mặt tu từ. Có thể bạn đúng, nhưng tránh sao chép mã là một nguyên tắc quá tốt để sống theo để khuyến khích điều ngược lại ..
Tulains Córdova

Tôi đã đặt ở đây lập luận. Bạn đã không. Các tài liệu tham khảo cho chính quyền sẽ không làm việc kể từ thế kỷ 16. Bạn không thể đảm bảo rằng bạn đã hiểu họ một cách chính xác và họ cũng là người có thẩm quyền đối với tôi.
Gangnus

Bạn đã đúng, sao chép mã không phải là một trong những vấn đề cốt lõi trong phát triển phần mềm và không có mô hình và phương pháp nào được phát triển để tránh điều đó.
Tulains Córdova
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.