Làm thế nào để giữ cho một sản phẩm phần mềm lớn và phức tạp có thể duy trì qua nhiều năm?


156

Tôi đã làm việc như một nhà phát triển phần mềm trong nhiều năm nay. Theo kinh nghiệm của tôi, các dự án trở nên phức tạp hơn và không thể nhầm lẫn khi nhiều nhà phát triển tham gia vào việc phát triển sản phẩm.

Dường như phần mềm ở một giai đoạn phát triển nhất định có xu hướng nhận được "tin tặc" và "tin tặc" nhất là khi không có thành viên nào trong nhóm xác định kiến ​​trúc làm việc tại công ty nữa.

Tôi thấy bực bội khi một nhà phát triển phải thay đổi một cái gì đó có một thời gian khó khăn để có được bức tranh lớn của kiến ​​trúc. Do đó, có xu hướng sửa chữa các vấn đề hoặc thay đổi theo cách làm việc chống lại kiến ​​trúc ban đầu. Kết quả là mã ngày càng phức tạp và thậm chí khó hiểu hơn.

Có lời khuyên hữu ích nào về cách giữ mã nguồn thực sự duy trì trong nhiều năm qua không?


9
sách rất khuyến khích: 'Hướng dẫn sống còn cho dự án phần mềm' của Steve McConnell, 'Phát triển nhanh' của Steve McConnell, 'Tái cấu trúc' của Martin Fowler
Imran Omar Bukhsh

15
... và 'Mã sạch' của chú Bob;) (Robert C. Martin)
Gandalf

34
Đây có phải là câu hỏi rất lớn đã tạo ra nhiều thập kỷ đáng đọc và toàn bộ các khóa học tại các trường đại học không?
gièm pha

17
@Eric Yin - Tôi không đồng ý với ý kiến. Đối với tôi chúng là một mùi mã và trong các dự án dài hạn có xu hướng gây hại nhiều hơn là tốt vì chắc chắn chúng sẽ bị lỗi thời và trở nên sai lệch.
JohnFx

8
@Eric Yin: phấn đấu để tự viết mã. Sử dụng ý kiến ​​của ý định chỉ nơi họ tăng cường sự hiểu biết.
Mitch Wheat

Câu trả lời:


138

Giải pháp thực sự duy nhất để tránh thối mã là mã tốt!

Làm thế nào để mã tốt là một câu hỏi khác. Nó đủ khó ngay cả khi bạn là một lập trình viên xuất sắc làm việc một mình. Trong một nhóm không đồng nhất, nó vẫn trở nên khó khăn hơn nhiều. Trong các dự án thuê ngoài (phụ) ... chỉ cần cầu nguyện.

Các thực hành tốt thông thường có thể giúp:

  1. Giữ cho nó đơn giản.
  2. Giữ cho nó đơn giản. Điều này đặc biệt áp dụng cho kiến ​​trúc, "bức tranh lớn". Nếu nhà phát triển đang có thời gian khó khăn để có được những bức tranh lớn, họ đang đi để mã hóa chống lại nó. Vì vậy, làm cho kiến ​​trúc đơn giản để tất cả các nhà phát triển có được nó. Nếu kiến ​​trúc phải nhỏ hơn đơn giản, thì các nhà phát triển phải được đào tạo để hiểu kiến ​​trúc đó. Nếu họ không nội hóa nó, thì họ không nên viết mã trong đó.
  3. Nhằm mục đích cho khớp nối thấpđộ gắn kết cao . Hãy chắc chắn rằng tất cả mọi người trong nhóm hiểu ý tưởng này. Trong một dự án bao gồm các phần gắn kết lỏng lẻo, gắn kết, nếu một số phần trở nên lộn xộn không thể nhầm lẫn, bạn có thể chỉ cần rút và viết lại phần đó. Khó hơn hoặc gần như không thể nếu khớp nối chặt chẽ.
  4. Hãy kiên định. Những tiêu chuẩn để làm theo vấn đề ít, nhưng xin vui lòng làm theo một số tiêu chuẩn. Trong một đội, tất cả mọi người nên tuân theo các tiêu chuẩn giống nhau của khóa học. Mặt khác, thật dễ dàng để trở nên quá gắn bó với các tiêu chuẩn và quên đi phần còn lại: xin vui lòng hiểu rằng trong khi các tiêu chuẩn là hữu ích, chúng chỉ là một phần nhỏ trong việc tạo ra mã tốt. Đừng làm cho một số lượng lớn của nó.
  5. Đánh giá mã có thể hữu ích để có được một nhóm làm việc nhất quán.
  6. Đảm bảo rằng tất cả các công cụ - IDE, trình biên dịch, kiểm soát phiên bản, xây dựng hệ thống, trình tạo tài liệu, thư viện, máy tính , ghế , môi trường tổng thể , v.v. - được duy trì tốt để các nhà phát triển không phải lãng phí thời gian với các vấn đề thứ cấp như như xung đột phiên bản tập tin dự án, cập nhật Windows, tiếng ồn và bất cứ thứ gì tầm thường nhưng gây khó chịu. Phải liên tục lãng phí thời gian đáng kể với những thứ không thú vị như vậy làm giảm tinh thần, điều này ít nhất sẽ không cải thiện chất lượng mã. Trong một nhóm lớn, có thể có một hoặc nhiều người có công việc chính là duy trì các công cụ dành cho nhà phát triển.
  7. Khi đưa ra quyết định công nghệ, hãy nghĩ những gì sẽ cần để chuyển đổi công nghệ; quyết định nào là không thể đảo ngược và quyết định nào là không thể đảo ngược. Đánh giá các quyết định không thể đảo ngược thêm cẩn thận. Ví dụ, nếu bạn quyết định viết dự án bằng Java , đó là một quyết định không thể đảo ngược. Nếu bạn quyết định sử dụng một số định dạng nhị phân tự đun sôi cho các tệp dữ liệu, đó cũng là một quyết định khá khó đảo ngược (một khi mã bị loại ra ngoài tự nhiên và bạn phải tiếp tục hỗ trợ định dạng đó). Nhưng màu sắc của GUI có thể dễ dàng được điều chỉnh, các tính năng ban đầu bị bỏ qua có thể được thêm vào sau đó, vì vậy hãy nhấn mạnh về các vấn đề như vậy.

