Java

Java Core Best Practices (Should Know)

Bài viết này mình xin trình bày một số điều nhỏ nhỏ các lập trình viên java nên xem xét để thực hiện nó trong các đoạn mã chương trình chúng ta viết hàng ngày. Nó giúp các lập trình viên Java viết mã tốt phù hợp với các thực tiễn tốt nhất.

Using Naming Conventions

Trước hết, khi bắt đầu dự án, bạn hoặc cả team nên chỉ định một tập hợp các quy ước đặt tên cho dự án Java của bạn, chẳng hạn như cách đặt tên các class và các interface, method, constan, biến, ... và sau đó quy ước này được tuân thủ bởi tất cả các member trong team của bạn.
Trong cuốn clean code có một chương nói về điều này, một mã định danh (một lớp, một phương thức và một biến) cần có các đặc điểm sau:
Self-explanatory : Một tên phải tiết lộ ý định của nó để mọi người có thể hiểu và maintain dễ dàng. Ví dụ, inputText không tiết lộ rõ ý định của nó.
Lưu ý rằng nếu một tên yêu cầu một comment để mô tả chính nó, thì tên đó không phải là tên rõ nghĩa (tự giải thích)

Pronounceable : tên nên được phát âm tự nhiên như ngôn ngữ nói bởi vì chúng ta là con người - rất giỏi về từ ngữ. Ví dụ, trong 2 tên sau, tên nào bạn có thể phát âm và ghi nhớ dễ dàng: genStamp hoặc GenerationTimestamp?

Dưới đây là một số quy tắc đặt tên chung:

  • Tên class và interface nên là danh từ, bắt đầu bằng chữ in hoa. Ví dụ: Student, Car, Rectangle, Painter, etc.
  • Tên biến nên là danh từ, bắt đầu bằng chữ in thường. Ví dụ : number, counter, birthday, gender, etc.
  • Tên method nên là động từ, bắt đầu bằng chữ in thường. Ví dụ: run, start, stop, execute, etc.
  • Tên hằng nên là là CHỮ IN HOA, nếu là từ ghép thì giữa mỗi từ được nối với nhau bằng gạch dưới. Ví dụ: MAX_SIZE, MIN_WIDTH, MIN_HEIGHT, etc.
  • Sử dụng dạng camelCase cho tên. Ví dụ : StudentManager, CarController, numberOfStudents, runAnalysis, etc.

Phải nói rằng, việc đặt tên rất quan trọng trong lập trình khi chúng ta đặt tên mọi thứ từ các class đến interface, đến các phương thức, biến, hằng số, v.v. Vì vậy, đừng viết code chỉ để trình biên dịch chạy được, viết code cũng cần sao cho dễ đọc và có thể hiểu được bởi con người - đầu tiên là cho chính bạn, sau đó là cho team của bạn và cho những người khác cuối cùng maintain dự án của bạn. java convention Oracle

Ordering Class Members by Scopes

Cách thực hành tốt nhất để sắp xếp các biến thành viên của một lớp theo phạm vi của chúng từ hạn chế nhất đến ít hạn chế nhất. Điều đó có nghĩa là chúng ta nên sắp xếp các thành viên theo mức độ hiển thị của các mức độ truy cập: private, default (package), protected, public. Và chia chúng thành các group, được phân tách bới dòng trắng.
Ví dụ đoạn code dưới đây là lộn xộn:

public class StudentManager {
    protected List<Student> listStudents;
    public int numberOfStudents;
    private String errorMessage;
    float rowHeight;
    float columnWidth;
    protected String[] columnNames;
    private int numberOfRows;
    private int numberOfColumns;
    public String title;
 
}

Sau khi sắp xếp lại code sẽ gọn gàng hơn.

public class StudentManager {
    private String errorMessage;
    private int numberOfColumns;
    private int numberOfRows;
 
 
    float columnWidth;
    float rowHeight;
 
    protected String[] columnNames;
    protected List<Student> listStudents;
 
    public int numberOfStudents;
    public String title;
 
}

Việc sắp xếp các biến theo group và theo thứ tự giúp chúng ta quản lý biến và tìm kiếm dễ hơn, vì theo thời gian, chúng ta code thêm các chức năng, số biến dần tăng lên.

Class Members should be private

Cái này mình cũng thỉnh thoảng mắc lỗi, nhiều khi không để ý đến access modifier của biến. Chúng ta nên giảm thiểu khả năng truy cập của các thành viên trong một class (các thuộc tính) càng khó tiếp cận càng tốt. Điều đó có nghĩa là chúng ta nên sử dụng mức truy cập thấp nhất có thể (private) để bảo vệ các thuộc tính. Việc này giúp nâng cao tính đóng gói thông tin trong thiết kế phần mềm.
Cùng thảo luận đoạn mã dưới đây:

public class Student {
    public String name;
    public int age;
}

Vấn đề ở đây là bất cừ ai cũng có thể thay đổi giá trị của các thuộc tính.

Student student = new Student();
student.name = "";
student.age = 2005;

Tất nhiên chúng ta không muốn tên không hợp lệ và tuổi không thực tế. Vì vậy, cách implements dưới đây khuyến khích chúng ta ẩn các thuộc tính và cho phép mã code bên ngoài thay đổi chúng thông qua các phương thức setter. Dưới đây là một ví dụ về một thiết kế tốt hơn:

