Chapter 13: Writing Less Code
Biết khi nào không viết code có khả năng là kĩ năng quan trọng nhất 1 lập trình viên có thể học. Mỗi dòng code bạn viết là 1 dòng cần phải kiểm thử và bảo trì. Bằng cách sử dụng lại các thư viện hoặc loại bỏ các tính năng, bạn có thể tiết kiện thời gian và giữ codebase của bạn mỏng và có ý nghĩa.
KEY IDEA
The most readable code is no code at all.
Don’t Bother Implementing That Feature—You Won’t Need It
Khi bạn bắt đầu 1 project, về lẽ tự nhiên sẽ rất hào hứng và nghĩ về tất cả các chức năng cool ngầu bạn sẽ làm. Nhưng lập trình viên có xu hướng đánh giá quá cao có bao nhiêu chức năng thực sự thiết yếu cho project của họ. Rất nhiều các chức năng chưa hoàn thành hoặc không hữu ích hoặc chỉ làm phức tạp thêm cho ứng dụng.
Lập trình viên cũng có xu hướng đánh giá thấp họ cần bao nhiêu effort để thực thi 1 chức năng. Chúng ta ước lượng lạc quan thời gian cần có thực thi một chức năng nguyên mẫu nhưng quên thời gian thêm liên quan đến việc bảo trì, viết tài liệu và thêm “weight” đến codebase.
Question and Break Down Your Requirements
Không phải tất cả các lập trình viên đều cần nhanh, 100% chính xác, và có thể xử lý mọi đầu vào có thể. Nếu bạn thực sự xem xét kĩ lưỡng yêu cầu của mình, đôi khi bạn có thể thấy 1 số vấn đề đơn giản mà yêu cầu ít code hơn. Hãy xem xét 1 vài ví dụ đưới đây.
Example: A Store Locator
Giả sử bạn đang viết 1 chương trình “định vị cửa hàng” để kinh doanh. Bạn nghĩ rằng các yêu cầu là:
For any given user’s latitude/longitude, find the store with the closest latitude/longitude.
Nhận đầu vào là kinh độ vĩ độ từ người dùng, tìm cửa hàng có kinh độ, vĩ độ gần nhất.
Để thực thi với 100% chính xác, bạn cần xử lý:
- Khi địa điểm nằm ở 2 bên của International Date Line.
- Khi địa điểm gần Bắc Cực hoặc Nam Cực.
- Điều chỉnh độ cong của Trái Đất, như thay đổi “tính toán theo chiều dọc trên mỗi dặm”.
Xử lý tất cả các trường hợp trên yêu cầu một số tiền hợp lý =))
Tuy nhiên, đối với ứng dụng của bạn, chỉ có 30 cửa hàng ở bang Texas. Trong 1 khu vực khỏ hơn, 3 vấn đề trong danh sách không quan trọng. Và như là kết quả, bạn có thể giảm yêu cầu thành:
For a user near Texas, find (approximately) the closest store in Texas.
Giải quyết vấn đề này dễ dàng hơn vì bạn có thể thoát khỏi chỉ vòng lặp với mỗi cửa hàng và tính toán khoảng cách Euclidean giữa kinh độ/vĩ độ.
Example: Adding a Cache
Chúng tôi đã từng có 1 ứng dụng Java thường xuyên đọc các objects thật từ đĩa. Tốc độ của ứng dụng bị giới hạn bởi cách đọc này, do đó chúng tôi cần caching 1 vài thứ. Một thứ tự chung đọc sẽ như sau:1
2
3
4
5
6
7
8read Object A
read Object A
read Object A
read Object B
read Object B
read Object C
read Object D
read Object D
Như bạn có thể nhìn thấy, có rất nhiều các truy cập lặp lại với các đối tượng giống nhau do đó cache có thể hữu ích cho trường hợp này.
Khi đối mặt với vấn đề này, bản năng đầu tiên của chúng ta là sử dụng cache. Chúng ta không có 1 thư viện khả dụng, dó đó chúng tôi phải tự thực thi. Điều này không phải là vấn đề mặc dù, do chúng ta đã thực thi với kiểu cấu trúc như vậy trước đó (nó liên quan cả 1 hash table và 1 danh sách liên kết đơn - có thể tổng cộng 100 dòng code).
Tuy nhiên, chúng tôi nhận thấy rằng việc truy cập từng hàng có thể luôn luôn cho mỗi hàng. Do đó thay vì thực thi URL cache, chúng ta sẽ thực thi one-item cache:1
2
3
4
5
6
7DiskObject lastUsed; // class member
DiskObject lookUp(String key) {
if (lastUsed == null || !lastUsed.key().equals(key)) {
lastUsed = loadDiskObject(key);
}
}
Điều này cho chúng ta 90% lợi ích mà không cần quá nhiều code, và chương trình có bộ nhớ nhỏ hơn.
Lợi ích của việc “xóa bỏ yêu cầu” và “giải quyết các vấn đề đơn giản hơn” không thể được nói quá. Yêu cầu thường xem kẽ với nhau theo 1 cách tinh tế. Điều này có nghĩa là việc giải quyết một nửa vấn đề có thể chỉ mất một phần tư nỗ lực code.
Keeping Your Codebase Small
Khi bạn bắt đầu 1 dự án phần mềm và bạn chỉ có 1 hoặc 2 files gốc, điều này thật tuyệt. Biên dịch và chạy code là 1 snap, dễ dàng thay đổi và dễ dàng nhơ mỗi hàm, lớp được định nghĩa.
Sau đó, project phát triển, các thư mục của bạn càng ngày càng nhiều files hơn. Bạn cần nhiều thư mục để tổ chức lại chúng. Nó trở nên khó nhớ hơn các hàm gọi các hàm khác và mất nhiều việc hơn để theo dõi ra được bug.
Cuối cùng, bạn có rất nhiều mã nguồn được trải rộng vào nhiều thư mục khác nhau. Dự án sẽ lớn và và không còn 1 mình ai hiểu tất cả nó. Thêm chức năng mới trở thành cái gì đó đau đớn và làm việc với các đoạn code này thì cồng kềnh và khó chịu.
Cách tốt nhất để đối phó là giữ codebase của bạn nhỏ và nhẹ kí (lightweight) nhất có thể, thậm chí dự án của bạn lớn dần. Vì vậy bạn nên:
- Tạo nhiều code “tiện ích” chung nhất có thể để loại bỏ lặp lại code (Xem thêm chương 10, Extracting Unrelated Subproblems).
- Xóa các code không hữu ích hoặc chức năng không sử dụng.
- Giữ dự án của bạn ngăn cách với các dự án con đã ngắt kết nối (các dự án không kết nối với nhau thì nên tách biệt).
- Nói chung là, có ý thức về “weight” của codebase. Giữ nó nhẹ và nhanh.
REMOVING UNUSED CODE
Người làm vườn thường tỉa cây để giữ chúng sống và lớn lên. Tương tự, đây cũng là 1 ý tưởng tốt để tỉa 1 vài đoạn code không sử dụng, cái mà làm cản trở.
Một khi code đã được viết ra, coders thường không thích, phân vân xóa nó, vì nó diễn tả rất nhiều công việc thực tế. Để nó xóa, cũng đồng nghĩa với việc thời gian giành cho nó là lãng phí. Well, hãy vượt qua nó! Đây là 1 lĩnh vực sáng tạo - thợ chụp ảnh, nhà văn và nhà làm phim không giữ tất cả công việc của họ.
Xóa các hàm cô lập là đơn giản nhưng đôi khi “các đoạn code không sử dụng” thực tế kết hợp với nhau trong dự án của bạn, mà bạn không biết. Đây là 1 vài ví dụ:
- Bạn thiết kế ban đầu hệ thống của bạn để xử lý các tập tin quốc tế, và bây giờ đoạn code xảy ra các vấn đề về convention. Tuy nhiên, đoạn code không đầy đủ chức năng và ứng dụng của bạn không bao giờ sử dụng với tập tin quốc tế dù sau đi nữa?
Tại sao bạn không xóa những hàm này?
- Bạn muốn chương trình của bạn làm việc ngay cả khi hệ thống hết bộ nhớ, do đó bạn có rất nhiều logic thông minh để cố gắng khôi phục các vấn đề out-of-memory. Đó là 1 ý tưởng tốt nhưng trên thực tế, khi hệ thống tràn bộ nhớ, chương trình của bạn chỉ trở thành 1 zombie không ổn định - tất cả các chức năng chính không sử dụng được và có thể chỉ 1 click chuột là oẳng.
Tại sao bạn không chấm dứt chương trình luôn với 1 thông báo đơn giản “Hệ thống đã tràn bộ nhớ, xin lỗi” và xóa tất cả các code out-of-memory?
Be Familiar with the Libraries Around You: Làm quen với các thư viện xung quanh bạn
Rất nhiều thời gian, các lập trình viên không nhận thức được rằng các thư viện đã tồn tại có thể giải quyết được vấn đề của họ. Hoặc thỉnh thoảng họ quên mất rằng 1 thư viện có thể làm gì. Rất quan trọng để biết khả năng code thư viện bạn cùng có thể làm gì để bạn có thể sử dụng nó hiệu quả.
Đây là một gợi ý nhã nhặn: thỉnh thoảng, hãy dành ra 15 phút đọc tên và tất cả các hàm/modules/kiểu trong thư viện tiểu chuẩn của bạn. Điều này bao gồm thư viện Standard Template Library (STL) trong C++, Java API, các build-in Python modules và 1 vài cái khác.
Mục đích không phải nhớ tất cả thư viện. Nó chỉ cho bạn cảm giác có những thứ gì đó đã có, để trong lần tới bạn làm việc với các đoạn code mới, bạn sẽ nghĩ “Chờ chút, cái này nghe tương tự như cái mèo gì đó tôi đã thấy ở 1 API …”. Chúng tôi tin rằng làm công việc này sẽ có hiệu quả nhanh chóng vì bạn sẽ có xu hướng sử dụng nhiều hơn thư viện này trong lần nhìn đầu tiên.
Example: Lists and Sets in Python
Giả sử bạn có 1 danh sách phần tử Python (như [2,1,2]) và bạn muốn danh sách các phần tử là duy nhất (trong trường hợp này là [2,1]). Bạn có thể thực thi phần này như sau:1
2
3
4
5
6
7def unique(elements):
temp = {}
for element in elements:
temp[element] = None # The value doesn't matter.
return temp.keys()
unique_elements = unique([2,1,2])
Nhưng thay vào đó bạn có thể chỉ sử dụng kiểu set ít được biết đến:1
unique_elements = set([2,1,2]) # Remove duplicates
Đối tượng này lặp đi lặp lại, chỉ như 1 list bình thường. Nếu bạn muốn lại một danh sách, bạn có thể chỉ sử dụng:1
unique_elements = list(set([2,1,2])) # Remove duplicates
Rõ ràng set
là một công cụ tốt cho công việc này. Nhưng nếu bạn không biết kiểu set
, bạn phải tự thực thi hàm unique()
như trên.
Why Reusing Libraries Is Such a Win?
Một thống kê thường được trích dẫn là trung bình kĩ sư phần mềm tạo ra 10 dòng code có thể chuyển đổi trong 1 ngày. Khi lần đầu tiên các lập trình viên nghe thấy điều này, họ có thể biện hộ “10 dòng code? Tôi có thể viết trong 1 phút!”.
Từ khóa ở đây là có thể chuyển đổi. Mỗi dòng code trong thư viện đã được đầu tư để thiết kế, debugging, viết lại, xây dựng tài liệu, tối ưu và test. Đó là lý do tại sao sử dụng lại thư viện lại là 1 chiến thắng, trong khi nó vừa tiết kiệm thời gian mà lại vừa viết code ít hơn.
Example: Using Unix Tools Instead of Coding
Khi một ứng dụng web thường xuyên trả về mã HTTP 4xx hoặc 5xx, nó là 1 dấu hiệu của vấn đề ngầm (4xx là client error và 5xx là server error). Do đó chúng tôi muốn viết 1 chương trình có thể chuyển 1 logs địa chỉa của web server và xác định xem URLs nào gây ra nhiều lỗi.
Phần truy cập log đó cơ bản nhìn như sau:1
2
3
41.2.3.4 example.com [24/Aug/2010:01:08:34] "GET /index.html HTTP/1.1" 200 ...
2.3.4.5 example.com [24/Aug/2010:01:14:27] "GET /help?topic=8 HTTP/1.1" 500 ...
3.4.5.6 example.com [24/Aug/2010:01:15:54] "GET /favicon.ico HTTP/1.1" 404 ...
...
Nói chung là chúng chứa những dòng với form:1
browser-IP host [date] "GET /url-path HTTP/1.1" HTTP-response-code ...
Viết 1 chương trình để tìm kiếm url-path
với nhiều lỗi 4xx hoặc 5xx nhất khá dễ dàng, chỉ mất tầm 20 dòng code trong ngôn ngữ như C++ hoặc Java.
Thay vì đó, trên Unix, bạn có thể gõ câu lệnh sau:1
2cat access.log | awk '{ print $5 " " $7 }' | egrep "[45]..$" \
| sort | uniq -c | sort -nr
Và đây là output:1
2
3
4
595 /favicon.ico 404
13 /help?topic=8 500
11 /login 403
...
<count> <path> <http response code>
Dòng lệnh này thật tuyệt và chúng ta tránh được viết code “thật” hoặc kiểm tra 1 điều gì đó trong code.
Summary
Adventure, excitement — a Jedi craves not these things.
Phiêu lưu, phấn khích — một Jedi không thèm muốn những điều này.
_Yoda
Chương này nói về việc viết ít code mới nhất có thể. Mỗi dòng code mới cần được kiểm thử, xây dựng tài liệu và bảo trì. Thêm nữa, nhiều code trong codebase, sự “nặng nề” nó nhận vào và làm khó để phát triển hơn.
Bạn có thể tránh viết những dòng code mới theo 1 số cách:
- Loại bỏ các chức năng không quan trọng từ sản phẩm của bạn.
- Xem xét lại các yêu cầu để giải quyết theo phiên bản dễ nhất của vấn đề mà vẫn xong được công việc.
- Làm quen với các thư viện chuẩn bằng cách đọc định kì toàn bộ các APIs của chúng.
Chapter 13: Writing Less Code
http://yoursite.com/2020/05/24/Chapter-13-Writing-Less-Code/