8
Đây là những điểm tuyệt vời. Tôi phải thừa nhận rằng tôi đấu tranh với "giữ cho nó đơn giản". Nó dường như có nghĩa là những điều khác nhau đối với những người khác nhau trong các bối cảnh khác nhau, điều này làm cho "đơn giản" khá phức tạp (nhưng sau đó tôi có xu hướng tự nhiên làm phức tạp mọi thứ).
Kramii

3
Tôi hoàn toàn đồng ý với quan điểm của bạn, đặc biệt là "KIS". Nhưng tôi thấy một xu hướng ngày càng nhiều nhà phát triển (trẻ hơn?) Sử dụng các cấu trúc khá phức tạp để mô tả ngay cả những bối cảnh đơn giản nhất.
chrmue


8
Umm, và "8. Giữ cho nó đơn giản".
Dawood ibn Kareem

7
# 6 xứng đáng với +10.
Jim G.

55

Bài kiểm tra đơn vị là bạn của bạn . Thực hiện chúng lực lượng khớp nối thấp. Điều đó cũng có nghĩa là các phần "hacky" của chương trình có thể dễ dàng được xác định và tái cấu trúc. Điều đó cũng có nghĩa là mọi thay đổi đều có thể được kiểm tra nhanh chóng để đảm bảo chúng không phá vỡ chức năng hiện có. Điều này sẽ khuyến khích các nhà phát triển của bạn sửa đổi các phương thức hiện có thay vì sao chép mã vì sợ phá vỡ mọi thứ.

Các bài kiểm tra đơn vị cũng hoạt động như một chút tài liệu bổ sung cho mã của bạn, nêu rõ những gì mỗi phần nên làm. Với các bài kiểm tra đơn vị mở rộng, các lập trình viên của bạn không cần phải biết toàn bộ kiến ​​trúc của chương trình để thực hiện các thay đổi và sử dụng các lớp / phương thức thoát.

Là một tác dụng phụ tốt đẹp, các bài kiểm tra đơn vị cũng hy vọng sẽ làm giảm số lượng lỗi của bạn.


3
Điểm rất quan trọng. Tôi đã tiếp quản một hệ thống cũ, nhiều lớp, nhiều dòng mã, không có tài liệu, không có bài kiểm tra đơn vị. Sau khi chăm chỉ tạo các bài kiểm tra đơn vị cho tất cả các sửa lỗi và cải tiến mã, thiết kế hệ thống đã phát triển thành trạng thái sạch hơn và dễ bảo trì hơn. Và chúng tôi có "can đảm" để viết lại các phần cốt lõi quan trọng (được bao phủ bởi các bài kiểm tra đơn vị).
Sam Goldberg

40

Mọi người ở đây đều nhanh chóng đề cập đến thối mã , và tôi hoàn toàn hiểu và đồng ý với điều này, nhưng nó vẫn bỏ lỡ bức tranh lớn hơn và vấn đề lớn hơn trong tầm tay ở đây. Mã thối không xảy ra. Hơn nữa, các bài kiểm tra đơn vị được đề cập là tốt, nhưng chúng không thực sự giải quyết vấn đề. Người ta có thể có phạm vi kiểm tra đơn vị tốt và mã tương đối không có lỗi, tuy nhiên vẫn có mã và thiết kế bị mục nát.

Bạn đã đề cập rằng nhà phát triển làm việc trong một dự án gặp khó khăn khi triển khai một tính năng và bỏ lỡ bức tranh lớn hơn về kiến ​​trúc tổng thể, và do đó thực hiện một vụ hack vào hệ thống. Đâu là sự lãnh đạo kỹ thuật để thực thi và ảnh hưởng đến thiết kế? Đâu là những đánh giá mã trong quá trình này?

Bạn không thực sự bị thối mã, nhưng bạn đang bị thối nhóm . Thực tế là nó không thành vấn đề nếu những người tạo ra phần mềm ban đầu không còn trong đội. Nếu lãnh đạo kỹ thuật của nhóm hiện tại hiểu đầy đủ và thực sự hiểu thiết kế cơ bản và có vai trò tốt trong vai trò là người lãnh đạo công nghệ, thì đây sẽ không phải là vấn đề.


Điểm rất tốt, bạn đập vào mắt con bò đực! Thật buồn khi nói, nhưng đó chính xác là những gì đang xảy ra ở đây. Và dường như không thể thay đổi mọi thứ nếu không có sự dẫn dắt của công nghệ ...
chrmue

4
@chrmue Chỉ là cách mọi thứ diễn ra tôi đoán, nhưng tôi đang trở nên mệt mỏi với nó. Theo nhiều cách, tôi ước mình trở thành một nhà phát triển cơ sở một lần nữa khi tôi không nhận thức được mọi thứ xung quanh mình có vẻ sai như thế nào. Có vẻ như tôi đang sớm vượt qua cuộc khủng hoảng giữa sự nghiệp. Nhưng tôi đang lan man ... rất vui được giúp đỡ.
maple_shaft

1
@Murph Tại sao bạn không nên có một nhóm lãnh đạo toàn diện trong giai đoạn Bảo trì? Mọi ông chủ tôi từng mong đợi không kém gì một người lãnh đạo nhóm, và khi tôi là trưởng nhóm, tôi không mong đợi gì ở bản thân mình.
maple_shaft

1
@maple_shaft vì a) Tôi không cho rằng có một nhóm trưởng dành riêng cho một dự án đó và đó ít nhiều là yêu cầu đầu tiên và b) Tôi nghĩ bạn cần hiểu thiết kế triển khai (tất cả đều như vậy) và đó là cứng. Một mặt tất cả chúng ta lập luận rằng chúng ta không nên có một phông chữ của tất cả các kiến ​​thức về các dự án của mình và ở đây chúng ta đang nói rằng chúng ta phải có một? Điều đó không thêm?
Murph

