Xin chào mừng bạn đã ghé thăm blog của Nguyễn Văn Tiến!.Chúc bạn sức khỏe, niềm vui và an lành!.

Thứ Ba, 7 tháng 6, 2016

Những nguyên tắc, định luật của lập trình mà chúng ta nên có sẵn trong đầu

Một vài định luật về lập trình hay nên share cho mọi người và cũng là textnote cho bản thân:

Nguyên tắc Demeter

Còn có tên gọi khác là nguyên tắc “càng biết ít càng tốt”.
Demeter là tên gọi của Nữ thần nông nghiệp, cũng là nữ thần phân phát trong thần thoại Hi Lạp. Tên bà được dùng để đánh dấu sự ra đời của nguyên tắc này, đây có thể xem là một triết lý nền tảng của việc lập trình được sinh ra từ một aspect-oriented programming (AOP) project cùng tên.
Quan điểm cơ bản của nguyên tắc này chính là : tối giản sự hiểu biết của 1 object về cấu trúc, thuộc tính của các object khác ngoài nó (bao gồm các thành phần con).
Nói một cách đơn giản là không được tiếp xúc với thuộc tính, method của các object khác một cách trực tiếp.
#Vi phạm nguyên tắc Demeter
console.log(aStudent.class.grade)

#Không vi phạm nguyên tắc Demeter
console.log(aStudent.getGrade())

Định luật Wirth

“Software gets slower faster than hardware gets faster” – “Tốc độ tiến hóa của phần cứng không bằng tốc độ thoái hóa của phần mềm.”
Có lẽ ý chính của nó là : lập trình ngày càng dùng nhiều tài nguyên phong phú nên framework phải luôn tiến hóa để phục vụ cho việc đó. Suy ra, tốc độ phần cứng dù có tang lên đi nữa thì tốc độ phần mềm cũng chẳng hề thay đổi gì.

Định luật Brook

Đây là một định luật dựa trên kinh nghiệm thực tế : “Đưa thêm người vào 1 project đang chậm, sẽ chỉ khiến nó càng chậm hơn.”
Hay có thể nói theo một cách khác nữa là “Tập hợp 9 bà bầu lại cũng không thể khiến đứa trẻ ra đời sau 1 tháng.”
Luận thuyết cơ bản của định luật này là:

Định luật Conway

“Organizations which design systems … are constrained to produce designs which are copies of the communication structures of these organizations.”
“Một công ty thiết kế hệ thống thế nào cũng sẽ làm ra những thiết kế giống y hệt với thiết kế hệ thống của chính công ty họ.”
Nghiên cứu gần đây chỉ ra rằng hệ thống của công ty là nhân tố ảnh hưởng lớn nhất đến vấn đề phát sinh ra bug của sản phẩm.

Nguyên tắc bất ngờ nhỏ nhất (least astonishment)

Trong trường hợp trên cùng 1 interface có 2 yếu tố hành xử mâu thuẫn với nhau, hoặc cách hành xử không rõ ràng thì cần phải chọn cách hành xử nào gây bất ngờ ít nhất cho người sử dụng.
Đây là 1 nguyên tắc về giao diện người dùng.
Một ví dụ đơn giản :
Trên 1 interface có 2 chức năng :
  • Ấn ctrl+Q để thoát chương trình.
  • Nhập macro (lưu 1 tổ hợp phím mang 1 chức năng nào đó để tiện cho việc sử dụng về sau).
Sẽ có trường hợp user muốn dùng Ctrl+Q cho macro của mình, nên hành xử đúng với nguyên tắc bất ngờ nhỏ nhất chính là : trong khi nhập macro thì ctrl+Q được coi như là tổ hợp phím bình thường, không phải là lệnh tắt chương trình. Đây chính là điều gây bất ngờ ít nhất cho người dùng.

Nguyên tắc Boy Scout

Nguyên tắc của các tổ chức Boy scout chính là : lúc đi phải sạch đẹp hơn lúc đến.
Trong lĩnh vực lập trình thì nguyên tắc đó sẽ được hiểu là “Khi bạn checkin 1 module thì lúc đó nó phải đẹp hơn lúc bạn checkout.”

Nguyên tắc YAGNI

Viết tắt của “You ain’t gonna need it” – Cái (chức năng, phần) ấy rồi sẽ không cần thiết.
Đó là một câu khẩu ngữ nhắc nhở người lập trình rằng trong quy trình Extreme Programming (lập trình cực hạn) thì : “Chưa phải lúc cần thiết thì chưa được phép làm.”

