Đọc bài viết gốc: http://coding-geek.com/what-is-a-good-application/
Gần đây, tôi ứng tuyển vào một vị trí kỹ thuật ở công ty đang làm hiện tại. Một trong những câu hỏi mà người phỏng vấn hỏi tôi đó là “Thế nào là một ứng dụng tốt?”.
Hơi bất ngờ vì tôi chưa thực sự nghĩ về điều này trước đây. Vì thế tôi nghĩ đây sẽ là một bài tập tốt cho bản thân mình khi tổng hợp lại góc nhìn của mình về “Thế nào là một ứng dụng tốt?”. Thời điểm được phỏng vấn, tôi đã đưa ra một câu trả lời từ góc nhìn của một dân kỹ thuật. Và bây giờ, tôi lại nghĩ về câu hỏi đó một lần nữa, và đây là những suy nghĩ mới nhất của tôi cho câu hỏi này.
Một ứng dụng tốt là một ứng dụng đơn giản
Khi tôi thiết kế một ứng dụng, tôi luôn nghĩ đến nguyên tắc “càng đơn giản càng tốt”. Các developers chỉ nên tập trung chủ yếu vào yêu cầu của ứng dụng khi thiết kế thay vì:
- Thiết kế quá mức yêu cầu
- Tối ưu quá mức cần thiết
- Phát triển dư hơn mức yêu cầu
- Lựa chọn kiến trúc “mềm dẻo” một cách không cần thiết
Một giải pháp đơn giản nhưng có thể được làm một cách thuần thục vẫn tốt hơn những thiết kế được tính quá xa hơn nhu cầu thực tế. Và hầu hết những thiết kế “dư” như vậy sẽ không được dùng đến 100%, có khi còn chưa đến 50% bởi vì:
- Nhu cầu sẽ thay đổi
- Mức trông đợi qúa cao hoặc quá xa (ai mà biết được chuyện sẽ diễn ra 5 năm sau, đặc biệt là start-up?)
- Kinh phí của dự án đã giảm rất nhiều
- Chiến lược của công ty đã thay đổi.
Đó là lý do mà tôi sẽ lựa chọn thứ kiến trúc đơn giản trước nhất, mặc dù có thể là sau đó nó không còn phù hợp nữa với các loại yêu cầu tương lai, khi đó tôi sẽ thay đổi kiến trúc khác phù hợp hơn.
Ví dụ, tại sao bạn lại muốn dùng một Big Data cluster cho một ứng dụng mới khởi đầu và vì vậy mà vẫn cần rất ít dữ liệu? Là một nhà phát triển ứng dụng lớn (Big Data), hầu hết các ứng viên tôi đã phỏng vấn đã làm việc với các dự án Big Data trong đó họ chỉ xử lý vài triệu dòng dữ liệu. Khi tôi hỏi tại sao họ lại lựa chọn sử dụng các công nghệ dữ liệu lớn, một vài trong số họ bảo rằng “bởi vì có rất nhiều dữ liệu cần phải xử lý” trong khi một số khác thì lại bảo “bởi vì trong tương lai ứng dụng sẽ cần phải xử lý rất nhiều dữ liệu”. Một vài năm trước, tôi có cơ hội làm việc tại một ngân hàng lớn nơi chúng tôi phải xử lý hàng trăm triệu dòng dữ liệu “chỉ với” cơ sở dữ liệu quan hệ. Giữa một bên là một hệ thống cơ sở dữ liệu quan hệ được xây dựng một cách hoàn thiện với một bên là một hệ thống Hadoop cluster đầy lỗi, hẳn là bạn sẽ dễ thấy lý do vì sao chúng tôi lại chọn phương án đầu tiên.
Với tôi, phức tạp hơn nghĩa là:
- Đắt hơn (vì nó sẽ khó hiểu nên sẽ tốn nhiều thời gian để bảo trì)
- Bugs nhiều hơn (bởi vì nó hiểu, khó test)
- Khó theo dõi và bảo trì hơn (cũng lại bởi vì nó khó hiểu)
Dĩ nhiên, nói đi thì cũng phải nói lại là nếu nhu cầu tương lai đã được xác định một cách chính xác 100% thì việc thiết kế một kiến trúc phức tạp là hoàn toàn cần thiết.
Một ứng dụng tốt là một ứng dụng có những dòng code dễ đọc
Một ứng dụng tốt là một ứng dụng có code trong sáng và dễ đọc, bởi vì nó sẽ được viết và đọc bởi rất nhiều lập trình viên khác nhau nên điều quan trọng ở đây là họ không phải dành quá nhiều thời gian để đọc những dòng code cũ.
Trong lập trình hướng đối tượng, điều này có nghĩa là các class nên có các hàm ngắn và ít trách nhiệm hơn. Tôi là một fan cứng của nguyên tắc “ít kết dính” (loose coupling) vì nó sẽ giúp cho các thành phần độc lập nhau và các lập trình viên sẽ dễ dàng chia sẻ công việc của mình nhiều hơn.
Một điều quan trọng khác đó là code cần phải mạch lạc và nhất quán. Trong những dự án lớn, hàng trăm lập trình viên sẽ phải chỉnh sửa mã nguồn của mình. Nếu ai cũng code theo cách mà họ muốn thì kho mã nguồn sẽ trở thành một cơn ác mộng cho mọi người bởi vì có quá nhiều phong cách khác nhau. Giống như bạn đang đọc một quyển sách tiếng Anh, nhưng thỉnh thoảng nó lại chen lẫn tiếng Pháp, Đức, Nga, … vậy. Các team phải triển cần phải thống nhất một phong cách chung. Tôi đặc biệt thích việc bản thân code phải rõ ràng đến mức chúng ta có thể hiểu nó mà chẳng cần comments. Điều này có thể đạt được thông qua cách đặt tên hàm, lớp, biến, … Dĩ nhiên, việc viết documentation vẫn cần thiết.
Một trong những documentation tốt nhất mà tôi đã từng đọc là thuộc về Spring Framework (https://projects.spring.io/spring-framework/): nó có hàng triệu dòng mã nhưng những phần tôi đã đọc qua như (Spring Core, Spring Batch và Spring Data) rất mạch lạc và có phong cách rất nhất quán. Một ví dụ ngược lại đó là thư viện C nổi tiếng: https://www.ffmpeg.org/libavcodec.html. Thư viện này được sử dụng bởi hầu hết các ứng dụng có dùng đến việc nén và giải nén dữ liệu audio, video stream. Bản thân thư viện này rất tốt nhưng nếu bạn là người mới bắt đầu và cố gắng tìm hiểu nó thì … chúc may mắn. Tôi đã dành khoảng 15 giờ để hiểu một vài phần trong đó trong lúc đang xây dựng một server audio streaming của mình trong thời gian rảnh nhưng cuối cùng đành phải bỏ cuộc: không có đủ tư liệu và phong cách code quá khó hiểu.
Một ứng dụng tốt là một ứng dụng có những dòng code “test” được
Khi tôi bắt đầu đi làm, một đồng nghiệp đã dạy tôi về unit test và integration test, lúc đó tôi đã nghĩ “ái chà, tại sao tôi lại cần tests nhỉ, hầu hết các đoạn mã của tôi đều chạy đúng ngay lần đầu tiên”. Thật lòng mà nói, một phần nào đó trong tôi vẫn còn tin vào điều đó … nhưng nhìn chung là suy nghĩ của tôi đã thay đổi nhiều.
Bây giờ khi tôi code, tôi đều tự hỏi: “những đoạn mã này có test được một cách riêng rẽ hay không?”. Đây là một bài tập tốt bởi vì nó bắt buộc tôi phải luôn luôn suy nghĩ làm sao cho chương trình của mình ít “kết dính” nhất có thể. Hơn nữa, có integration tests và acceptance tests giúp bạn có thể refactor code của mình cũng như chỉnh sửa code của người khác mà không sợ làm thay đổi behavior của ứng dụng.
Nhưng mà có tests liệu đã đủ chưa? Giả sử bạn có unit tests, integration tests, acceptance tests với code coverage tốt, hơn nữa việc thực thi các test cases đã được tự động hoá thông qua Jenkins thì câu hỏi tiếp theo là các test cases được viết có hợp lí hay không? Có hai ví dụ mà tôi muốn giới thiệu:
- Trong một dự án trước đây, khi mọi người trong team đã viết các tests “giả” chỉ vì muốn nâng cao tỉ lệ code coverage sao cho dự án của họ có vẻ “tốt” với các sếp. Họ không có đủ thời gian để viết các test cases thật trong khi đó yêu cầu về code coverage cao lại là yêu cầu bắt buộc.
- Trong một dự án khác, tôi kiểm tra code của một anh bạn khác thì phát hiện ra là tests của anh ấy được viết ra nhưng anh ấy không test các đoạn code của mình, mà lại đi test logic của các đối tượng “mock” được dùng trong đó….
Rõ ràng, trong cả hai tình huống đó thì code coverage đều cao nhưng mà ứng dụng thì lại không được tests cẩn thận.
Để có được những bộ tests đàng hoàng, team phát triển cần phải ý thức được yêu cầu của khách hàng cũng như lý do vì sao phải viết tests. Dĩ nhiên, họ cũng cần có đủ thời gian nữa.
Một ứng dụng có thể được “theo dõi”
Ok, bạn đã có một ứng dụng tốt, nhưng mà liệu ứng dụng của bạn sẽ vẫn hoạt động tốt sau 3 tháng nữa không? Bạn có chắc chắn về điều đó không?
Để trả lời câu hỏi trên thì bạn phải theo dõi ứng dụng của mình. Đầu tiên là phải có một hệ thống ghi log phù hợp với nhiều cấp độ log được ghi nhận.
Hơn nữa, bạn cần phải log lại hết những thông tin liên quan khi một vấn đề phát sinh. Tôi đã từng gặp nhiều tình huống khi ứng dụng bị crashed nhưng log chỉ ghi đúng một chữ “ERROR”. Tuyệt, tôi biết rồi, nhưng điều gì gây ra lỗi này? Tôi phải mất rất nhiều thời gian để tìm ra nguyên nhân tại sao gây ra lỗi. (và nói thật, tôi muốn …. tên nào đã viết ra những dòng code củ chuối này).
Một khía cạnh khác đó là bạn cần phải có những chỉ báo dùng để đo đạc sức khoẻ của hệ thống. Ví dụ như độ sử dụng memory, số lượng process, số lượng khách hàng/hợp đồng được tạo ra mỗi ngày … Những chỉ báo này sẽ giúp cho bạn phát hiện ra những dấu hiện bất thường trong hệ thống của mình. Có lẽ là bộ nhớ hoặc CPU đã bị lạm dụng bởi vì bạn đang bị hacked chẳng hạn. Hoặc là có vẻ có quá nhiều khách hàng đã đăng ký mới, nhưng thực ra là do một con bug làm cho số lượng đăng ký bị trùng lặp.
Tất cả những logs và các chỉ báo này nên được ghi nhận ở đâu đó. Và nhấn mạnh một lần nữa là tôi thích những cách làm đơn giản. Chí vì vậy, nếu hệ thống của bạn còn đơn giản, chỉ cần ghi thông tin này ra file là được. Tuy nhiên nếu ứng dụng của bạn có nhiều server thì việc tập trung logs vào một chỗ là cần thiết (logstash/kibana/elastic search). Tôi đã từng làm việc cho một dự án có hơn 30 server trên production mà không có một nơi chung để tổng hợp logs, khiến cho việc đọc log để kiểm tra tình trạng của mỗi server là một điều cực tốn thời gian.
Một ứng dụng tốt là một ứng dụng mang lại sự thoả mãn của khách hàng
Tôi đã từng nhìn thấy nhiều lập trình viên đã tập trung quá nhiều về khía cạnh công nghệ của sản phẩm. Nhưng mà họ quên rằng một ứng dụng được tạo ra không phải để thoả mãn công nghệ, nó được tạo ra để đáp ứng nhu cầu của khách hàng.
Tôi rất yêu thích công nghệ đặc biệt là các loại công nghệ mới, nhưng thỉnh thoảng tôi lại chọn một công nghệ cũ mà tôi không thích nhưng lại phù hợp với nhu cầu của khách hàng.
Hơn nữa, bỏ ra phần lớn thời gian để chỉ để hiểu một vài khía cạnh ngóc ngách của một ngôn ngữ khá là vô ích, trong khi thời gian đó nên được dùng để hiểu về khía cạnh business của sản phẩm. Tôi chẳng hiểu nổi các bạn lập trình viên không thèm quan tâm đến business domain mà cứ quan tâm đến một vài loại công nghệ nhất định. Họ có làm cho một hệ thống của ngân hàng, của một nhà máy hạt nhân hay là một web crawler thì đối với họ chẳng có gì khác biệt. Họ chỉ hiện thực những tính năng được yêu cầu mà không quan tâm đến việc hiểu tại sao những yêu cầu đó lại tồn tại.
Đối với tôi, đây là điều rất quan trọng. Nếu code của bạn rất “xấu”, nhưng bù lại những thứ bạn mang lại làm cho khách hàng vui vẻ và thoả mãn, đấy mới là điều quan trọng. Và để làm được điều đó thì bạn phải hiểu được business domain của khách hàng.
Đối với tôi, sự thoã mãn của khách hàng nên được xem là yêu cầu quan trọng nhất.
Một vài lời chốt
Bạn vừa đọc hết những suy nghĩ của tôi về việc “thế nào là một ứng dụng tốt” rồi đấy. Bạn có thể nhận ra là nó khá giống với nguyên tắc. Tôi đã không nói nhiều về những thứ như hình thức hoặc tính khả dụng của hệ thống bởi vì có quá nhiều dạng ứng dụng khác nhau.
Còn ý kiến của bạn là như thế nào?