2
@maple_shaft có lẽ tôi là một lập trình viên già cằn cỗi (-: Nhưng có một vấn đề ở chỗ nó thường được thực hiện theo "phong cách" cần được theo dõi sâu trong một cơ sở mã hiện tại - và điều đó có thể xa lạ với cả lập trình viên và lãnh đạo (đối với rất nhiều về lý do thực tế).
Murph

18

Có một số điều chúng ta có thể làm:

Giao cho một người trách nhiệm chung về kiến ​​trúc. Khi chọn người đó, đảm bảo họ có tầm nhìn và kỹ năng để phát triển và duy trì kiến ​​trúc, và họ có ảnh hưởng và quyền hạn để giúp các nhà phát triển khác theo kiến ​​trúc. Người đó nên là một nhà phát triển dày dạn, được quản lý tin tưởng và được đồng nghiệp tôn trọng.

Tạo một văn hóa nơi tất cả các nhà phát triển nắm quyền sở hữu kiến ​​trúc. Tất cả các nhà phát triển cần tham gia vào quá trình phát triển và duy trì tính toàn vẹn kiến ​​trúc.

Phát triển một môi trường nơi các quyết định kiến ​​trúc dễ dàng được truyền đạt. Khuyến khích mọi người nói về thiết kế và kiến ​​trúc - không chỉ trong bối cảnh của dự án hiện tại, mà nói chung, cũng vậy.

Các thực tiễn mã hóa tốt nhất giúp kiến ​​trúc dễ nhìn thấy hơn từ mã - dành thời gian để cấu trúc lại, nhận xét mã, phát triển các thử nghiệm đơn vị, v.v. Những thứ như quy ước đặt tên và thực hành mã hóa sạch có thể giúp ích rất nhiều trong việc truyền đạt kiến ​​trúc, vì vậy bạn cần một nhóm dành thời gian để phát triển và làm theo tiêu chuẩn của riêng bạn.

Đảm bảo rằng tất cả các tài liệu cần thiết là rõ ràng, súc tích, cập nhật và có thể truy cập. Làm cho cả sơ đồ kiến ​​trúc cấp cao và cấp thấp công khai (ghim chúng vào tường có thể giúp đỡ) và có thể duy trì công khai.

Cuối cùng (như một người cầu toàn tự nhiên) tôi cần phải nhận ra rằng tính toàn vẹn của kiến ​​trúc là một khát vọng xứng đáng, nhưng có thể có những điều quan trọng hơn - như xây dựng một nhóm có thể làm việc tốt với nhau và thực sự vận chuyển một sản phẩm làm việc.


1
+1, đặc biệt là "xây dựng một nhóm có thể làm việc tốt với nhau và thực sự vận chuyển một sản phẩm hoạt động."
deworde

1
Đó là một ý tưởng tốt để có một người là gốc chịu trách nhiệm về kiến ​​trúc. Tuy nhiên, bạn có "mùi đồng đội" nếu trách nhiệm đó được sử dụng thường xuyên: Nhóm nên tự nhiên đi đến các kết luận chung, thay vì dựa vào một người để đưa ra câu trả lời. Tại sao? Tổng kiến ​​thức của dự án luôn được chia sẻ, ghim nó vào một người sẽ dẫn đến những vấn đề lớn hơn cuối cùng: Chỉ có quan điểm của anh ta được thỏa mãn, cắt cánh hiệu quả của phần còn lại của đội. Thay vào đó hãy thuê những người giỏi nhất và để họ cùng nhau giải quyết.
casper

1
@casper: Chính xác. Bạn thể hiện những gì tôi có trong tâm trí tốt hơn tôi đã làm.
Kramii

18

Cách tôi đi về vấn đề này là bắn tỉa tận gốc:

Giải thích của tôi sẽ sử dụng các thuật ngữ từ Microsoft / .NET , nhưng sẽ được áp dụng cho bất kỳ nền tảng / hộp công cụ nào:

  1. Sử dụng các tiêu chuẩn để đặt tên, mã hóa, kiểm tra, lưu lượng lỗi, quy trình xử lý - về cơ bản là bất cứ điều gì.
  2. Đừng ngại nói lời tạm biệt với các thành viên trong nhóm, những người không tuân thủ các tiêu chuẩn. Một số nhà phát triển chỉ đơn giản là không thể làm việc trong một bộ tiêu chuẩn được xác định và sẽ trở thành kẻ thù cột thứ 5 trên chiến trường để giữ cho cơ sở mã sạch sẽ
  3. Đừng ngại phân bổ các thành viên nhóm có kỹ năng thấp hơn để thử nghiệm trong thời gian dài.
  4. Sử dụng mọi công cụ trong kho vũ khí của bạn để tránh kiểm tra mã mục nát: điều này liên quan đến các công cụ chuyên dụng, cũng như các bài kiểm tra đơn vị được viết sẵn để kiểm tra các tệp xây dựng, tệp dự án, cấu trúc thư mục, v.v.
  5. Trong một nhóm gồm khoảng 5-8 thành viên, hãy để anh chàng tốt nhất của bạn thực hiện tái cấu trúc gần như liên tục - dọn dẹp mớ hỗn độn mà những người khác để lại. Ngay cả khi bạn tìm thấy các chuyên gia giỏi nhất trong lĩnh vực này, bạn vẫn sẽ gặp rắc rối - điều đó là không thể tránh khỏi, nhưng nó có thể bị hạn chế bởi việc tái cấu trúc liên tục.
  6. Viết các bài kiểm tra đơn vị và duy trì chúng - KHÔNG phụ thuộc vào các bài kiểm tra đơn vị để giữ cho dự án sạch sẽ, chúng không.
  7. Thảo luận mọi thứ. Đừng ngại dành hàng giờ để thảo luận về những điều trong nhóm. Điều này sẽ phổ biến thông tin và sẽ loại bỏ một trong những nguyên nhân gốc rễ cho mã xấu: nhầm lẫn về công nghệ, mục tiêu, tiêu chuẩn, v.v.
  8. Hãy rất cẩn thận với các chuyên gia tư vấn viết mã: mã của họ, gần như theo định nghĩa, sẽ là thứ tồi tệ thực sự.
  9. Thực hiện đánh giá tốt nhất là bước quy trình trước khi đăng ký. Đừng sợ để cam kết rollback.
  10. Không bao giờ sử dụng nguyên tắc mở / đóng trừ khi trong giai đoạn cuối trước khi phát hành: nó chỉ đơn giản dẫn đến mã mục nát bị ngửi.
  11. Bất cứ khi nào gặp phải một vấn đề, hãy dành thời gian để hiểu nó hết mức trước khi thực hiện giải pháp - hầu hết sự thối mã xuất phát từ việc thực hiện giải pháp cho các vấn đề chưa được hiểu đầy đủ.
  12. Sử dụng đúng công nghệ. Chúng thường xuất hiện theo bộ và mới mẻ: Tốt hơn là phụ thuộc vào phiên bản beta của khung mà bạn được đảm bảo hỗ trợ trong tương lai, hơn là phụ thuộc vào các khung cực kỳ ổn định nhưng lỗi thời, không được hỗ trợ.
  13. Thuê những người giỏi nhất.
  14. Nghỉ việc - bạn không điều hành quán cà phê.
  15. Nếu quản lý không phải là kiến ​​trúc sư giỏi nhất và họ can thiệp vào quá trình ra quyết định - hãy tìm một công việc khác.

1
Bây giờ, bạn sẽ làm gì nếu bạn phải duy trì và nâng cao ứng dụng VB6 12 tuổi ác mộng với 100 biểu mẫu và lớp trong khi làm việc trên C # viết lại trong 'thời gian rảnh'?
jfrankcarr

7
Không đồng ý với # 3. Kiểm tra không nên được coi là một hình phạt cho người không được đào tạo. Trong thực tế, nó nên được thực hiện bởi tất cả các nhà phát triển, và được tính là quan trọng như mã hóa và thiết kế! Nếu bạn có Mr Untrained trong đội, thì hãy đưa anh ấy ra khỏi đội để được huấn luyện!.
NWS

1
"Không đồng ý với # 3. Thử nghiệm không nên được coi là một hình phạt cho người chưa được đào tạo." - Nó không nên và không phải là những gì tôi đã viết, hãy để tôi giải thích: Kiểm tra là một cách tốt để cho phép những người mà bạn không, tuy nhiên, tin tưởng để cam kết thay đổi để nhận được mã của anh ấy. Những người thử nghiệm tốt nhất mà tôi đã tìm thấy là những người khao khát trở thành người đóng góp mã và đang chứng minh năng lực của họ bằng cách xem qua mã, chạy chương trình và cho thấy khả năng tương quan các phát hiện của họ trong thời gian chạy với mã nguồn. Nó không phải là một hình phạt - đào tạo của nó.
casper

1
"Không đồng ý với số 10 - giúp cải thiện chất lượng mã nếu được thực hiện đúng" Điều này hoàn toàn sai trong kinh nghiệm làm việc của tôi: Mã bị khóa không thể được cấu trúc lại, có nghĩa là nó sẽ ở trạng thái hiện tại cho đến khi được mở khóa. Trạng thái này có thể được xác minh là hoạt động ở giai đoạn nào đó, nhưng ở giai đoạn sau, xác minh này là dương tính giả: Tất cả mã phải được mở để tái cấu trúc cho đến giai đoạn trước khi thử nghiệm hệ thống cuối cùng.
casper

3
@casper: IMHO, Không nên hiểu nguyên tắc mở / đóng là "bạn không thể thay đổi nguồn", mà là "thiết kế mã giống như mã sẽ bị đóng băng". Hãy chắc chắn rằng có thể mở rộng mã dưới dạng không cần thiết mà không yêu cầu thay đổi mã. Kết quả là kết hợp lỏng lẻo và gắn kết cao hơn so với mã trung bình. Nguyên tắc này cũng rất quan trọng khi phát triển thư viện để sử dụng bởi các bên thứ ba, vì họ không thể chỉ cần đi vào và sửa đổi mã của bạn, vì vậy bạn cần nó có thể mở rộng đúng cách.
Kevin Cathcart

12

Làm sạch mã mục nát bằng cách tái cấu trúc, trong khi viết bài kiểm tra đơn vị. Trả nợ thiết kế (này) cho tất cả các mã bạn chạm vào, bất cứ khi nào bạn:

  • Phát triển một tính năng mới
  • Sửa chữa vấn đề

Tăng tốc độ chu kỳ phát triển thử nghiệm đầu tiên của bạn bằng cách:

  • Tái cấu trúc để chuyển đổi các mô-đun mã sang ngôn ngữ kịch bản
  • Sử dụng máy kiểm tra nhanh, dựa trên đám mây

Mã tái cấu trúc để sử dụng khớp nối thấp (của các đơn vị có độ kết dính bên trong cao) bằng cách:

  • Đơn giản hơn, (nhiều hơn) chức năng (thói quen)
  • Mô-đun
  • Đối tượng (và các lớp hoặc nguyên mẫu)
  • Chức năng thuần túy (không có tác dụng phụ)
  • Ưu tiên ủy quyền, thừa kế
  • Các lớp (với API)
  • Bộ sưu tập các chương trình nhỏ, một mục đích có thể hoạt động cùng nhau

Tăng trưởng hữu cơ là tốt; thiết kế phía trước lớn là xấu.

Có một nhà lãnh đạo am hiểu về thiết kế hiện tại. Nếu không, hãy đọc mã của dự án cho đến khi bạn có kiến ​​thức.

Đọc sách tái cấu trúc.


1
+1 Câu trả lời đầu tiên tốt, cách hoàn hảo để giới thiệu bản thân với chúng tôi!
yannis

11

Câu trả lời đơn giản: bạn không thể .

Đó là lý do tại sao bạn nên nhắm đến việc viết phần mềm nhỏđơn giản . Nó không dễ.

Điều đó chỉ có thể nếu bạn nghĩ đủ lâu về vấn đề có vẻ phức tạp của mình để xác định nó theo cách đơn giản và ngắn gọn nhất có thể.

Giải pháp cho các vấn đề thực sự lớn và phức tạp thường vẫn có thể được giải quyết bằng cách xây dựng trên các mô-đun nhỏ và đơn giản.

Nói cách khác, như những người khác đã chỉ ra, sự đơn giản và khớp nối lỏng lẻo là thành phần chính.

Nếu điều đó là không thể hoặc không khả thi, có lẽ bạn đang thực hiện nghiên cứu (các vấn đề phức tạp không có giải pháp đơn giản đã biết hoặc không có giải pháp nào được biết đến). Đừng mong đợi nghiên cứu sẽ trực tiếp sản xuất các sản phẩm có thể bảo trì, đó không phải là nghiên cứu dành cho.


9

Tôi làm việc trên cơ sở mã cho một sản phẩm đã được phát triển liên tục từ năm 1999, vì vậy bây giờ bạn có thể tưởng tượng nó khá phức tạp. Nguồn tin tặc lớn nhất trong cơ sở mã của chúng tôi là từ nhiều lần chúng tôi phải chuyển nó từ ASP Classic sang ASP.NET , từ ADO sang ADO.NET, từ postback sang Ajax , chuyển đổi thư viện UI, tiêu chuẩn mã hóa, v.v.

Tất cả trong tất cả chúng ta đã thực hiện một công việc hợp lý là giữ cho cơ sở mã có thể duy trì. Những điều chính chúng tôi đã làm mà đóng góp vào đó là:

1) Tái cấu trúc liên tục - Nếu bạn phải chạm vào một đoạn mã bị hack hoặc khó hiểu, bạn sẽ phải mất thêm thời gian để dọn dẹp và được đưa ra trong lịch trình để thực hiện. Các bài kiểm tra đơn vị làm cho điều này bớt đáng sợ hơn nhiều, bởi vì bạn có thể kiểm tra chống lại hồi quy dễ dàng hơn.