Nguyên tắc DRY

Viết tắt của “Don’t repeat yourself” – với ý nghĩa là “Đừng lặp lại những gì giống nhau”.
Khi nguyên tắc này được áp dụng tốt, dù ta có thay đổi 1 phần thì những phần không liên quan cũng sẽ không bị thay đổi theo. Hơn nữa, những phần có liên quan sẽ được thay đổi cùng 1 lượt, giúp ích rất nhiều cho cả khâu estimate và khâu thực hiện.

Nguyên tắc KISS

Viết tắt của “Keep it simple, stupid” – “Cứ đơn giản thôi, đồ ngu!”. Đây là 1 triết lí của Hải quân Mỹ.
Những triết lý tương tự có thể kể đến là :
Phương châm dao cạo Okham (Okham’s razor) – “Không đưa ra nhiều giả thiết nếu không cần thiết. Cái gì cần ít giả thiết để chứng minh sẽ không thể chứng minh được bằng nhiều giả thiết.”
Albert Einstein – “Làm cái gì cũng nên đơn giản nhất có thể, nhưng đơn giản quá thì không được”.
Leonardo da Vinci – “Đơn giản nhất chính là tinh xảo nhất”.
Antoine de Saint- Exupéry – “Hoàn hảo, không phải là không thêm vào được nữa, mà là không thể bớt đi được nữa”.

Nguyên tắc SOLID

Tập hợp những nguyên tắc trong lập trình hướng đối tượng. Các chữ cái đầu hợp lại thành SOLID.
SRP (Single Responsibility Principle) – “Một class chỉ được có 1 nhiệm vụ” hay nói cách khác, “nếu muốn chỉnh sửa class thì chỉ được phép có 1 và chỉ 1 lý do”.
OCP (Open/closed principle) – “Mở class khi cần mở rộng nó, đóng class khi cần chỉnh sửa nó”.
LSP (Liskov substitution principle) – “Subtype phải luôn có thể được thay thế bằng supertype”.
ISP (Interface segregation principle) – “Việc dùng nhiều interface cho các client khác nhau, tốt hơn là việc chỉ dùng 1 interface cho cùng lúc nhiều mục đích” hay nói cách khác “Không được phép hạn chế access vào những method mà client không sử dụng”.
DIP (Dependency inversion principle) – “Module tầng trên không được phụ thuộc vào module tầng dưới. Bất cứ module nào cũng phải phụ thuộc vào cái trừu tượng, không phải vào cái cụ thể”.

Những thói quen xấu kìm hãm sự tiến bộ của lập trình viên

Những thói quen xấu kìm hãm sự tiến bộ của lập trình viên


Gần đây, tôi được phân công hỗ trợ những người lập trình chưa giỏi, dưới hình thức một kèm một (pair programming). Trong khi làm công việc đó, tôi nhận thấy có một vài thói quen xấu khiến họ khó tiến bộ và thói quen tốt nữa mà tôi sẽ liệt kê ra như dưới đây.

Sức mạnh của thói quen

Hàng ngày, tôi vẫn tự hỏi mình năng lực lập trình là do thứ gì quyết định? Có người học lập trình rất nhanh nhưng có người lại học rất chậm. Thông thường, người ta hay gọi cái đó là “cảm giác tốt, cảm giác chưa tốt” và cho qua.
Nhưng tôi thấy rõ ràng có những yếu tố quyết định đến việc học nhanh hay chậm.
Vài năm trước vào một lần nọ, tôi có xem màn hình của đồng nghiệp ngồi bên cạnh và thỉnh thoảng đi quanh phòng làm việc xem màn hình của những người khác nữa. Có một điều làm tôi thực sự ngạc nhiên. Người hay “nộp bài” chậm thì màn hình lại thường có xu hướng hiển thị stack trace (dùng cho đầu ra của web application).
Đồng nghiệp ngồi cạnh tôi thường lập trình theo quy trình dưới đây. Tôi không thống kê chi tiết, nhưng đại loại là vậy.
  • Viết code
  • Lưu code
  • Switch màn hình sang browser
  • Reload
  • Màn hình lỗi hiện ra
  • Switch sang màn hình code luôn
