post-image

4 Nguyên Tắc Cần Nhớ Khi Tái Cấu Trúc Hàm

1. Tổng quan

Các hàm là thành phần thiết yếu thao tác dữ liệu trong các dự án lập trình. Do đó, viết các hàm tốt là một trong những kỹ năng quan trọng nhất mà một lập trình viên cần phải có. Một quan niệm sai lầm mà một số lập trình viên mới vào nghề đó là cố gắng viết code (được cho là) tốt ngay từ lần đầu. Nếu các đoạn code tính năng đơn giản thì có vẻ không vấn đề gì, nhưng đối với hầu hết các hàm trong một dự án thực tế, ta sẽ phải viết nhiều phiên bản. Quá trình viết lại các hàm thường được gọi là tái cấu trúc – chính xác là việc tái cấu trúc các hàm.

Trong bài viết này, tôi muốn chia sẻ với bạn một số tips mà tôi sử dụng để cấu trúc lại các hàm. Và, bắt đầu thôi nào:

1. Loại bỏ duplication

Trở ngại đầu tiên mà nhiều junior developer gặp phải là họ không biết khi nào cần cấu trúc lại các hàm. Vì vậy, nguyên tắc đầu tiên đó là Remove duplicate. Sự trùng lặp là dấu hiệu rõ ràng nhất cho thấy cần phải tái cấu trúc. Giả sử rằng chúng ta có ví dụ đoạn code sau:

function sayHello(name) {
    // Apply some formatting operations
    let name0 = name;
    let name1 = name0;
    let name2 = name1;
    let formattedName = name2;
    console.log("Hello,", formattedName);
}

function sayHi(name) {
    // Apply some formatting operations
    let name0 = name;
    let name1 = name0;
    let name2 = name1;
    let formattedName = name2;
    console.log("Hi,", formattedName);
}

Như bạn có thể thấy, có code trùng lặp giữa các hàm sayHello và sayHi. Đây là thời điểm tốt để cấu trúc lại hàm của bạn. Thông thường, những gì bạn cần làm là trích xuất code và biến nó thành một hàm. Vì vậy, bước đầu tiên sẽ làm như này:

function sayHelloV1(name) {
    let formattedName = formatName(name);
    console.log("Hello,", formattedName);
}

function sayHiV1(name) {
    let formattedName = formatName(name);
    console.log("Hi,", formattedName);
}

function formatName(name) {
    // Apply some formatting operations
    let name0 = name;
    let name1 = name0;
    let name2 = name1;
    return name2;
}
function greet(greetingWord, name) {
    let formattedName = formatName(name);
    console.log(greetingWord + ", " + formattedName);
}

Vậy là, chúng ta kết hợp hàm sayHello và sayHi bằng cách trừu tượng hóa tất cả các thành phần được chia sẻ giữa hai hàm này. Hơn nữa, cách cho phép người dùng sử dụng từ để chào khác ví dụ như good morning thay vì Hi hay Hello

2. Chia nhỏ code

Một nguyên tắc lập trình quan trọng cần tuân theo là bạn cần chia nhỏ code. Tại sao?

Trong thực tế, việc giải quyết một công việc phức tạp tại một thời điểm là rất khó khăn. Chúng ta thường chia nhỏ các công việc ra để thực hiện rồi lấy kết quả của từng phần công việc nhỏ hợp lại thành sản phẩm cuối cùng. Đối với việc thiết kế và phát triển một chương trình, ứng dụng phần mềm cũng tương tự như vậy. Một phần mềm được thiết kế nhằm giải quyết một vấn đề thực tế nào đó, nhưng khi gặp vấn đề quá lớn, lập trình viên cần tách các công việc cụ thể ra để giao cho các chương trình con giải quyết.
Giả sử rằng bạn có hàm sau:

function processData(filename) {
    // 3 lines of code: read the data from the filename
    // 5 lines of code: verify the data structure
    // 10 lines of code: recode some columns
    // 5 lines of code: remove outliers
    // 10 lines of code: do some calculations with the groups
    let results = [];
    return results;
}

Mặc dù không có mã trùng lặp nào ở đây, nhưng code này không dễ đọc. Trừ khi bạn viết comment cho từng thành phần của hàm, mục đích cá nhân không quá đơn giản. Ngoài ra, khi chương trình của bạn gặp bug, có thể bạn sẽ không dễ dàng đì bug. Hãy xem xét phiên bản được tái cấu trúc sau:

function processDataRefactored(filename) {
    let data = readData(filename);
    verifyData(data);
    recodeData(data);
    removeOutliers(data);
    let processedData = summarizeData(data);
    return processedData;
}

function readData(filename) {
    // just a simple list for demonstration purposes
    let data = [1, 2, 3, 4];
    return data;
}

function verifyData(data) {
    // verify the data
}

function recodeData(data) {
    // recode the data
}

function removeOutliers(data) {
    // remove outliers
}

function summarizeData(data) {
    let results = [];
    return results;
}