2) Giữ môi trường phát triển gọn gàng - Hãy thận trọng trong việc xóa mã không còn được sử dụng và đừng để các bản sao dự phòng / bản sao làm việc / mã thử nghiệm tồn tại trong thư mục dự án.

3) Các tiêu chuẩn mã hóa nhất quán cho vòng đời của Dự án - Hãy đối mặt với nó, quan điểm của chúng tôi về các tiêu chuẩn mã hóa phát triển theo thời gian. Tôi đề nghị gắn bó với tiêu chuẩn mã hóa mà bạn đã bắt đầu trong vòng đời của một dự án trừ khi bạn có thời gian quay lại và trang bị lại tất cả các mã để tuân thủ tiêu chuẩn mới. Thật tuyệt khi bây giờ bạn đã vượt qua ký hiệu Hungary , nhưng hãy áp dụng bài học đó cho các dự án mới và đừng chuyển đổi giữa dòng cho dự án mới đó.


8

Vì bạn đã gắn thẻ câu hỏi với quản lý dự án, tôi đã cố gắng thêm một số điểm không phải mã :)

  • Lập kế hoạch cho doanh thu - giả định rằng toàn bộ nhóm phát triển sẽ biến mất vào thời điểm nó đạt đến giai đoạn bảo trì - không nhà phát triển nào xứng đáng với muối của họ muốn bị mắc kẹt duy trì hệ thống của mình mãi mãi. Bắt đầu chuẩn bị vật liệu bàn giao ngay khi bạn có thời gian.

  • Tính nhất quán / đồng nhất không thể được nhấn mạnh đủ. Điều này sẽ ngăn cản văn hóa 'đi một mình' và khuyến khích các nhà phát triển mới hỏi, nếu họ nghi ngờ.

  • Giữ cho nó chính thống - các công nghệ được sử dụng, các mẫu thiết kế và tiêu chuẩn - bởi vì một nhà phát triển mới cho nhóm (ở mọi cấp độ) sẽ có nhiều cơ hội để bắt đầu và chạy nhanh hơn.

  • Tài liệu - đặc biệt là kiến ​​trúc - tại sao các quyết định được đưa ra và các tiêu chuẩn mã hóa. Đồng thời lưu tài liệu tham khảo / ghi chú / lộ trình vào tài liệu về lĩnh vực kinh doanh - bạn sẽ ngạc nhiên về việc doanh nghiệp doanh nghiệp khó giải thích những gì họ làm với nhà phát triển không có kinh nghiệm về miền.

  • Đặt ra các quy tắc rõ ràng - không chỉ cho nhóm phát triển hiện tại của bạn, mà hãy nghĩ về các nhà phát triển bảo trì trong tương lai. Nếu điều này có nghĩa là đặt một siêu liên kết đến tài liệu tiêu chuẩn thiết kế và mã hóa có liên quan trên mỗi trang, thì hãy là nó.

  • Đảm bảo rằng các kiến trúc và đặc biệt là đang lớp được phân định rõ ràng và tách ra - điều này sẽ có khả năng cho phép thay thế các lớp mã như công nghệ mới đi cùng, ví dụ, thay thế một giao diện người dùng Web Forms với HTML5 jQuery UI , vv, mà có thể mua một năm hoặc lâu hơn tuổi thọ thêm.