Như thế này thì tôi nghĩ năng suất làm việc sẽ rất tồi. Phải nói thêm là cậu ta đang viết một phần thuộc model (và tất nhiên phần đó có ở bên controller nữa). Ngay lập tức, tôi chỉ cho cậu ấy cách check syntax và cách chạy thử từng module. Dựa vào đó, tôi chỉ thêm cho cậu ấy cách viết những test code nhỏ cho phần logic nữa. Nhưng hình như cậu ấy vốn đã biết cả rồi. Tuy nhiên, vì nghĩ là phiền phức nên cậu ấy đã bỏ qua. Tôi hỏi tại sao lại phiền thì cậu ấy có hơi lúng túng, giải thích khá nhiều nhưng đại ý là “Đằng nào cũng phải hiển thị, nên xem nó hiển thị thế nào là nhanh nhất”. Đây là vấn đề về thói quen. Ngày đó công ty tôi chưa có quy trình CI (continous integration) nên hầu như không ai có thói quen viết test cho code cả.
Về sau, quy trình CI được áp dụng, việc viết test cho code trở thành bắt buộc. Tuy nhiên khi đó thì sau khi hiển thị ngon hết rồi cậu ta mới viết, với một thái độ không lấy gì làm vui vẻ cho lắm. Quy trình phía trên của cậu ấy, do chạy code sau khi đã ghép nên màn hình sẽ hiện một đống lỗi rất phức tạp làm cho công việc kéo dài ra. Cậu ta dường như không quan tâm đến chuyện đó mà có khi cũng chẳng đọc lỗi nữa. Cậu ấy không quen test từng phần một, mà sau khi ghép code sẽ khó test hơn rất nhiều. Quy trình vốn sinh ra để tăng năng suất, bây giờ do thói quen của người làm việc, thành ra lại làm giảm năng suất.

Thói quen xấu

Gần đây, tôi có pair programming với vài kĩ sư trẻ. Một ngày tôi làm với họ vài tiếng, chiếm phần lớn thời gian ở công ty của tôi. Tôi sẽ giới thiệu vài thói quen xấu của họ mà trong khi làm việc tôi đã phát hiện và nhắc nhở.

Ít đọc code

Tùy từng project mà thời gian đọc code khác nhau, nhưng nhìn chung thời gian đọc code luôn chiếm tỉ lệ lớn. Cách phân bố thời gian thường thấy là 80% đọc, 20% viết. Đọc code là bắt buộc để có thể hiểu được những phần liên quan, và hiểu framework. Đối với những kĩ sư mà công việc hay bị trục trặc, không tiến triển, tôi thấy tỉ lệ thời gian họ dành cho việc đọc code là rất thấp. Một ví dụ là có một cậu định sử dụng chức năng có trong framework thì bị lỗi. Tôi nói với cậu ấy là ở framework đang hiển thị lỗi sai đầu vào. Tuy nhiên cậu ấy sau đó không hề xem chỗ tôi bảo trên framework. Vậy thì cậu ấy làm gì?
Nhìn chăm chú vào code của mình, ngẩn ngơ tìm chỗ sai. Nghiền ngẫm một đoạn code trên mạng để xem có nên copy paste không. Nghĩa là cứ khi nào code không chạy thì cậu ấy đều ngồi xem code mình viết có gì sai.

Cách sửa

Tôi chỉ cho cậu ấy cách tìm đoạn bị sai tương ứng trên framework. Đồng thời, tôi bảo cậu ấy đọc xem đoạn ấy xử lí cụ thể như thế nào và kết luận tại sao code không chạy.

Vấn đề tâm lí

Tôi hỏi tại sao cậu ấy không đọc code của framework thì cậu ấy trả lời rằng “Nhìn phức tạp lắm, với lại em muốn làm xong sớm”. Tôi cũng công nhận là nội dung bên trong framework phức tạp thật, và đúng là rất khó để nắm được tổng thể. Tuy nhiên bằng cách nghiên cứu nó, kiến thức về ngôn ngữ và khả năng đọc hiểu thư viện sẽ tăng lên, trình độ bản thân sẽ tăng lên. Tôi đã cho cậu ấy trải nghiệm việc phá bỏ rào cản tâm lí đó, ngồi nghiên cứu code sẽ khiến vấn đề được giải quyết nhanh như thế nào.
Vì những lẽ ở trên, tôi nghĩ là người hướng dẫn cũng không nên nói những câu đại loại như “Phần này không hiểu cũng được”, “Đừng đọc nhiều làm gì, cứ cho chạy đi đã” - điều đó cũng rất quan trọng. Tôi luôn nghĩ : giới hạn trưởng thành của bạn cao đến mức nào, phụ thuộc vào việc bạn hiểu code sâu đến mức nào.

Không test riêng những đoạn xử lí phức tạp

