Chia sẻ kiến thức lập trình, kĩ năng mềm từ góc nhìn của một Engineer

Viết code với những thói quen tốt này sẽ giúp bạn giỏi hơn

Xin chào các bạn, lại là mình đây, lâu rồi mình không viết bài thường xuyên do đợt vừa rồi khá bận với những dự án cá nhân cũng như chạy OKR cuối năm. Nhân dịp đầu xuân năm mới xin gửi lời chúc chân thành tới tất cả mọi người, chúc cho mọi người năm mới đạt được thật nhiều thành công trong công việc cũng như trong cuộc sống.

Hôm nay mình muốn chia sẻ một vài những thói quen code tốt, hoặc đứng ở góc nhìn của mình thì mình nghĩ nó cần thiết với mỗi engineer để có thể trở nên ngày càng tốt hơn, giỏi hơn.

Viết code xong nhớ kĩ cần phải self test trước

Có bao nhiêu bạn đọc đến đoạn này và tự cảm thấy nhột chưa? đã có lần nào từng code xong commit rồi đi pha cafe, một lúc sau QC/QA ping ầm ầm báo bug chưa???

Việc self test sau khi triển khai xong chức năng là điều cơ bản cần phải có, ít nhất là có thể cover được những happy-case và không ảnh hưởng đến những chức năng của anh em khác triển khai trên cùng dự án, và bắt buộc không được ảnh hưởng đến những chức năng cũ đã triển khai lên production.

Mới mấy tháng trước thôi, ở công ty mình bị dính vài trường hợp củ chuối như này:

Ông A triển khai chức năng trên một service, đã golive được hơn một năm, dự án chạy bon bon không lỗi lầm gì, cho đến một ngày ông B vào triển khai một chức năng khác, có dùng lại model JPA của chức năng cũ, code thêm một vài đoạn join jiếc (many to one, one to many,…) trên model JPA cũ và bùm, điều kì diệu đã xảy ra.

QC báo với ông A là cột dữ liệu trong bảng chức năng mà ngày xưa ông A đã triển khai đột nhiên bị mất data bất ngờ,…

Sau một thời gian tracing, loại trừ đủ mọi nghi vấn thì cuối cùng xác định được nguyên nhân là do ông B triển khai chức năng mới, cấu hình không chính xác và gây ra lỗi lầm khiến ông A phải mất công đi tìm nguyên nhân cho cái chức năng ngày xưa đã triển khai đúng, tình đồng nghiệp giảm sút đi “một chút“.

Đừng bao giờ tin những gì end-user nhập vào

Nếu đặt vị trí bản thân mình ở end-user thì khi sử dụng một chức năng của ai đó dựng, hay một đầu api của đối tác,…mình thường thử đủ loại case khác nhau, thử nhiều trường hợp, với những loại data dữ liệu giời ơi đất hỡi,..v.v…

Ví dụ như email: thì cũng có dăm bảy loại kiểu email, cái theo tiêu chuẩn, cái không, cái có dấu, cái có kí tự đặc biệt, cái không đúng định dạng,…

Số điện thoại thì: định dạng, số lượng, có nhập chữ hay không, nhập chữ dấu, dấu cách thì validate thế nào….

Rồi là ô số nhập nhữ, ô cần chữ nhập số, kí tự đặc biệt, icon,….

Đơn giản như việc tích hợp api giữa các đối tác, thì nhiều bên nhận không hề ràng buộc dữ liệu gì trong quá trình tích hợp, xong khi bên gửi truyền dữ liệu ảo/rác/lẫn lộn thì lúc đó mới đi tranh cãi về việc chuẩn hóa dữ liệu đầu vào????

Thế nên là để chắc ăn, hãy cứ validate chặt chẽ nguồn dữ liệu đầu vào trên những phần required, báo lỗi rõ ràng tới người dùng.

Những đoạn logic phức tạp thì cần thêm comment rõ ràng

Thật ra viết đoạn này mình cũng hơi lấn cấn một chút, vì nhiều lý do, trước giờ mình luôn suy nghĩ như này:

Viết code tốt là không cần comment, người đọc chỉ cần nhìn vào code đọc là hiểu được vấn đề

Nguyên nhân chính không nên viết comment quá nhiều bởi vì nếu có quá nhiều comment trên một hàm sẽ rất dễ khiến người đọc bị rối, bị loạn, xong rồi đọc code tiếp sẽ bị mông lung, chưa kể trong nhiều trường hợp comment và code đều không được up to date, điều này làm cho việc đọc code rất khó khăn.

Ngược lại, nếu đoạn mô tả comment thực sự rõ ràng và khái quát hóa được nội dung logic khó của toàn bộ hàm thì sẽ là một điều cực kì tốt, giúp cho người maintance code hiểu nhanh hơn, tiếp nhận nhanh hơn.

Nói đi cũng phải nói lại, dù các bạn có comment nhiều và rõ ràng đến cỡ nào, thì khi những người maintance lại code của các bạn cũng chưa chắc đã up to date những đoạn comment dài ngoằng đó đâu.

Nên là khi viết code, triển khai chức năng hãy viết thực sự rõ ràng và có ý nghĩa, clean code nhất có thể, có thể bạn sẽ thấy hơi chậm lúc đầu nhưng sau này bạn sẽ tự thầm cảm ơn chính mình vì điều đó đấy.

Cần tránh việc gọi liên tiếp các phương thức getter liên lục

String district = person.getInformation().getCountry().getAddress().getDistrict();

Code kiểu này thực sự rất tối nghĩa, rất khó maintain và debug, đôi khi chỉ là một lỗi NullpointerException ngớ ngẩn mà phải mất công đi tìm xem lỗi nằm ở phần nào, thiếu nguồn dữ liệu nào, thực sự rất không nên làm vậy.

Đừng join quá 3 bảng trong một câu SQL

Chuyện kể rằng có một ông em intern nào đấy, trong quá trình code có sự hỗ trợ đắc lực của chatGPT, và đã viết một câu native sql query join 5 hay 6 bảng gì đó với nhau, mục đích là để có thể lấy ra dữ liệu cần thiết trong 1 lần.

Nghe câu chỉ cần viết một câu query là lấy hết được những dữ liệu cần thiết nằm ở trên các bảng khác nhau có vẻ là hợp lý??? cơ mà thật sự là không hề hợp lý tí nào đâu, đây là một sự đánh đổi.

Triển khai code kiểu đó rất khó maintain sau này, việc tái hiện và debug nếu gặp phải lỗi cũng sẽ cực khó khăn. Hơn nữa không chắc chắn được các bảng kia không bị phình cột, phình dữ liệu, sau này cái query trên sẽ rất chậm chạp.

Vậy nên làm ơn nếu có join bảng hãy chỉ nên dừng lại ở 3 bảng mà thôi, chỉ lấy ra những dữ liệu thực sự cần thiết sử dụng, cân nhắc việc lưu thêm một vài cột dữ liệu (không có nhiều sự thay đổi dữ liệu) để tránh việc collect thêm data ở những bảng khác.

Viết câu query để người đọc có thể nhìn phát là hiểu mới khó chứ đọc câu query nhìn phát không hiểu gì thì dễ lắm.

Luôn luôn check null khi lấy ra một thuộc tính của đối tượng

Điều này giống với điều 4 ở trên, tuy nhiên mình nghĩ nó cần phải được viết riêng ra thành một thói quen để có thể phòng tránh đến mức thấp nhất việc xảy ra NullPointerException.

Đừng code kiểu cẩu thả, không cần biết đối tượng đó như thế nào, cứ mặc định lấy ra sử dụng thì sẽ rất dễ vô tình gây lỗi.

Các bạn cứ phải nếm trải đủ nhiều đau thương thì cái thói quen này nó mặc nhiên trở thành phản xạ khi code luôn mà chẳng cần phải nhớ nữa.

Đừng gọi liên tục theo vòng lặp khi gọi vào database hoặc gọi qua thirdparty

