Viet Dev

Tuan for Unicloud Group

Posted on

Lập trình tường minh — clean code

Lời nói đầu

Các lập trình viên khi bước vào sự nghiệp viết mã từ dòng mã “hello world” đầu tiên cũng rất hào hứng rằng mình đã và sẽ điều khiển được máy tính thực hiện các nhiệm vụ tự động hoá, chuyên môn hoá, tạo tiền đề cho các sản phẩm phần mềm giúp ích cho nhiều lĩnh vực sử dụng công nghệ trong tương lai.

Tuy nhiên sự hào hứng đó thường biến thành nỗi ám ảnh khi số lượng dòng mã chúng ta viết đủ nhiều, đủ phức tạp vượt quá sự hiểu biết, kiểm soát của tư duy chính người viết ra nó. Và khi tính đến mức độ đóng góp mã nguồn của hàng trăm, hàng ngàn lập trình viên thì ác mộng bảo trì, kế thừa phát triển, kiểm soát chất lượng dự án càng trở nên hiện hữu, thường xuyên hơn.

Lập trình tường minh là gì? Và tại sao cần chú trọng?

Đó là làm mọi thứ dễ hiểu với chính chúng ta và người đọc mã nguồn đó, nghe có vẻ đơn giản, nhưng việc làm (cấu trúc) cho 1 vấn đề dễ hiểu với người khác là 1 loại kỹ năng cần rèn luyện.

Khi 1 vấn đề chúng ta có thể làm dễ hiểu với người khác, nó cũng sẽ giúp chúng ta dễ hiểu hơn trong tương lai khi nhìn lại.

Xuyên suốt quá trình viết mã, để lập trình tường minh được ta cần hiểu rõ vấn đề của mình, cách thức giải quyết nó 1 cách khoa học, được phân loại, cấu trúc 1 cách logic, bài bản, và áp dụng thường xuyên, mọi thao tác, mọi nơi trong quá trình viết mã.

Và code “sạch” giúp:

Bảo trì dễ

code chart

Tránh các thể loại hell

hell

Củng cố thân tình đồng nghiệp

code review

Các nguyên tắc cơ bản để viết mã “sạch”

  • Tuân thủ định dạng & đồng nhất phương pháp
  • Đơn giản hoá nhiều nhất có thể mọi vấn đề (Keep it simple, stupid)
  • Đóng gói tính năng vào hộp đen, thoát khỏi chiều sâu xử lý chi tiết càng sớm càng tốt
  • Tuân thủ các nguyên tắc kỹ thuật lập trình mã “sạch”

Tuân thủ định dạng & đồng nhất phương pháp

Điều đầu tiên đó là đảm bảo thống nhất về mặt coding-styles. Có rất nhiều quy tắc viết mã cho nhiều loại ngôn ngữ lập trình, hãy chọn 1 style dễ nhớ và đảm bảo mọi nơi tuân thủ 1 quy tắc.


//DON'T
public void DoSomething()
{
for (var i = 0; i < 1000; i++)
{
var productCode = $"PRC000{i}";
//...implementation
}
}

//DO
public void DoSomething()
{
    for (var i = 0; i < 1000; i++)
    {
        var productCode = $"PRC000{i}";
        //...implementation
    }
}
Enter fullscreen mode Exit fullscreen mode

Đối với các ngôn ngữ lập trình hiện nay thì đều có công cụ định dạng tự động, kiểm soát lỗi lint…, và đều có các extension cho IDE/Editor, cần cài đặt và sử dụng ngay khi bắt đầu dự án.

Ngoài ra cũng cần đồng nhất cách xử lý các vấn đề giữa các editor như Newline, CR/LF, indent tab/space. Tiện ích mở rộng (cho đa số các Editor) là EditorConfig có thể hữu ích.

Đơn giản hoá nhiều nhất có thể mọi vấn đề (Keep it simple, stupid)

Các vấn đề đều có nhiều cách giải quyết, tuỳ theo mong muốn, từ việc tối ưu hoá tốc độ xử lý, sử dụng lại mã, gõ ít code nhất nhưng xử lý nhiều nhất … Tuy nhiên, việc giải quyết các vấn đề đơn giản bằng các thuật toán, các thức tính toán phức tạp có thể gây ra sự khó hiểu và làm chậm đi quá trình xử lý vấn đề khi có trục trặc phát sinh. Vì thế luôn cần cân bằng giữ sự dễ hiểu, đơn giản và các vấn đề khác.