Vệc chia nhỏ các hàm rất quan trọng. Tại sao lại phải chia? “Vì như thế thì sẽ nhàn hơn”. Tuy nhiên đối với người chưa thuần thục thì lại cảm thấy như thế là “khổ hơn”.
Ví dụ:
Ruby
function process(list){

    for(var i=0,l=list.length;i<l;i++){
        ...
        // viết cái gì cần xử lí
    }

    ...
    // xử lí thêm gì nữa thì viết vào đây`

}
Hàm có cấu trúc như thế này nghĩa là muốn thực hiện xử lí riêng từng phần một. Người chưa thuần thục thường có xu hướng cứ thế viết những gì muốn xử lí vào những chỗ comment ở trên.
Nhưng đúng ra nó phải là như thế này :
Ruby
function process(list){
    var next = []:
    for(var i=0,l=list.length;i<l;i++){
        ...
        var elem = doSomethingForElement(list[i]);

        if ( elem ) {
            next.push(elem):
        }
    }

    ...

    if( next.length == 0 ){
        return doSomethingWhenNoResponse(args);
    }

    ...

}

function doSomethingForElement(){
    #viết những gì cần xử lí
}

function doSomethingWhenNoResponse(){
    #viết những gì cần xử lí
}
Những phần muốn xử lí viết tách ra ngoài, độc lập (gọi là sprout method). Làm như thế sẽ tránh cho hàm gốc bị phức tạp hóa, và giúp ta test được từng xử lý một.

Cách sửa

Tôi cho họ làm quen với quy trình là trước khi động tay vào hàm gốc thì hãy tách những gì mình cần xử lí ra thành những hàm rời, sau đó viết và test những cái rời trước. Sau đó họ đã thấy kết quả là việc giải quyết lỗi phát sinh trở nên nhanh như thế nào. Đồng thời, tôi giúp họ ý thức đầu ra đầu vào là gì, phần mình làm ảnh hưởng các phần khác như thế nào.

Vấn đề tâm lí

Nguyên nhân của chuyện cảm thấy việc chia nhiều hàm là khổ chính là từ suy nghĩ “muốn xong sớm” mà ra. Họ nghĩ “Sau này có lỗi thì chỉ cần xem lỗi ở đâu rồi sửa là xong” nên họ thấy chia nhiều hàm rất là “phức tạp hóa vấn đề”, chỉ là chuyện “nếu thừa thời gian thì làm”. Tuy nhiên, nếu gộp hết tất cả thành 1 hàm rồi chạy, thì việc hiểu code sẽ rất khó và cái vòng luẩn quẩn “chạy và lỗi” sẽ kéo dài, lỗi lại còn rất nặng. Như thế mới chính là đang làm mất thời gian. Cũng như trong Hình học để giải bài toán ta hay phải kẻ đường phụ. Việc học cách kẻ đường đó như thế nào là một điều rất quan trọng.

Không kiểm tra quy trình làm việc của bản thân

Như phần đầu tôi đã nói, thói quen chính là sức mạnh. Chúng ta cần phải có một quy trình làm việc nhanh nhất có thể, hướng đến mục tiêu là tự động hóa.
Từ hôm pair programming, tôi mới nhận ra một cậu hay ngồi nhìn code chằm chằm sau khi viết. Tôi hỏi là đang làm gì thế thì cậu ấy bảo “Em đang kiểm tra”. Cậu ấy đúng là đang kiểm tra từng dòng code một, xem mình có viết sai gì không. Tôi bảo cậu ấy trước tiên hãy check syntax đi thì cậu ấy hỏi “Check syntax nghĩa là sao ạ?”. Sau khi tôi chỉ cậu ấy cách làm thì cậu ấy sau đó cứ liên tục hỏi tôi “Câu này đúng chưa anh?”, “Thế này được chưa?”. Tôi trả lời là trước hết cứ tự mình kiểm tra đi đã, cậu ấy không hiểu ý tôi là gì.
Tôi đã nhận ra rằng ngoài quy trình check xem code của mình sau khi build và cho chạy thật đã ngon chưa, họ không có thêm một quy trình nào khi làm việc cả. Unit test vốn là thứ được sinh ra để xem từng phần có hoạt động chính xác không. Nếu trong khi làm không chuẩn bị trước, không phải là người có thói quen vừa làm vừa test (test first), thì sau này làm unit test sẽ rất mệt.

Cách sửa