public class Student {
 
    private String name;
    private int age;
 
    public void setName(String name) {
        if (name == null || name.equals("")) {
            throw new IllegalArgumentException("Name is invalid");
        }
 
        this.name = name;
    }
 
    public void setAge(int age) {
        if (age < 1 || age > 100) {
            throw new IllegalArgumentException("Age is invalid");
        }
 
        this.age = age;
    }
}

Như mọi người thấy thông tin của các thuộc tính đã được ẩn đi, và các giá trị đã được validate cho phù hợp.

Using Underscores in Numeric Literals

Bản cập nhật nhỏ này từ Java 7 giúp chúng ta viết các chữ số dài dễ đọc hơn nhiều. Hãy xem xét các tuyên bố sau:

int maxUploadSize = 20971520;
long accountBalance = 1000000000000L;
float pi = 3.141592653589F;

// sau khi viết lại (bên dưới)

int maxUploadSize = 20_971_520;
long accountBalance = 1_000_000_000_000L;
float pi = 3.141_592_653_589F;

Mọi người thấy cách viết dưới dễ đọc hơn không? 😃

Avoid Empty Catch Blocks

Một thói quen rất xấu là bỏ trống các khối catch, khi đó chương trình thất bại trong im lặng, khiến việc gỡ lỗi trở nên khó khăn hơn. Chúng ta cùng xem xét chương trình dưới đây:

public class Sum {
    public static void main(String[] args) {
        int a = 0;
        int b = 0;
 
        try {
            a = Integer.parseInt(args[0]);
            b = Integer.parseInt(args[1]);
 
        } catch (NumberFormatException ex) {
        }
 
        int sum = a + b;
 
        System.out.println("Sum = " + sum);
    }
}

Giả sử chúng ta chạy chương trình bằng command

java Sum 123 456y

Nó sẽ fail, và kết quả là

Sum = 123

Nguyên nhần vì đối số thứ hai 456y khiến cho chương trình ném ra NumberFormatException, nhưng ở đó, không có mã xử lý nào trong khối catch, do đó chương trình tiếp tục với kết quả không chính xác. Vì vậy cần tránh việc bỏ trống khổi catch không bắt ngoại lệ.
Khi xử lý ngoại lệ nên chú ý một số điều sau:

  • Thống báo cho người dùng về ngoại lệ, ví dụ: thông báo đã nhập sai đầu vào hoặc các message thông báo lỗi. Điều này được khuyến khích nhất.
  • Log exception bắn ra bằng JDK Logging hoặc Log4j
  • Bọc và ném lại ngoại lệ dưới một ngoại lệ mới

Tùy thuộc vào bản chất của chương trình, mã để xử lý ngoại lệ có thể khác nhau. Nhưng quy tắc là không sử dụng try/catch mà khổi catch không xử lý gì cả, cũng như không throw exception mà ko xử lý gì cả. Dưới đây là đoạn mã viết lại cho xử lý bắt exception của đoạn code trên

public class Sum {
    public static void main(String[] args) {
        int a = 0;
        int b = 0;
 
        try {
            a = Integer.parseInt(args[0]);
            b = Integer.parseInt(args[1]);
 
        } catch (NumberFormatException ex) {
            System.out.println("One of the arguments are not number." +
                               "Program exits.");
            return;
        }
 
        int sum = a + b;
 
        System.out.println("Sum = " + sum);
    }
}

Using StringBuilder or StringBuffer instead of String Concatenation

Trong java chúng ta sử dụng toán tử '+' để nối chuỗi như dưới đây

public String createTitle(int gender, String name) {
    String title = "Dear ";
 
    if (gender == 0) {
        title += "Mr";
    } else {
        title += "Mrs";
    }
 
    return title;
}

Điều này là hoàn toàn tốt vì chỉ có vài String object tham gia. Tuy nhiên, với mã liên quan đến việc nối nhiều chuỗi, chẳng hạn như xây dựng một câu lệnh SQL phức tạp hoặc tạo văn bản HTML dài, toán tử + trở nên kém hiệu quả khi trình biên dịch Java tạo ra nhiều String object trung gian trong quá trình nối.
Do đó, cách tốt nhất là bạn nên sử dụng StringBuilder hoặc StringBuffer để thay thế toán tử + để ghép nhiều đối tượng String lại với nhau khi chúng sửa đổi String mà không tạo thêm các String object trung gian. Ví dụ:

String sql = "Insert Into Users (name, email, pass, address)";
sql += " values ('" + user.getName();
sql += "', '" + user.getEmail();
sql += "', '" + user.getPass();
sql += "', '" + user.getAddress();
sql += "')";

Với StringBuilder chúng ta viết lại được mã như sau

StringBuilder sbSql
    = new StringBuilder("Insert Into Users (name, email, pass, address)");
 
sbSql.append(" values ('").append(user.getName());
sbSql.append("', '").append(user.getEmail());
sbSql.append("', '").append(user.getPass());
sbSql.append("', '").append(user.getAddress());
sbSql.append("')");
 
String sql = sbSql.toString();

Cùng tìm hiểu tại sao lên sử dụng StringBuilder và StringBuffer tại đây

Registration Login
Sign in with social account
or
Lost your Password?
Registration Login
Sign in with social account
or
A password will be send on your post
Registration Login
Registration