7

Một thuộc tính của mã rất cao là độ tinh khiết của hàm .

Độ tinh khiết có nghĩa là các hàm sẽ trả về cùng một kết quả cho cùng một đối số. Đó là, họ không nên phụ thuộc vào tác dụng phụ của các chức năng khác. Ngoài ra, nó rất hữu ích nếu bản thân chúng không có tác dụng phụ.

Thuộc tính này dễ chứng kiến ​​hơn các thuộc tính khớp nối / kết dính. Bạn không cần phải cố gắng để đạt được nó và cá nhân tôi cho rằng nó có giá trị hơn.

Khi chức năng của bạn là thuần túy, loại của nó là một tài liệu rất tốt của chính nó. Ngoài ra, việc viết và đọc tài liệu về các đối số / giá trị trả về dễ dàng hơn nhiều so với việc đề cập đến một số trạng thái toàn cầu (có thể được truy cập bởi các luồng khác O_O).

Như một ví dụ về việc sử dụng độ tinh khiết rộng rãi để giúp duy trì, bạn có thể thấy GHC . Đây là một dự án lớn khoảng 20 năm, nơi các phép tái cấu trúc lớn đang được thực hiện và các tính năng chính mới vẫn đang được giới thiệu.

Cuối cùng, tôi không thích điểm "Giữ nó đơn giản" quá nhiều. Bạn không thể giữ chương trình của mình đơn giản khi bạn đang mô hình hóa những thứ phức tạp. Hãy thử tạo một trình biên dịch đơn giản và mã được tạo của bạn có thể sẽ bị chết chậm. Chắc chắn, bạn có thể (và nên) làm cho các chức năng riêng lẻ trở nên đơn giản, nhưng kết quả là toàn bộ chương trình sẽ không đơn giản.


2
Một tên khác cho những gì bạn mô tả là thuộc tính mang tính quyết định
jinglểula

6

Ngoài các câu trả lời khác, tôi muốn giới thiệu các lớp. Không quá nhiều nhưng đủ để tách các loại mã khác nhau.

Chúng tôi sử dụng mô hình API nội bộ cho hầu hết các ứng dụng. Có một API nội bộ kết nối với cơ sở dữ liệu. Sau đó, một lớp UI . Những người khác nhau có thể làm việc ở mỗi cấp độ mà không làm gián đoạn hoặc phá vỡ các phần khác của ứng dụng.

Một cách tiếp cận khác là khiến mọi người đọc comp.risksThe Daily WTF để họ tìm hiểu hậu quả của thiết kế tồi và lập trình xấu, và họ sẽ sợ hãi khi thấy mã của riêng họ được đăng trên The Daily WTF .


