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ễ
Tránh các thể loại hell
Củng cố thân tình đồng nghiệp
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
}
}
Đố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;
}
Đó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
#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));
}
}
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();
- 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;
- 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;
}
- 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)
}
- 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;
}
Oldest comments (0)