Phần code dùng để check syntax hãy để riêng ra editor và dùng thường xuyên. Nếu cần thiết, cứ khi save code là tự động cho chạy luôn để test. Vì cậu ấy dùng editor là vim nên cần cài plugin như quickrun hay syntastic.
Các class và các hàm cũng vậy, ở đâu cũng được, nhưng cần phải tạo môi trường để kiểm tra từng cái một. Nếu kiểm tra thấy ngon rồi, thì hãy lưu lại vào một file test. Đó là quy trình mà tôi muốn cậu ấy thực hiện. Trước khi hỏi “Thế này được chưa” thì hãy làm tất cả những việc trên đã. Khi đã quen rồi, nên tạo một môi trường CI dựa trên cách dùng debug hoặc fswatch. Mỗi khi save code thì cho chạy luôn cái CI đó để test là tốt nhất.

Vấn đề tâm lí

Vấn đề về môi trường như bên trên có vẻ như liên quan đến kiến thức chứ không liên quan đến tâm lí. Tuy nhiên sau khi phân tích, tôi thấy nó liên quan đến nhận thức về 2 khái niệm thường gặp khi lập trình là “code chuẩn” và “code lỗi mà không biết vì sao”.
Một thực tế là, không phải chỉ có duy nhất một “code chuẩn”.
Tuy nhiên, người hay copy paste thường có xu hướng nghĩ rằng : copy paste mà cũng không chạy được thì chắc là do mình thao tác sai chỗ nào đó, copy paste bị thiếu chỗ nào đó và thường có thói quen ngồi tìm kiếm những cái đó. Vì lí do đó, code của họ sau khi viết xong chẳng khác nào mật mã ngoài hành tinh, và họ sẽ phải tìm lỗi sai trong đống mật mã đó. Phần mà họ tự viết ra cũng dựa trên những đoạn copy paste, mà sau này rồi cũng sẽ trở thành thứ mà họ không thể tự lí giải nổi. Tâm lí của họ là luôn nghĩ việc đọc hiểu và kiểm tra từng dòng một, kém năng suất hơn việc copy paste cả một đoạn rất nhiều.

Không đọc error message, không đọc log

Error message của ngôn ngữ lập trình, hay error message của thư viện đều có nhiệm vụ là chỉ ra chỗ sai bằng văn bản con người có thể hiểu được, viết bởi con người. Tuy nhiên, những lập trình viên tiến bộ chậm thường không đọc những cái đó. Họ chỉ ý thức được là có lỗi xảy ra. Nếu là code trên IDE thì có thể kích chuột để jump ngay đến câu lệnh lỗi. Nhưng nếu làm trên vim hoặc Web browser thì việc đọc hiểu log message, và jump đến câu lệnh lỗi là nhiệm vụ của người lập trình. Không đọc, cũng không jump đến câu lệnh lỗi nên màn hình error thường bị switch đi trong tích tắc. Trong khi pair programming với vài người, họ switch màn hình nhanh đến nỗi tôi còn tưởng họ đang thử độ phản ứng của mắt với vật thể chuyển động.
Kết quả là họ ngồi nhìn chằm chằm vào code họ vừa viết, tìm xem có lỗi chính tả, lỗi font nào không bằng cách ngó đi ngó lại method name. Vấn đề là error message vừa nãy đâu có nói là method name có lỗi, hoặc không tồn tại. Một khi không đọc error message thì phạm vi có thể phát sinh lỗi là vô hạn. Tuy nhiên, họ chỉ luôn nghĩ được là mình “viết sai một cái gì đó”.

Cách sửa

Tất nhiên là đọc error message và tìm hiểu ý nghĩa của từng message một. Thêm vào đó, tôi giúp họ lí giải mối liên hệ giữa error message và môi trường. Đồng thời, hướng dẫn họ tạo ra những đoạn code nhỏ để tái hiện các lỗi đó (snippet) có thể dùng cho sau này và dự trù các case tương tự có thể xảy ra.

Vấn đề tâm lí

Có thanh niên nói “Em sợ tiếng Anh lắm, nhìn cứ như mật mã”. Có điều, ngữ pháp của các error message chỉ dừng ở mức học sinh cấp Hai, mà tôi biết là cậu ta thừa sức hiểu. Cũng phải công nhận một thực tế là muốn hiểu những lỗi liên quan đến ngôn ngữ lập trình và framework thì phải hiểu cấu trúc và từ ngữ chuyên ngành của chúng. Điều này khó. Nhưng như vậy không có nghĩa là ngôn ngữ hoặc framework đó “không thân thiện” mà vì bản thân error message không phải sinh ra chỉ để hiển thị những lỗi liên quan đến code người lập trình viết.
Vì vậy ta cần theo sát cả framework, cả ngôn ngữ lập trình, và cả code. Có như vậy ta mới có thể móc nối error message và vấn đề cần giải quyết với nhau. Để có thể tự mình làm như vậy, hàng ngày cần tự tạo thói quen tìm hiểu những thứ đó cho mình. Nếu không cho dù đến bao giờ chăng nữa, error message cũng vẫn mãi chỉ là những “mật mã” mà thôi.