6

Vì nhiều câu trả lời trong số này dường như tập trung vào các đội lớn, ngay từ đầu, tôi sẽ đưa quan điểm của mình vào nhóm phát triển hai người (ba nếu bạn bao gồm nhà thiết kế) cho một công ty khởi nghiệp.

Rõ ràng, các thiết kế và giải pháp đơn giản là tốt nhất, nhưng khi bạn có một chàng trai trả lương theo nghĩa đen, bạn không nhất thiết phải có thời gian để nghĩ về giải pháp thanh lịch, đơn giản và dễ bảo trì nhất. Với ý nghĩ đó, điểm lớn đầu tiên của tôi là:

Tài liệu Không phải là nhận xét, mã nên chủ yếu là tự viết tài liệu, nhưng những thứ như tài liệu thiết kế, phân cấp lớp và phụ thuộc, mô hình kiến ​​trúc, v.v ... Bất cứ điều gì giúp lập trình viên mới, hoặc thậm chí hiện có, hiểu được cơ sở mã. Ngoài ra, việc ghi lại những thư viện giả kỳ lạ xuất hiện cuối cùng, như "thêm lớp này vào một thành phần cho chức năng này" có thể giúp ích, vì nó cũng ngăn mọi người viết lại chức năng.

Tuy nhiên, ngay cả khi bạn có giới hạn thời gian nghiêm trọng, tôi thấy rằng một điều tốt khác cần ghi nhớ là:

Tránh hack và sửa chữa nhanh chóng. Trừ khi sửa chữa nhanh là sửa chữa thực tế, tốt hơn hết là tìm ra vấn đề tiềm ẩn cho một cái gì đó, và sau đó sửa nó. Trừ khi bạn thực sự có một kịch bản "làm cho điều này hoạt động trong 2 phút tiếp theo hoặc bạn bị sa thải", thực hiện sửa lỗi ngay bây giờ là một ý tưởng tốt hơn, bởi vì bạn sẽ không sửa mã sau, bạn sẽ tiếp tục chuyển sang nhiệm vụ tiếp theo bạn có.

Và mẹo yêu thích cá nhân của tôi là nhiều hơn một trích dẫn, mặc dù tôi không thể nhớ nguồn:

"Mã như thể người đến sau bạn là một kẻ tâm thần giết người biết bạn sống ở đâu"


Tôi luôn thấy các nhận xét về chức năng và lớp là hữu ích ngay cả khi chỉ cung cấp phân tách các phân đoạn mã tại các vị trí của chức năng và lớp bằng cách sử dụng tô sáng cú pháp. Tôi rất hiếm khi đưa ý kiến ​​vào mã chức năng, nhưng tôi viết một dòng cho mỗi lớp và hàm, chẳng hạn như /** Gets the available times of a clinic practitioner on a specific date. **/hoặc /** Represents a clinic practitioner. **/.
Nick Bedford

5

Một nguyên tắc chưa được đề cập nhưng tôi thấy quan trọng là nguyên tắc mở / đóng .

Bạn không nên sửa đổi mã đã được phát triển và thử nghiệm: bất kỳ đoạn mã nào như vậy đều được niêm phong. Thay vào đó, mở rộng các lớp hiện có bằng các lớp con hoặc sử dụng chúng để viết các hàm bao, các lớp trang trí hoặc sử dụng bất kỳ mẫu nào bạn thấy phù hợp. Nhưng không thay đổi mã làm việc .

Chỉ 2 xu của tôi.


Điều gì xảy ra nếu các yêu cầu kinh doanh thể hiện trong mã làm việc thay đổi? Việc không chạm vào mã kém hiệu quả nhưng về mặt kỹ thuật có thể gây tổn hại cho bạn về lâu dài, đặc biệt là khi đến lúc phải thực hiện các thay đổi cần thiết.
jinglểula

@ Kinhleraula: Mở để mở rộng có nghĩa là bạn có thể thêm chức năng mới dưới dạng triển khai mới (ví dụ: lớp) của giao diện hiện có. Tất nhiên, mã có cấu trúc kém không cho phép điều này: cần có sự trừu tượng như giao diện cho phép mã "thay đổi" bằng cách thêm mã mới thay vì sửa đổi mã hiện có.
Giorgio

5
  • Hãy là một trinh sát . Luôn luôn để mã sạch hơn bạn tìm thấy nó.

  • Sửa các cửa sổ bị hỏng . Tất cả những nhận xét "thay đổi trong phiên bản 2.0" khi bạn ở phiên bản 3.0.

  • Khi có những vụ hack lớn, hãy thiết kế một giải pháp tốt hơn như một nhóm và thực hiện nó. Nếu bạn không thể sửa lỗi hack theo nhóm, thì bạn không hiểu rõ về hệ thống. "Yêu cầu một người lớn giúp đỡ." Những người già nhất xung quanh có thể đã nhìn thấy điều này trước đây. Hãy thử vẽ hoặc trích xuất một sơ đồ của hệ thống. Hãy thử vẽ hoặc trích xuất các trường hợp sử dụng đặc biệt là hack như sơ đồ tương tác. Điều này không sửa chữa nó, nhưng ít nhất bạn có thể nhìn thấy nó.

  • Những giả định nào không còn đúng mà đã đẩy thiết kế theo một hướng cụ thể? Có thể có một cấu trúc lại nhỏ ẩn đằng sau một số mớ hỗn độn đó.

  • Nếu bạn giải thích cách hệ thống hoạt động (thậm chí chỉ một trường hợp sử dụng) và thấy mình phải xin lỗi hệ thống con nhiều lần, thì đó là vấn đề. Những hành vi nào sẽ làm cho phần còn lại của hệ thống đơn giản hơn (cho dù nó có khó thực hiện như thế nào so với những gì đang có). Hệ thống con cổ điển để viết lại là một hệ thống gây ô nhiễm mọi hệ thống con khác với ngữ nghĩa hoạt động và triển khai của nó. "Ồ, bạn phải dò dẫm các giá trị trước khi đưa chúng vào hệ thống con froo, sau đó bạn hủy bỏ chúng một lần nữa khi bạn nhận được đầu ra từ froo. Có lẽ tất cả các giá trị sẽ được tìm kiếm khi đọc từ người dùng và lưu trữ, và phần còn lại của hệ thống là sai? Điều này trở nên thú vị hơn khi có hai hoặc nhiều đường rãnh khác nhau.

  • Dành một tuần như một nhóm loại bỏ các cảnh báo để có thể nhìn thấy các vấn đề thực sự.

  • Định dạng lại tất cả các mã theo tiêu chuẩn mã hóa.

  • Đảm bảo hệ thống kiểm soát phiên bản của bạn được gắn với trình theo dõi lỗi của bạn. Điều này có nghĩa là những thay đổi trong tương lai là tốt đẹp và có trách nhiệm, và bạn có thể tìm ra TẠI SAO.

  • Làm một số khảo cổ học. Tìm các tài liệu thiết kế ban đầu và xem xét chúng. Họ có thể ở trên chiếc PC cũ đó ở góc văn phòng, trong không gian văn phòng bỏ hoang hoặc trong tủ hồ sơ không ai mở.

  • Tái xuất bản các tài liệu thiết kế trên wiki. Điều này giúp thể chế hóa kiến ​​thức.

  • Viết các thủ tục giống như danh sách kiểm tra để phát hành và xây dựng. Điều này ngăn mọi người phải suy nghĩ, vì vậy họ có thể tập trung giải quyết vấn đề. Tự động xây dựng bất cứ nơi nào có thể.

  • Hãy thử tích hợp liên tục . Càng sớm xây dựng một bản dựng thất bại, dự án càng mất ít thời gian hơn.

  • Nếu nhóm trưởng của bạn không làm những điều này, điều đó thật tệ cho công ty.

  • Cố gắng đảm bảo tất cả các mã mới được kiểm tra đơn vị thích hợp với độ bao phủ đo được. Vì vậy, vấn đề không thể tồi tệ hơn nhiều.

  • Hãy thử kiểm tra đơn vị một số bit cũ không phải là đơn vị được kiểm tra. Điều này giúp cắt giảm nỗi sợ thay đổi.

  • Tự động hóa kiểm tra tích hợp và hồi quy của bạn nếu bạn có thể. Ít nhất là có một danh sách kiểm tra. Phi công rất thông minh và được trả nhiều tiền và họ sử dụng danh sách kiểm tra. Họ cũng rất khó chịu.


