Cách đây 17 năm, tôi đã viết dòng mã C# đầu tiên của mình (.NET Framework 3.5). Đó là khi tôi còn học ở trường CNTT.
12 năm trước, tôi đã viết ứng dụng đầu tiên của mình bằng .NET và đưa nó lên môi trường sản xuất. Nó hoạt động, nhưng mã nguồn không tốt. Lúc đó tôi không biết, nhưng vài năm sau đó tôi đã dành thời gian để học cách cải thiện nó.
Hơn 12 năm qua, tôi đã phát hành nhiều phiên bản phần mềm lỗi. Tôi đã thiết kế quá phức tạp các tính năng mà chẳng ai yêu cầu. Tôi đã tranh luận về dấu tab và dấu cách trong các yêu cầu kéo (pull request).
Tôi từng gỡ lỗi các hệ thống phân tán vào cuối tuần, trễ hẹn và chứng kiến những kiến trúc “đảm bảo tương lai” dần trở nên lỗi thời.
Một số bài học rất đau đớn. Một số khác thì vô giá.
Không có điều nào trong số đó đến từ sách giáo khoa; chúng đến từ các dự án thực tế, các lỗi thực tế và những đồng đội thực sự đã thúc đẩy tôi trở nên tốt hơn.
Hôm nay, tôi muốn chia sẻ 40 bài học quan trọng nhất mà tôi đã rút ra được trong suốt sự nghiệp của mình. Cùng bắt đầu nhé.Chia sẻ
0. Đoạn mã tốt nhất là đoạn mã bạn không viết.
Mỗi dòng mã đều tiềm ẩn rủi ro trong tương lai. Nó cần được đọc, kiểm thử, gỡ lỗi, triển khai và bảo trì trong nhiều năm.
Mã giảm giá rẻ nhất, an toàn nhất và nhanh nhất là mã không bao giờ được gửi đi.
Trước khi viết bất cứ điều gì mới, tôi luôn tự hỏi: Chúng ta có thực sự cần điều này không? Có cách nào đơn giản hơn không? Liệu một công cụ, thư viện hoặc tính năng hiện có có thể giải quyết vấn đề thay thế được không?
Nhiều dự án thất bại vì có quá nhiều mã lập trình. Ít mã hơn đồng nghĩa với ít lỗi hơn, quá trình làm quen với hệ thống nhanh hơn và ít thời gian bảo trì những thứ không ai sử dụng.
1. Bạn không được trả tiền để viết mã – bạn được trả tiền để giải quyết vấn đề.
Mã nguồn chỉ là một công cụ. Sản phẩm thực sự là giá trị bạn mang lại cho doanh nghiệp và người dùng.
Tôi từng chứng kiến các nhà phát triển dành hàng tuần để hoàn thiện một tính năng mà chẳng ai yêu cầu, trong khi một lỗi nghiêm trọng vẫn tồn tại trong môi trường sản xuất. Tôi cũng từng thấy các nhóm xây dựng những khung phần mềm phức tạp trong khi một đoạn mã đơn giản chạy trong 5 phút đã có thể hoàn thành công việc.
Những kỹ sư giỏi nhất mà tôi biết đều bắt đầu từ vấn đề, chứ không phải từ công nghệ. Họ đặt câu hỏi, hiểu bối cảnh, và chỉ sau đó mới chọn công cụ phù hợp. Và đôi khi, công cụ đó thậm chí không cần viết mã.
2. Mọi thứ đều là sự đánh đổi. Không có công cụ nào là “tốt nhất”.
Mỗi quyết định về kiến trúc, lựa chọn framework hay mẫu thiết kế đều có chi phí riêng. SQL hay NoSQL? Kiến trúc microservices hay kiến trúc monolith? CQRS hay CRUD đơn giản? Câu trả lời luôn luôn là: tùy thuộc vào từng trường hợp.
Khi ai đó nói với tôi rằng một công cụ nào đó là “tốt nhất”, tôi sẽ hỏi: tốt nhất cho mục đích gì? Tốt nhất ở quy mô nào? Tốt nhất cho nhóm nào?
Nguyên tắc của tôi: chọn phương án đơn giản nhất giải quyết được vấn đề hiện tại và không gây ra rắc rối trong tương lai. Sự đánh đổi là bản chất của kỹ thuật phần mềm. Sai lầm là giả vờ như chúng không tồn tại.
3. Viết mã mà các lập trình viên khác sẽ thích làm việc cùng.
Bạn không phải là người duy nhất đọc mã của mình. Đồng đội của bạn, chính bạn trong tương lai và lập trình viên gia nhập công ty năm sau đều phụ thuộc vào việc mã nguồn phải rõ ràng và dễ sử dụng.
Hãy coi việc xem xét mã, đặt tên, định dạng và cấu trúc sao cho dễ hiểu nhất có thể. Mã sạch là món quà dành cho người tiếp theo sẽ làm việc và gỡ lỗi mã của bạn.
Khi viết một thứ gì đó, tôi tự hỏi: liệu một lập trình viên có hiểu được nó trong năm phút không? Nếu không, nó cần được chỉnh sửa lại nhiều hơn.
4. Viết các thông báo commit có ý nghĩa
Một thông báo commit tốt kể một câu chuyện. Nó giải thích những gì đã thay đổi và, quan trọng hơn, lý do tại sao.
Hãy so sánh hai commit này:
fix: bug in orders request
fix: prevent duplicate creation of orders when client retries POST
Clients sometimes retry POST /api/orders due to network timeouts.
We now use the Idempotency-Key header to detect retries and return
the original response instead of creating a duplicate order.
Cách thứ hai giúp lập trình viên tiếp theo hiểu được vấn đề và cách khắc phục mà không cần mở bản so sánh mã.
Tôi tuân theo định dạng Commit thông thường: feat, fix, BREAKING CHANGE, refactor. Định dạng này giúp tìm kiếm lịch sử dễ dàng, thân thiện với tự động hóa và dễ xem lại.
5. Trước tiên hãy làm cho nó hoạt động, sau đó hãy làm cho nó đẹp.
Tôi từng dành hàng giờ để hoàn thiện một hàm trước khi nó hoạt động trọn vẹn. Đó là một sai lầm lớn.
Việc hoàn thiện quá sớm là một hình thức trì hoãn. Bạn tối ưu hóa những chi tiết có thể hóa ra là sai khi bạn quan sát hành vi thực tế.
Thứ tự đúng là: viết mã hoạt động trước, sau đó mới tái cấu trúc. Đảm bảo kiểm thử thành công, có điểm cuối hoạt động và tích hợp được tính năng: rồi mới cải thiện việc đặt tên, cấu trúc và hiệu năng.
6. Ra mắt sớm, liên tục cải tiến.
Một tính năng được bật bằng cờ tính năng trong môi trường sản xuất có giá trị bằng mười tính năng vẫn còn trên nhánh đó.
Người dùng thực sự gặp phải vấn đề thực sự. Dữ liệu thực cho thấy các mô hình thực sự. Không có bất kỳ thử nghiệm nào trên môi trường dàn dựng có thể thay thế phản hồi từ môi trường sản xuất thực tế.
Hãy triển khai tính năng hữu ích nhỏ nhất, học hỏi từ cách nó hoạt động và liên tục cải tiến. Điều này áp dụng cho các tính năng, công cụ nội bộ và thậm chí cả kiến trúc hệ thống.
Mã nguồn càng tồn đọng lâu chưa được phát hành, nỗi lo ngại về việc công bố nó càng tăng lên. Việc phát hành nhỏ lẻ, thường xuyên giúp giảm rủi ro hiệu quả hơn nhiều so với việc phát hành lớn, cẩn trọng.
7. Các ước tính không bao giờ chính xác.
Việc ước tính chi phí phần mềm chỉ là một ước lượng sơ bộ. Mỗi dự án đều có những yếu tố không lường trước được: các tích hợp hoạt động không đúng cách, các yêu cầu thay đổi, các phụ thuộc bị lỗi.
Tôi đã học cách đưa ra khoảng giá trị thay vì chỉ một con số duy nhất, và nói “Tôi chưa biết” cũng như những điều đang cản trở tôi đưa ra ước tính.
Tôi chia nhỏ các nhiệm vụ lớn thành các nhiệm vụ nhỏ hơn. Các nhiệm vụ nhỏ dễ ước tính và dễ hoàn thành hơn. Mục tiêu của việc ước tính không phải là chính xác tuyệt đối, mà là để phát hiện rủi ro và điều chỉnh kỳ vọng.
Nếu một bên liên quan cần sự chính xác, thì điều họ thực sự cần là phạm vi nhỏ hơn.
8. Liên tục và từng bước tái cấu trúc mã nguồn.
Các dự án tái cấu trúc lớn thường thất bại. Chúng kéo dài hàng tháng trời, đi chệch hướng khỏi nhánh chính và bị hủy bỏ khi các ưu tiên thay đổi.
Tôi thích nguyên tắc của hướng đạo sinh hơn: hãy để lại mã nguồn tốt hơn một chút so với lúc bạn tìm thấy nó. Đổi tên biến gây nhầm lẫn. Tách một phương thức nhỏ. Xóa nhánh không cần thiết. Mỗi khi bạn động đến một tập tin.
Những cải tiến nhỏ, liên tục sẽ tích lũy dần. Chúng giúp duy trì mã nguồn ở trạng thái tốt mà không cần đến “chương trình tái cấu trúc quy mô lớn” gây tranh cãi.
9. Việc rà soát mã nguồn không chỉ cải thiện chất lượng mà còn cải thiện hiệu quả làm việc của nhóm.
Đánh giá là cách rẻ nhất và hiệu quả nhất để truyền bá kiến thức trong toàn nhóm. Lập trình viên mới vào nghề học hỏi các mẫu thiết kế. Lập trình viên kỳ cựu giải thích lý do của họ. Mọi người cùng chia sẻ bối cảnh về mã nguồn.
Một bài đánh giá tốt là một cuộc thảo luận xoay quanh mã nguồn. Nó đặt câu hỏi, đề xuất các giải pháp thay thế và khen ngợi những ý tưởng hay. Một bài đánh giá tồi thì ngắn gọn, gay gắt và chỉ tập trung vào những lỗi nhỏ về phong cách.
Khi nhóm của tôi đánh giá tốt, mã nguồn sẽ tốt hơn, hệ số hoạt động được cải thiện, và cả nhóm cùng nhau nâng cao trình độ.
10. Không bao giờ tin tưởng vào thông tin do người dùng cung cấp.
Mọi dữ liệu đầu vào từ bên ngoài hệ thống đều có thể chứa thông tin sai lệch hoặc thậm chí là các cuộc tấn công độc hại. Điều đó bao gồm các trường biểu mẫu, chuỗi truy vấn, tiêu đề, tải lên tệp, nội dung hàng đợi tin nhắn và thậm chí cả phản hồi từ các dịch vụ thượng nguồn “đáng tin cậy”.
Kiểm tra tính hợp lệ tại ranh giới. Sử dụng các kiểu dữ liệu nghiêm ngặt. Từ chối sớm.
Các thư viện kiểm tra tính hợp lệ như FluentValidation giúp việc này trở nên rẻ. Cái giá phải trả khi bỏ qua bước này: dữ liệu bị hỏng, lỗ hổng bảo mật, lỗi khó hiểu.
11. Ghi chép chính xác, không quá chi tiết.
Các tệp nhật ký chứa mọi thứ nhưng lại không cung cấp thông tin gì hữu ích. Chúng trở thành những thông tin nhiễu làm chậm quá trình gỡ lỗi.
Ghi lại các sự kiện quan trọng bằng dữ liệu có cấu trúc. Sử dụng các cấp độ ghi nhật ký phù hợp: Informationcho các sự kiện nghiệp vụ, Warningcho các sự cố có thể khắc phục, Errorcho các vấn đề thực sự. Debugdành cho các chi tiết nội bộ không phải lúc nào cũng cần thiết.
csharp// Bad - noisy and unstructured
logger.LogInformation("Entered method");
logger.LogInformation("Got order " + orderId + " for customer " + customerId);
logger.LogInformation("Returning result");
// Good - structured and intentional
logger.LogInformation(
"Order {OrderId} created for customer {CustomerId} with total {Total:C}",
order.Id, order.CustomerId, order.Total);
Các nhật ký có cấu trúc có thể được tìm kiếm trong Seq, Elastic hoặc Application Insights.
12. Tự động hóa mọi thứ có thể tự động hóa.
Các bước thủ công là nơi phát sinh lỗi. Bất cứ việc gì bạn làm hai lần đều nên được chuyển thành kịch bản tự động. Bất cứ việc gì bạn làm mười lần đều nên được xử lý tự động theo quy trình.
Tự động hóa các quy trình xây dựng, kiểm thử, triển khai, định dạng mã, cập nhật phụ thuộc và di chuyển cơ sở dữ liệu. Tự động hóa không chỉ tiết kiệm thời gian mà còn loại bỏ nguy cơ sai sót của con người.
Nếu một quy trình quá phức tạp để tự động hóa, hãy đơn giản hóa nó trước.
Và với các công cụ trí tuệ nhân tạo hiện có, việc tự động hóa nhiều thứ trở nên dễ dàng hơn.
13. Sự phức tạp sẽ giết chết các dự án, đừng thiết kế quá mức cần thiết.
Cách nhanh nhất để giết chết một dự án là xây dựng nó cho một tương lai không tưởng tượng.
Đa số các dự án đều thất bại khi chỉ có 1000 người dùng, chứ không phải 1.000.000 người dùng.
Tôi từng chứng kiến các nhóm thiết kế cho “1 triệu người dùng” trong khi họ chỉ có 5000 người dùng. Họ xây dựng các lớp trừu tượng cho cơ sở dữ liệu có thể thay thế, các microservice cho một công cụ quản trị 5 trang và event sourcing cho một biểu mẫu CRUD. Kết quả là, dự án đã trễ hạn một tháng.
Hãy xây dựng giải pháp cho vấn đề bạn đang gặp phải hiện nay. Chỉ thêm độ phức tạp khi giải pháp đơn giản bắt đầu gặp trục trặc.
Hầu hết các đoạn mã được “chuẩn bị cho tương lai” đều trở thành mã lỗi thời trước khi tương lai mà người ta hình dung ra kịp đến.
14. Khắc phục nguyên nhân gốc rễ, không phải triệu chứng
Khối try/catch bỏ qua ngoại lệ chỉ khắc phục được triệu chứng. Nhưng lỗi vẫn còn đó.
Khi gặp sự cố, hãy luôn hỏi “tại sao” cho đến khi tìm ra nguyên nhân thực sự.
Phương pháp năm câu hỏi “tại sao” hoạt động rất hiệu quả: mỗi câu trả lời sẽ hé lộ thêm một lớp thông tin nữa.
Triệu chứng: API trả về mã lỗi 500.
- Tại sao? Bởi vì lệnh gọi cơ sở dữ liệu đã hết thời gian chờ.
- Tại sao? Bởi vì đã có một truy vấn kéo dài rất lâu.
- Tại sao? Bởi vì truy vấn thiếu chỉ mục.
- Tại sao? Bởi vì chưa ai thử nghiệm nó với khối lượng dữ liệu thực tế.
Giải pháp không phải là “tăng thời gian chờ”. Giải pháp là “thêm chỉ mục và chạy thử nghiệm tải trước khi hợp nhất”.
15. Đo lường trước, tối ưu hóa sau.
Trực giác về hiệu suất hầu như luôn sai. Phần chậm chạp hiếm khi nằm ở nơi bạn nghĩ.
Trước khi tối ưu hóa bất cứ điều gì, hãy đo lường: bằng trình phân tích hiệu năng, BenchmarkDotNet, OpenTelemetry, Application Insights, hoặc đơn giản là dấu thời gian trong nhật ký. Tối ưu hóa đường dẫn thực sự gây ra nhiều tác vụ.
Tôi đã dành nhiều ngày để “cải thiện” đoạn mã chỉ chạy một lần mỗi ngày, trong khi một truy vấn 100ms chạy hàng nghìn lần mỗi giờ lại không hề bị chú ý. Số liệu luôn có sức thuyết phục hơn ý kiến cá nhân.
16. Giảm thiểu sự phụ thuộc, tối đa hóa sự gắn kết
Liên kết (Coupling) thể hiện mức độ phụ thuộc lẫn nhau giữa các mô-đun. Sự gắn kết (Cohesion) thể hiện mức độ liên quan giữa các thành phần bên trong một mô-đun.
Mã nguồn liên kết chặt chẽ rất khó thay đổi – chỉ cần động đến một chỗ cũng có thể làm hỏng năm chỗ khác. Mã nguồn có tính liên kết thấp thì khó tìm thấy – logic liên quan nằm rải rác khắp dự án.
Hãy hướng đến điều ngược lại: mỗi mô-đun chỉ làm tốt một việc, và các mô-đun giao tiếp với nhau thông qua các giao diện nhỏ, ổn định. Đây là cốt lõi của các hệ thống nguyên khối dạng mô-đun, kiến trúc gọn gàng và các phân vùng theo chiều dọc .
17. Giảm thiểu và quản lý tốt các phụ thuộc của bên thứ ba.
Mỗi sự phụ thuộc đều là một canh bạc. Bạn đặt cược rằng thư viện đó sẽ tiếp tục hoạt động, được bảo trì và không gây ra vấn đề bảo mật.
Trước khi thêm một gói NuGet, hãy tự hỏi: Mình có thực sự cần cái này không? Một vài dòng mã có thể giải quyết cùng một vấn đề không?
Khi bạn phụ thuộc vào một thứ gì đó, hãy quản lý nó cẩn thận. Sử dụng hệ thống quản lý gói tập trung, mã hóa phiên bản, quét tìm lỗ hổng bảo mật và cập nhật thường xuyên.
18. Không bao giờ mã hóa cứng thông tin nhạy cảm.
Lưu trữ khóa bí mật trong hệ thống quản lý mã nguồn là một sai lầm kinh điển và tốn kém. Một khi khóa đã nằm trong lịch sử Git, bạn phải thay đổi nó ở khắp mọi nơi và cầu nguyện rằng không ai sao chép kho lưu trữ trước khi bạn phát hiện ra.
Hãy áp dụng phương pháp quản lý bí mật đúng cách ngay từ ngày đầu tiên:
Đối với phát triển cục bộ, hãy sử dụng User Secrets ( dotnet user-secrets). Đối với sử dụng trong môi trường sản xuất, hãy sử dụng Azure Key Vault, AWS Secrets Manager hoặc HashiCorp Vault. Đối với CI/CD, hãy sử dụng các biến môi trường được mã hóa.
Hãy coi bất kỳ thông tin mật nào bị rò rỉ đều đã bị xâm phạm và cần phải thay thế chúng ngay lập tức.
19. Lỗi phải được báo hiệu rõ ràng và ngay lập tức.
Những lỗi không báo động là loại lỗi tồi tệ nhất. Hệ thống trông có vẻ hoạt động bình thường, nhật ký hệ thống trông sạch sẽ, nhưng dữ liệu lại sai.
Nếu có lỗi xảy ra, không thể bỏ qua được. Hãy ném ra một ngoại lệ rõ ràng, trả về phản hồi lỗi thực sự và để lớp xử lý phía trên quyết định phải làm gì.
20. Hãy chọn sự rõ ràng thay vì sự khéo léo.
Một câu nói ngắn gọn, sắc sảo mà mất 5 phút để đọc thì tệ hơn năm dòng đơn giản mà ai cũng có thể hiểu trong vài giây.
csharp// Clever - dense, hard to debug, hard to extend
var totals = orders
.GroupBy(o => o.CustomerId)
.ToDictionary(g => g.Key, g => g.SelectMany(o => o.Items)
.Sum(i => i.Quantity * i.Price * (1 - (i.Discount ?? 0))));
// Clear - longer, but easy to follow and easy to change
var totals = new Dictionary<int, decimal>();
foreach (var customerOrders in orders.GroupBy(o => o.CustomerId))
{
decimal customerTotal = 0;
foreach (var order in customerOrders)
{
customerTotal += order.CalculateTotal();
}
totals[customerOrders.Key] = customerTotal;
}
Phiên bản rõ ràng hơn sẽ dài hơn, nhưng nó có thể được gỡ lỗi, mở rộng và hiểu ngay lập tức. Mã nguồn được đọc thường xuyên hơn nhiều so với khi được viết ra. Hãy tối ưu hóa cho người đọc.
Nắm rõ chính xác những gì cần học tiếp theo trong .NET.
Lộ trình .NET 2026 miễn phí: những kỹ năng mà nhà tuyển dụng cần, từ lập trình viên junior đến kiến trúc sư.Địa chỉ emailXem lộ trình phát triển .NET
Bạn cũng sẽ nhận được bản tin .NET hàng tuần của tôi. Bạn có thể hủy đăng ký bất cứ lúc nào.
21. Hãy chọn cách đặt tên mang tính mô tả thay vì lời bình luận giải thích.
Một bình luận giải thích ý nghĩa của một biến thường có nghĩa là biến đó có tên không chính xác.
csharp// Bad - the comment compensates for a poor name
// Number of days the order can still be cancelled
int d = 7;
// Good - the name speaks for itself
int cancellableWindowInDays = 7;
Những cái tên hay giúp loại bỏ nhu cầu về nhiều chú thích. Chúng biến chính mã nguồn thành tài liệu không bao giờ lỗi thời.
Bạn có thể thay thế các bình luận bằng:
- Các biến có tên mô tả rõ ràng.
- Các phương thức riêng tư với tên gọi mang tính mô tả.
- Các lớp học có tên gọi mang tính mô tả.
- Các bài kiểm tra đóng vai trò như tài liệu ghi lại hành vi đó.
Chỉ sử dụng chú thích để giải thích TẠI SAO khi hành vi của mã không rõ ràng. Và tuyệt đối không viết chú thích để giải thích CÁI GÌ.
Lưu ý: Tóm tắt mã XML rất hữu ích, nhưng không giống như chú thích. Hãy viết tóm tắt mã cho các thành viên công khai bất cứ khi nào có thể.
22. Ưu tiên sáng tác hơn là kế thừa
Kế thừa có tính cứng nhắc. Một khi bạn mở rộng một lớp cơ sở, bạn sẽ kế thừa cấu trúc, các giả định và phạm vi bề mặt của nó mãi mãi. Và rất khó để thay đổi thứ tự các lớp trong hệ thống phân cấp.
Cấu trúc công việc rất linh hoạt. Bạn kết hợp các phần nhỏ với trách nhiệm rõ ràng và thay thế chúng khi nhu cầu thay đổi.
csharp// Inheritance - tightly bound to the base class hierarchy
public abstract class PaymentProcessorBase { /* ... */ }
public class StripePaymentProcessor : PaymentProcessorBase { /* ... */ }
public class LoggingStripePaymentProcessor : StripePaymentProcessor { /* ... */ }
// Composition
public class PaymentProcessor(
IPaymentGateway gateway,
IFraudDetector fraudDetector,
IPaymentLogger logger)
{
public async Task<PaymentResult> ChargeAsync(PaymentRequest request, CancellationToken ct)
{
if (!await fraudDetector.IsSafeAsync(request))
{
logger.Log(request, PaymentResult.Rejected);
return PaymentResult.Rejected;
}
var result = await gateway.ChargeAsync(request, ct);
logger.Log(request, result);
return result;
}
}
Trong .NET hiện đại, các hàm tạo chính và cơ chế tiêm phụ thuộc giúp việc kết hợp gần như không tốn kém. Chỉ nên sử dụng kế thừa khi bạn thực sự cần mô hình hóa mối quan hệ “là một”, và ngay cả khi đó, vẫn nên ưu tiên giao diện.
23. Độ phức tạp không thể mở rộng; sự đơn giản thì có thể.
Mỗi lớp trừu tượng, mỗi tính năng “phòng ngừa”, mỗi cấu hình linh hoạt – tất cả đều làm tăng thêm gánh nặng. Ban đầu, chi phí này không thể nhận thấy. Qua nhiều năm, nó trở thành lý do khiến việc phát triển tính năng mất cả tháng thay vì một tuần.
Mã nguồn đơn giản có khả năng mở rộng. Kiến trúc đơn giản có khả năng mở rộng. Quy trình đơn giản có khả năng mở rộng.
Khi một hệ thống trở nên khó thay đổi, thường là do giải pháp đã trở nên quá phức tạp.
24. Tôn trọng nguyên tắc ít gây bất ngờ nhất
Mã lệnh phải thực hiện đúng chức năng mà tên và chữ ký của nó thể hiện. Không hơn không kém.
Một phương thức được gọi GetUserByIdkhông nên đồng thời gửi email. Một thuộc tính được gọi IsActivekhông nên thực hiện truy vấn cơ sở dữ liệu.
Một Validatephương thức không nên báo lỗi vì cơ sở dữ liệu bị sập.
Và thường thì mã nguồn sẽ gây bất ngờ cho người đọc khi nó bị lỗi trong quá trình thực thi hoặc trả về một lỗi kỳ lạ.
25. Hãy xóa mã không sử dụng ngay lập tức.
Mã chết chứa những lời nói dối. Nó trông có vẻ quan trọng, chiếm không gian màn hình và gây khó hiểu cho bất kỳ người đọc nào cố gắng hiểu hệ thống thực sự làm gì.
“Nhưng có thể sau này chúng ta sẽ cần đến nó” không phải là lý do để giữ lại. Git ghi nhớ mọi thứ. Nếu bạn cần lấy lại, chỉ cần một thao git logtác đơn giản.
Xóa bỏ các lớp không sử dụng, các tham số không sử dụng, các nhánh không sử dụng và các khối mã đã được chú thích. Mã nguồn sẽ lại được “thở” (thoát khỏi sự gò bó).
26. Hãy suy nghĩ và lập trình theo các đơn vị nhỏ, có thể kiểm thử được.
Các lớp lớn và các phương thức dài rất khó bảo trì. Chúng có quá nhiều sự phụ thuộc, quá nhiều đường dẫn mã và quá nhiều tác dụng phụ.
Các đơn vị nhỏ dễ kiểm thử, dễ hiểu và dễ kết hợp. Một phương thức chỉ thực hiện một việc, một lớp với một trách nhiệm duy nhất, một Handler chỉ tạo ra một khoản thanh toán – tất cả đều trở nên dễ hiểu hơn.
27. Hãy nhất quán với các tiêu chuẩn lập trình của bạn.
Tính nhất quán quan trọng hơn bất kỳ lựa chọn kiểu dáng cụ thể nào. Sử dụng tab hay dấu cách, varhay kiểu dữ liệu rõ ràng, vị trí đặt dấu ngoặc nhọn – tất cả đều không quan trọng bằng việc chọn một quy tắc và tuân thủ nó.
Các tệp .editorconfig , trình phân tích và trình định dạng giúp loại bỏ tranh luận trong các yêu cầu kéo (pull request). Nhóm chỉ cần đồng ý một lần, và công cụ sẽ thực thi điều đó mãi mãi. Người đánh giá tập trung vào logic, chứ không phải vào định dạng.
28. Sự trừu tượng nên che giấu sự phức tạp, chứ không phải tạo ra sự phức tạp.
Một lớp trừu tượng tốt giúp cuộc sống của người gọi trở nên đơn giản hơn. Một lớp trừu tượng tồi lại thêm một lớp không cần thiết.
Nhiều “lớp trừu tượng” mà tôi từng thấy trong thực tế chỉ là những lớp bao bọc mỏng manh xung quanh công cụ cơ bản:
- Các kho lưu trữ phản ánh EF Core.
DbSet - Các dịch vụ bao bọc một phương thức gọi duy nhất
- Các nhà máy tạo ra một tầng lớp duy nhất.
Trước khi giới thiệu một giao diện, hãy tự hỏi: liệu nó có che giấu sự phức tạp thực sự hay chỉ che giấu kiểu dữ liệu? Nếu bạn không thể chỉ ra sự phức tạp mà nó che giấu, thì bạn chưa cần đến sự trừu tượng hóa đó.
29. Ưu tiên rõ ràng hơn ngầm
Ảo thuật nghe có vẻ thú vị cho đến khi có điều gì đó không ổn. Khi đó, không ai có thể tìm ra chuyện gì đang xảy ra hoặc ở đâu.
csharp// Implicit - magic interceptor silently rewrites DELETE into UPDATE
// Somewhere in a service — looks like a real delete, but isn't
await context.Orders.DeleteAsync(orderId);
await context.SaveChangesAsync(); // interceptor silently kicks in
public class SoftDeleteInterceptor : SaveChangesInterceptor
{
public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
DbContextEventData eventData, InterceptionResult<int> result, CancellationToken ct)
{
var entries = eventData.Context!.ChangeTracker.Entries<ISoftDeletable>()
.Where(e => e.State == EntityState.Deleted);
foreach (var entry in entries)
{
entry.State = EntityState.Modified;
entry.Entity.IsDeleted = true;
entry.Entity.DeletedAt = DateTime.UtcNow;
}
return base.SavingChangesAsync(eventData, result, ct);
}
}
csharp// Explicit - the intent is visible right where the action happens
public async Task SoftDeleteOrderAsync(Guid orderId, CancellationToken ct)
{
var order = await context.Orders.FindAsync(orderId, ct);
if (order is null)
{
return;
}
order.IsDeleted = true;
order.DeletedAt = DateTime.UtcNow;
await context.SaveChangesAsync(ct);
}
Mã nguồn tường minh có thể tìm kiếm, gỡ lỗi và tự giải thích. Mã nguồn ngầm định giúp tiết kiệm một vài thao tác gõ phím nhưng lại tốn hàng giờ điều tra sau này.
30. Mỗi dòng mã đều phải chứng minh được sự tồn tại của nó.
Đây là câu hỏi tôi luôn đặt ra trong mỗi lần xem xét mã: Tại sao dòng mã này lại xuất hiện ở đây?
Nếu câu trả lời là “phòng trường hợp”, “có thể chúng ta sẽ cần đến nó sau này”, hoặc “tôi không chắc”, thì nó không nên có trong mã nguồn.
Mỗi phương thức, tham số, nhánh và sự phụ thuộc đều làm tăng chi phí bảo trì. Nó cần phải chứng minh được giá trị của mình.
31. Luôn nỗ lực để hiểu rõ hoạt động kinh doanh đằng sau mã nguồn.
Viết mã mà thiếu bối cảnh kinh doanh là rất nguy hiểm. Bạn có thể phát hành các tính năng đúng về mặt kỹ thuật nhưng lại giải quyết sai vấn đề.
Hãy trò chuyện với quản lý sản phẩm, bộ phận hỗ trợ, và thậm chí cả khách hàng khi có thể. Hiểu được người dùng đang cố gắng thực hiện điều gì, chứ không chỉ những gì tài liệu yêu cầu nghiệp vụ (BRD) nói.
Những kỹ sư giỏi nhất mà tôi từng làm việc cùng đều hiểu rõ hoạt động kinh doanh cũng như hiểu rõ mã lập trình. Họ đề xuất những giải pháp tốt hơn vì họ hiểu mục tiêu thực sự.
32. Giữ cho các Pull Request nhỏ gọn và dễ quản lý.
Một bài PR dài 2.000 dòng nhận được bình luận “LGTM” (Good’s Go). Một bài PR ngắn 200 dòng nhận được đánh giá thực sự.
Người đánh giá không thể nhớ hàng ngàn dòng mã. Họ chỉ đọc lướt qua, bỏ sót vấn đề và phê duyệt vì mệt mỏi. Những yêu cầu kéo (PR) nhỏ sẽ nhận được sự chú ý, phản hồi thực sự và được hợp nhất nhanh hơn.
Điều tương tự cũng áp dụng cho các công cụ đánh giá bằng AI, càng nhiều mã lập trình thì chất lượng đánh giá của các công cụ AI càng kém.
Chia nhỏ công việc thành nhiều commit nhỏ hơn, viết mã thành các phần dọc. Phát hành một bản cải tiến nhỏ, hoạt động được, rồi tiếp tục phát triển dựa trên đó.
33. Đầu tư vào CI/CD ngay từ đầu
Một dự án mới mà không có CI/CD là một dự án đã có nợ kỹ thuật.
Ngay từ ngày đầu tiên, hãy thiết lập một quy trình tự động bao gồm xây dựng, chạy thử nghiệm và (khi sẵn sàng) triển khai. GitHub Actions, Azure DevOps, GitLab — hãy chọn một và bắt đầu với quy mô nhỏ.
Khi quy trình CI/CD đã được thiết lập, mọi thay đổi đều được kiểm tra tự động, mọi lần triển khai đều có thể tái hiện và bạn không cần phải thay đổi mã của mình sau này để phù hợp với quy trình CI/CD.
34. Thiết kế API dễ sử dụng đúng cách và khó bị lạm dụng.
Cấu trúc của API hướng dẫn người dùng. Nếu có khả năng lạm dụng, chắc chắn sẽ có người làm điều đó. Nếu việc sử dụng đúng cách yêu cầu đọc tài liệu, thì một nửa số người dùng sẽ không làm vậy.
csharp// Easy to misuse - which is "from" and which is "to"?
public Money Convert(decimal amount, string from, string to);
Convert(100, "EUR", "USD");
Convert(100, "USD", "EUR"); // both compile, only one is correct
// Hard to misuse - the types tell the story
public Money Convert(Money amount, Currency target);
Convert(new Money(100, Currency.EUR), Currency.USD);
Các kiểu dữ liệu mạnh, tham số được đặt tên, mẫu thiết kế Builder và các đối số bắt buộc khiến mã sai trở nên khó nhận biết. Trình biên dịch trở thành người đánh giá đầu tiên của bạn.
Điều tương tự cũng áp dụng cho API web.
Nếu bạn muốn tìm hiểu sâu hơn, hãy xem bài viết của tôi về các phương pháp tốt nhất để xây dựng API REST .
35. Hãy ghi lại lý do, chứ không chỉ là kết quả.
Mã tốt thể hiện rõ chức năng của nó. Chú thích chỉ có thể giải thích lý do tại sao nó hoạt động như vậy, khi điều đó không rõ ràng từ mã nguồn.
csharp// Bad - the comment just repeats what the code already says
// Skip inactive users
var activeUsers = users.Where(u => u.IsActive);
// Good - the comment explains what the code cannot
// Inactive users still appear in the database for audit purposes,
// but they must never receive communications per GDPR Article 17.
// Filtering here is the last safety net before any message is dispatched.
var activeUsers = users.Where(u => u.IsActive);
36. Hãy quên câu “nó hoạt động trên máy của tôi” đi.
Nếu bài kiểm tra chỉ thành công trên máy của bạn, thì nó không được coi là thành công. Nếu việc triển khai chỉ hoạt động khi bạn thực hiện, thì đó không phải là một quá trình triển khai thực sự.
Hãy sử dụng container cho việc phát triển. Sử dụng cùng phiên bản .NET, cùng cơ sở dữ liệu và cùng các thư viện phụ thuộc như trong môi trường sản xuất. Aspire và Docker Compose giúp việc này trở nên dễ dàng trong .NET hiện đại.
Mục tiêu: bất kỳ ai trong nhóm cũng có thể sao chép kho lưu trữ, chạy một lệnh duy nhất và có được môi trường làm việc chỉ trong vài phút.
37. Hãy nhận thức về nợ kỹ thuật; trả nợ từng bước một.
Nợ kỹ thuật không phải là điều xấu. Đôi khi đó là quyết định đúng đắn: phát hành nhanh chóng ngay bây giờ, chỉnh sửa lại sau. Sai lầm là giả vờ như nó không tồn tại.
Theo dõi nó. Thảo luận về nó. Lên lịch dọn dẹp nhỏ trong mỗi sprint. Áp dụng quy tắc của hướng đạo sinh.
Việc tập trung vào 10% mã nguồn mỗi bản phát hành sẽ tạo nên một codebase lành mạnh hơn trong suốt một năm, hiệu quả hơn nhiều so với một “tháng tái cấu trúc lớn” mà không bao giờ được phê duyệt.
Nợ nần mà bạn phớt lờ sẽ ngày càng chồng chất. Đừng làm vậy.
38. Cân bằng nguyên tắc YAGNI (“Bạn sẽ không cần đến nó”) với thiết kế chu đáo
Nguyên tắc YAGNI (You Ain’t Gonna Need It – Bạn sẽ không cần đến nó) khuyên bạn không nên xây dựng các tính năng mà bạn chưa cần. Đây là một nguyên tắc tuyệt vời để tránh việc thiết kế quá mức cần thiết.
Nhưng nguyên tắc YAGNI (You Ain’t Gonna Need It – Bạn sẽ không cần đến nó) không phải là lý do để bỏ qua các điểm mở rộng rõ ràng. Nếu bạn biết rằng sẽ có thêm các kênh thông báo được thêm vào, đừng mã hóa cứng một kênh nào cả. Nếu bạn biết rằng Service của mình có thể chuyển từ kiến trúc Monolith sang chức năng Servless, hãy thiết kế nó một cách linh hoạt.
Hãy giữ mọi thứ đơn giản hôm nay, nhưng đừng làm cho việc thay đổi ngày mai trở nên khó khăn. Hãy làm cho giải pháp hôm nay đơn giản nhất có thể, đồng thời chừa chỗ cho sự mở rộng và phát triển trong tương lai.
39. Hãy nhờ giúp đỡ khi bạn gặp khó khăn.
Đừng lãng phí hàng giờ, hàng ngày loay hoay với một vấn đề. Thông thường, vấn đề của bạn có thể được giải quyết bởi những người phù hợp chỉ trong vài phút.
Một nguyên tắc chung hữu ích: hãy cố gắng khắc phục sự cố trong 1-2 giờ, sau đó hãy hỏi ý kiến người khác. Có lẽ thành viên kỳ cựu trong nhóm của bạn đã giải quyết chính xác vấn đề này năm ngoái rồi.
Thảo luận nhóm. Đăng bài trong nhóm chat. Mở yêu cầu hỗ trợ với nhà cung cấp.
Việc chủ động tìm kiếm sự giúp đỡ sẽ giúp bạn phát triển nhanh hơn. Những kỹ sư giỏi nhất mà tôi biết là những người không bao giờ ngại nói “Tôi không biết”, bất kể họ có thâm niên đến đâu.
40. Đừng bao giờ ngừng học hỏi và đặt câu hỏi về những giả định của bạn.
Đoạn mã tôi viết khi mới bắt đầu hoàn toàn khác so với những gì tôi viết ngày nay. Những khuôn mẫu mà tôi từng tin dùng mười năm trước, giờ đây tôi sẽ phản đối.
Đọc sách, blog. Đọc mã nguồn. Xem các khóa học trực tuyến.
Hãy theo dõi những người đang xây dựng các công cụ mà bạn sử dụng. Hãy thử các ngôn ngữ lập trình mới. Hãy đặt câu hỏi về cách mà nhóm của bạn “vẫn luôn làm”.
Ngày bạn ngừng học hỏi cũng là ngày kỹ năng của bạn bắt đầu lỗi thời.
Sau 12 năm, tôi đang làm Kiến trúc sư phần mềm và tôi học hỏi được nhiều hơn cả 12 năm trước.
Hi vọng bản tin này sẽ hữu ích với bạn. Hẹn gặp lại lần sau.
LINK gốc: https://antondevtips.com/blog/40-lessons-i-learned-in-12-years-as-a-dotnet-developer


