Không biết cách đào sâu vấn đề

Có lỗi xảy ra, nghĩa là chúng ta có thể tìm ra manh mối để giải quyết vấn đề bằng cách xem xét phần code bị báo lỗi. Ở đó có lỗi, nghĩa là ít nhất thì code đã chạy được cho đến đó. Hơn thế nữa, nguyên nhân lỗi phát sinh rất có thể là do ảnh hưởng của những gì mà chúng ta đã nhập vào. Chẳng hạn, bằng cách dùng printf để debug, chúng ta có thể biết nhập cái gì vào module, biến đổi nó ra sao thì sẽ sinh ra lỗi. Thông qua việc đó, chúng ta sẽ hiểu khái quát vấn đề, sau đó tuần tự thử các bước chạy của chương trình là có thể chỉ ra được cụ thể phần nào bị lỗi.
Tuy nhiên, người không biết cách đào sâu vấn đề thường cho chương trình chạy với tâm lí là sẽ chạy ngon và check code với tâm lí “đáng ra nó phải chạy ngon”. Họ thường không quan tâm đến thứ tự các bước chạy của chương trình, cách làm việc của họ chẳng khác nào lần mò trong đám mây. Trong một vài trường hợp, họ sẽ mãi “ngẩn ngơ” vì không hiểu sai từ đâu. Họ không có trong tay một chiến thuật để tìm ra chỗ sai nên sẽ nhanh chóng rơi vào trạng thái người ta vẫn gọi là “quay cuồng”.

Cách sửa

Sau khi xảy ra lỗi, trước khi họ làm một động tác gì tôi đều yêu cầu họ trả lời câu hỏi này trước “Bây giờ chúng ta cần làm gì?”. Cụ thể, tôi muốn họ nêu ra phương pháp tìm ra chỗ sai. Tôi giúp họ xây dựng phương pháp đó bằng cách hỏi những câu như “Code chạy ngon đến đoạn nào?”. Sau khi tìm ra một manh mối gì đó, tôi lại hỏi “Thông tin này có ý nghĩa gì?”, “Có cần thay đổi phương pháp không?”. Tôi muốn họ ý thức được là mình cần đào sâu có mục tiêu đàng hoàng, chứ không phải là lần mò trong đám mây.

Vấn đề tâm lí

Đối với một chương trình, việc xảy ra lỗi là một minh chứng cho việc “đã gần hoàn thiện”. Tại sao? Vì nó thường chỉ cho ta sáng tỏ một điều mà trước đây ta không quan tâm, chỉ cho ta biết giả thiết mà ta đã tạo ra là sai, và chỉ cho ta hành động tiếp theo mà ta phải làm là sửa nó. Ta đã có định hướng. Tuy nhiên, đối với những progammer không giỏi đào sâu vấn đề thì thường có xu hướng bị “ngây người” vì họ luôn nghĩ rằng “Đáng ra phải chạy ngon rồi chứ nhỉ?”. Và sau đó họ lại nghĩ “Toi rồi, sai hết rồi”. Có thể nói rằng họ không bao giờ test những cái mà chắc sẽ thất bại, trong khi giá trị cao nhất của việc test là sau khi thất bại, nó sẽ cho ta một thông tin hữu ích nào đó.
Tôi bảo họ chạy thử thì họ thường nói “Lỗi là cái chắc” và không làm. Còn khi miễn cưỡng làm rồi chương trình không chạy thì họ bảo “Đấy, thấy chưa?”. Thực tế là lỗi ở môi trường chạy thật thì không được phép, nhưng ở môi trường test thì sẽ chẳng có ai phàn nàn gì cả, thậm chí còn có ích. Tôi nghĩ họ cần được tự mình trải nghiệm và lí giải những điều đó.

Thói quen tốt

Dựa vào những thói quen xấu đã nói ở trên, chúng ta có thể biết được đâu là thói quen tốt.
  • Đầu tư thời gian để đọc code và hiểu code.
  • Luôn có xu hướng dùng kiến thức để đơn giản hóa vấn đề trước mắt
  • Không sợ lỗi, luôn dùng test code để tìm kiếm thông tin, manh mối
  • Luôn hành động với mục đích rõ ràng để từng bước một giải quyết vấn đề