4

Đọc và sau đó đọc lại Code Complete của Steve McConnell. Nó giống như một cuốn kinh thánh viết phần mềm tốt, từ thiết kế dự án ban đầu cho đến một dòng mã và mọi thứ ở giữa. Điều tôi thích nhất ở đây là nó được hỗ trợ bởi hàng thập kỷ dữ liệu vững chắc; nó không chỉ là phong cách mã hóa tốt nhất tiếp theo.


3

Tôi đã từng gọi đây là "Hiệu ứng ngôi nhà bí ẩn Winchester". Giống như ngôi nhà, nó bắt đầu đủ đơn giản, nhưng trong nhiều năm, nhiều công nhân khác nhau đã thêm vào rất nhiều tính năng kỳ lạ mà không có kế hoạch tổng thể mà không ai thực sự hiểu nó nữa. Tại sao cầu thang này đi đến hư không và tại sao cánh cửa đó chỉ mở một chiều? Ai biết?

Cách để hạn chế hiệu ứng này là bắt đầu với một thiết kế tốt được tạo ra ở nơi nó đủ linh hoạt để xử lý việc mở rộng. Một số gợi ý đã được đưa ra về điều này.

Tuy nhiên, thường thì bạn sẽ nhận một công việc trong đó thiệt hại đã được thực hiện và đã quá muộn cho một thiết kế tốt mà không thực hiện một thiết kế lại và viết lại tốn kém và có khả năng rủi ro cao. Trong những tình huống đó, tốt nhất bạn nên cố gắng tìm cách hạn chế sự hỗn loạn trong khi nắm lấy nó ở một mức độ nào đó. Điều này có thể gây khó chịu cho sự nhạy cảm trong thiết kế của bạn rằng mọi thứ phải trải qua một lớp 'người quản lý' đơn lẻ, xấu xí hoặc lớp truy cập dữ liệu được kết hợp chặt chẽ với UI, nhưng học cách đối phó với nó. Mã phòng thủ trong khuôn khổ đó và cố gắng mong đợi điều bất ngờ khi 'bóng ma' của các lập trình viên xuất hiện trong quá khứ.


2

Tái cấu trúc mãkiểm tra đơn vị là hoàn toàn tốt. Nhưng vì dự án dài hạn này đang chạy vào hack, điều này có nghĩa là ban quản lý sẽ không đặt chân xuống để làm sạch mục nát. Nhóm được yêu cầu giới thiệu các bản hack, bởi vì ai đó không phân bổ đủ nguồn lực để đào tạo mọi người và phân tích vấn đề / yêu cầu.

Duy trì một dự án chạy dài là trách nhiệm của người quản lý dự án như một nhà phát triển cá nhân.

Mọi người không giới thiệu hack vì họ thích nó; họ bị ép buộc bởi hoàn cảnh.


Buộc hoàn cảnh? Mọi người giới thiệu hack vì (a) họ không biết rõ hơn => cần huấn luyện, (b) họ không nhìn thấy bức tranh lớn hơn => truyền thông, tài liệu và kỷ luật cần thiết, (c) họ nghĩ rằng họ thông minh hơn => đó là lớn nhất vượt qua để vượt qua (d) bị ép buộc bởi các tình huống => hotfix nhanh chóng ổn khi chịu áp lực thời gian, nhưng ai đó phải chịu trách nhiệm và dọn sạch mã sau đó. Bất kỳ "hoàn cảnh" nào khác chỉ đơn giản là BS . Có những ngoại lệ cho quy tắc đó, nhưng ngoại lệ được gọi là ngoại lệ "lười biếng" nhất.
JensG

2

Tôi chỉ muốn đặt một vấn đề phi kỹ thuật và một cách tiếp cận thực tế (có thể).

Nếu người quản lý của bạn không quan tâm đến chất lượng kỹ thuật (mã có thể quản lý, kiến ​​trúc đơn giản, cơ sở hạ tầng đáng tin cậy, v.v.), việc cải thiện dự án sẽ trở nên khó khăn. Trong trường hợp này, cần phải giáo dục người quản lý nói và thuyết phục "đầu tư" các nỗ lực vào khả năng duy trì và giải quyết nợ kỹ thuật .

Nếu bạn mơ ước với chất lượng mã được tìm thấy trong những cuốn sách đó, bạn cũng cần một ông chủ quan tâm đến vấn đề này.