//for understanding weekday2 there are more details to think about
const char *weekday2(int dayOfWeek)
{
    if ((dayOfWeek < 1) || (dayOfWeek > 7)) {
        ESP_LOGE(TAG, "dayOfWeek must be in range 1..7");
        return NULL;
    }

    const char *weekdays = {
        "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"
    };
    return weekdays[dayOfWeek - 1];
}

// Simple, easy to understand
const char *weekday1(int dayOfWeek)
{
    switch (dayOfWeek) {
        case 1: return "Monday";
        case 2: return "Tuesday";
        case 3: return "Wednesday";
        case 4: return "Thursday";
        case 5: return "Friday";
        case 6: return "Saturday";
        case 7: return "Sunday";
        default: ESP_LOGE(TAG, "dayOfWeek must be in range 1..7");
    }
    return NULL;
}
Enter fullscreen mode Exit fullscreen mode

Đóng gói tính năng (mô đun hoá), thoát khỏi chiều sâu xử lý chi tiết càng sớm càng tốt

Unix

#define AWAIT(a) if (a != OK) continue;

status_t _connect(state_t *state)
{
    if (state >= CONNECTED) {
        return OK;
    }
    // do connect, return FAIL if failed
    state = CONNECTED;
    return OK;
}

status_t _register(state_t *state)
{
    if (state < CONNECTED) {
        return FAIL;
    }
    if (state > CONNECTED) {
        return OK;
    }
    // do register, return FAIL if failed
    state = DATA;
    return OK;
}

status_t _exchange_data(state_t *state)
{
  if (state != DATA) {
    return FAIL;
  }

  return OK;
}

void main() {
  state_t state = INITILIZED;

  while (true) {
      AWAIT(_connect(&state));
      AWAIT(_register(&state));
      AWAIT(_exchange_data(&state));
  }
}
Enter fullscreen mode Exit fullscreen mode

Tuân thủ các nguyên tắc kỹ thuật lập trình mã “sạch”

  • Sử dụng Tên có ngữ nghĩa bao gồm thông tin thuộc tính
/*
Bad: We have a bunch of get_data, and after a few months,
we don't know which get_data is for what
*/
data_t *get_data();

/*
Good
*/
mp3_audio_data_t *get_mp3_data_from_buffer();
Enter fullscreen mode Exit fullscreen mode
  • Sử dụng Tên có thể đọc được
typedef struct {
    date_time_t playhhmmdd; /* BAD */
} mp3_audio_data_t;

typedef struct {
    date_time_t play_time; /* GOOD */
} mp3_audio_data_t;
Enter fullscreen mode Exit fullscreen mode
  • Sử dụng Tên có thể tìm kiếm được
/* BAD */
for (int i = 0; i < 5; i++)
{
    s += (t[i] * 4) / 5;
}

/* GOOD */
int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum 0;
for (int i = 0; i < NUMBER_OF_TASKS; i++)
{
    int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
    int realTaskWeeks = (realTaskDays / WORK_DAYS_PER_WEEK);
    sum += realTaskWeeks;
}
Enter fullscreen mode Exit fullscreen mode
  • Diễn giải các điều kiện dễ hiểu
/* BAD */

if ((food.status == 'available'
    && user.status == 'active'
    && user.money > food.price
    && shipper.status == 'free')
    || (user.sos && user.call911
    && food.status == 'available'
    && shipper.status == 'free')
    ) {

    deliver(food);
 }

 /* GOOD */

 bool user_can_buy_food = user.status == 'active'\
                         && user.money > food.price;
bool user_need_help = user.sos && user.call911;
bool we_can_deliver_food = food.status == 'available'\
                           && shipper.status == 'free';

if ((user_can_buy_food || user_need_help)
    && we_can_deliver_food) {

    deliver(food)
}
Enter fullscreen mode Exit fullscreen mode
  • Stateless
/* GOOD */  
// The state is derived by what is passed into the function
function int addOne(int number)
{
    return number + 1;
}


/* BAD */   

// The state is maintained by the function

private int _number = 0; //initially zero

function int addOne()
{
   _number++;
   return _number;
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)