Ngoài ra, còn vài thói quen mà tôi nghĩ là tốt như :
  • Cải tiến công cụ và tự động các hóa quy trình
  • Boy scout policy
  • Học vài ngôn ngữ
  • Học kiến thức một cách tổng thể

Cải tiến công cụ và tự động hóa các quy trình

Thói quen tốt thường được hỗ trợ bởi công cụ tốt. Chẳng hạn mỗi khi học ngôn ngữ mới, tôi đều tìm kiếm code formatter của ngôn ngữ đó. Ngoài ra, tôi có coding style của mình - không muốn ấn quá nhiều phím, nên tôi chọn IDE có chức năng tự động nhập space cho tôi.

Boy scout policy

Có một tổ chức gọi là boy scout có phương châm là : nhặt hết rác trên các đoạn đường mình đi qua. Cũng như vậy, người lập trình cần có phương châm sửa hết lỗi của những code liên quan đến phần mình làm. Dĩ nhiên không đọc cẩn thận thì sẽ không sửa được, nhưng dù không sửa thì đọc code thôi cũng là có ích rồi. Làm như vậy thì tự nhiên sẽ ngày càng hiểu sâu về code hơn, và code tổng thể sẽ đẹp hơn.

Học vài ngôn ngữ

Nhiều khi vấn đề ở ngôn ngữ này ta có thể lí giải được trong khi học ngôn ngữ khác. Và nhiều khi, một khái niệm nghe có vẻ lạ, thực ra chỉ là đem từ ngôn ngữ khác vào. Thế nên, việc học nhiều ngôn ngữ sẽ giúp ta lập trình mau thuần thục hơn.

Học kiến thức một cách tổng thể

Kiến thức về background, về lịch sử ngôn ngữ, và vài ba bài tập ta đọc ở đâu đó sẽ khiến cho chất lượng code của ta tăng lên. Nếu mỗi ngày ta không gặm nhấm một ít kiến thức, không có cách hiểu về cú pháp, về sample của riêng ta thì kiến thức sẽ không còn là kiến thức mà chỉ dừng ở mức mẹo vặt vì ta không biết ứng dụng chúng sang những trường hợp khác. Cứ thế, ta sẽ chỉ mãi mãi chạy theo những mẹo vặt để giải quyết vấn đề mà thôi.

Lời kết

Năng lực tạo ra bởi tập hợp các thói quen nên việc cải thiện các thói quen sẽ khiến cho năng lực của chúng ta tăng lên. Đó là suy nghĩ của tôi. Tiếp theo là do những rào cản tâm lí nên bản thân việc cải thiện các thói quen cũng thường không mấy dễ dàng. Cải thiện xong rồi, cũng không phải ngay lập tức đạt đến trình độ siêu nhân ngay được.
Nhưng đừng bao giờ nghĩ rằng mình vô dụng. Điều ảnh hưởng nhất đến kết quả cuối cùng của bạn là việc bạn có nghĩ được rằng : con đường học những điều mới là con đường hạnh phúc, hay không.
Theo Viblo/Phan Hoang Minh
Bài viết được đăng trên blog Fsd14, các bạn có thể copy về blog của mình hoặc share bất kỳ đâu nhưng vui lòng ghi rõ nguồn về blog fsd14 như một sự tôn trọng công sức biên soạn và dịch bài của tác giả. 

Xin cảm ơn các bạn!

Thứ Năm, 2 tháng 6, 2016

[Java] Sự khác nhau giữa String và StringBuffer - StringBuilder