Hoặc nếu bạn chỉ muốn chế ngự một "dự án Frankenstein" thì đây là những lời khuyên của tôi:

  • Sắp xếp và đơn giản hóa
  • Rút ngắn chức năng
  • Ưu tiên khả năng đọc hơn hiệu quả (tất nhiên khi chấp nhận được và một số lợi ích hiệu quả quá khốn khổ để giữ)

Theo kinh nghiệm của tôi, lập trình là entropic chứ không phải là xuất hiện (ít nhất là trong mô hình cấu trúc mệnh lệnh phổ biến). Khi mọi người viết mã để "chỉ làm việc", xu hướng là mất tổ chức của nó. Bây giờ tổ chức mã đòi hỏi thời gian, đôi khi nhiều hơn là làm cho nó chỉ hoạt động.

Ngoài việc thực hiện tính năng và sửa lỗi, hãy dành thời gian để dọn dẹp mã.


"... dự án đã bị tiêu diệt" - theo kinh nghiệm của tôi, điều này không nhất thiết phải như vậy. Thay thế là giáo dục người quản lý nói và thuyết phục các nỗ lực "đầu tư" vào khả năng duy trì và giải quyết nợ kỹ thuật
gnat

Xin lỗi, tôi không thể giữ bản thân mình bằng văn bản vì tôi đã có kinh nghiệm khi người quản lý bỏ qua tất cả các lời khuyên của tôi về nợ kỹ thuật. Nhưng tôi nghĩ bạn đã đúng: khoảng một năm sau khi tôi từ bỏ dự án đó, người quản lý đã mất toàn bộ quyền lực đối với đội ngũ công nghệ vì không có khả năng quản lý chúng.
Eric.Void

1

Tôi đã rất ngạc nhiên khi thấy rằng không có câu trả lời nào trong số rất nhiều câu trả lời rõ ràng: làm cho phần mềm bao gồm nhiều thư viện nhỏ, độc lập. Với nhiều thư viện nhỏ, bạn có thể xây dựng một phần mềm lớn và phức tạp. Nếu các yêu cầu thay đổi, bạn không phải vứt bỏ toàn bộ cơ sở mã hóa hoặc điều tra cách sửa đổi một cơ sở mã hóa lớn để làm một cái gì đó khác với những gì nó đang làm. Bạn chỉ cần quyết định thư viện nào vẫn còn phù hợp sau khi yêu cầu thay đổi và cách kết hợp chúng lại với nhau để có chức năng mới.

Sử dụng bất kỳ kỹ thuật lập trình nào trong các thư viện đó giúp cho việc sử dụng thư viện trở nên dễ dàng. Lưu ý rằng, ví dụ, bất kỳ ngôn ngữ hỗ trợ chức năng không hướng đối tượng nào đều hỗ trợ lập trình hướng đối tượng (OOP) thực sự. Vì vậy, ví dụ trong C, bạn có thể làm OOP.

Bạn thậm chí có thể xem xét chia sẻ những thư viện nhỏ, độc lập giữa nhiều dự án (mô hình con git là bạn của bạn).

Không cần phải nói, mỗi thư viện nhỏ, độc lập nên được kiểm tra đơn vị. Nếu một thư viện cụ thể không thể kiểm tra được đơn vị, bạn đang làm sai.

Nếu bạn sử dụng C hoặc C ++ và không thích ý tưởng có nhiều tệp .so nhỏ, bạn có thể liên kết tất cả các thư viện với nhau thành tệp .so lớn hơn hoặc thay vào đó bạn có thể thực hiện liên kết tĩnh. Điều này cũng đúng với Java, chỉ cần thay đổi .so thành .jar.


Điều này có vẻ hơi quá lý thuyết theo quan điểm của tôi. Tất nhiên các dự án tôi đề cập trong câu hỏi của tôi được xây dựng từ nhiều thư viện và mô-đun. Kinh nghiệm của tôi trong 26 năm phát triển phần mềm gần đây là dự án cũ hơn được tổ chức ban đầu
chrmue

0

Đơn giản: giảm chi phí bảo trì của hầu hết mã của bạn xuống 0 cho đến khi bạn có số lượng bộ phận chuyển động có thể bảo trì. Mã không bao giờ cần phải thay đổi phát sinh không có chi phí bảo trì. Tôi khuyên bạn nên nhắm đến việc tạo mã thực sự có chi phí bảo trì bằng 0 , không cố gắng giảm chi phí qua nhiều lần lặp lại tái cấu trúc nhỏ và phức tạp. Làm cho nó chi phí bằng không ngay lập tức.

Được rồi, phải thừa nhận rằng nó rất nhiều, khó hơn nhiều so với âm thanh. Nhưng không khó để bắt đầu. Bạn có thể lấy một đoạn của cơ sở mã, kiểm tra nó, xây dựng một giao diện đẹp trên nó nếu thiết kế giao diện là một mớ hỗn độn và bắt đầu phát triển các phần của cơ sở mã đáng tin cậy, ổn định (như thiếu lý do để thay đổi), đồng thời thu nhỏ những phần không đáng tin cậy và không ổn định. Các bộ mã có cảm giác như một cơn ác mộng cần duy trì thường không phân biệt các bộ phận chuyển động cần được thay đổi khỏi các bộ phận không có, vì mọi thứ được coi là không đáng tin cậy và dễ bị thay đổi.

Tôi thực sự khuyên bạn nên tìm mọi cách tách tổ chức cơ sở mã của bạn thành các phần "ổn định" và "không ổn định", với các phần ổn định là một PITA khổng lồ để xây dựng lại và thay đổi (đó là một điều tốt, vì họ không cần phải được thay đổi và xây dựng lại nếu chúng thực sự thuộc về phần "ổn định").

Nó không phải là kích thước của một cơ sở mã gây khó khăn cho việc bảo trì. Đó là kích thước của cơ sở mã cần được duy trì. Tôi phụ thuộc vào hàng triệu dòng mã bất cứ khi nào tôi nói, sử dụng API của hệ điều hành. Nhưng điều đó không góp phần vào chi phí bảo trì sản phẩm của tôi, vì tôi không phải duy trì mã nguồn của hệ điều hành. Tôi chỉ sử dụng mã và nó hoạt động. Mã mà tôi chỉ sử dụng và không bao giờ phải duy trì phát sinh mà không phải trả chi phí bảo trì.

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.