Như bạn có thể thấy, ta có thể tạo ra nhiều chức năng hơn, mỗi chức năng có một số LOC có thể quản lý được. Nói cách khác, các chức năng này nhỏ hơn, có những lợi ích sau:

  • Mỗi chức năng phục vụ một mục đích và ý định của nó rất rõ ràng.
  • Việc thay đổi một chức năng nhỏ sẽ dễ dàng hơn vì chúng ta sẽ xử lý một điểm dữ liệu vào và ra
  • Logic rõ ràng hơn và có cấu trúc hơn bằng cách chỉ cần nhìn vào các chức năng riêng lẻ.

3. Đặt tên có ý nghĩa

Điều này đã được thể hiện ngầm trong 2 phần trước. Khi chúng ta định nghĩa một hàm, điều đầu tiên cần cân nhắc thường là cách chúng ta đặt tên cho hàm mới. Bạn không thể cẩu thả trong giai đoạn này. Rốt cuộc, đó là code trong hàm thực hiện các hoạt động. Dưới đây là một ví dụ nhỏ về một tình huống có thể xảy ra:

// This function gets the account information for a user using his/her user id number
function getData(id) {
    // the operations
}

Hai vấn đề tồn tại với chức năng trên. Đầu tiên, chức năng không nói rõ về dữ liệu mà nó đang nhận. Thứ hai, cũng không rõ id có nghĩa là gì đối với tham số. Điều thú vị là lập trình viên đang cố gắng giải thích mọi thứ trong comment. Đó là một sai lầm mà nhiều lập trình viên mới vào nghề thường mắc phải: Họ cố gắng sử dụng các comment để bổ sung một phần cho code của họ. Trong nhiều trường hợp, nó thực sự không cần thiết. Hãy xem xét version được tái cấu trúc sau:

function getUserAccountInfo(userIdNumber) {
    // the operations
}
  • Tên hàm cho biết rõ mục đích của hàm.
  • Các tham số cũng phải có tên có ý nghĩa.

Trong phần này, chúng ta chỉ nói về việc đặt tên có ý nghĩa cho một chức năng. Tuy nhiên, các tên có ý nghĩa nên được đặt cho tất cả các chức năng và cần xem xét những điều sau:

Các hàm có mục đích tương tự nên có tên tương tự. Ví dụ: nếu một tập hợp các hàm đang nhận một số loại dữ liệu, chúng ta có thể đặt tên cho tất cả chúng theo cách bắt đầu bằng get, chẳng hạn như getUserAccountInfo và getUserFriendList. Đừng ngại đặt một cái tên dài. IDE hiện đại có tính năng autofill. Bạn sẽ được gợi ý với danh sách biến có thể áp dụng và do đó bạn chỉ cần nhập một vài ký tự trước khi có thể tìm thấy tên hàm cần thiết mà bạn vừa cung cấp.

Hãy nhớ rằng một cái tên dài có ý nghĩa luôn tốt hơn một cái tên ngắn nhưng không rõ ràng.

4. Giảm thiểu số lượng tham số

Tôi không phải là người theo chủ nghĩa tối giản, nhưng tôi muốn giảm thiểu số lượng tham số nếu có thể. Nó không chỉ giúp khai báo hàm của bạn rõ ràng hơn mà còn giúp hàm của bạn dễ gọi. Hãy xem xét ví dụ sau:

function createNewUserAccount(username, email, password, phoneNumber, address) {
    let user = {
        "username": username,
        "email": email,
        "password": password,
        "phoneNumber": phoneNumber,
        "address": address
    }
    // create the new user in the database
}

Trong ví dụ trên, hàm có năm tham số mà bạn cần thiết lập khi gọi hàm này. Nếu bạn chuyển nhầm thứ tự, thao tác sẽ dẫn đến lỗi dữ liệu. Làm thế nào về phiên bản tái cấu trúc sau đây?

function createNewUserAccount(newUserInfoDict) {
    // create the new user in the database
}

let userInfo = {
    "username": username,
    "email": email,
    "password": password,
    "phoneNumber": phoneNumber,
    "address": address
}
createNewUserAccount(userInfo)
Đoạn code trên bao gồm các tham số liên quan trong một dictionary. Tuy nhiên, việc cấu trúc lại chức năng của bạn bằng cách tạo các mô hình dữ liệu thích hợp, như bên dưới phổ biến hơn:
class User {
    constructor(username, email, password) {
        this.username = username;
        this.email = email;
        this.password = password;
    }
}

function createNewUserAccount(newUser) {
    // create the new user in the database
}

let user = new User("username", "[email protected]", "12345678");
user.phoneNumber = "1234567890";
user.address = "123 Main Street";
createNewUserAccount(user);

Bây giờ chúng ta có class  User dùng để nắm bắt thông tin liên quan cho người dùng thay vì sử dụng từ điển. Chúng tôi để lại một số thông tin tùy chọn (ví dụ: phone number) bên ngoài hàm để mang lại sự linh hoạt cho việc user creation.

Kết luận

Trong bài viết này, tôi đã giới thiệu bốn nguyên tắc thiết yếu mà bạn có thể thực hiện khi cấu trúc lại các hàm của mình. Đừng cảm thấy thất vọng nếu bạn gặp nhiều lỗi trong code của mình. Đó là cách bạn có thể học để cải thiện kỹ năng lập trình của bạn. Cần có thời gian để xây dựng kỹ năng viết code của bạn. Hãy kiên nhẫn với chính mình.

Leave a Reply

Your email address will not be published.