Bạn học Java lâu chưa? Bạn đã code những phần mềm nào với Java mà phải xử lý các chuỗi? Tôi chắn rằng bạn biết trong Java có một số lớp liên quan đến chuỗi (xâu) và xử lý chuỗi (VD: String, StringBuilder, StringBuffer, StringTokenizer), có khi nào bạn đặt câu hỏi rằng đã có String sao lại phải có thêm StringBuider? Hay bạn mặc nhiên dùng String mà không thèm đoái hoài gì đến StringBuffer và StringBuilder?! Việc bạn không dùng StringBuilder có thể do bạn chưa hiểu được vì sao lại có lớp này trong Java. Vậy chúng ta cùng tìm hiểu về sự khác biệt giữa các lớp này nhé.
Nói chung, khác biệt đáng kể nhất giữa String và StringBufferStringBuilder trong Java đó là đối tượng String là không thể thay đổi (immutable – không thể thay đổi giá trị) trong khi đó các đối tượng StringBuffer, StringBuilder lại có thể thay đổi (mutable – có thể thay đổi giá trị). Tức là giá trị (chuỗi) được lưu trữ trong đối tượng String không thể thay đổi được. Nếu vậy chắc rằng bạn sẽ đặt tiếp câu hỏi “như thế thì làm thế nào để tôi thay đổi giá trị của đối tượng này?” hay “tôi vẫn thay được giá trị của nó đấy thôi?”. Thực ra, việc thay đổi giá trị của đối tượng String được thực hiện bằng cách tạo một đối tượng mới có giá trị mà bạn muốn đổi sang. Cụ thể như sau:
Bạn khai báo một chuỗi:
1
2
3
4
String myName = "Tạp chí Lập trình";
//Tiếp đến để thêm vào cuối chuỗi này bạn làm như sau:
myName = myName + " - 2012";
Khi đó, nếu xem nội dung của chuỗi myName chắc chắn bạn nhận được là “Tạp chí Lập trình – 2012”, tuy nhiên để làm được điều này JVM sẽ phải tạo một đối tượng mới có chứa nội dung là “Tạp chí Lập trình – 2012” rồi gán nó cho tham chiếu myName thay vì sửa nội dung của đối tượng ban đầu. Đến đây chắc là bạn sẽ nghĩ tới tình huống mà ta cần thực hiện các hành động như thêm nội dung, bớt nội dung, thay thế, v.v. với một đối tượng String sẽ dẫn đến phát sinh hàng loạt các đối tượng String mới trong bộ nhớ.
Vậy vấn đề của chúng ta ở đây đó là hiệu suất (performance)? Quá rõ ràng rồi, nếu chúng ta thao tác nhiều với chuỗi (thay đổi nội dung)  thông qua các đối tượng String thì HIỆU SUẤTchính là vấn đề mà bạn gặp phải. Tức là càng tạo ra nhiều đối tượng tạm thì bộ thu dọn rác (Garbage Collection) càng phải làm việc nhiều hơn để dọn dẹp lại bộ nhớ.
Trong trường hợp này StringBuilder hoặc StringBuffer sẽ giúp bạn giải quyết vấn đề về hiệu suất. Tại sao vậy?
Như đã khẳng định ở phần đầu, hai lớp trên đều thuộc lọai mutable, chúng ta có thể thay đổi được các giá trị của chúng, hay nói cách khác các chuỗi chứa trong StringBuffer, StringBuilder có thể thay đổi được giá trị. Nhờ đặc tính này mà khi thay đổi các chuỗi trong những đối tượng này JVM không phải tạo các đối tượng mới (đối tượng tạm) và vấn đề về performance như kể trên với String được giải quyết triệt để ở StringBuffer, StringBuilder. Chúng ta cùng xem thao tác thay đổi nội dung một chuỗi với StringBuilder:
Tạo mới một chuỗi:
1
2
3
4
StringBuilder myName = new StringBuilder("Tạp chí Lập trình");
//Bổ sung vào cuối một chuỗi
myName.append(" - 2012");
Vậy là bạn đã đổi được nội dung của một chuỗi với StringBuilder mà không lo sợ về performance.
Nếu bạn muốn có bằng chứng thuyết phục hơn về sự hiệu quả của StringBuilder so với String, xin mời đọc thêm bài viết này: Dùng Profiler đo hiệu năng ứng dụng Java.
Cuối cùng, bạn có muốn biết sự khác biệt giữa StringBuilder và StringBuffer không? Hai lớp này được thiết kế với mục đích giống nhau, đó là thao tác hiệu quả với chuỗi, chúng cũng có các phương thức giống nhau để làm việc với chuỗi. Điểm khác biệt đáng lưu ý: StringBuffer thuộc loại synchronized do đó các phương thức của nó đều là “thread safe” (thích hợp với xử lý đa luồng – multi thread), trong khi StringBuilder thì ngược lại, không synchronized. Với đặc tính “thread safe”, các phương thức của StringBuffer sẽ chạy chậm hơn so với StringBuilder, vì vậy nếu không làm về Multi-thread bạn nên chọn StringBuilder thay vì chọn StringBuffer.
Lưu ý: StringBuilder mới được giới thiệu từ Java 1.5, do đó nếu bạn đang phát triển trên Java 1.4 bạn sẽ không sử dụng được lớp này.
Nguyễn Việt Khoa.

Bài mới

Bài đăng nổi bật