Điều này thì liên quan nhiều đến việc xử lý performance của hệ thống, thay vì xử lý từng lần hit vào db thì nên chia xử lý theo từng batch, mỗi batch bao nhiêu item gì đó (tính toán để đưa ra một con số tối ưu).

Việc gọi api đồng bộ qua một đối tác nào đấy để xử lý nghiệp vụ có thể xảy ra nhiều trường hợp như timeout, retry, rồi là lỗi hệ thống, lỗi kết nối,…

Các bạn cứ thử được trải nghiệm làm việc với những đối tác, mà api của họ cung cấp ra, khi gọi postman lần 1 thì oke, lần 2 thì lỗi 400, lần 3 lỗi 404, các lần tiếp theo lỗi 502, 503 liên tục, rồi là gọi tận 60s cũng không có phản hồi ???

Thế nên một khi kết nối đã lỗi rồi thì phần vòng lặp sẽ bị vô tận không biết bao giờ mới dừng, nặng hơn có thể crash service, các bạn cố gắng hãy hạn chế đừng để rơi vào vòng luẩn quẩn này.

Không nên try catch quá nhiều trong code, trừ những trường hợp đặc biệt

Việc triển khai try catch trong dự án hoàn toàn không có gì sai, bản chất là mình xác định rõ đoạn code đang xử lý đó có khả năng gây ra lỗi và sẽ có phương án xử lý cụ thể ở phần catch cho thích hợp.

Hơn nữa việc sử dụng try-catch nhiều không hề ảnh hưởng đến performance của hệ thống (mình sẽ viết chi tiết ở một bài viết khác) mà nó chỉ làm code xấu hơn thôi.

Việc code của bạn liên tục có những khối try catch liên tục, hoặc luồng nhau sẽ khiến code xấu, khó đọc, khó maintain. Thay vào đó hãy xử lý handle exception global cho toàn bộ dự án.

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleGlobalException(Exception e) {
        return new ResponseEntity<>("Global Exception Handler: " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(NullPointerException.class)
    public ResponseEntity<String> handleNullPointerException(NullPointerException e) {
        return new ResponseEntity<>("NullPointerException Handler: " + e.getMessage(), HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<String> handleResourceNotFoundException(ResourceNotFoundException e) {
        return new ResponseEntity<>("ResourceNotFoundException Handler: " + e.getMessage(), HttpStatus.NOT_FOUND);
    }

    // Thêm các phương thức xử lý ngoại lệ khác tùy thuộc vào yêu cầu của bạn.
}

Tất nhiên là không phải lúc nào bạn cũng handle exception global là xong chuyện, có một vài tình huống cụ thể thì vẫn phải sử dụng try-catch để xử lý những case lạ hoặc trường hợp ngoại lệ.

Khi sử dụng đa luồng, cần xem xét đảm bảo an toàn tuyến tính

Một thói quen nữa mình muốn chia sẻ với các bạn đó là trong việc xử lý đa luồng, khi triển khai xử lý đa luồng thì an toàn tuyến tính là một trong những thuộc tính quan trọng, đặc biệt là khi có sự chia sẻ về dữ liệu.

Nó mô tả cách mà các hoạt động trên dữ liệu được chia sẻ sẽ được thực hiện sao cho kết quả cuối cùng của mọi chuỗi hoạt động này sẽ trông như chúng đã được thực hiện tuần tự, theo một thứ tự xác định.

Ít nhiều khi triển khai code đa luồng, các bạn sẽ gặp nhiều trường hợp cần xử lý an toàn tuyến tính, ví dụ những trường hợp một biến đếm được nhiều luồng cùng xử lý, luồng đến trước luồng đến sau cần chọc vào và tăng giảm giá trị, cần phải rất cẩn thận để tránh bị race condition và data race.

Xem thêm một số bài viết nổi bật

1 Comment

  1. Thuhamisa

    Năm mới em chúc anh thật nhiều sức khoẻ và thành công trong công việc ạ!

© 2024 Cafeincode

Theme by Anders NorenUp ↑