Copy Constructor hay hàm khởi tạo sao chép, có mục đích là sao chép một đối tượng sang một đối tượng khác. Điều đó có nghĩa là một phương thức khởi tạo sao chép sẽ sao chép một đối tượng và các giá trị của nó, vào một đối tượng khác, với điều kiện là cả hai đối tượng đều thuộc cùng một lớp.
Trái ngược với các hàm khởi tạo, khởi tạo các đối tượng và chỉ định không gian cho chúng, các hàm hủy cũng là các phương thức đặc biệt. Nhưng hàm hủy giải phóng tài nguyên và bộ nhớ được sử dụng bởi một đối tượng. Bộ hủy được tự động gọi khi một đối tượng đang bị hủy.
Không. Lớp và cấu trúc không giống nhau. Mặc dù chúng có vẻ giống nhau, nhưng chúng có những điểm khác biệt khiến chúng trở nên khác biệt.
Ví dụ, cấu trúc được lưu trong bộ nhớ stack, trong khi lớp được lưu trong bộ nhớ heap. Ngoài ra, tính trừu tượng hóa dữ liệu không thể có trong cấu trúc, nhưng với lớp, tính trừu tượng được sử dụng chủ yếu.
Kế thừa (inheritance) là một trong những đặc điểm chính của lập trình hướng đối tượng, theo đó một thực thể kế thừa một số đặc điểm và hành vi của một thực thể khác và biến chúng thành của riêng mình. Kế thừa giúp cải thiện và tạo điều kiện sử dụng lại code.
Ta sẽ giải thích bằng một ví dụ dễ hiểu. Ta có ba phương tiện khác nhau – xe hơi, xe tải hoặc xe buýt. Ba loại này hoàn toàn khác nhau với những đặc điểm và hành vi cụ thể của riêng chúng. Nhưng ở cả ba, bạn sẽ tìm thấy một số yếu tố chung, như vô lăng, chân ga, ly hợp, phanh, v.v.
Mặc dù những yếu tố này được sử dụng trên các loại xe khác nhau, chúng vẫn có những đặc điểm chung của tất cả các loại xe. Điều này đạt được nhờ vào sự kế thừa. Xe hơi, xe tải và xe buýt đều được thừa hưởng các tính năng như vô lăng, chân ga, ly hợp, phanh, v.v. và sử dụng chúng như của riêng mình. Do đó, chúng không phải tạo các thành phần này từ đầu, nhờ thế tạo điều kiện sử dụng lại code.
Có. Với nhiều quyền lực hơn đi kèm với nhiều phức tạp hơn. Kế thừa là một tính năng rất mạnh trong OOP, nhưng nó cũng có một số hạn chế. Kế thừa cần thêm thời gian để xử lý, vì nó cần điều hướng qua nhiều lớp để triển khai.
Ngoài ra, các lớp liên quan đến Kế thừa – lớp cơ sở và lớp con, được kết hợp rất chặt chẽ với nhau.
Vì vậy, nếu một người cần thực hiện một số thay đổi, họ có thể cần thực hiện các thay đổi lồng nhau trong cả hai lớp. Kế thừa cũng có thể phức tạp để thực hiện. Vì nếu không được triển khai chính xác, có thể dẫn đến lỗi không mong muốn hoặc kết quả đầu ra không chính xác.
Interface đề cập đến một loại lớp đặc biệt, chứa các phương thức, nhưng không phải là định nghĩa của chúng. Chỉ cho phép khai báo các phương thức bên trong một interface. Bạn không thể tạo các đối tượng bằng cách dùng interface. Thay vào đó, bạn cần triển khai interface đó và xác định các phương pháp để triển khai chúng.
Còn gọi là đa hình thời gian biên dịch. Đa hình tĩnh là tính năng mà một đối tượng được liên kết với hàm hoặc toán tử tương ứng dựa trên các giá trị trong thời gian biên dịch. Đa hình thời gian tĩnh hoặc thời gian biên dịch có thể đạt được thông qua nạp chồng phương thức hoặc nạp chồng toán tử.
Còn gọi là đa hình thời gian chạy, theo đó việc triển khai thực sự của hàm được quyết định trong thời gian chạy hoặc thực thi. Tính đa hình động hoặc thời gian chạy có thể đạt được với sự trợ giúp của ghi đè phương thức.
32. Sự khác biệt giữa nạp chồng (Overloading) và ghi đè (Overriding)?
Nạp chồng (Overloading) là một tính năng đa hình thời gian biên dịch, trong đó một thực thể có nhiều triển khai có cùng tên. Ví dụ: nạp chồng phương thức và nạp chồng toán tử.
Trong khi ghi đè (Overriding) là một tính năng đa hình thời gian chạy trong đó một thực thể có cùng tên, nhưng việc triển khai của nó thay đổi trong quá trình thực thi. Ví dụ: ghi đè phương thức.
33. Quá trình trừu tượng hóa dữ liệu được thực hiện như thế nào?
Sự trừu tượng hóa dữ liệu được thực hiện với sự trợ giúp của các phương thức trừu tượng hoặc các lớp trừu tượng.
Một lớp trừu tượng là một lớp đặc biệt chứa các phương thức trừu tượng. Đặc trưng của lớp trừu tượng là các phương thức trừu tượng bên trong nó không được thực thi mà chỉ được khai báo. Do đó, khi một lớp con kế thừa lớp trừu tượng và cần sử dụng các phương thức trừu tượng của nó, chúng cần phải định nghĩa và triển khai chúng.
35. Một lớp trừu tượng khác với một interface như thế nào?
Interface và lớp trừu tượng đều là những kiểu lớp đặc biệt chỉ chứa phần khai báo các phương thức chứ không chứa phần thực thi của chúng. Nhưng interface hoàn toàn khác với một lớp trừu tượng. Sự khác biệt chính của cả hai là, khi một interface được triển khai, lớp con phải xác định tất cả các phương thức của nó và cung cấp cách triển khai của nó. Trong khi đó khi một lớp trừu tượng được kế thừa, lớp con không cần cung cấp định nghĩa về phương thức trừu tượng của nó, cho đến khi lớp con sử dụng nó.
Ngoài ra, một lớp trừu tượng có thể chứa các phương thức trừu tượng cũng như các phương thức không trừu tượng.
36. Các chỉ định truy cập là gì và ý nghĩa của chúng?
Các chỉ định truy cập là một loại từ khóa đặc biệt, được sử dụng để kiểm soát hoặc chỉ định khả năng truy cập của các thực thể như lớp, phương thức, v.v. Một số từ chỉ định truy cập hoặc công cụ sửa đổi truy cập bao gồm “private”, “public”, v.v… Các chỉ định truy cập này cũng đóng một vai trò rất quan trọng trong việc đạt được tính đóng gói – một trong những tính năng chính của OOP.
Một ngoại lệ (Exception) có thể được coi là một sự kiện đặc biệt, xuất hiện trong quá trình thực hiện một chương trình trong thời gian chạy, khiến việc thực thi bị tạm dừng. Lý do cho ngoại lệ chủ yếu là do một vị trí trong chương trình, nơi người dùng muốn làm điều gì đó mà chương trình không được chỉ định, chẳng hạn như đầu vào không mong muốn.
Không ai muốn phần mềm của mình bị lỗi hoặc gặp sự cố. Các trường hợp ngoại lệ là lý do chính dẫn đến lỗi phần mềm. Các ngoại lệ có thể được xử lý trước trong chương trình và ngăn việc thực thi dừng lại. Đây được gọi là xử lý ngoại lệ.
Vì vậy, xử lý ngoại lệ là cơ chế xác định các trạng thái không mong muốn mà chương trình có thể có và xác định các kết quả mong muốn của các trạng thái đó.
Try-catch là phương pháp phổ biến nhất được sử dụng để xử lý các ngoại lệ trong chương trình
Lập trình hướng đối tượng xoay quanh các thực thể như các đối tượng. Mỗi đối tượng sử dụng bộ nhớ và có thể có nhiều đối tượng của một lớp. Vì vậy, nếu các đối tượng này và dữ liệu của chúng không được xử lý đúng cách, thì nó có thể dẫn đến một số lỗi liên quan đến bộ nhớ và hệ thống có thể bị lỗi.
Bộ dọn rác (garbage collection) đề cập đến cơ chế xử lý bộ nhớ trong chương trình. Thông qua việc thu gom rác, bộ nhớ không mong muốn sẽ được giải phóng bằng cách loại bỏ các đối tượng không còn cần thiết.
40. Có thể chạy ứng dụng Java mà không triển khai các khái niệm OOP?
Không. Các ứng dụng Java dựa trên các mô hình lập trình hướng đối tượng hoặc khái niệm OOP, do đó chúng không thể được triển khai nếu không có nó.
Tuy nhiên, mặt khác, C++ có thể được thực hiện mà không cần OOP, vì nó cũng hỗ trợ mô hình lập trình cấu trúc giống C.
Bài viết được sự cho phép của tác giả Nguyễn Thành Nam
I. Lập trình hướng đối tượng là gì?
OOP (Object-Oriented Programming) là một mô hình lập trình để giải quyết vấn đề xoay quanh khái niệm “Đối tượng“. Đối tượng có thể được xem như là các thể hiện của thực thế ở thế giới thực như là lớp, chúng chứa một số đặc điểm và hành vi được chỉ định trong lớp mẫu.
Ở các ngôn ngữ đơn giản, một lớp có thể là một bản mẫu, dựa trên nó để tạo ra các đối tượng. Thế nên Đối tượng có thể được xem như là một thể hiện của một lớp và thỉnh thoảng nó có thể được gọi là “thực thể”. Các thuật ngữ đặc trưng ám chỉ đối tượng đó là gì, và hành vi chỉ đối tượng đó như thế nào.
Ví dụ, ta tạo mô hình OOP với một chiếc xe:
Lớp: mẫu xe cụ thể như Audi A4, BMW I8, Maruti Suzuki Vitara Brezza,…
Đối tượng: Bất kỳ chiếc xe cụ thể nào, như xe của bạn
Đặc trưng: Màu sắc của xe? Số khung xe của bạn là gì?
Hành vi: Xe chạy như thế nào? Làm thế nào để chuyển số?
Đặc trưng có thể hiểu là dữ liệu, thuộc tính còn hành vi là chức năng, phương thức trong ngôn ngữ lập trình.
Khái niệm “đối tượng” cho phép mô hình OOP dễ dàng truy cập, sử dụng và sửa đổi dữ liệu thực thể và phương thức, tương tác với các đối tượng khác và xác định các phương thức trong thời gian chạy (trong quá trình thực thi chương trình). Điều này mang lại ý nghĩa cho mô hình OOP và làm cho nó trở nên đa dạng trong việc triển khai.
Trên thực tế, mô hình OOP rất phổ biến, đến nỗi nhiều ngôn ngữ lập trình được sử dụng rộng rãi nhất đều hỗ trợ và sử dụng mô hình Lập trình hướng đối tượng hoặc OOP này, chẳng hạn như Java, C++, Python, C#, v.v.
II. Câu hỏi phỏng vấn về Lập trình hướng đối tượng (OOP)
1. Thuật ngữ OOP có nghĩa là gì?
OOP đề cập đến Lập trình hướng đối tượng. Đó là mô hình lập trình được xác định bằng cách sử dụng các đối tượng. Các đối tượng có thể được coi là các thể hiện trong thế giới thực của các thực thể như lớp, có một số đặc điểm và hành vi
2. Tại sao cần OOP?
Có nhiều lý do khiến OOP hầu hết được ưa thích, nhưng quan trọng nhất trong số đó là:
OOP giúp người dùng hiểu phần mềm một cách dễ dàng, mặc dù họ không biết cách triển khai thực tế.
Với OOP, khả năng đọc, hiểu và khả năng bảo trì của code tăng lên gấp nhiều lần.
Ngay cả những phần mềm rất lớn cũng có thể được viết và quản lý dễ dàng bằng OOP.
3. Một số ngôn ngữ lập trình hướng đối tượng
Các ngôn ngữ lập trình sử dụng và tuân theo mô hình lập trình hướng đối tượng hoặc OOP, được gọi là ngôn ngữ lập trình hướng đối tượng. Một số ngôn ngữ lập trình hướng đối tượng chính bao gồm:
Java
C++
Javascript
Python
PHP
4. Một số mô hình lập trình khác ngoài OOP là gì?
Mô hình lập trình đề cập đến phương pháp phân loại ngôn ngữ lập trình dựa trên các tính năng của chúng. Chủ yếu có hai loại Mô hình lập trình:
Mô hình lập trình mệnh lệnh
Mô hình lập trình khai báo
Bây giờ, các mô hình này có thể được phân loại thêm dựa trên:
Mô hình lập trình mệnh lệnh: Lập trình mệnh lệnh tập trung vào CÁCH thực thi logic chương trình và định nghĩa luồng điều khiển là các câu lệnh thay đổi trạng thái chương trình. Điều này có thể được phân loại thêm là:
Mô hình lập trình hướng thủ tục: Lập trình theo thủ tục xác định các bước mà một chương trình phải thực hiện để đạt được trạng thái mong muốn, thường được đọc theo thứ tự từ trên xuống dưới.
Lập trình hướng đối tượng hoặc OOP: Lập trình hướng đối tượng (OOP) tổ chức các chương trình dưới dạng các đối tượng, chứa một số thuộc tính và có một số hành vi.
Lập trình song song: Mô hình lập trình song song chia một nhiệm vụ thành các nhiệm vụ con và tập trung vào việc thực hiện chúng đồng thời cùng một lúc.
Mô hình lập trình khai báo: Lập trình khai báo tập trung vào CÁI GÌ để thực thi và định nghĩa logic chương trình, nhưng không phải là một luồng điều khiển chi tiết. Mô hình khai báo có thể được phân loại thêm thành:
Mô hình lập trình logic: Mô hình lập trình logic dựa trên logic hình thức, đề cập đến một tập hợp các câu diễn đạt các dữ kiện và quy tắc về cách giải quyết một vấn đề
Mô hình lập trình chức năng: Mô hình lập trình chức năng là mô hình lập trình trong đó các chương trình được xây dựng bằng cách áp dụng và soạn thảo các hàm.
Mô hình lập trình cơ sở dữ liệu: Mô hình lập trình cơ sở dữ liệu được sử dụng để quản lý dữ liệu và thông tin có cấu trúc như trường, bản ghi và file.
5. Lập trình có cấu trúc nghĩa là gì?
Lập trình có cấu trúc đề cập đến phương pháp lập trình bao gồm một luồng điều khiển hoàn toàn có cấu trúc. Ở đây cấu trúc đề cập đến một khối, chứa một tập hợp các quy tắc và có một luồng điều khiển cuối cùng, chẳng hạn như (if/then/else), (while và for), cấu trúc khối và chương trình con.
Gần như tất cả các mô hình lập trình đều bao gồm Lập trình có cấu trúc, bao gồm cả mô hình OOP.
6. Các tính năng chính của OOP?
OOP hoặc Lập trình hướng đối tượng chủ yếu bao gồm bốn tính năng dưới đây và đảm bảo rằng bạn không bỏ lỡ bất kỳ tính năng nào sau đây:
Tính kế thừa
Tính đóng gói
Tính đa hình
Tính trừu tượng
7. Lợi ích khi sử dụng OOP?
OOP rất hữu ích trong việc giải quyết các vấn đề ở mức độ rất phức tạp.
Các chương trình có độ phức tạp cao có thể được tạo, xử lý và bảo trì dễ dàng bằng cách sử dụng lập trình hướng đối tượng.
OOP thúc đẩy việc tái sử dụng code, do đó giảm bớt sự dư thừa.
OOP cũng giúp ẩn những chi tiết không cần thiết với sự trợ giúp của tính trừu tượng .
OOP dựa trên cách tiếp cận từ dưới lên, không giống như mô hình lập trình cấu trúc, sử dụng cách tiếp cận từ trên xuống.
Tính đa hình cung cấp rất nhiều tính linh hoạt trong các OOP.
8. Tại sao OOP lại phổ biến như vậy?
Mô hình lập trình OOP được coi là một phong cách lập trình tốt hơn. Nó không chỉ giúp dễ dàng viết một đoạn code phức tạp mà còn cho phép người dùng xử lý và duy trì chúng một cách dễ dàng. Không chỉ vậy, các tính năng chính của OOP – Trừu tượng hóa, Đóng gói, Kế thừa và Đa hình, giúp lập trình viên dễ dàng giải quyết các tình huống phức tạp. Do đó, OOP rất phổ biến.
9. Lớp (class) là gì?
Một lớp (class) có thể được hiểu là một khuôn mẫu hoặc một bản thiết kế, chứa một số giá trị, được gọi là thuộc tính và một số bộ quy tắc, được gọi là hành vi hoặc chức năng. Vì vậy, khi một đối tượng được tạo, nó sẽ tự động lấy thuộc tính và phương thức đã được định nghĩa trong lớp.
Do đó, về cơ bản lớp là một khuôn mẫu hoặc bản thiết kế cho các đối tượng. Ngoài ra, người ta có thể tạo bao nhiêu đối tượng tùy thích dựa trên một lớp.
Ví dụ: đầu tiên, mẫu của một chiếc ô tô được tạo. Sau đó, nhiều chiếc xe được tạo ra dựa trên mẫu đó.
10. Đối tượng (object) là gì?
Một đối tượng (object) đề cập đến thực thể của lớp, bao gồm toàn bộ các thuộc tính và phương thức được xác định trong lớp mẫu. Trong thực tế, một đối tượng là một thực thể thực tương tác với người dùng trong khi lớp là bản thiết kế cho đối tượng đó. Vì vậy, các đối tượng tiêu thụ không gian và có một số hành vi đặc trưng.
Người ta có thể hình dung đóng gói là phương pháp đưa mọi thứ cần thiết để thực hiện công việc vào bên trong một viên thuốc và trình bày viên thuốc đó cho người dùng. Điều đó có nghĩa là bằng đóng gói, tất cả các dữ liệu và phương thức cần thiết được liên kết với nhau và tất cả các chi tiết không cần thiết được ẩn đối với người dùng bình thường. Vì vậy, đóng gói là quá trình liên kết các thuộc tính và các phương thức của một chương trình với nhau để thực hiện một công việc cụ thể, mà không để lộ các chi tiết không cần thiết.
Đóng gói có thể được xác định theo hai cách:
Data hiding: đóng gói là quá trình ẩn thông tin không mong muốn, hạn chế các truy cập đến thuộc tính của đối tượng.
Data binding: là quá trình liên kết các thuộc tính và các phương thức với nhau như một tổng thể.
12. Tính đa hình (polymorphism) là gì?
Đa hình đề cập đến một thứ có thể có nhiều hình dạng.
Trong OOP, đa hình đề cập đến quá trình mà một số code, dữ liệu, phương thức hoặc đối tượng hoạt động khác nhau trong các trường hợp hoặc ngữ cảnh khác nhau. Đa hình thời gian biên dịch và đa hình thời gian chạy là hai loại đa hình trong các ngôn ngữ OOP.
13. Sự khác nhau giữa đa hình thời gian biên dịch và đa hình thời gian chạy?
Compile Time Polymorphism: còn gọi là đa hình tĩnh, đề cập đến kiểu đa hình xảy khi khi biên dịch. Có nghĩa là trình biên dịch quyết định hình dạng hoặc giá trị nào được thực thể sử dụng.
// In this program, we will see how multiple functions are created with the same name,// but the compiler decides which function to call easily at the compile time itself.classCompileTimePolymorphism{// 1st method with name addpublicintadd(int x,int y){return x+y;}// 2nd method with name addpublicintadd(int x,int y,int z){return x+y+z;}// 3rd method with name addpublicintadd(double x,int y){return(int)x+y;}// 4th method with name addpublicintadd(int x,double y){return x+(int)y;}}classTest{publicstaticvoidmain(String[] args){CompileTimePolymorphism demo=newCompileTimePolymorphism();// In the below statement, the Compiler looks at the argument types and decides to call method 1System.out.println(demo.add(2,3));// Similarly, in the below statement, the compiler calls method 2System.out.println(demo.add(2,3,4));// Similarly, in the below statement, the compiler calls method 4System.out.println(demo.add(2,3.4));// Similarly, in the below statement, the compiler calls method 3System.out.println(demo.add(2.5,3));}}
Trong ví dụ trên, có bốn phiên bản của các phương thức add(). Phương thức đầu tiên nhận hai tham số trong khi phương thức thứ hai nhận ba tham số. Đối với phương pháp thứ ba và thứ tư, có sự thay đổi thứ tự của các tham số. Trình biên dịch xem xét dấu hiệu của phương thức và quyết định phương thức nào sẽ gọi cho một lệnh gọi cụ thể tại thời điểm biên dịch.
Runtime Polymorphism: còn gọi là đa hình động, đề cập đến kiểu đa hình xảy khi khi đang chạy. Điều đó có nghĩa là nó không thể được quyết định bởi trình biên dịch. Do đó, hình dạng hoặc giá trị nào phải được thực hiện tùy thuộc vào quá trình thực thi.
classAnyVehicle{publicvoidmove(){System.out.println(“Any vehicle should move!!”);}}classBikeextendsAnyVehicle{publicvoidmove(){System.out.println(“Bike can move too!!”);}}classTest{publicstaticvoidmain(String[] args){AnyVehicle vehicle =newBike();// In the above statement, as you can see, the object vehicle is of type AnyVehicle// But the output of the below statement will be “Bike can move too!!”,// because the actual implementation of object ‘vehicle’ is decided during runtime vehicle.move(); vehicle =newAnyVehicle();// Now, the output of the below statement will be “Any vehicle should move!!”, vehicle.move();}}
14. C++ hỗ trợ đa hình thế nào?
C++ là ngôn ngữ lập trình hướng đối tượng và hỗ trợ đa hình rất tốt:
Compile Time Polymorphism: C++ hỗ trợ tính đa hình thời gian biên dịch với sự trợ giúp của các tính năng như mẫu, nạp chồng hàm và các tham số mặc định.
Runtime Polymorphism: C++ hỗ trợ tính đa hình Runtime với sự trợ giúp của các tính năng như hàm ảo. Các hàm ảo có hình dạng của các hàm dựa trên loại đối tượng trong tham chiếu và được giải quyết trong thời gian chạy.
Thuật ngữ “kế thừa” có nghĩa là “nhận được một số phẩm chất hoặc hành vi từ cha mẹ cho con cái”. Trong lập trình hướng đối tượng, kế thừa là cơ chế mà một đối tượng hoặc lớp (được gọi là con) được tạo ra bằng cách sử dụng định nghĩa của một đối tượng hoặc lớp khác (được gọi là cha). Kế thừa không chỉ giúp giữ cho việc triển khai đơn giản hơn mà còn giúp tạo điều kiện sử dụng lại code.
16. Trừu tượng là gì?
Nếu bạn là người dùng và bạn có một vấn đề, bạn không muốn biết các thành phần của phần mềm hoạt động như thế nào hoặc nó được tạo ra như thế nào. Bạn chỉ muốn biết cách phần mềm giải quyết vấn đề của bạn. Trừu tượng hóa là phương pháp che giấu những chi tiết không cần thiết khỏi những cái cần thiết. Đây là một trong những tính năng chính của OOP.
Ví dụ như một chiếc xe. Bạn chỉ cần biết cách chạy chiếc xe đó, còn nó gồm những bộ phận, động cơ có mấy xi lanh thì không cần quan tâm. Điều này được gọi là trừu tượng.
17. Một lớp chiếm bao nhiêu bộ nhớ?
Lớp không chứa bất kỳ bộ nhớ nào. Nó là một bản thiết kế để cái đối tượng dựa vào nó mà tạo. Chỉ khi đối tượng được tạo, nó mới thực sự tạo các thuộc tính và phương thức và mới tốn bộ nhớ.
18. Có phải lúc nào cũng cần tạo các đối tượng từ lớp không?
Không. Một đối tượng chỉ được tạo nếu lớp cơ sở có các phương thức động. Nhưng nếu lớp có các phương thức tĩnh, thì các đối tượng không cần phải được tạo. Bạn có thể gọi phương thức lớp trực tiếp trong trường hợp này, bằng cách sử dụng tên lớp.
19. Constructor là gì?
Constructor hay hàm khởi tạo là một phương thức đặc biệt có tên giống như tên lớp. Hàm khởi tạo dùng cho mục đích đặc biệt là khởi tạo đối tượng.
Ví dụ: ta có lớp tên là MyClass, khi bạn khởi tạo lớp này, bạn truyền cú pháp:
MyClass myClassObject =newMyClass();
Ở đây, phương thức được gọi sau từ khoá new là MyClass() là hàm khởi tạo của lớp này. Điều này giúp khởi tạo thuộc tính và các phương thức để gán chúng cho đối tượng myClassObject.
Đây là một khái niệm rất quan trọng trong Functional Programming. Ở đây mình sẽ cho ví dụ dựa trên Javascript, cụ thể là TypeScript, do đó mình hi vọng các bạn đã có một số kiến thức nhất định về JS trước. Điều này sẽ giúp bạn nắm bắt nội dung bài viết dễ dàng hơn.
Higher order Function là gì?
Higher order function không không phải là một khái niệm mới. Nó khá đặc thù và phổ biến trong Functional Programming.
Concept đơn giản ở đây là một Function nhận một hoặc nhiều function khác làm tham số, sau đó trả về giá trị hoặc một function mới.
Ví dụ, Javascript có hàm Array.filter. Đây là một higher order function, do nó nhận một function làm tham số.
Javascript cũng có một hàm khác Array.slice. Tuy nhiên, đây không phải là higher order function, do nó không nhận tham số là function, và giá trị trả về của nó cũng không phải một function nào cả.
Đoạn code trên có vẻ không được linh hoạt cho lắm. Thử tưởng tượng bạn cần lọc ra một danh sách các number với giá trị lớn hơn 5 chẳng hạn, bạn cần phải tạo một hàm khác, cập nhật thêm một mớ logic khác.
Hàm numberGreaterThan và stringsLengthGreaterThan hầu như giống nhau, chỉ khác nhau về điều kiện so sánh dữ liệu, điều này thì khá là tệ.
Mình theo phong cách viết càng ít logic càng tốt. Viết càng ít logic thì càng giảm thiểu số lượng unit tests cho các logic này. Rõ ràng là vừa đỡ mệt, vừa đỡ tốn thời gian, lại vừa dễ dàng bảo trì hơn, đúng không?Giờ mình sẽ sửa lại đoạn code trên sử dụng higher order function:
Đấy, code đã clean hơn rất nhiều. Do logic filter đã được tách biệt khỏi điều kiện filter, nên bạn cũng tái sử dụng lại được logic từ hai hàm numberGreaterThan và stringsLengthGreaterThan.
Ở ví dụ trên, có thể có nhiều bạn sẽ thắc mắc tại sao mình lại đem iteratee function làm đối số đầu tiên, và array dữ liệu mẫu làm đối số thứ hai. Thực ra, chúng đều có dụng ý cả.
Ví dụ bên dưới đây sẽ sử dụng thêm thư viện lodash/fp.
Mình sẽ thực hiện việc loại bỏ các giá trị thừa và lặp lại thông qua hàm _.compact và _.uniq, sau đó sẽ trả về các QR codes có length lớn hơn 6.
Kết quả vẫn giống như ví dụ trước, nhưng do đối số đầu tiên của hàm higherOrderFilterReverse là một string array (tức kết quả trả về của hàm _.uniq trước đó), chúng ta bắt buộc phải tạo một hàm mới để có thể khai báo iteratee function.
Mình luôn cố gắng viết các đối số cuối cùng của higher order function sao cho có thể nhận giá trị trả về từ các function trước. Lúc đó, code của bạn sẽ ngắn gọn hơn rất nhiều.
Kết
Higher order Function làm một phần cực kỳ quan trọng trong Functional Programming. Nắm rõ chúng sẽ giúp các bạn viết code tốt hơn, ngắn gọn, clean hơn, ứng dụng của bạn cũng sẽ dễ dàng bảo trì hơn.
Mình hi vọng các bạn thích bài viết này. Nếu bạn có câu hỏi, đừng ngại inbox mình hoặc để lại comment nhé.
Bài viết được sự cho phép bởi tác giả Vũ Thành Nam
Khi bạn vào một website nào đó, bạn sẽ thường thấy chức năng đăng nhập, đăng xuất của một ứng dụng. Đa số chúng ta biết đến tính năng này với việc điền tên tài khoản và mật khẩu để có thể truy cập sử dụng một số tính năng khác trong hệ thống.
Phải nói đây có lẽ là một trái tim của một trang web giúp ứng dụng có thể xác thực bạn là ai, cần được quyền truy cập hay tiếp tục thao tác trên ứng dụng hay không.
Đây trong chuyên ngành chính là Authentication và Authorization
Loạt bài viết này hãy cùng mình đi tìm hiểu chuyên sâu một chút về 2 thuật ngữ này và cách thức hoạt động của nó khi sử dụng và không sử dụng framework nhé!
Bắt đầu thôi!
Authentication (xác thực) có nghĩa là xác nhận danh tính của riêng bạn, trong khi authorization (ủy quyền) có nghĩa là cấp quyền truy cập vào hệ thống. Nói một cách đơn giản, authentication là quá trình xác minh bạn là ai, trong khi authorization là quá trình xác minh những gì bạn có quyền truy cập.
Authentication là gì?
Authentication là về việc xác thực thông tin đăng nhập của bạn như tên người dùng hoặc định danh người dùng và mật khẩu để xác minh danh tính của bạn. Hệ thống xác thực danh tính người dùng thông qua mật khẩu đăng nhập. Authentication thường được thực hiện bởi tên người dùng và mật khẩu tuy nhiên nó lại kết hợp với các yếu tố xác thực khác như mã OTP hay mã PIN.
Các nhân tố xác thực (Authentication Factor) mà hệ thống sử dụng để xác minh một danh tính trước khi cấp cho họ quyền truy cập vào thực hiện bất cứ điều gì trên ứng dụng của bạn.
Dựa trên cấp độ bảo mật, Authentication Factor có thể thay đổi theo một trong các cách sau:
– Single-Factor Authentication (Xác thực một lớp): Nó là phương thức xác thực đơn giản nhất thường dựa vào mật khẩu đơn giản để cấp cho người dùng quyền truy cập vào một hệ thống cụ thể là một website hoặc network. Người này có thể yêu cầu quyền truy cập vào hệ thống chỉ bằng một trong các thông tin đăng nhập để xác minh danh tính của mình. Ví dụ phổ biến nhất về xác thực một yếu tố sẽ là thông tin đăng nhập chỉ yêu cầu mật khẩu đối với tên người dùng hoặc địa chỉ email.
– Two-Factor Authentication (Xác thực hai lớp): Như tên của nó, nó có một quy trình xác minh gồm hai bước, không chỉ yêu cầu tên người dùng và mật khẩu, mà còn một thứ mà chỉ người dùng biết, để đảm bảo mức độ bảo mật bổ sung, chẳng hạn như pin ATM, chỉ người dùng mới biết. Sử dụng tên người dùng và mật khẩu cùng với một thông tin bí mật bổ sung khiến cho những kẻ lừa đảo hầu như không thể đánh cắp dữ liệu có giá trị.
– Multi-Factor Authentication (Xác thực nhiều lớp): Nó có một phương thức xác thực tiên tiến nhất sử dụng hai hoặc nhiều mức bảo mật từ các loại xác thực độc lập để cấp quyền truy cập cho người dùng vào hệ thống. Tất cả các yếu tố phải độc lập với nhau để loại bỏ bất kỳ lỗ hổng nào trong hệ thống.
Authentication các có thể kết hợp một hoặc nhiều các yếu tố sau:
+ Password hay Pin: Là mật khẩu hay mã pinMật khẩu là một phương pháp xác thực vô cùng đơn giản, dễ triển khai nên đang được sử dụng rất rộng rãi và phổ biến.
Mỗi khi người dùng thực hiện truy cập vào thì mỗi hệ thống đều sẽ lưu lại mật khẩu dưới dạng mã hóa một chiều (các loại mã hóa có thể là md5, sha1, hoặc tự viết cơ chế mã hóa).
Đây là tính năng sẽ đảm bảo cho mật khẩu dù bị hack nhưng cũng không thể khôi phục được thành chuỗi gốc. Đây là phương pháp có nhiều biến thể khác nhau như: thiết kế dưới dạng biến thể Swipe Pattern PIN hoặc mật khẩu chỉ bằng cách dùng trong một lần (nó chuyên sử dụng cho những chức năng quan trọng).
+ Security token: Các mã bảo mật.Đây là phương pháp dựa vào thuật toán mã hóa khóa công cộng và khóa cá nhân để xác thực.
Muốn đăng nhập vào hệ thống, thì bạn chỉ cần có khóa cá nhân ở trên máy rồi thực hiện đăng nhập vào hệ thống mà không cần phải nhớ đến những thông tin đăng nhập như việc sử dụng mật khẩu. Thường thì các hệ thống quản trị server sẽ thường xuyên áp dụng biện pháp này.
+ Biometric verification: Sẽ xác minh sinh trắc học, sử dụng tròng mắt, dấu vân tay hoặc khuôn mặt là một trong những phương pháp xác thực dựa trên các yếu tố đặc trưng của một người. Phương pháp này mang lại ưu điểm là Id và mật khẩu sẽ luôn được đi cùng nhau nên bạn hoàn toàn không cần phải lo lắng bị quên hay lạc mất nó.
Mỗi khi bạn muốn đăng nhập lại thì chỉ cần chủ động sử dụng các yếu tố xác thực này một cách dễ dàng, mà không gặp bất kỳ khó khăn nào.
Mặc dù có nhiều phương pháp để có thể xác thực cho một tài khoản, tuy nhiên thì bạn sẽ không thể tránh khỏi những rủi ro như: mất đi mật khẩu, vân tay bị đánh cắp, mất mã khóa cá nhân.
Authorization là quá trình để xác định xem người dùng được xác thực có quyền truy cập vào các tài nguyên cụ thể hay không. Nó xác minh quyền của bạn để cấp cho bạn quyền truy cập vào các tài nguyên như thông tin, cơ sở dữ liệu, file… Authorization thường được đưa ra sau khi xác thực xác nhận các đặc quyền của bạn để thực hiện. Nói một cách đơn giản hơn, nó giống như cho phép ai đó chính thức làm điều gì đó hoặc bất cứ điều gì.
Authorization xảy ra sau khi hệ thống của bạn được authentication (xác thực) thành công, cuối cùng cho phép bạn toàn quyền truy cập các tài nguyên như thông tin, file, cơ sở dữ liệu, quỹ, địa điểm, hầu hết mọi thứ. Nói một cách đơn giản, authorization xác định khả năng của bạn để truy cập hệ thống và ở mức độ nào. Khi danh tính của bạn được hệ thống xác minh sau khi xác thực thành công, bạn sẽ được phép truy cập tài nguyên của hệ thống.
Ví dụ quy trình xác minh và xác nhận mã nhân viên và mật khẩu trong một tổ chức được gọi là Authentication, nhưng xác định nhân viên nào có quyền truy cập vào tầng nào được gọi là Authorization
Quá trình Authentication và Authorization diễn ra như nào?
Bước 1: Khi người dùng muốn truy cập vào một tài nguyên nào đó, bước đầu tiên họ sẽ phải gửi một request truy cập đến nguồn chứa tài nguyên đó. Để xác định người dùng đó là ai trong hệ thống thì nguồn chứa tài nguyên phải sinh ra một dấu hiệu trước đó. Đây chính là bước đăng ký mà các bạn vẫn hay làm. Hoặc có thể là do phía Admin cấp cho bạn tài khoản định danh. Dấu hiệu này sẽ có thể là mã nhân viên, tên định danh, tên đăng nhập…
Bước 2: Người dùng sẽ vào ứng dụng của bạn và điền các thông tin của dấu hiệu kể trên và yêu cầu quyền truy cập vào ứng dụng. Đây chính là quá trình đăng nhập của bạn.
Bước 3: Nguồn tài nguyên sẽ kiểm tra yêu cầu với thông tin truy cập trên và xác định được người đó là ai, họ có thể là khách, là người mua hàng, người quản lý, hay cộng tác viên. Khi này tài nguyên cần muốn biết phân loại được người truy cập vừa rồi có được quyền sử dụng, chỉnh sửa hay chỉ xem tài nguyên thì hệ thống sẽ tiếp tục xác thực quyền.
Bước 4, 5: Có nhiều cách xác thực quyền truy cập dựa trên các yêu cầu sau khi xác thực định danh (Mình sẽ đề cập rõ hơn trong bài viết sau). Đến đây khi hệ thống biết bạn có những quyền gì nó sẽ chỉ cấp cho bạn đúng quyền hạn đó mà thôi.
Bước 6,7,8: Khi nhận được quyền hạn truy cập các request tiếp theo bạn có thể truy cập được tài nguyên với quyền hạn đi kèm đã cấp.
Hàm – Function là một khái niệm cơ bản quan trọng nhất trong mọi ngôn ngữ lập trình mà bạn cần nắm vững để có thể sử dụng. Bài viết hôm nay chúng ta cùng nhau tìm hiểu về hàm trong Python, cú pháp khai báo hàm cũng như lời gọi hàm sử dụng trong ngôn ngữ này nhé.
Hàm trong Python là gì?
Trong lập trình Python, hàm được kí hiệu là function là một khối code hay nhóm lệnh có tổ chức và có thể tái sử dụng được dùng để triển khai một hành động liên quan, thực hiện một tác vụ cụ thể. Nó giúp chia một chương trình Python ra thành những khối/ phần/ mô đun nhỏ hơn, có tổ chức để dễ dàng quản lý hơn và tối ưu hóa việc tái sử dụng.
Trong Python có 2 loại hàm cơ bản chính gồm:
Hàm Python tích hợp sẵn – Built-in Functions: hàm có sẵn trong thư viện cơ bản được Python cung cấp
return: từ khóa để giúp hàm trả về một giá trị nhất định
Với Python, một function có thể trả về dữ liệu (chứa từ khóa return) hoặc không. Trường hợp không có lệnh return thì hàm sẽ trả về None.
Ngoài ra để phục vụ cho việc tạo tài liệu (documents) của hàm, Python sử dụng khai báo bên dưới tiêu đề hàm và nằm trong 3 ký tự [“””]. Phần này được gọi là Docstring (documentation string) giúp giải thích ngắn gọn về chức năng của hàm.
Một ví dụ đầy đủ khi khai báo hàm Python bạn có thể tham khảo dưới đây:
Cú pháp gọi hàm trong Python
Chúng ta định nghĩa một hàm như dưới đây:
defsayHello(name):"""This function to say hello, name is a argument"""print("Hello "+name+". Have nice day!")
Để gọi sử dụng hàm trên trong Python, chúng ta đơn giản chỉ cần nhập tên hàm và truyền tham số tương ứng, ví dụ:
sayHello("TopDev")# Hello TopDev. Have nice day!
Lưu ý rằng trong Python, code định nghĩa hàm luôn phải nằm trước lời gọi hàm, nếu không thì chương trình sẽ trả về lỗi hàm chưa được định nghĩa (NameError … not defined).
Trong Python, phạm vi của biến chính là đoạn chương trình mà ở đó biến được thừa nhận, có thể sử dụng. Với một hàm, biến và các tham số có phạm vi chỉ trong phần khai báo của hàm đó; nghĩa là chúng ta không thể sử dụng các biến đó bên ngoài hàm.
Thời gian tồn tại của biến trong hàm cũng được đánh dấu tương ứng với lời gọi hàm, chúng được tạo ra khi hàm có lời gọi đến, xuất hiện ở bộ nhớ trong quá trình thực thi hàm và sẽ bị hủy khi chương trình thoát khỏi hàm.
Trong Python, có 2 khái niệm mà nếu mới tiếp xúc với ngôn ngữ này bạn có thể dễ bị nhầm lẫn, đó là method – phương thức và function – hàm. Cả 2 đều khá giống nhau trong cách khai báo, định nghĩa cũng như lời gọi, cách sử dụng; bạn có thể xem ở ví dụ dưới đây.
Điểm khác nhau cơ bản giữa method và function đấy là việc method thuộc về một lớp hay đối tượng và để gọi hay truy cập vào method cần thông qua lớp, đối tượng đó. Như ở ví dụ trên, phương thức to_pounds được định nghĩa bên trong class Weight và để gọi được thì bắt buộc phải thông qua đối tượng w.
Những hàm hữu ích tích hợp sẵn và hay được sử dụng trong Python mà bạn nên biết:
reduce(): duyệt qua từng phần tử trong một danh sách và trả về một giá trị đơn lẻ
split(): ngắt một chuỗi dựa theo tiêu chí
enumerate(): trả về độ dài của một vòng lặp
eval(): triển khai các biểu thức toán học
map(): duyệt qua từng phần tử trong danh sách
min(), max(): trả về mục được xếp hạng thấp nhất/ cao nhất trong một lần lặp
getattr(): trả về thuộc tính của một đối tượng
Kết bài
Qua bài viết này chắc hẳn các bạn đã nắm được kiến thức cơ bản về hàm, cách định nghĩa cũng như sử dụng hàm trong ngôn ngữ lập trình Python. Hy vọng bài viết hữu ích dành cho bạn và hẹn gặp lại trong các bài viết tiếp theo của mình.
Bạn mới dấn thân vào con đường lập trình web và bối rối vì quá nhiều thuật ngữ như .NET, .NET core, .NET framework. Vậy sự khác biệt giữa chúng là gì? Nên chọn cái nào tốt nhất để học phát triển web? Trong bài viết này của TopDev, chúng ta sẽ tập trung đi sâu tìm hiểu về .NET core là gì và lịch sử hình thành của nó, đồng thời giúp bạn giải đáp thắc mắc ở đầu bài.
.NET core là gì? Lịch sử hình thành và phát triển của .NET core
Tổng quan về .NET Core
.NET Core là gì?
.NET Core là phiên bản cải tiến của .NET Framework, là một nền tảng phát triển đa năng, mã nguồn mở, miễn phí được duy trì bởi Microsoft. Nó là một framework đa nền tảng chạy trên các hệ điều hành Windows, macOS và Linux.
.NET Core có thể được sử dụng để xây dựng các loại ứng dụng khác nhau như thiết bị di động, máy tính để bàn, web, đám mây, IoT, máy học, microservice, trò chơi, v.v.
Do một số hạn chế khi sử dụng .NET Framework như nó chỉ chạy trên nền tảng Windows. Ngoài ra, bạn cần sử dụng các API .NET khác nhau cho các thiết bị Windows khác nhau như Windows Desktop, Windows Store, Windows Phone và ứng dụng Web. Ngoài ra, .NET Framework còn là một framework toàn máy. Bất kỳ thay đổi nào được thực hiện đối với nó đều ảnh hưởng đến tất cả các ứng dụng phụ thuộc vào nó.
Ngày nay, việc có một ứng dụng chạy trên nhiều thiết bị là điều phổ biến; một backend trên máy chủ web, giao diện quản trị trên desktop Windows, các ứng dụng web và di động cho người dùng. Do đó, cần có một framework duy nhất hoạt động ở mọi nơi. Với ý tưởng này, Microsoft đã tạo ra .NET Core. Mục tiêu chính của .NET Core là làm cho .NET Framework trở thành mã nguồn mở, tương thích đa nền tảng và có thể được sử dụng trong nhiều lĩnh vực khác nhau, từ trung tâm dữ liệu đến các thiết bị cảm ứng.
Lịch sử hình thành .NET Core
Tên .NET Core đã được thay đổi sau .NET Core 3.1. Phiên bản tiếp theo của .NET Core sau phiên bản 3.1 được đặt tên là .NET 5. Phiên bản mới nhất của .NET Core là .NET 7 tính đến thời điểm viết bài này.
.NET 5 hợp nhất các nền tảng .NET Core, .NET Framework và Xamarin riêng biệt trước đây, giúp các nhà phát triển dễ dàng sử dụng một nền tảng duy nhất cho nhiều loại ứng dụng khác nhau. Và hiện nay được gọi ngắn gọn là .NET
Tương tự .NET Core nhưng mở rộng và hợp nhất một số tính năng.
Phát triển ứng dụng web (ASP.NET Core), ứng dụng console, dịch vụ microservices
Phát triển ứng dụng desktop (WinForms, WPF), dịch vụ web (ASP.NET WebForms, ASP.NET MVC)
Hỗ trợ
Tất cả các loại ứng dụng (web, mobile, desktop, IoT, AI).
Hiệu suất cao, linh hoạt, nhẹ và hiện đại
Đầy đủ các thư viện và API
Đặc điểm cốt lõi của .NET Core
Khung nguồn mở: .NET Core là một khung nguồn mở được Microsoft duy trì và có sẵn trên GitHub theo giấy phép MIT và Apache 2. Đây là một dự án thuộc .NET Foundation. Bạn có thể xem, tải xuống hoặc đóng góp cho mã nguồn qua các kho lưu trữ GitHub.
Đa nền tảng: .NET Core chạy trên Windows, macOS và Linux, có các thời gian chạy khác nhau cho mỗi hệ điều hành nhưng tạo ra cùng một đầu ra.
Nhất quán trên các kiến trúc: .NET Core thực thi mã với cùng hành vi trên các kiến trúc tập lệnh khác nhau, bao gồm x64, x86 và ARM.
Hỗ trợ nhiều loại ứng dụng: .NET Core cho phép phát triển và chạy nhiều loại ứng dụng khác nhau như thiết bị di động, desktop, web, đám mây, IoT, máy học, microservices và trò chơi.
Hỗ trợ nhiều ngôn ngữ lập trình: .NET Core hỗ trợ các ngôn ngữ lập trình C#, F# và Visual Basic. Bạn có thể sử dụng các IDE yêu thích như Visual Studio 2017/2019, Visual Studio Code, Sublime Text, Vim, và nhiều hơn nữa.
Kiến trúc mô-đun: .NET Core hỗ trợ kiến trúc mô-đun thông qua các gói NuGet. Các tính năng khác nhau có thể được thêm vào dự án khi cần thiết, giảm dung lượng bộ nhớ và tăng tốc hiệu suất.
Công cụ CLI: .NET Core bao gồm các công cụ CLI (Giao diện dòng lệnh) để phát triển và tích hợp liên tục.
Triển khai linh hoạt: Ứng dụng .NET Core có thể được triển khai trên toàn người dùng, toàn hệ thống hoặc với Docker Container.
Khả năng tương thích: .NET Core tương thích với .NET Framework và Mono API thông qua .NET Standard.
Hiệu suất cao: .NET Core được tối ưu hóa về hiệu suất với các tính năng như biên dịch Just-In-Time (JIT), dịch mã thành hướng dẫn máy trong thời gian chạy để cải thiện tốc độ thực thi.
Nền tảng hợp nhất: Từ .NET Core 3.1 trở đi, Microsoft đã hợp nhất các nền tảng .NET, tập hợp .NET Core, .NET Framework và Xamarin thành một nền tảng duy nhất gọi là “.NET” bắt đầu từ .NET 5, nhằm cung cấp tính nhất quán API và hành vi thời gian chạy trên các loại ứng dụng khác nhau.
Như vậy, bài viết của TopDev đã giúp bạn hiểu rõ .NET Core là gì? Đồng thời biết về lịch sử hình thành và phát triển .NET framework và .NET Core… Hãy cũng khám phá nhiều hơn về nền tảng phát triển này qua các bài tiếp theo của chúng tôi!
Trong thời đại công nghệ hiện nay, việc phát triển các ứng dụng web động và dịch vụ web là một nhu cầu thiết yếu của các doanh nghiệp và lập trình viên. ASP.NET, một framework mã nguồn mở được phát triển bởi Microsoft, đã nhanh chóng trở thành công cụ không thể thiếu cho việc này. Được thiết kế để đơn giản hóa quá trình phát triển ứng dụng web, ASP.NET cung cấp nhiều tính năng mạnh mẽ và linh hoạt, giúp lập trình viên dễ dàng tạo ra các ứng dụng chất lượng cao, bảo mật và hiệu quả.
Cùng TopDev tìm hiểu ASP.NET là gì? và các lợi ích tuyệt vời của ASP.NET ngay trong bài viết dưới đây!
ASP.NET là gì? Phân tích thành phần và phương thức hoạt động chi tiết
ASP.NET là gì?
ASP.NET là một framework mã nguồn mở phía server được phát triển bởi Microsoft, nhằm mục đích xây dựng các ứng dụng web động và dịch vụ web. Nó cho phép các lập trình viên tạo ra các trang web và ứng dụng sử dụng HTML5, CSS, và JavaScript. Được giới thiệu lần đầu tiên vào đầu thế kỷ 21, ASP.NET đã nhanh chóng trở thành một công cụ quan trọng trong việc phát triển ứng dụng web, đặc biệt là trong môi trường doanh nghiệp.
ASP.NET được cấu thành từ ba thành phần chính, mỗi thành phần đảm nhận một vai trò cụ thể trong việc phát triển ứng dụng:
Thành phần của ASP.NET
Ngôn ngữ lập trình
C#: Ngôn ngữ lập trình hướng đối tượng, phổ biến nhất trong .NET framework, được sử dụng rộng rãi để phát triển các ứng dụng web và dịch vụ web.
VB.NET: Ngôn ngữ lập trình lâu đời của Microsoft, cũng được sử dụng để phát triển các ứng dụng web.
F#: Ngôn ngữ lập trình chức năng, hỗ trợ lập trình hướng đối tượng và hướng chức năng.
Thư viện
Thư viện .NET (CoreFX): Chứa các lớp và phương thức cần thiết để thực hiện các chức năng cơ bản như quản lý tệp tin, xử lý ngoại lệ, giao tiếp mạng và nhiều chức năng khác.
Common Language Infrastructure (CLI)
Common Language Runtime (CLR): Nền tảng thực thi các chương trình Dot Net, giúp quản lý bộ nhớ, bảo mật và xử lý ngoại lệ.
Common Intermediate Language (CIL): Mã trung gian được biên dịch từ mã nguồn, sau đó được CLR thực thi.
ASP.NET hỗ trợ nhiều mô hình phát triển ứng dụng khác nhau, tùy thuộc vào yêu cầu cụ thể của từng dự án:
Web Forms: Cho phép xây dựng các ứng dụng web với giao diện người dùng phong phú, sử dụng kéo và thả để thiết kế trang web.
ASP.NET MVC: Mô hình phát triển theo kiến trúc MVC (Model-View-Controller), tách biệt giữa logic ứng dụng, giao diện người dùng và luồng dữ liệu, giúp dễ dàng quản lý và mở rộng ứng dụng.
ASP.NET Web Pages: Đơn giản hóa việc phát triển ứng dụng web bằng cách sử dụng Razor syntax, cho phép viết mã HTML và C# trong cùng một trang.
ASP.NET Web API: Dùng để xây dựng các dịch vụ web và API RESTful, cho phép các ứng dụng khác tương tác thông qua HTTP.
ASP.NET Core: Phiên bản mới nhất và linh hoạt nhất của ASP.NET, hỗ trợ đa nền tảng và cải thiện hiệu suất, cho phép chạy trên Windows, macOS và Linux.
Đơn giản, bảo mật và hỗ trợ tốt: ASP.NET cung cấp quản lý bộ nhớ tự động, bảo mật tích hợp và xử lý ngoại lệ hiệu quả, giúp bảo vệ ứng dụng khỏi các mối đe dọa bảo mật. Nó được xây dựng trên môi trường Windows server quen thuộc, giúp tiết kiệm thời gian thiết lập và cấu hình cho các lập trình viên.
Tốc độ và hiệu suất cao: Các ứng dụng ASP.NET được biên dịch, giúp mã nguồn chạy nhanh và hiệu quả hơn so với các mã nguồn được diễn dịch như PHP và JavaScript. Quá trình biên dịch tạo ra mã đối tượng, sau đó được thực thi nhanh chóng bởi nền tảng .NET, giúp tăng tốc độ và khả năng mở rộng của ứng dụng.
Tiết kiệm chi phí: ASP.NET giúp giảm chi phí phát triển và triển khai nhờ vào việc sử dụng phần mềm miễn phí và khả năng lưu trữ trên nhiều nền tảng như Linux, macOS và Windows. Các công cụ phát triển như Visual Studio Code miễn phí cho tất cả người dùng, kể cả các công ty lớn, giúp tiết kiệm chi phí phần mềm.
Cộng đồng lớn và tài nguyên hỗ trợ phong phú: ASP.NET có một cộng đồng lập trình viên .NET lớn và nhiều tài nguyên học tập, bao gồm các khóa học, tài liệu và diễn đàn hỗ trợ. Microsoft đầu tư mạnh mẽ vào việc phát triển và duy trì ASP.NET, đảm bảo rằng nền tảng này luôn cập nhật với các tính năng và công nghệ mới nhất.
Quản lý trạng thái và bộ nhớ đệm (Caching): ASP.NET cho phép quản lý trạng thái và bộ nhớ đệm hiệu quả, giúp cải thiện hiệu suất ứng dụng. Các trang web và ứng dụng thường được truy cập có thể được lưu vào bộ nhớ đệm, giảm thiểu thời gian phản hồi và cung cấp trải nghiệm người dùng tốt hơn.
Khả năng mở rộng và tích hợp: ASP.NET dễ dàng tích hợp với các công nghệ và dịch vụ khác của Microsoft, cũng như các công cụ và nền tảng của bên thứ ba, giúp xây dựng các ứng dụng mạnh mẽ và đa chức năng.
.NET đã được phát hành và ứng dụng từ nhiều năm trước. Vào năm 2016, Microsoft giới thiệu một phiên bản mới hơn gọi là .NET Core, và ngược lại gọi phiên bản ban đầu là .NET Full Framework. Ban đầu, bạn có thể sử dụng một trong hai .NET Full Framework hoặc .NET Core. Tuy nhiên, đến hiện tại, .NET Full Framework không còn được phát triển và .NET Core hiện nay chỉ được gọi là .NET (với phiên bản .NET 5 trở đi).
ASP.NET là một framework phát triển web chạy trên nền tảng .NET. Phiên bản đầu tiên của ASP.NET hiện được biết đến là ASP.NET WebForms, thuộc phần của ASP.NET Full Framework và không còn được phát triển nữa. Sau WebForms là ASP.NET MVC, một phiên bản giống Ruby on Rails nhưng về mặt kỹ thuật nó được xây dựng trên nền tảng ASP.NET thuộc Full Framework. Khi .NET Core được phát hành, một phiên bản cập nhật của ASP.NET MVC cũng được phát hành cùng với nó và được gọi là ASP.NET Core.
Tóm lại, ta có thể phân biệt đơn giản .NET là nền tảng và ASP.NET Core là framework phát triển web chạy trên nền tảng đó.
ASP.NET là một framework mạnh mẽ và linh hoạt, lý tưởng cho việc phát triển các ứng dụng web và dịch vụ web. Với các tính năng vượt trội về hiệu suất, bảo mật và khả năng mở rộng, ASP.NET giúp các doanh nghiệp và lập trình viên tạo ra các ứng dụng web chất lượng cao, đáp ứng nhu cầu của người dùng. Hãy cân nhắc sử dụng ASP.NET cho dự án tiếp theo của bạn để tận dụng những lợi ích mà nền tảng này mang lại.
Nguồn tham khảo: www.shareitsolutions.com/blog/what-is-asp-net/
.NET là gì? .NET là nền tảng phát triển ứng dụng toàn diện của Microsoft, đã được sử dụng trong nhiều thập kỷ để xây dựng các ứng dụng web, desktop và di động, từ các startup đến các doanh nghiệp lớn. .NET không chỉ đóng vai trò trung tâm trong ngành phát triển phần mềm mà còn được ưa chuộng rộng rãi trong cộng đồng lập trình viên. Điều này thể hiện qua số lượng dự án mã nguồn mở và sự hiện diện của C# trong top năm ngôn ngữ lập trình phổ biến nhất. Với phiên bản mới nhất, .NET 5, Microsoft đã cách mạng hóa ngành công nghiệp bằng việc tiên phong khái niệm phát triển phần mềm toàn cầu.
Cùng TopDev tìm hiểu chi tiết về .NET trong bài viết dưới đây!
.NET là gì? Nền tảng phát triển ứng dụng toàn diện của Microsoft
.NET là gì?
.NET hay còn được gọi là dotnet là một nền tảng phát triển mã nguồn mở và đa nền tảng, được thiết kế để xây dựng nhiều loại ứng dụng khác nhau. Được phát triển bởi Microsoft, .NET hỗ trợ nhiều ngôn ngữ lập trình và thư viện để xây dựng các ứng dụng web, di động, desktop, IoT và nhiều hơn nữa.
Các ngôn ngữ lập trình hỗ trợ bởi .NET
C# (C sharp): Ngôn ngữ lập trình hướng đối tượng hiện đại, thuộc họ ngôn ngữ C. Cú pháp của nó quen thuộc với các lập trình viên C, C++, Java và JavaScript.
F# (F sharp): Ngôn ngữ lập trình chức năng, cũng hỗ trợ lập trình hướng đối tượng.
Visual Basic: Ngôn ngữ lập trình lịch sử của Microsoft, nay đã trở thành ngôn ngữ lập trình hướng đối tượng hoàn chỉnh trong .NET.
Ngoài các ngôn ngữ trên, .NET còn hỗ trợ nhiều ngôn ngữ khác thông qua Common Language Infrastructure (CLI), đảm bảo khả năng tương tác giữa các ngôn ngữ trên nền tảng này.
Thị trường .NET tuyển dụng như thế nào? Xem ngay việc làm .NET tại TopDev
Kiến trúc và thành phần của .NET
.NET cho phép bạn xây dựng nhiều loại ứng dụng khác nhau nhờ vào kiến trúc tối ưu và mô đun của nó. Các thành phần chính bao gồm:
CoreCLR: Đây là runtime của .NET, chịu trách nhiệm thực thi các chương trình CLI và bao gồm một trình biên dịch just-in-time.
CoreFX: API của nền tảng, cung cấp các thư viện chuẩn để thực hiện các chức năng thông dụng như quản lý hệ thống tệp, xử lý ngoại lệ, giao tiếp mạng, đa luồng, và nhiều hơn nữa.
Mô hình ứng dụng .NET
Ngoài các thành phần cốt lõi trên, .NET cung cấp các framework hỗ trợ phát triển các loại ứng dụng khác nhau:
ASP.NET: Framework cho phép xây dựng các ứng dụng web và web API.
Windows Presentation Foundation (WPF): Giao diện người dùng đồ họa cho các ứng dụng desktop Windows.
Xamarin: Framework để xây dựng các ứng dụng di động, TV và desktop đa nền tảng.
Blazor: Framework để xây dựng ứng dụng web client bằng C#, cũng cho phép tạo các ứng dụng web client bằng mã WebAssembly.
ML.NET: Framework học máy, giúp tích hợp các mô hình học máy vào ứng dụng .NET của bạn.
Ngoài ra, .NET còn hỗ trợ nhiều tác vụ lập trình thông dụng khác như quản lý tệp, giao tiếp mạng, bảo mật, truy cập cơ sở dữ liệu và nhiều hơn nữa. Các thư viện cụ thể có thể được tìm thấy trên kho lưu trữ NuGet, giúp bạn tạo, chia sẻ và sử dụng các thư viện .NET cho hầu hết mọi mục đích.
.NET không chỉ hỗ trợ nhiều ngôn ngữ lập trình mà còn khuyến khích các best practices như Dependency Injection, giúp giảm sự phụ thuộc giữa các thành phần của ứng dụng và dễ dàng kiểm thử. .NET cũng hỗ trợ kiểm thử đơn vị và tích hợp thông qua xUnit.
.NET được ra mắt vào năm 2002 với mục tiêu tạo ra một nền tảng phát triển toàn cầu cho mọi ngôn ngữ lập trình. Ban đầu, .NET chủ yếu hướng đến Windows, nhưng sau đó đã mở rộng ra nhiều nền tảng khác như Linux, hệ thống nhúng, thiết bị di động và trình duyệt.
.NET Framework: Phiên bản ban đầu của .NET, chỉ chạy trên Windows và hiện đang trong giai đoạn kết thúc vòng đời sau khi phát hành .NET 5.
Mono: Dự án mang .NET đến các máy Linux, nhưng không luôn tương thích hoàn toàn với .NET Framework.
.NET Core: Phiên bản viết lại hoàn toàn của .NET Framework với mục tiêu đa nền tảng, hỗ trợ Windows, Linux và Mac.
.NET Standard: Tiêu chuẩn hóa các API của .NET để tạo ra các thư viện đa nền tảng.
Vì vậy, chúng tôi biết mình muốn làm việc với C# và chúng tôi biết mình cần .NET. Làm cách nào để có được nền tảng .NET trên máy tính của chúng tôi?
Cài đặt .NET – cách 1
Tải xuống Visual Studio – một môi trường phát triển tích hợp (IDE) dành cho các ứng dụng .NET. Nó hoạt động như một ứng dụng, giống như trình duyệt web bạn đang sử dụng để xem bài viết này. Visual Studio đi kèm với nền tảng .NET, một trình chỉnh sửa mã, và các công cụ bổ sung để giúp bạn viết mã.
Tải xuống .NET SDK (bộ công cụ phát triển phần mềm). SDK cũng đi kèm với nền tảng .NET, nhưng không có công cụ chỉnh sửa mã. Thay vì là một ứng dụng trên máy tính của bạn, SDK được truy cập thông qua giao diện dòng lệnh (CLI) — để sử dụng SDK, bạn sẽ mở terminal trên máy tính của mình và gõ lệnh thay vì nhấp vào các nút. Trong ví dụ này, bạn có thể thấy một terminal trong đó người dùng đã chạy các lệnh như dotnet new và dotnet run để xây dựng một ứng dụng mới, sau đó chạy nó (ứng dụng chỉ in ra “Hello World!“).
Hiệu Suất Cao: .NET có hiệu suất cao nhờ vào khả năng biên dịch Just-In-Time (JIT) và tối ưu hóa mã nguồn.
Đa Nền Tảng: .NET Core và .NET 5+ hỗ trợ chạy trên nhiều hệ điều hành như Windows, macOS, và Linux, giúp phát triển ứng dụng đa nền tảng dễ dàng hơn.
Hỗ Trợ Nhiều Ngôn Ngữ Lập Trình: .NET hỗ trợ nhiều ngôn ngữ lập trình như C#, F#, Visual Basic, giúp lập trình viên lựa chọn ngôn ngữ phù hợp với nhu cầu của mình.
Bảo Mật Tốt: .NET cung cấp nhiều tính năng bảo mật như xác thực và ủy quyền, mã hóa dữ liệu, và bảo vệ chống tấn công XSS, CSRF.
Thư Viện Phong Phú: .NET có một hệ thống thư viện phong phú (CoreFX) hỗ trợ nhiều chức năng từ quản lý tệp, giao tiếp mạng, đến xử lý dữ liệu.
Phát Triển Nhanh Chóng với Visual Studio: Visual Studio là môi trường phát triển tích hợp mạnh mẽ, hỗ trợ tốt cho lập trình .NET, giúp tăng tốc quá trình phát triển và gỡ lỗi.
Cộng Đồng Lớn và Tài Nguyên Hỗ Trợ: .NET có một cộng đồng lập trình viên lớn và nhiều tài nguyên học tập, giúp dễ dàng tìm kiếm hỗ trợ và giải pháp cho các vấn đề kỹ thuật.
Hỗ Trợ Kiểm Thử: .NET hỗ trợ tốt cho việc kiểm thử đơn vị và kiểm thử tích hợp với các công cụ như xUnit, MSTest, và NUnit.
Nhược Điểm của .NET
Khó Khăn Trong Việc Học: .NET có thể khó khăn đối với người mới bắt đầu do sự phức tạp của hệ thống và yêu cầu kiến thức về nhiều ngôn ngữ và công nghệ khác nhau.
Chi Phí Cao: Một số công cụ và dịch vụ liên quan đến .NET, đặc biệt là các phiên bản cao cấp của Visual Studio và Azure, có chi phí cao.
Kích Thước Lớn: Ứng dụng .NET thường có kích thước lớn do cần bao gồm nhiều thư viện và phụ thuộc.
Tốc Độ Cập Nhật Nhanh: .NET thường xuyên cập nhật phiên bản mới, điều này đòi hỏi lập trình viên phải luôn cập nhật kiến thức và có thể gặp khó khăn trong việc duy trì tính tương thích của các ứng dụng hiện có.
Hiệu Suất Đối Với Ứng Dụng Nhẹ: Đối với một số ứng dụng nhẹ và đơn giản, việc sử dụng .NET có thể dẫn đến hiệu suất không tối ưu so với các công nghệ khác như Node.js hoặc Python.
Phụ Thuộc vào Microsoft: .NET là sản phẩm của Microsoft, do đó có sự phụ thuộc vào các quyết định và chiến lược của công ty này.
Tạo ứng dụng di động chỉ trong 1 nốt nhạc với TERAAPP.NET
.NET đã và đang là nền tảng phát triển ứng dụng toàn diện, đáp ứng mọi nhu cầu từ web, di động, desktop đến IoT. Với .NET 5, Microsoft tiếp tục củng cố vị thế của mình trong ngành phát triển phần mềm, mang lại cho các nhà phát triển một công cụ mạnh mẽ và linh hoạt để xây dựng các ứng dụng hiện đại.
Xem việc làm .NET fresher update mới nhất tại TopDev
Trong thế giới lập trình đa dạng, Ruby nổi lên như một ngôn ngữ lập trình độc đáo và đầy tiềm năng. Được ra mắt vào năm 1995 bởi Yukihiro Matsumoto, ngôn ngữ Ruby nhanh chóng thu hút sự chú ý của giới lập trình viên bởi sự linh hoạt, dễ sử dụng và khả năng hướng đối tượng mạnh mẽ.
Bài viết này sẽ giúp bạn hiểu rõ hơn về ngôn ngữ lập trình Ruby, từ khái niệm, các đặc điểm nổi bật, cho đến những ứng dụng thực tiễn và lý do tại sao Ruby là ngôn ngữ bạn nên học. Hãy cùng khám phá và tìm hiểu chi tiết về ngôn ngữ lập trình đầy tiềm năng này.
Ngôn ngữ lập trình Ruby là gì?
Ruby là một ngôn ngữ lập trình hướng đối tượng, linh hoạt và dễ đọc, được phát triển bởi Yukihiro Matsumoto vào giữa những năm 1995. Mục tiêu của Ruby là mang lại sự đơn giản và hiệu quả, giúp lập trình viên có thể viết mã một cách tự nhiên và dễ hiểu. Với cú pháp rõ ràng và gần gũi với ngôn ngữ tự nhiên, Ruby giúp giảm bớt gánh nặng lập trình và tăng cường khả năng sáng tạo của người viết mã.
Đặc biệt, Ruby nổi tiếng với framework Ruby on Rails, một công cụ mạnh mẽ giúp phát triển ứng dụng web nhanh chóng và hiệu quả. Sự kết hợp giữa tính đơn giản, mạnh mẽ và cộng đồng hỗ trợ nhiệt tình đã làm cho Ruby trở thành một lựa chọn phổ biến trong giới lập trình viên.
Ứng dụng của ngôn ngữ Ruby
Ngôn ngữ Ruby được ứng dụng rộng rãi trong nhiều lĩnh vực nhờ tính linh hoạt, dễ sử dụng và khả năng mở rộng mạnh mẽ. Dưới đây là một số ứng dụng tiêu biểu của Ruby:
1. Phát triển Web với Ruby on Rails
Ruby on Rails (RoR): Đây là framework web nổi tiếng nhất của Ruby, giúp các nhà phát triển xây dựng các ứng dụng web một cách nhanh chóng và hiệu quả. Ruby on Rails cung cấp các công cụ mạnh mẽ và thư viện phong phú, giúp giảm bớt công việc lặp đi lặp lại và tập trung vào logic kinh doanh.
Các dự án nổi bật: Nhiều ứng dụng và website lớn đã được xây dựng bằng Ruby on Rails, bao gồm GitHub, Airbnb, Shopify, và Basecamp. Những dự án này minh chứng cho khả năng mở rộng và tính ổn định của Ruby on Rails.
2. Xử lý dữ liệu và scripting
Ruby được sử dụng rộng rãi trong việc viết các script để tự động hóa các tác vụ lặp lại, xử lý dữ liệu và tạo báo cáo. Khả năng dễ đọc và viết của Ruby giúp việc xử lý dữ liệu trở nên dễ dàng hơn.
Data Processing: Ruby có thể được sử dụng để phân tích và xử lý dữ liệu, đặc biệt là với các thư viện mạnh mẽ như Nokogiri để xử lý XML và HTML, hay CSV để xử lý các tập tin CSV.
3. Automation và DevOps
Ruby được sử dụng trong các công cụ tự động hóa và quản lý cấu hình như Chef và Puppet. Những công cụ này giúp quản lý các hệ thống và cấu hình máy chủ một cách tự động, giảm thiểu sai sót và tiết kiệm thời gian.
Chef và Puppet: Đây là hai công cụ DevOps nổi tiếng được viết bằng Ruby, giúp quản trị viên hệ thống tự động hóa việc quản lý cấu hình, triển khai ứng dụng và giám sát hệ thống.
4. Phát triển ứng dụng di động
RubyMotion: Đây là một công cụ cho phép các nhà phát triển viết ứng dụng iOS và Android bằng Ruby. RubyMotion cung cấp một môi trường phát triển tích hợp, cho phép viết mã Ruby và biên dịch thành các ứng dụng di động gốc.
Nhờ vào tính linh hoạt và dễ sử dụng, Ruby đã trở thành một công cụ mạnh mẽ trong nhiều lĩnh vực khác nhau, từ phát triển web, xử lý dữ liệu, tự động hóa đến phát triển ứng dụng di động. Sự phổ biến của Ruby và Ruby on Rails minh chứng cho khả năng và tiềm năng của ngôn ngữ này trong ngành công nghệ.
Ruby là ngôn ngữ lập trình tuyệt vời với nhiều ưu điểm nổi bật. Tuy nhiên, cũng cần lưu ý đến một số nhược điểm của Ruby trước khi sử dụng. Hãy cùng TopDev điểm qua một số ưu nhược điểm nổi bật của Ruby dưới đây:
1. Ưu điểm của ngôn ngữ Ruby
Cú pháp dễ đọc và dễ viết: Ruby có cú pháp gần gũi với ngôn ngữ tự nhiên, giúp lập trình viên dễ dàng hiểu và viết mã. Điều này làm giảm bớt thời gian học tập và tăng năng suất làm việc.
Tính hướng đối tượng mạnh mẽ: Ruby là ngôn ngữ lập trình hướng đối tượng, với mọi thứ đều là đối tượng. Điều này giúp cấu trúc mã trở nên rõ ràng, dễ bảo trì và mở rộng.
Ruby on Rails: Framework Ruby on Rails là một trong những lý do chính khiến Ruby trở nên phổ biến. Rails giúp việc phát triển các ứng dụng web trở nên nhanh chóng và hiệu quả, với nhiều công cụ và thư viện hỗ trợ sẵn có.
Cộng đồng mạnh mẽ: Ruby có một cộng đồng lập trình viên rất tích cực và hỗ trợ, với nhiều tài liệu, thư viện (gems), và plugin được phát triển liên tục. Điều này tạo điều kiện thuận lợi cho việc học tập và giải quyết các vấn đề kỹ thuật.
Thư viện phong phú: Ruby có nhiều thư viện và gem hỗ trợ cho hầu hết các nhu cầu lập trình, từ web development, automation, đến data processing.
Khả năng mở rộng và tùy biến: Ruby cho phép lập trình viên mở rộng và tùy biến các thành phần của ngôn ngữ, từ các lớp cơ bản đến các thư viện và framework. Điều này giúp đáp ứng tốt các yêu cầu đặc thù của dự án.
2. Nhược điểm của ngôn ngữ Ruby
Hiệu suất: So với một số ngôn ngữ lập trình khác như C++ hay Java, Ruby có hiệu suất thấp hơn. Điều này có thể là một vấn đề khi xử lý các tác vụ yêu cầu tính toán cao hoặc xử lý nhiều dữ liệu lớn.
Tiêu thụ tài nguyên: Ruby tiêu thụ nhiều tài nguyên hệ thống hơn, đặc biệt là bộ nhớ. Điều này có thể gây ra vấn đề khi triển khai ứng dụng trên các hệ thống có tài nguyên hạn chế.
Độ phổ biến và nhu cầu thị trường: So với các ngôn ngữ phổ biến khác như JavaScript, Python, hay Java, Ruby có ít cơ hội việc làm hơn, đặc biệt là ở một số thị trường nhất định. Điều này có thể làm giảm khả năng tìm kiếm việc làm cho lập trình viên Ruby.
Khó khăn trong việc bảo trì mã nguồn: Do tính linh hoạt cao, mã Ruby có thể trở nên khó bảo trì nếu không được viết cẩn thận. Các tính năng như monkey patching có thể dẫn đến các lỗi khó phát hiện và sửa chữa.
So sánh Ruby với Python: Nên chọn ngôn ngữ lập trình nào?
Ruby và Python đều là những ngôn ngữ lập trình hướng đối tượng, dễ học và phổ biến, được sử dụng rộng rãi trong nhiều lĩnh vực như phát triển web, phân tích dữ liệu, lập trình khoa học và tự động hóa. Tuy nhiên, hai ngôn ngữ này cũng có những điểm khác biệt riêng về cú pháp, hiệu suất, cộng đồng và ứng dụng, dẫn đến những lựa chọn phù hợp cho từng đối tượng người dùng khác nhau.
Dưới đây là bảng so sánh chi tiết giữa Ruby và Python:
Ruby
Python
Cú pháp
Linh hoạt, gần gũi với ngôn ngữ tự nhiên
Rõ ràng, nhất quán, dễ đọc và dễ bảo trì
Hiệu suất
Chậm hơn so với nhiều ngôn ngữ khác, đặc biệt là trong tác vụ yêu cầu cao
Tốt hơn Ruby trong nhiều trường hợp, có thể tối ưu hóa với thư viện như NumPy
Cộng đồng
Nhiệt tình, đặc biệt xung quanh Ruby on Rails
Rất lớn và đa dạng, nhiều tài liệu học tập và tài nguyên miễn phí
Ứng dụng thực tiễn
Phát triển web (Ruby on Rails), Automation và DevOps (Chef, Puppet)
Khoa học dữ liệu, học máy (Pandas, TensorFlow, Scikit-learn), phát triển web (Django, Flask), Automation và scripting
Khả năng mở rộng và bảo trì
Linh hoạt, nhưng có thể khó bảo trì nếu không được quản lý tốt
Nhất quán và rõ ràng, dễ bảo trì và mở rộng
Tiêu thụ tài nguyên
Tiêu thụ nhiều tài nguyên hệ thống, đặc biệt là bộ nhớ
Sử dụng ít tài nguyên hơn so với Ruby, nhưng không phải ngôn ngữ tiết kiệm nhất
Framework phổ biến
Ruby on Rails
Django, Flask
Thư viện phổ biến
Nhiều gem hỗ trợ phát triển web và DevOps
Thư viện mạnh mẽ cho khoa học dữ liệu, học máy, xử lý dữ liệu
Cơ hội việc làm
Ít cơ hội hơn so với Python, nhưng vẫn phổ biến trong phát triển web
Rất nhiều cơ hội trong nhiều lĩnh vực khác nhau
Tiềm năng phát triển và mức lương của lập trình viên Ruby
Nhu cầu tuyển dụng lập trình viên Ruby tại Việt Nam đang tăng cao do sự phát triển mạnh mẽ của ngành công nghệ thông tin, đặc biệt là lĩnh vực phát triển web và ứng dụng di động. Ruby on Rails, framework web phổ biến nhất được xây dựng dựa trên Ruby, ngày càng được nhiều doanh nghiệp lựa chọn cho các dự án của mình.
Bên cạnh đó, Ruby có cộng đồng lập trình viên lớn và hoạt động tích cực tại Việt Nam, luôn sẵn sàng chia sẻ kiến thức và hỗ trợ lẫn nhau. Điều này giúp các lập trình viên Ruby dễ dàng học hỏi, nâng cao kỹ năng và tìm kiếm cơ hội việc làm.
Về mức lương, lập trình viên Ruby có mức lương cao so với mặt bằng chung của ngành lập trình. Theo BÁO CÁO THỊ TRƯỜNG IT VIỆT NAM 2023 của TopDev, mức lương của Ruby Developer cấp bậc Junior (1 – 2 năm) và Middle Level (3-4 năm) lần lượt là 888 USD và 1.590 USD.
Kết luận
Vậy là TopDev đã tổng hợp đầy đủ các thông tin chi tiết về ngôn ngữ Ruby. Hy vọng những chia sẻ này sẽ giúp bạn đưa ra lựa chọn phù hợp cho con đường lập trình của mình. Nếu có hứng thú về ngành công nghệ thông tin, hãy đón chờ thêm nhiều bài viết hấp dẫn khác từ TopDev nhé!
Trong lập trình, toán tử có thể hiểu là một hàm với các toán hạng là các giá trị đầu vào (input), thực hiện một số các phép toán cụ thể và trả về một giá trị đầu ra (output). Mỗi toán tử được quy định với các ký hiệu, biểu tượng riêng đặc trưng trong từng ngôn ngữ lập trình.
Hầu hết các ngôn ngữ lập trình đều sẽ hỗ trợ các loại toán tử cơ bản giống nhau. Với một ngôn ngữ mạnh về khả năng tính toán như Python, việc nắm được các loại toán tử và sử dụng chúng là điều cực kỳ quan trọng để tối ưu source code dự án. Bài viết hôm nay chúng ta cùng tìm hiểu xem Python hỗ trợ những loại toán tử nào và cách sử dụng chi tiết từng loại toán tử đó nhé.
Giới thiệu về toán tử Python
Trong Python, các toán tử được khai báo bằng các biểu tượng, từ khóa tương tự như các ký hiệu toán học giúp dễ dàng ghi nhớ và sử dụng. Các phép toán cơ bản cộng (+), trừ (-), nhân (*) hay chia (/) được sử dụng với ý nghĩa tương tự như khi chúng ta làm toán. Ngoài ra còn có các toán tử thao tác logic giúp chúng ta dễ dàng xử lý với biến được Python thêm vào để sử dụng.
Thành phần của một toán tử bao gồm các toán hạng và ký hiệu toán tử tương ứng. Trong Python, khái niệm toán tử một ngôi là phép toán hoạt động với chỉ một toán hạng, tức là chỉ có duy nhất một giá trị đầu vào. Ví dụ như phép phủ định hay thao tác đảo ngược bit. Tương tự thì toán tử hai ngôi là phép toán hoạt động với 2 toán hạng, là loại toán tử phổ biến nhất.
Python còn có khái niệm toán tử ba ngôi (ternary operator) được sử dụng trong việc viết rút gọn mã nguồn, chẳng hạn như câu điều kiện (if/ else). Tuy nhiên loại này mình sẽ không được đề cập trong bài viết này nhé.
Các loại toán tử trong Python
Dựa trên chức năng, toán tử trong Python được chia thành 7 loại bao gồm:
1. Toán tử số học – Arithmetic Operators
Toán tử số học là những phép tính gần gũi với chúng ta nhất, được sử dụng để thực hiện các phép toán số học trên các toán hạng.
Ví dụ với 2 biến a = 12 và b = 5, chúng ta có các toán tử như bảng dưới đây:
Ký hiệu
Toán tử
Mô tả
Ví dụ
+
Phép cộng
Cộng 2 toán hạng với nhau
a+b = 17
–
Phép trừ
Trừ 2 toán hạng với nhau
a-b = 7
*
Phép nhân
Nhân 2 toán hạng với nhau
a*b = 60
/
Phép chia
Chia 2 toán hạng với nhau
a/b = 2.4
%
Phần dư
Lấy phần dư còn lại sau khi thực hiện chia 2 toán hạng
a%b = 2
**
Phép mũ
Thực hiện phép tính mũ toán hạng bởi số mũ
a**b = 248832
//
Làm tròn
Thực hiện làm tròn xuống phép chia 2 toán hạng
a//b = 2
2. Toán tử quan hệ – Relational Operators
Toán tử quan hệ là những phép toán thực hiện việc so sánh giá trị của hai toán hạng; kết quả đầu ra sẽ chỉ cho ra giá trị đúng (True) hoặc sai (False).
Ví dụ với 2 biến a = 12 và b = 5, chúng ta có các toán tử như bảng dưới đây:
Lưu ý: toán tử <> (có giá trị tương tự với toán tự !=) đã bị bỏ đi ở Python 3 nên mình không liệt kê vào đây.
Ký hiệu
Toán tử
Mô tả
Ví dụ
==
So sánh bằng
Trả về True nếu 2 toán hạng bằng nhau
a==b => False
!=
So sánh khác
Trả về True nếu 2 toán hạng khác nhau
a!=b => True
<
Nhỏ hơn
Trả về True nếu toán hạng bên trái nhỏ hơn toán hạng bên phải
a<b => False
>
Lớn hơn
Trả về True nếu toán hạng bên trái lớn hơn toán hạng bên phải
a>b => True
>=
Lớn hơn hoặc bằng
Trả về True nếu toán hạng bên trái lớn hơn hoặc bằng toán hạng bên phải
a>=b => True
<=
Nhỏ hơn hoặc bằng
Trả về True nếu toán hạng bên trái nhỏ hơn hoặc bằng toán hạng bên phải
a<=b => False
3. Toán tử gán – Assignment Operators
Toán tử gán là những phép toán lấy đầu vào là giá trị ở phía bên phải của nó và gán giá trị đó cho toán hạng ở phía bên trái. Python hỗ trợ toán tử gán bằng (=) và một số toán tử gán phức hợp khác (thực hiện phép toán trước khi gán) tương tự như các ngôn ngữ lập trình khác.
Ví dụ với biến a = 12, chúng ta có các toán tử như bảng dưới đây:
Toán tử logic là những toán tử thực hiện phép toán logic trên các toán hạng. Khác với một số ngôn ngữ dùng ký tự cho phép toán logic như C, Java, JS, … thì Python sử dụng từ khóa tường minh cho toán tử logic.
Ký hiệu
Toán tử
Mô tả
Ví dụ
and
Phép và
Trả về kết quả True khi cả 2 điều kiện đều True
5>4 and 4<3 => False
or
Phép hoặc
Trả về kết quả True khi 1 trong 2 điều kiện là True
5>4 or 4<3 => True
not
Phép phủ định
Đảo ngược trạng thái logic của toán hạng
not(4<3) => True
5. Toán tử bitwise – Bitwise Operators
Toán tử bitwise là các phép toán được thực hiện trên các chuỗi bit hay số nhị phân tại cấp độ của từng bit riêng biệt. Thao tác trên từng bit được thực hiện nhanh và hỗ trợ trực tiếp bởi CPU.
Ví dụ với 2 biến a = 12 và b = 15, khi biểu diễn dưới hệ nhị phân chúng ta có: a = 00001100 và b = 00001111.
Ký hiệu
Toán tử
Mô tả
Ví dụ
&
Thao tác bit AND
Sao chép một bit tới kết quả nếu bit này tồn tại trong cả hai toán hạng
a&b = 00001100 => 12
|
Thao tác bit OR
Sao chép một bit tới kết quả nếu bit này tồn tại trong bất kỳ toán hạng nào
a|b = 00001111 => 14
^
Thao tác bit XOR
Sao chép những bit 1 chỉ tồn tại trong một toán hạng
a^b = 00000010 => 2
~
Thao tác bit NOT
Thao tác đảo ngược bit 1 thành 0 và ngược lại
~a = -00001101 => -13
<<
Dịch chuyển trái
Dịch chuyển sang trái 1 số lượng bit được xác định
a<<2 = 00110000 => 48
>>
Dịch chuyển phải
Dịch chuyển sang trải 1 số lượng bit được xác định
a>>2 = 00000011 => 3
6. Toán tử định danh – Indentity Operators
Toán tử định danh trong Python giúp so sánh vị trí ô nhớ của 2 đối tượng trong Python, bao gồm 2 toán tử:
is: Trả về True nếu như các biến ở 2 bên toán tử trỏ về cùng 1 đối tượng
is not: Trả về False nếu các biến ở 2 bên toán tử trỏ về cùng 1 đối tượng
Ví dụ: a=10; b=25; c=10;
a is b => False
a is c => True
a is not b => True
7. Toán tử membership – Membership Operators
Toán tử membership trong Python có đầu vào gồm 2 toán hạng, nó giúp kiểm tra xem một biến có nằm trong một dãy hay không. Có 2 toán tử membership bao gồm:
in: Trả về True nếu biến nằm trong dãy các biến
not in: Trả về True nếu biến không nằm trong dãy các biến
Trong một biểu thức chứa nhiều toán tử, chúng ta cần xác định được mỗi toán tử trong biểu thức đó thực hiện công việc gì và thứ tự mà chúng thực hiện. Thứ tự thực hiện các phép tính trong biểu thức đó gọi là độ ưu tiên của toán tử – Operator Precedence.
Trong Python thứ tự ưu tiên của các toán tử được thể hiện như ở bảng dưới đây, thứ tự ưu tiên từ cao xuống thấp (cao sẽ được thực hiện trước):
Toán tử
Mô tả
**
Toán tử mũ
~ + –
Phần bù,phép cộng, trừ cho toán tử một ngôi
* / % //
Phép nhân, chia, lấy phần dư, làm tròn
+ –
Phép cộng/ trừ
>> <<
Dịch bit trái/ phải
&
Phép và
^ |
Phép XOR, OR
<= < > >=
Toán tử so sánh
== !=
Toán tử so sánh bằng
= %= /= //= -= += *= **=
Toán tử gán
is is not
Toán tử định danh
in not in
Toán tử Membership
not or and
Toán tử Logic
Kết bài
Hy vọng qua bài viết này, bạn đã có thể nắm được đầy đủ các toán tử cơ bản trong Python; các ký hiệu toán học, các loại toán tử cùng thứ tự ưu tiên để thực hiện chính xác một phép toán phức tạp khi lập trình. Cảm ơn các bạn đã đọc bài và hẹn gặp lại trong các bài viết tiếp theo của mình.
Bài viết được sự cho phép của tác giả Nguyễn Thành Nam
Hiệu ứng văn bản với gradient text là một cách tuyệt vời để tạo điểm nhấn cho văn bản trên trang web của bạn. Sử dụng CSS, bạn có thể dễ dàng áp dụng hiệu ứng gradient text để làm cho văn bản trở nên hấp dẫn và nổi bật. Dưới đây là một ví dụ sử dụng hiệu ứng gradient text được tạo bằng CSS.
Để làm được như trên, bạn làm theo các bước hướng dẫn dưới đây:
Bước 1: Tạo HTML cơ bản
Đầu tiên, chúng ta cần tạo 1 khối HTML để chứa văn bản.
<div><p>thanhnamnguyen.dev</p></div>
Bước 2: Thêm CSS Gradient
Sử dụng CSS sau để tạo hiệu ứng chuyển màu trên văn bản (gradient text). Mình sử dụng thuộc tính background: linear-gradient để tạo dải màu sắc tương ứng. Gradient này chạy từ trái qua phải và bao gồm bốn màu: #7953cd, #00affa, #0190cd, và #764ada (các mã màu này đang áp dụng ở chế độ darkmode). Các con số (20%, 30%, 70%, 80%) xác định vị trí của màu trong gradient.
-webkit-background-clip và background-clip: Định nghĩa việc clip gradient vào văn bản.
-webkit-text-fill-color và text-fill-color: Đặt màu chữ trong suốt để hiển thị gradient.
background-size: Đặt kích thước của background gradient là 500% theo chiều ngang và tự động theo chiều dọc.
Sử dụng animation textShine với thời gian 5 giây, kiểu easing-in-out, lặp vô hạn và thay đổi đối diện.
Cùng với CSS ở trên, chúng ta cũng cần thêm hiệu ứng bằng cách sử dụng @keyframes textShine { ... }: Định nghĩa animation textShine. Nó bắt đầu với một vị trí background ở 0% theo chiều ngang và 50% theo chiều dọc, và kết thúc ở vị trí 100% theo chiều ngang và 50% theo chiều dọc, tạo hiệu ứng di chuyển gradient từ trái sang phải.
Từ trước tới nay, các bạn được học về Node.js đều được bảo là Node.js chỉ xử lý đơn luồng. Tức là tại một thời điểm, chỉ có một Thread được thực hiện.
Nói đơn giản cho dễ hiểu: bạn có CPU 8 nhân, 16 threads. Giờ bạn muốn duyệt một 1 triệu records để tìm phần tử lớn nhất. Với node.js, sẽ chỉ có 1 thread của CPU là thực hiện công việc duyệt tìm vì mặc định Node.js là single-thread. 1 thread chạy cắm đầu, 7 threads kia ngồi cười khúc khích.
Nhưng với Java, công việc được chia đều ra cho các threads, nên tốc độ sẽ xử lý trong bài toán ví dụ này sẽ nhanh hơn.
Đến đây, mình tin là bạn sẽ bật ra thắc mắc: Vậy không có cách nào để Node.js thực hiện đa luồng à?
Thế mạnh của Node.js là cơ chế none-blocking I/O, giúp ứng dụng có tốc độ rất nhanh. Tuy nhiên, có những bài toán yêu cầu phải xử lý đa luồng để tận dụng sức mạng CPU đa nhân. Tất nhiên là có thể làm được nhờ sự trợ giúp của cluster.
Bài viết này, chúng ta sẽ cùng nhau tìm hiểu về Cluster trong Node.js
Cluster trong Node.js
Khi nào nên sử dụng Cluster
Như mình đã đề cập ở trên, thế mạnh của Node.js là none-blocking I/O, điều này giúp cho ứng dụng mặc dù chỉ sử dụng 1 thread để chạy nhưng tốc độ thì không hề tồi chút nào. Đơn giản vì Node.js tận dụng được thời gian nhàn rỗi của CPU.
Tuy nhiên, điều này chỉ phù hợp với các ứng dụng mà mỗi tác vụ chỉ thực hiện trong thời gian ngắn. Kiểu như các ứng dụng chat, ứng dụng chạy Real-Time…
Còn với các ứng dụng nặng về tính toán như xử lý ảnh, crawler data,… thì đơn luồng như Node.js không phù hợp.
Nếu bạn vẫn “chày cối” chọn Node.js thì vẫn có cách. Đó là sử dụng cluster để Node.js có thể xử lý đa luồng, tận dụng các nhân CPU còn nhàn rỗi.
Cluster trong Node.js hoạt động như thế nào?
Cluster trong Node.js được tạo một cách đơn giản, bạn sử dụng một module cùng tên: Cluster module.
Cơ chế hoạt động của nó cũng khá đơn giản. Cluster module cho phép Node.js tạo số luồng nhỏ hơn hoặc bằng số lõi của CPU, tự động phân chia công việc để tận dụng sức mạnh của CPU.
Cách sử dụng Cluster
Lý thuyết thì cơ bản vậy thôi, chúng ta sẽ cùng nhau thực hành tạo cluster để xử lý đa luồng trong Node.js
Đoạn code dưới mình sẽ tạo một server lắng nghe cùng một port thông qua cluster.
/* Đoạn code demo tạo clusters*/'use strict';// Importing các Modules cần thiếtconst http = require('http'), cluster = require('cluster'), os = require('os').cpus().length;/*Chúng ta sẽ tạo số cluster tương ứng với số nhân của CPU.*/if(cluster.isMaster){ for(let i =0; i < os; i++){ cluster.fork(); console.log(`The Worker number: ${i +1}is alive`); } cluster.on('exit',(worker)=>{ console.log(`The Worker number: ${worker.id} has died`); });}else{ // Chúng ta sẽ tạo các cluster cùng lắng cùng một port http.createServer((sol, res)=>{ res.end('Hi, we are harnessing the power of clusters :)'); }).listen(3000,()=> console.log('The server is running on the port:3000')); console.log(`The Worker number: ${cluster.worker.id}is running`);}
Như đoạn code trên, cluster đầu tiên được tìm thấy sẽ là master cluster. Sau đó thì sẽ nhân bản ra các cluster từ master cluster, đó chính là cách mà chúng ta chia sẻ port. Một listener cũng được thêm vào cluster để biết được trạng thái stop/failed của một worker.
Sử dụng Cluster kết hợp với Express.js
Ở ví dụ trên, chúng ta đã biết tạo cluster với http module. Giờ chúng ta sẽ làm một ví dụ về cách sử dụng cluster khi kết hợp http và express.js
/* Creating clusters and serving an application that uses express*/'use strict';// importing the modulesconst cluster = require('cluster'), os = require('os').cpus().length, server = require('./serverHttp');/* We see if it is the master cluster in case it is, we will clone the cluster amount at the same time as the cores of the processor.*/if(cluster.isMaster){const Master = require('./work');const master =new Master({cluster:cluster});for(let i =0; i < os; i++){ master.liftWorker();} cluster.on('exit',(worker)=>{ console.log(worker) console.log(`The Worker number: ${worker.id}is dead.`); master.liftWorkerError();});}else{ // Creating a server with http and express. let app =new server(); app.initiate(); console.log(`cluster ${cluster.worker.id}is running.`)}
Bạn thấy đấy, cách viết code cũng tương tự như lúc trước. Chỉ khác là lần này chúng ta sẽ chia công việc thành nhiều module.
// Class to pick cluster workersclass Master { constructor(config){ this.config= config ||{}; this.cluster=this.config.cluster; } // Pick up a new Worker liftWorker(){ let worker =this.cluster.fork(); console.log(`The worker ${worker.id}is started.`); } // Raise a worker when one dies in case of errors liftWorkerError(){ let worker =this; setTimeout(()=>{ worker.liftWorker(); },200); } }
module.exports= Master;
Về cơ bản là chúng ta sẽ tạo riêng một file là work.js. Mục đích của module này là cung cấp các phương thức để tạo mới một hoặc nhiều workers khi một worker bị die.
Cuối cùng là chúng ta sẽ có module chính để tạo server và lắng nghe một port.
'use strict';const http = require('http'), express = require('./express');// Creating the server with httpclass Servidor{ constructor(config){ this.config= config ||{}; this.app=new express(); this.server= http.createServer(this.app.server); this.app.gets(); } // Starting the server initiate(){ this.server.listen(3000,()=> console.log('The server is running on port 3000')); }}module.exports= Servidor;
'use strict';const express = require('express');class Server { constructor(config){ this.config= config ||{}; this.server= express(); } // Serving the routes get gets(){ this.server.get('/',(sol, res, next)=>{ res.send(`This route is served by the workes`); }); this.server.get(`/hello`,(sol, res, next)=>{ res.send('This route too 🙈'); }); }}module.exports= Server;
Kết luận
Node.js là giải pháp thích hợp để xử lý các ứng dụng có lượng traffic lớn. Tuy nhiên, với các ứng dụng nặng về tính toán thì Node.js cũng có thể làm tốt.
Mình biết về cluster trong Node.js cũng khá lâu rồi. Nhưng hồi đó cluster còn chưa ổn định, và ngon lành như bây giờ. Giờ thì ngon lành rồi.
Tất nhiên, cũng như các ngôn ngữ, giải pháp khác, việc xử lý đa luồng chưa bao giờ là dễ cả, code sẽ cần nhiều logic hơn. Nhưng nhìn chung, cluster sẽ giúp bạn đơn giản hóa rất nhiều việc xử lý đa luồng trong Node.js
Cảm nhận của bạn về cluster khi sử dụng trong dự án như thế nào? Để lại bình luận bên dưới nhé.
Bài viết được sự cho phép của tác giả Tống Xuân Hoài
Vấn đề
Tôi đã có một bài viết về vấn đề blog của tôi sử dụng RediSearch làm cơ sở dữ liệu chính ở RediSearch là gì? Estacks đang sử dụng RediSearch làm cơ sở dữ liệu!, bên cạnh đó là lý do tôi dùng RediSearch vì tính năng Tìm kiếm fulltext trong RediSearch mà tôi luôn muốn tìm kiếm của blog trở nên mạnh mẽ và hữu ích hơn cho bạn đọc.
Cho đến hiện tại mọi thứ vẫn đang hoạt động rất tốt, chỉ có một điều Redis không có kiểu dữ liệu JSON trong khi tôi lại muốn thao tác với dữ liệu dạng JSON một cách thân thiện nhất. Do đó trong quá trình tìm hiểu tôi phát hiện ra Redis cung cấp một module có tên là RedisJSON đã giúp tôi làm được điều đó. Nếu như bạn đang dùng Redis hay RediSearch mà muốn thao tác với dữ liệu JSON thì đây quả là một điều tuyệt vời.
RedisJSON là gì?
RedisJSON là một module cung cấp hỗ trợ JSON cho Redis. RedisJSON cho phép bạn lưu trữ, cập nhật và truy xuất các giá trị JSON trong cơ sở dữ liệu Redis tương tự như bất kỳ kiểu dữ liệu Redis nào khác. RedisJSON cũng hoạt động với RediSearch để cho phép bạn lập chỉ mục và truy vấn các tài liệu JSON.
127.0.0.1:6379> JSON.SET obj $ '{"title": "Chào các Developer - Hôm nay uống gì và code gì?", "url": "https://2coffee.dev"}'"OK"
127.0.0.1:6379> JSON.GET obj $
[{"title":"Chào các Developer - Hôm nay uống gì và code gì?","url":"https://2coffee.dev"}]
Cách sử dụng
Để sử dụng RedisJSON trước tiên bạn cần cài đặt Redis v6.x trên server của mình. Redis cung cấp nhiều cách khác nhau để tải module RedisJSON. Hai cách phổ biến là:
Nếu đã quen làm việc với NoSQL thì RedisJSON cung cấp cho chúng ta một cách thuận tiện cho việc thao tác với các dữ liệu dạng JSON mà không lo sai sót dữ liệu. SQL cũng đã tích hợp kiểu dữ liệu JSON từ lâu giúp cho chúng ta có nhiều lựa chọn hơn trong việc tổ chức dữ liệu.
RedisJSON dễ dàng tích hợp với RediSearch để lập chỉ mục và tìm kiếm, việc này vừa mang lại tính thuận tiện trong lưu trữ, cộng với khả năng tìm kiếm fulltext mạnh mẽ của RediSearch và tốc độ của Redis. Tôi đang sử dụng bộ 3 công cụ này còn bạn có dự định gì cho dự án sắp tới chưa? 😀
IRAC (Issue – Rule – Analysis – Conclusion) là một phương pháp phổ biến và quen thuộc với sinh viên luật và dân luật nói chung. Cá nhân mình thấy phương pháp này khá hay và hoàn toàn có thể áp dụng vào bất cứ công việc hoặc ngành nghề nào.
Giới thiệu
Phương pháp IRAC (đọc là eye-rack hoặc ai rách haha) là một cái sườn giúp bạn có thể sắp xếp câu trả lời cho một vấn đề nào đó một cách chi tiết và rõ ràng. Thực ra phương pháp này bắt đầu được đưa ra và áp dụng bởi các công ty luật ở Mỹ.
Cấu trúc của một câu trả lời chuẩn IRAC bao gồm các thành phần cơ bản: Issue – Vấn đề, Rule – Quy phạm, Analysis – Phân tích và Conclusion – Kết luận.
Mình là lập trình viên, nên sẽ cố gắng giải thích một cách đơn giản và non-legal hết sức có thể ^_^
Issue: các vấn đề mà khách hàng đưa ra cho chúng ta mà chúng ta cần giải quyết và tư vấn cho họ.
Rule: các quy tắc và những thứ common sense cần tuân thủ trong quá trình thực hiện yêu cầu. Các quy tắc ở đây có thể là một vài tiêu chuẩn chung hoặc những quy định cụ thể từ phía khách hàng.
Analysis: phân tích, làm rõ ràng các yêu cầu của khách hàng. Dựa vào các Rule mà chúng ta liệt kê ra các solutions hợp lý để giải quyết Issue.
Conclusion: từ kết quả Analysis ở trên, chúng ta tổng kết lại các phương pháp tốt nhất hoặc phù hợp nhất để khách hàng có thể áp dụng.
Trong một số trường hợp, chúng ta có thể gộp chung phần Analysis và Conclusion lại với nhau mà không cần phải tách biệt chúng.
Thông thường đây là quá trình tốn nhiều thời gian nhất trong quá trình lấy yêu cầu. Chúng ta cần phải tưởng tượng ra một big picture về chức năng khách hàng mong muốn, sau đó dần xoáy sâu vào phần details.
Trong quá trình này, để tiết kiệm thời gian cũng như giúp khách hàng dễ nắm bắt ý kiến của mình, chúng ta có thể đưa ra một số bản mockup mô tả chức năng. Dựa vào các bản mockup này, khách hàng sẽ có thể giúp bạn thay đổi, hiệu chỉnh một số thứ cho phù hợp. Khách hàng khá thích làm việc với những người làm việc rõ ràng như vậy, nên các dev nào có ý muốn chuyển sang hướng manager hãy chú ý nhé ^_^.
Hãy cố gắng đặt càng nhiều câu hỏi càng tốt, khách hàng sẽ không thấy phiền lòng đâu. Bạn càng đặt nhiều câu hỏi thì điều đó càng chứng tỏ bạn đang hiểu rõ hơn về thứ mà khách hàng mong muốn. Tuy nhiên, hãy tránh hỏi những câu hỏi ngu ngốc.
Đừng quên keep track tất cả những thông tin mà bạn nhận được. Bạn sẽ cần nó để viết documents lại sau này. Mình khuyên bạn nên keep track mọi thứ qua email nếu có thể.
Conclusion
Sau quá trình phân tích thông tin và clear requirements với khách hàng, lúc này bạn cần viết một bản tổng kết nội dung, kèm theo FSD (Functional Specification Document) rồi gửi cho khách hàng. Có thể sẽ có một vài chỉnh sửa nho nhỏ nào đó.
Việc tiếp theo là ngồi rung đùi chờ khách hàng sign-off rồi bắt tay vào làm thôi. Với một số công ty nhỏ và quy trình không quá strictly thì bạn cũng có thể làm luôn được rồi.
Bài viết của mình đến đây là hết, cám ơn các bạn đã theo dõi nhé 😀
Bài viết được sự cho phép của tác giả Nguyễn Thành Nam
Github không chỉ là một nơi để lưu trữ mã nguồn mở và làm việc nhóm, mà còn là một nền tảng mạng xã hội cho các nhà phát triển và chuyên gia công nghệ. Và giới thiệu mới nhất của Github chính là Github Profile README – một cách tuyệt vời để cá nhân hóa trang cá nhân của bạn và giới thiệu về bản thân mình một cách sáng tạo. Trong bài viết này, chúng ta sẽ cùng nhau khám phá tính năng mới này và làm thế nào để tận dụng tối đa.
1. Github Profile là gì?
Github Profile README là một trang README.md đặc biệt được hiển thị ngay ở phần đầu trang của trang cá nhân GitHub của bạn. Điều này cung cấp cho bạn một không gian để tạo ra một bảng tự giới thiệu động với văn bản, hình ảnh, liên kết và thậm chí là các biểu tượng đặc sắc. Nó giúp bạn nổi bật hơn trong cộng đồng GitHub và tạo ấn tượng mạnh mẽ từ những người đang xem trang cá nhân của bạn.
Github Profile README là một cách tuyệt vời để làm cho trang cá nhân của bạn nổi bật trong cộng đồng phát triển. Việc tạo ra một trang cá nhân độc đáo không chỉ giúp bạn chia sẻ thông tin về bản thân mình mà còn là cơ hội để thể hiện sự sáng tạo của bạn. Hãy bắt đầu sáng tạo ngay hôm nay và để cho trang cá nhân của bạn trở nên độc đáo và ấn tượng trên Github!
Một API cho phép giao tiếp hai chiều giữa các ứng dụng phần mềm thông qua các requests. Một Webhook là một API nhẹ, hỗ trợ chia sẻ dữ liệu một chiều được kích hoạt bởi các events.
Một API cho phép giao tiếp hai chiều giữa các ứng dụng phần mềm thông qua các requests. Một Webhook là một API nhẹ, hỗ trợ chia sẻ dữ liệu một chiều và thường được kích hoạt bởi các events.
Khi kết hợp cùng nhau, chúng cho phép các application chia sẻ dữ liệu và function, giúp cho các services đạt được kết quả to lớn hơn tổng các thành phần của chúng.
API và Webhook đều cho phép các hệ thống phần mềm khác nhau đồng bộ và chia sẻ thông tin. Khi các ứng dụng phần mềm trở nên ngày càng kết nối, điều quan trọng là các nhà phát triển hiểu rõ sự khác biệt giữa hai phương tiện này để chia sẻ dữ liệu và lựa chọn công cụ phù hợp nhất với nhu cầu của công việc đang thực hiện.
API là gì?
API là viết tắt của cụm từ Application Programming Interface.
Một API giống như một cổng thông tin mà thông qua đó thông tin và chức năng có thể được chia sẻ giữa các services. Từ interface là chìa khóa để hiểu rõ mục đích của một API. Giống như một trình duyệt web là một interface cho người sử dụng cuối để nhận, gửi và cập nhật thông tin trên web server, một API là một interface cung cấp chức năng tương tự cho các ứng dụng phần mềm.
Có nhiều loại và danh mục khác nhau của API (chúng ta sẽ khám phá sau), nhưng nói chung, API là cách phổ biến nhất để các hệ thống phần mềm khác nhau kết nối và chia sẻ thông tin.
Webhook là gì?
Một webhook có thể được coi là một loại API được kích hoạt bởi các events thay vì requests.
Thay vì một service tạo một request để nhận một phản hồi từ service khác, một webhook là một dịch vụ cho phép một service gửi dữ liệu đến một service khác ngay sau khi một event cụ thể được emit. Webhooks đôi khi được coi là “API đảo ngược,” vì giao tiếp được khởi tạo bởi service gửi dữ liệu thay vì service nhận nó.
Với sự phát triển mạnh mẽ của các distributed system (hệ thống xử lý phân tán), webhook đang trở nên phổ biến hơn khi là một giải pháp gọn nhẹ cho việc kích hoạt notification và cập nhật dữ liệu theo thời gian thực mà không cần phải phát triển một API toàn diện.
Chẳng hạn, nếu bạn muốn nhận notification trên Slack khi có tweet đề cập đến một tài khoản cụ thể và chứa một #hashtag nhất định được publish, thay vì Slack liên tục yêu cầu Twitter về bài viết mới đáp ứng các tiêu chí này, việc Twitter gửi một thông báo đến Slack chỉ khi event này xảy ra là một lựa chọn tốt hơn nhiều.
Đây chính là mục đích của một webhook – thay vì phải liên tục yêu cầu dữ liệu, service nhận dữ liệu có thể ngồi lại và xử lý những gì nó cần mà không cần gửi các request lặp đi lặp lại đến hệ thống khác.
API cho khả năng tích hợp mạnh mẽ
Một đặc điểm quan trọng của API là chúng cung cấp giao tiếp hai chiều giữa các service khác nhau thông qua một chu kỳ request – response, thường sử dụng thông qua giao thức HTTP.
Trong một trường hợp sử dụng API điển hình, một service sẽ yêu cầu một tập hợp cụ thể dữ liệu từ một service khác bằng cách sử dụng yêu cầu HTTP GET request. Miễn là yêu cầu hợp lệ, hệ thống nhận sẽ gửi về response bằng dữ liệu được yêu cầu ở định dạng có thể đọc bằng máy, thường là XML hoặc JSON. Điều này làm cho các service có thể chia sẻ dữ liệu mà không cần quan tâm đến ngôn ngữ lập trình cá nhân hoặc các đặc tả nội bộ của chúng.
Tính chất phổ quát của tương tác API có thể tạo ra vô số kịch bản, từ người dùng iPhone kiểm tra nhiệt độ địa phương thông qua API của AccuWeather đến tài xế Grab điều hướng đến vị trí đón tiếp theo thông qua API của Google Maps.
Ngoài việc nhận dữ liệu, API cũng có thể xử lý toàn bộ các hoạt động “CRUD” (Create, Read, Update và Delete) giữa hai ứng dụng. Nói cách khác, API không chỉ để hiển thị dữ liệu cho người dùng trong một interface mà còn có thể được sử dụng để thay đổi dữ liệu trong service nơi nó được lưu trữ. Đây là cách mà API cho phép các hệ thống phần mềm mở rộng dịch vụ và chức năng của chúng, cũng như tích hợp với các nền tảng khác một cách toàn diện và có ý nghĩa.
Sự linh hoạt của API khiến chúng trở thành công cụ mạnh mẽ cho developers để mở rộng khả năng của ứng dụng.
Hầu hết các dịch vụ web hiện đại bao gồm API cho phép dữ liệu và chức năng của họ được tích hợp vào các công cụ khác – thực tế, rất hiếm khi gặp một dịch vụ web doanh nghiệp nào không tận dụng một API từ ít nhất một ứng dụng khác một cách nào đó.
Nhiều người có thể nghĩ rằng vì webhook là reatime event nên chúng khó triển khai từ mặt kỹ thuật.
Trên thực tế, một ưu điểm chính của webhook là chúng dễ thiết lập hơn và tốn ít tài nguyên hơn so với API. Việc tạo một API là một quy trình phức tạp, trong một số trường hợp có thể khó khăn như việc thiết kế và xây dựng cấu trúc của service, nhưng triển khai một webhook đôi khi chỉ đơn giản là thiết lập một HTTPPOST request duy nhất ở đầu gửi, thiết lập một URL ở đầu nhận để tiếp nhận và sau đó thực hiện một số hành động trên dữ liệu đó.
Các trường hợp sử dụng phổ biến cho webhook bao gồm:
gửi danh sách email subscriptions và unsubscriptions đến một hệ thống CRM,
tự động cập nhật phần mềm kế toán khi hóa đơn được thanh toán,
hoặc thiết lập bất kỳ loại thông báo nào.
Trong mỗi loại event này, data chỉ đi theo một hướng và không cần nhận hay xử lý dữ liệu được cập nhật.
Những đặc tính giống nhau khiến cho việc triển khai webhook tương đối dễ dàng cũng là lý do tại sao chúng giới hạn hơn nhiều so với APIs.
Việc cập nhật dữ liệu mà một webhook gửi đòi hỏi việc cấu hình lại nó hoàn toàn để lắng nghe event khác, và trong hầu hết các trường hợp, việc tạo một webhook mới sẽ hiệu quả hơn.
Khác với API, webhook không cho phép hệ thống gửi thêm, cập nhật và xóa dữ liệu ở đầu nhận, điều này là lý do tại sao webhook một mình quá hạn chế để cung cấp việc tích hợp đầy đủ giữa các service.
API có thể được phân loại dựa trên các giao thức và kiến trúc xác định cách chúng gửi – nhận dữ liệu.
Lịch sử cho thấy, mẫu kiến trúc phổ biến nhất cho thiết kế API là REST (Representational State Transfer), đặc biệt là đối với các API phục vụ ứng dụng dựa trên nền tảng WEB.
REST, được đặt ra bởi Roy Fielding vào năm 2000, cho phép giao tiếp giữa hai ứng dụng qua HTTP, tương tự như cách trình duyệt tương tác với máy chủ. REST không phải là một tiêu chuẩn chính thức mà là một bộ hướng dẫn về cách thiết kế API và các web serivce khác. Một API được coi là RESTful nếu thiết kế của nó tuân theo những đề xuất sau:
Client-Server (Khách hàng-Máy chủ): Tương tự như browser yêu cầu một trang web từ server, trong một API RESTful, một ứng dụng (client) yêu cầu một URL được lưu trữ trên một service khác (server) thông qua HTTP. Máy chủ đánh giá yêu cầu và response bằng dữ liệu được yêu cầu hoặc một thông báo lỗi.
Stateless (Không lưu trạng thái): Server không cần biết gì về state của client yêu cầu để cung cấp một response thích hợp. Một request từ client cần chứa đủ thông tin để server gửi response.
Cacheability (Có thể lưu trữ vào bộ nhớ đệm): Response từ server nên chỉ ra liệu client có nên lưu trữ dữ liệu vào bộ nhớ đệm hay không.
Layered Systems (Hệ thống đa lớp): API không phụ thuộc vào một hệ thống cụ thể để thực hiện yêu cầu; nó có thể gửi phản hồi qua các lớp khác nhau. Điều này có nghĩa là hệ thống nhận có thể là một client, một proxy, hoặc bất kỳ trung gian nào khác.
Ví dụ đơn giản sau đây https://www.boredapi.com/api/activity tuân theo quy ước REST. Khi bạn truy cập URL, browser của bạn sẽ hiển thị một activity được đề xuất để tham gia nếu bạn đang rảnh đến nhức cả trứng =)), được định dạng dưới kiểu JSON:
{"activity":"Shop at support your local farmers market","type":"relaxation","participants":1,"price":0.2,"link":"","key":"8979931","accessibility":0.1}
Mặc dù REST đã được sử dụng rộng rãi và vẫn còn phổ biến, những phương pháp và kiến trúc mới đang dần xuất hiện.
Một lựa chọn đáng chú ý khác là GraphQL, được phát triển bởi Facebook.
GraphQL cung cấp một cách linh hoạt và hiệu quả hơn cho khách hàng yêu cầu dữ liệu cụ thể mà họ cần, giảm việc lấy quá nhiều hoặc quá ít dữ liệu. Nó cho phép khách hàng xác định cấu trúc của phản hồi, tạo điều kiện cho tương tác linh hoạt và cá nhân hóa hơn.
mutation{
qrAssign(connectorId:"23024"poolName:"joint-tech"qrId:"AMPJ-1120311132"useCustom:true){
id
qrId
charger {
chargerUri
}}}
Trong tương lai, chúng ta có thể mong đợi sự tiếp tục phát triển trong kiến trúc API, với sự tập trung vào khả năng mở rộng, khả năng realtime và sự thích ứng với nhu cầu đa dạng của khách hàng.
Các bài trước mình đã hướng dẫn các bạn sử dụng Sqlite và Realm database. Tuy nhiên, vẫn còn một giải pháp thao tác với database cũng rất hay ho khác. Đó chính là Room database trong Android.
Vậy Room Database là gì? Cách sử dụng Room database như thế nào?
Chúng ta sẽ cùng nhau tìm hiểu thông qua một dự án ví dụ nhé.
#Giới thiệu Room database trong Android
Room database được phát triển và cải tiến từ sqlite. Room database giúp đơn giản hoá việc code,và giảm thiểu các công đoạn liên quan đến cơ sở dữ liệu.
Bản chất Room database là abstract layer gồm cơ sở dữ liệu chuẩn SQLite được Android thông qua.
Với 3 thành phần chính là: Database, DAO (Data Access Object) và entity. Mỗi thành phần đều có nhiệm vụ và chức năng riêng.
#Xây dựng ứng dụng sử dụng Room database trong Android
1. Cài đặt thư viện
Đầu tiên các bạn import thư viện vào file build.gradle
Sau đó các bạn tạo interface để thực hiện truy vấn.
DAO là interface được chú thích với @Dao, nó đóng vai trò trung gian truy cập vào các đối tượng trong cơ sở dữ liệu và các bảng của nó.
Có bốn chú thích cụ thể cho các hoạt động cơ bản của DAO: @Insert, @Update, @Delete, and @Query.
@Daopublicinterface EmployDao { @Insert(onConflict = REPLACE) voidinsertEmploy(Employee employee); @Insert(onConflict = IGNORE) voidinsertOrReplaceEmploy(Employee... employees); @Update(onConflict = REPLACE) voidupdateEmploy(Employee employee); @Query("DELETE FROM Employee") voiddeleteAll(); @Query("SELECT * FROM Employee") public List<Employee> findAllEmploySync();}
Và cuối cùng các bạn tạo AppDatabase.
Thành phần Database là một abstract class đã được chú giải bằng @Database. Nó extend RoomDatabase Class và trong đó định nghĩa một danh sách các Entities và các DAO.
@Database(entities = {Employee.class}, version = 1)publicabstractclass AppDatabase extends RoomDatabase { privatestatic AppDatabase INSTANCE; publicabstract EmployDao employDao(); publicstatic AppDatabase getInMemoryDatabase(Context context){ if(INSTANCE == null){ INSTANCE =
Room.inMemoryDatabaseBuilder(context.getApplicationContext(), AppDatabase.class) // To simplify the codelab, allow queries on the main thread. // Don't do this on a real app! See PersistenceBasicSample for an example. .allowMainThreadQueries() .build(); } return INSTANCE; } publicstaticvoiddestroyInstance(){ INSTANCE = null; }}
Như vậy, mình đã hướng dẫn các bạn từng bước sử dụng Room database trong Android. Với Room database, nhưng thao tác đọc, ghi database trở lên dễ dàng hơn bao giờ hết.
Bạn có thấy như vậy không? Toàn bộ source code của bài hướng dẫn, các bạn download ở đây nhé. Download Complete Code
Hy vọng bài viết sẽ giúp các bạn làm được và hiểu chi tiết cấu trúc và làm các dự án nâng cao hơn sau này!
Kỹ năng lập trình không phải là thứ quan trọng nhất. Ở bất cứ ngành nghề nào, giao tiếp giữa người với người luôn luôn được đánh giá cao.
Khi trò chuyện với các project manager, bạn thỉnh thoảng sẽ nghe đến vài câu chuyện khủng khiếp về các lập trình viên mà họ từng làm việc chung.
Bạn được kể về những cử chỉ thô lỗ mà các lập trình viên đối xử với khách hàng, khiến cho các project manager hiếm khi dám mang các lập trình viên tham dự các cuộc họp của họ. Bạn cũng nghe về các lí do tồi tệ mà các lập trình viên đưa ra khi không hoàn thành một thứ gì đó cũng như việc trả lời khách hàng thô lỗ qua email.
Dù cho bạn có là lập trình viên hay đảm nhận bất cứ vị trí nào khác ở bất kỳ ngành nghề nào, điều này thực sự rất tồi tệ. Nó chắc chắn sẽ giới hạn khả năng của bạn, khiến bạn rất rất khó bức phá đến một vị trí cao cấp hơn. Có một kỹ năng giao tiếp tốt luôn luôn tốt hơn là chỉ có mỗi kỹ năng nghề nghiệp xuất sắc, và nó cũng cực kỳ giúp ích cho bạn để phát triển sự nghiệp bản thân sau này.
Nếu bạn có thể trở thành một người mà mọi người yêu quý, thì họ sẽ muốn làm việc chung với bạn, thuê và giới thiệu bạn với những đồng nghiệp hay những người quen khác, hay thậm chí dành cho bạn nhiều sự ưu ái, hay bất cứ gì khác hơn mà bạn không nghĩ đến. Họ cũng sẽ sẵn sàng nói ra những điều tốt đẹp về bạn khi bạn muốn phỏng vấn ở những nơi khác.
Bạn cần phải có kiến thức tốt, nhưng đừng chỉ dừng lại ở đây. Kỹ năng giao tiếp sẽ luôn là trụ cột quan trọng thứ hai cho sự nghiệp của hầu hết mọi người.
Một chiến lược quan trọng để mọi người lắng nghe bạn – đó chính là bạn cần học cách lắng nghe họ.
Bất kể bạn có nắm giữ tất cả thông tin quan trọng và đang đứng thuyết trình giữa 20 con người khác trong một cuộc họp, nếu bạn không lắng nghe họ, họ cũng sẽ không có nghĩa vụ phải lắng nghe bạn.
Phát triển phần mềm là một dự án nhóm – team work
Các team tồi tệ nhất là team có nhiều người nóng tính và hay hờn dỗi. Họ không sẵn sàng chia sẻ suy nghĩ hay cảm xúc của họ. Họ tổ chức các cuộc họp không cấu trúc, không kế hoạch. Các thành viên lười nói chuyện, lười lắng nghe. Ai cũng có công việc riêng và không sẵn sàng chia sẻ. Không có sự lắng nghe sẽ không có sự hiểu biết. Không có sự hiểu biết sẽ không có sự hợp tác.
Ở một chiều hướng ngược lại, các team tuyệt vời luôn có những điếm nhấn cá nhân và đoàn thể riêng. Mọi người chờ mong cuộc họp, ai ai cũng chuẩn bị tốt hết sức có thể. Cuộc họp làm cho họ cảm thấy tốt, được đánh giá cao, có giá trị và có sự lắng nghe. Điều đó làm cả team có động lực, thoải mái và vui vẻ. Bạn sẽ dễ dàng tìm thấy sự hài hước của các thành viên trong các team như thế này.
Nếu bạn muốn trở nên tốt và tốt hơn khi làm việc với team, lắng nghe là kỹ năng số một mà bạn cần thành thạo. Hãy lắng nghe một cách tích cực và đưa ra những câu hỏi thông minh.
Trở thành người chủ động lắng nghe
Nếu người bạn muốn nói chuyện cảm thấy thông điệp của họ được truyền tải, họ được lắng nghe và thấu hiểu, lúc đó họ sẽ cởi mở hơn khi lắng nghe những câu chuyện của bạn.
Trở thành một người biết chủ động lắng nghe là cách để bạn có được những gì bạn muốn.
Nếu muốn nhận được từ ai đó thứ gì, thì mình cần phải sẵn sàng cho người khác thứ mà họ cần.
Nói cách khác, bạn cần lắng nghe họ nếu bạn muốn họ giúp bạn điều gì đó. Thậm chí, khi bạn biết lắng nghe, họ sẽ giới thiệu một giải pháp tốt hơn với cái bạn nghĩ đến. Điều đó luôn luôn có thể xảy ra.
Điều quan trọng không phải ai là người có cái tôi lớn hơn, hay ai sẽ là người chiến thắng. Đó chính là quá trình làm việc hiệu quả và học hỏi lẫn nhau để trở nên tốt hơn. Bạn không bao giờ đạt được chúng nếu bạn cứ khư khư nghĩ rằng mình đã có toàn bộ câu trả lời mình muốn.
Một số nghiên cứu cho thấy có khoảng 60% những người tự cho mình là đúng thường có xu hướng đạt hiệu quả kém. Sự tự tin thái quá của họ đã ngăn cản việc thấu hiểu động lực của đối tác cũng như cả sự thành công của họ.
Nếu bạn nghĩ bạn biết tất cả, bạn là người đang thua cuộc.
Tôi càng học được nhiều bao nhiêu thì tôi lại càng nhận ra được mình thiếu hiểu biết bấy nhiêu. – Albert Einstein
Cuộc trò chuyện cũng như vậy. Bạn có thể nghĩ rằng bạn hiểu đối phương đang muốn gì, hay thậm chí có thể diễn giải mọi điều họ nói như minh chứng cho sự tự tin của mình. Nhưng hành vi này người ta gọi là thành kiến xác nhận – confirmation bias.
Wikipedia định nghĩa nó như sau:
Thành kiến xác nhận là xu hướng tìm kiếm, giải thích, ủng hộ cũng như nhớ lại thông tin theo cách tiếp cận, tự củng cố hay giả thuyết của bản thân. Nó là một kiểu thành kiến nhận thức.
Không có gì đặt một mối quan hệ bên bờ vực nguy hiểm nhanh hơn sự kém cỏi trong lắng nghe đối phương. Không mất quá nhiều thời gian để người khác nhận ra được thành ý của bạn khi nghe họ nói.
Bạn có biết chỉ có 7% thông điệp là những lời bạn nói hay không? (Nghiên cứu của giáo sư tâm lý học UCLA, Albert Mehrabian, phát hiện ra rằng 7% thông điệp được lấy qua từ ngữ, 38% qua ngữ điệu và 55% qua biểu cảm khuôn mặt hoặc ngôn ngữ cơ thể.)
Rất khó để thuyết phục người khác bạn đang lắng nghe họ trong khi bản thân bạn đang bán đứng chính mình.
Có một ranh giới giữa mỏng manh giữa lắng nghe để trả lời hay thấu hiểu.
Khi chúng ta lắng nghe để trả lời, chúng ta thường sẽ chăm chú tìm kiếm những sai sót trong ngôn từ của đối phương. Ngay khi chúng ta phát hiện ra một vài điều gì đó, cuộc trò chuyện sẽ bị gián đoạn, chúng ta trở nên như những con sói vồ vập xông vào cắn xé đối phương để bảo vệ niềm tin của mình.
Chúng ta ai cũng đều có thể làm như thế, và tôi cũng vậy. Cách duy nhất để ngăn chặn điều đó là ý thức được về nó.
Bạn sẽ bỏ lỡ rất nhiều thông tin giá trị nếu bạn xem nhẹ vấn đề này.
Chúng ta có một số mẹo nhỏ để vượt qua vấn đề, và khiến bạn dần trở nên một người biết tích cực lắng nghe.
Giữ trạng thái tò mò khi lắng nghe người khác trình bày, đừng vội giả định và đưa ra kết luận
Khi người kia nói, hãy lặp lại vài từ cuối cùng của họ. Làm điều đó với một giọng điệu tò mò, và giữ im lặng. Chỉ cần lặp lại ba từ cuối cùng và im lặng. Lặp đi lặp lại nhiều từ hơn sẽ khiến đối phương nghĩ bạn không hiểu và bắt đầu giải thích điều họ đang nói.
Bắt đầu câu hỏi với “Cái gì – What” và “Như thế nào – How“. Làm cách nào để chúng ta giải quyết vấn đề này? Bạn đã áp dụng các bước như thế nào để đạt được điều đó?…
Tránh bắt đầu câu hỏi với mệnh đề “Tại sao – Why“. Bạn liệu có nhớ khi còn bé, mẹ bạn bắt đầu la hét bạn: “Tại sao con lại phá đồ của mẹ?”. Khi lớn lên, bạn quay ra hỏi đồng nghiệp của mình: “Tại sao bạn lại sử dụng kiến trúc này?”, “Tại sao bạn không học thêm về lập trình web?”… Những câu hỏi như vậy đem lại cảm giác như lời buộc tội, kể cả khi bạn có ý định tốt. Tốt hơn hết, bạn nên đặt câu hỏi nào đó như: “Thư viện mà anh chọn có gì khác với những thư viện khác không?”, hoặc “Kiến trúc ABC có đặc điểm nào tốt hơn kiến trúc XYZ mà chúng ta thảo luận trước đó không?”…
Đừng sử dụng “Tôi hiểu – I understand” khi người khác đang giải thích. Nó giống như một lối tắt kém cỏi để thuyết phục người khác rằng bạn đang hiểu đúng hướng.
Cụm từ “Tôi hiểu – I understand” hay “Tôi biết rồi – I know” thường thường sẽ đi kèm với mệnh đề “Nhưng – But”. Đại loại nó mang ý nghĩa “Tôi hiểu rồi nhưng bạn vui lòng lắng nghe những gì tôi nói sau đây…“.
Khi người khác nói với bạn “Tôi hiểu”, thì thực sự họ dường như không có khái niệm gì về những vấn đề của bạn. Họ chỉ muốn bạn ngừng nói để họ có thể cho chúng ta biết ý kiến của họ. Họ mong đợi chúng ta nghĩ rằng mình đã được lắng nghe, nhưng họ đã làm tổn hại cuộc giao tiếp hơn họ nghĩ. Chúng ta không cần thiết phải làm điều đó.
Nếu bạn muốn cho người khác biết bạn đang tập trung lắng nghe, hãy sử dụng các từ mang tính chất xác nhận ngắn hạn, như “OK”, “Uhm”, “Ah”, “Vâng/Dạ – Yes”… Hoặc sử dụng các cụm câu “Có vẻ như bạn đang muốn…”, “Hình như điều này đang khiến bạn…”, “Dường như bạn đang khá thất vọng về…”. Đây là những cụm từ mang ý nghĩa hên xui, bạn có thể đúng hoặc sai. Quan trọng nhất, nó khiến ngươi khác thoải mái, do đối tượng hướng đến ở đây là họ, không phải nói về bạn. Thử đi rồi họ sẽ đưa lượt cho bạn.
Tóm tắt, tóm tắt, và tóm tắt. Sau khi bạn đã có được một bức tranh cơ bản về vấn đề và quan điểm của họ, bước tiếp theo là lặp lại cho họ hiểu “thế giới” của họ như thế nào.
Bạn sẽ đến lượt phát biểu của mình sau khi người khác nói với bạn: “Đúng rồi đấy – That’s right!”. Khi họ nói như vậy, bạn biết rằng bạn đã chốt và hiểu được một chút. Nhưng đừng nhầm lẫn khi họ nói “Anh đúng rồi – You’re right!”. Khi họ nói vậy có nghĩa là họ đang muốn thoát khỏi cuộc trò chuyện, và họ muốn bạn để họ một mình.
Khi bạn đã thể hiện được kỹ năng lắng nghe tích cực của mình, đây là lúc bạn bắt đầu trình bày quan điểm của bạn về vấn đề này. Và khi đối phương cảm thấy được bạn đã lắng nghe và hiểu được quan điểm của họ, họ sẽ sẵn sàng lắng nghe bạn hơn.
Họ cũng sẽ cởi mở hơn để hợp tác vào cuộc trò chuyện, thậm chí nhận định rằng cách của bạn là tốt nhất. Đó là mục đích của cuộc giao tiếp khi mà thành quả mọi người đạt được sẽ là “Win – Win”.
Trong thế giới lập trình, trách nhiệm lớn nhất của chúng ta không phải chỉ làm cho code chạy được, mà còn phải đảm bảo rằng các đoạn code mà chúng ta viết có thể dễ dàng kiểm tra và bảo trì trong một khoảng thời gian dài.
Tạm biệt dirty code trong lập trình JS
Khi chúng ta bước chân vào thế giới lập trình, chúng ta có thể thấy được những điều hữu ích mà nó đem lại cho hàng triệu người. Chỉ bằng việc thao tác với các đoạn code, lập trình đã giúp cho cuộc sống của chúng ta trở nên dễ dàng hơn.
Tuy nhiên, “năng lực lớn đi đôi với trách nhiệm lớn“. Trong thế giới lập trình, trách nhiệm lớn nhất của chúng ta không phải chỉ làm cho code chạy được, mà còn phải đảm bảo rằng các đoạn code mà chúng ta viết có thể dễ dàng kiểm tra và bảo trì trong một khoảng thời gian dài.
Có một số thói quen nhỏ trong lập trình có thể gây tác động tiêu cực liên tục đến code mà chúng ta viết và sản phẩm mà chúng ta tạo ra. Mình đã trải nghiệm những vấn đề này trực tiếp.
Hôm nay mình sẽ chia sẻ những vấn đề này và lý do tại sao bạn nên tránh chúng bằng mọi giá.
1. Sử dụng var thay vì let và const
Bạn nên chỉ sử dụng let và const bởi một vài lý do sau:
Scope rõ ràng hơn.
Nó không tạo ra các đối tượng global.
Với const – nó hiển thị lỗi ngay khi chúng ta cố gắng khai báo lại một biến.
// Sử dụng var:var x =10;if(true){var x =20;}
console.log(x);// Output: 20// Khi chúng ta sử dụng var, biến có thể được khai báo lại và ghi đè giá trị của nó trong cùng phạm vi.// Sử dụng let:let y =10;if(true){let y =20;}
console.log(y);// Output: 10// Khi chúng ta sử dụng let, biến chỉ có thể được khai báo lại trong cùng khối lệnh và không ghi đè giá trị của nó ở ngoài khối lệnh đó.// Sử dụng const:const z =10;
z =20;// Error: Assignment to constant variable.// Khi chúng ta sử dụng const, biến không thể được khai báo lại và không thể được ghi đè giá trị của nó.
Kể cả khi bạn muốn code của mình hoạt động ổn định với các trình duyệt cũ như IE11 thì bạn cũng không nên vứt bỏ nó. Hãy sử dụng let/const kèm với polyfill. Tuy vậy, 2023 rồi ai mà xài IE cũ nữa đâu, cả Microsoft cũng đã có kế hoạch xoá sạch IE rồi 😀
2. Dùng comments để mô tả code
Comments (hay chú thích) là một phần cơ bản trong quá trình xây dựng phần mềm, nó giúp chúng ta hiểu rõ hơn về đoạn mã mà chúng ta đang đọc.
Tuy nhiên, chúng ta không nên mắc phải sai lầm khi giải thích từng bước mà code của chúng ta đang làm, mà chúng ta phải tạo ra code dễ đọc. Comments chỉ nên cung cấp context (bối cảnh).
Tránh sự lặp lại trong comments của bạn. Đừng viết những gì bạn đang làm, hãy viết lý do tại sao bạn làm nó.
Hãy đặt các tên biến/function/class mô tả một cách rõ ràng công việc của chúng, thay vì ngồi viết một đống comments.
Hãy cố gắng viết code rõ ràng và clean hết sức có thể. Hãy nhớ, không phải code càng ngắn càng tốt, mà code rõ ràng, sạch sẽ, dễ thay đổi, dễ quản lý mới là tốt.
Viết comments rõ ràng, tốt nhất là cùng một ngôn ngữ (tiếng Anh, tiếng Việt…). Đừng viết chỗ này tiếng Anh, chỗ kia tiếng Việt, chỗ khác lại phang tiếng Trung Quốc vô 😀
Comments nên súc tích, gọn gàng. Theo thời gian, comments thường không được bảo trì, code lại thay đổi thường xuyên.
3. Sử dụng so sánh bằng (==) thay vì so sánh bằng nghiêm ngặt (===)
Mặc dù chúng có vẻ rất giống nhau về hình thức, tuy nhiên chúng lại có những điều khác nhau về cách hoạt động.
Toán tử so sánh bằng (==) sẽ cố gắng chuyển đổi các phần tử so sánh về cùng kiểu, sau đó thực hiện so sánh xem có giống nhau hay không. Điều này có thể gây ra một vài lỗi vớ vẩn không đáng có.
So sánh bằng nghiêm ngặt (===) luôn kiểm tra xem các toán hạng có các kiểu dữ liệu và giá trị khác nhau hay không.
Nên tránh sử dụng toán tử so sánh bằng (==) vì nó có thể gây ra các kết quả không mong muốn khi các phần tử so sánh có kiểu dữ liệu khác nhau. Nếu bạn sử dụng toán tử so sánh bằng nghiêm ngặt (===), bạn sẽ có thể kiểm tra xem các phần tử so sánh có giống nhau hoàn toàn không, bao gồm cả kiểu dữ liệu của chúng.
let x =5;let y ="5";
console.log(x == y);// true
console.log(x === y);// false
console.log([]==0);// true
console.log([]===0);// false
4. Không sử dụng optional chaining
Toán tử optional chaining (?) cho phép chúng ta đọc giá trị của một thuộc tính nằm sâu trong chuỗi các đối tượng liên kết mà không cần phải kiểm tra từng tham chiếu trong chuỗi đó.
Chúng ta nên sử dụng optional chaining trong các tình huống khi chúng ta không chắc chắn rằng một thuộc tính nào đó tồn tại trong đối tượng hoặc nó có giá trị là gì.
Sử dụng optional chaining giúp chúng ta tránh được những lỗi không mong muốn khi truy cập vào một thuộc tính không tồn tại, đồng thời cũng làm cho mã của chúng ta trở nên ngắn gọn và dễ đọc hơn.
6. Truyền nhiều params vào function hay một single object chứa các params?
Việc truyền nhiều tham số hay truyền một đối tượng chứa nhiều tham số vào một hàm sẽ phụ thuộc vào từng trường hợp cụ thể và cách tiếp cận lập trình của từng người.
Tuy nhiên, khi truyền một đối tượng chứa nhiều params vào một hàm thì có thể giúp cho code dễ đọc hơn và dễ bảo trì hơn.
Ví dụ, việc truyền một đối tượng options vào hàm render() của một component có thể giúp cho việc hiểu rõ hơn những thiết lập khác nhau được truyền vào thành phần đó. Điều này thậm chí còn tốt hơn nếu chúng ta dùng kèm với Typescript.
Ngoài ra, việc truyền một object có thể giúp tránh tình trạng quên truyền params hoặc truyền params sai vị trí. Tuy nhiên, khi quá nhiều tham số được truyền vào một object, code có thể trở nên khó hiểu và khó bảo trì.
Do đó, cần cân nhắc và sử dụng phương pháp phù hợp với từng trường hợp cụ thể để có được code dễ đọc, dễ bảo trì và dễ mở rộng.
Chúng ta đã từng gặp tình huống kiểm tra xem một biến có tồn tại hay không hoặc nó có chứa giá trị khác với null hoặc undefined không. Vì thế, chúng ta thường phải thực hiện kiểm tra rất dài như thế này:
if(x !==''&& x !==null&& x !==undefined){// Do something}
Có một cách viết đơn giản và trang nhã hơn như sau:
if(!!x){// Do something}
Clean hơn nhiều đúng không 😀
Túm cái váy lại
Viết code sạch luôn là trách nhiệm của chúng ta. Mình đã học được rằng việc có một code dễ bảo trì và dễ đọc sẽ tiết kiệm được rất nhiều giờ làm việc cho bạn và team của bạn.
Hãy nhớ rằng chúng ta dành nhiều thời gian để đọc code hơn là viết code. Mình hy vọng những mẹo nhỏ này sẽ giúp cho bạn tạo ra những sản phẩm tuyệt vời và kỳ diệu.
Bài viết được sự cho phép của tác giả Lê Nhật Thanh
Target của bài viết: Những bạn muốn trở thành Senior backend developer hoặc mong muốn tìm hiểu CQRS là gì.
Một số thuật ngữ mình dùng trong bài viết các bạn có thể search thêm cụ Google để nắm hơn:
High availability: Một hệ thống website có tính sẵn sàng cao. Hoạt động liên tục 24/7 trong mọi điều kiện, kể cả khi có sự cố xảy ra.
High consistency: Tính nhất quán về dữ liệu, tất cả request của user đều thấy được dữ liệu mới nhất nếu nó được update.
Trade-off: Đánh đổi khi thiết kế hệ thống lớn
GIỚI THIỆU MỘT CHÚT TRƯỚC KHI TÌM HIỂU CQRS LÀ GÌ
Bài viết này rất dài, có thể sẽ mất của bạn vài chục phút đến vài giờ để đọc và hiểu nội dung. Nhưng nó sẽ rất đáng nếu bạn bỏ ra từng ấy thời gian vì những gì bạn sẽ thu lại được. Mình đã cố gắng viết nó ra thì bây giờ đến lượt bạn, cố gắng đọc hiểu nó và góp ý thêm cho mình nha.
Let’s go!
ĐIỂM XUẤT PHÁT: MÔ HÌNH TRUYỀN THỐNG VÀ THÁCH THỨC
Trước khi bắt đầu khám phá CQRS là gì, hãy xem xét một ứng dụng ví dụ sử dụng mô hình truyền thống (ví dụ MVC). Chúng ta sẽ sử dụng một dự án Rest API service với Java và Spring Boot framework.
MÔ HÌNH TRUYỀN THỐNG (VÍ DỤ SỬ DỤNG MVC)
Ở mức đơn giản, chúng ta thường có một cấu trúc thư mục quen thuộc với các tầng như Controller, Service, Repository và Model.
Trước khi vào bài thì mình có một yêu cầu nhỏ. Các bạn phải từng làm qua một framework backend rồi nha. Ví dụ NestJs, Laravel, .Net MVC hay gì cũng được. Hoặc các bạn đã nắm được về kiến trúc MVC. Hiểu thế nào là view, model, controller, repository, service, …
Trong bài viết này, mình sẽ lấy ví dụ với Java (Spring boot framework). Các bạn cũng đừng lo lắng. Vì nếu các bạn nắm được MVC rồi hoặc 1 trong các backend framework. Thì chúng ta cũng không cần quan tâm tới ngôn ngữ mình thể hiện trong bài viết nữa. Đừng quá phụ thuộc vào chúng, hãy cố gắng nắm được nội dung và hiểu nó.
Chúng ta sẽ tập trung vào tầng Model trước! Nếu bạn đã làm việc với hệ thống Backend thì bạn cũng đã thao tác rất nhiều với các Model.
À, từ đây đến hết bài viết, mình sẽ chia các HTTP request thành 2 loại:
Write side – hay còn gọi là Command side: Bao gồm tất cả các request chỉnh sửa vào database (POST, PUT, DELETE request)
Read side – hay còn gọi là Query side: Bao gồm tất cả các request dùng để lấy dữ liệu từ database (GET request)
Lưu ý là mình sẽ sử dụng 2 từ read side và write side trong xuyên suốt bài viết.
ĐI VÀO CODE MỘT CHÚT
Ok, mình sẽ lấy ví dụ triển khai một ứng dụng web báo điện tử. Ví dụ như VnExpress hay 24h,…
Giả sử mình có một model Article. Và khi các bạn thực hiện các API như createArticle, updateArticle, deleteArticle, getArticleById, getAll, … thì bạn vẫn cứ phải thao tác với model Article.
Ví dụ vào code nha. Lưu ý là chúng ta chỉ quan tâm hàm create thôi nha.
Chúng ta có một cấu trúc thư mục cực kì quen thuộc với hầu hết các bạn.
Các bạn có thể đọc lướt qua cũng được. Vì các bạn đã quá quen thuộc với kiểu viết API backend như vậy.
// ArticleController.java@RestController@RequestMapping("/api/v1/articles")@AllArgsConstructorpublicclassArticleController{privateArticleServiceInterface articleService;@PostMapping@ResponseStatus(HttpStatus.CREATED)publicvoidcreate(@RequestBodyArticleDto article){
articleService.create(article);}// Một số API khác như update, delete, getAll, getById, getByAuthor, ....}
// ArticleService.java@AllArgsConstructor@ServicepublicclassArticleService{privateArticleRepository articleRepository;privateUserRepository userRepository;publicvoidcreate(ArticleDto articleRequest){Optional<User> user
= userRepository.findById(articleRequest.getUserId());if(user.isEmpty()){thrownewUserNotFoundException("APPLICATION-ERROR-0001");}Article article =newArticle(
UUID.randomUUID().toString(),
articleRequest.getTitle(),
articleRequest.getContent(),
user.get().getId(),
articleRequest.getSummary(),
articleRequest.getThumbnail(),
articleRequest.getSlug(),// ...);
articleRepository.save(article);}// Một số API khác như update, delete, getAll, getById, getByAuthor, ....}
Repository trong Spring Boot thì khá lạ. Vì bạn chẳng thấy các method CRUD (create, read, update, delete) đâu cả. Vì ở đây Repository đang được kế thừa JpaRepository của Spring Data JPA trong Spring Boot. Và trong interface đó đã có hết toàn bộ những method CRUD và mở rộng hơn.
Bây giờ chúng ta sẽ đi qua model chính trong ứng dụng đơn giản này.
// ArticleEntity.java@AllArgsConstructor@NoArgsConstructor@Data@Builder@Entity@Table(name="articles")publicclassArticleEntityimplementsSerializable{@Serialprivatestaticfinallong serialVersionUID =6009937215357249661L;@Id@Column(nullable =false, unique =true, length =100)privateString id;@Column(nullable =false)privateString title;@Column(columnDefinition ="TEXT", length =20000)privateString content;@ManyToOne(fetch =FetchType.LAZY)privateUserEntity user;@Column(columnDefinition ="TEXT", length =1000)privateString summary;@Column(nullable =false, unique =true)privateString thumbnail;@Column(nullable =false, unique =true, length =100)privateString slug;// Một số properties khác nữa// ...// Bên dưới là business logic// Ví dụ: Slug chỉ được phép chứa các kí tự a-z, A-Z và dấu _}
Ở trên đây, model Article của chúng ta làm tới 2 nhiệm vụ là mapping với một ORM để thao tác trực tiếp với database table. Và nhiệm vụ thứ 2 là lưu trữ state (trạng thái) của hệ thống. Ở đây là lưu trữ state của một Article trong vòng đời của một request.
Model trong một ứng dụng enterprise sẽ làm khá nhiều nhiệm vụ:
Lưu trữ trạng thái (như mình nói ở trên)
Mapping với database (cũng giống mình nói ở trên)
Cái tiếp theo là thông thường trong model sẽ chứa business logic. Mà business logic của những model lớn thường sẽ rất phức tạp. Về thuật ngữ business logic (hay domain logic, hay nghiệp vụ) thì các bạn có thể search google để biết nha. Cái này cũng khá là dễ hiểu.
Rõ ràng chúng ta chỉ có một model Article. Vì vậy các thao tác read side và write side đều phải thao tác qua model này.
Nghĩa là một model sẽ phải chứa tất cả business logic cho cả CRUD. Và khi ứng dụng càng lớn, business logic càng nhiều. Và thay đổi càng nhiều thì sẽ khiến cho model càng ngày càng phức tạp. Kể cả các tầng khác như Controller, Service, hay Repository cũng sẽ phức tạp hơn rất nhiều. Codebase lúc này sẽ phình to ra và dẫn tới việc bảo trì và mở rộng khó khăn hơn.
ĐẶC BIỆT LÀ KHI BẠN SỬ DỤNG ORM ĐỂ TƯƠNG TÁC VỚI DATABASE
Write side: Khi bạn thực hiện các API write side (create, update, delete) thì bạn dùng ORM để mapping database khá là đơn giản. Ví dụ create thì lưu 1 lần xuống toàn bộ thông tin của entity (ứng dụng lớn thì lưu cả cụm aggregate). Vì vậy thao tác về mặt code sẽ khá tường minh và đơn giản (cho dù có liên kết n-n hay 1-n,…).
Read side: Câu này mình sẽ nhắc nhiều trong bài viết: Ở read side thì số lượng use case (có thể coi 1 use case là 1 API đi cho dễ) sẽ vô cùng nhiều và đặc biệt khó dự đoán được. Ví dụ một số use case
Lấy danh sách bài viết cùng với summary để hiển thị ở khu vực recommend hoặc homepage (chỉ lấy title, thumbnail, summary)
Lấy danh sách bài viết của một tác giả (chỉ lấy title, thumbnail, summary)
Lấy danh sách bài viết trong tháng này.
Lấy danh sách bài viết có lượng truy cập cao trong (ngày, tuần, tháng, …)
Lấy danh sách bài viết theo category, kết hợp với ngày tháng, tác giả, …
v/v (còn rất nhiều use case khác nữa)
CHO NÊN
Khi bạn dùng ORM đối với rất nhiều use case như vậy và tương tác với chỉ một entity Article thì toang. Business logic trong model này sẽ trở nên phức tạp. Chưa nói là phải join tùm lum bảng (kết hợp nhiều entity). Và chưa nói tiếp tới vụ dư thừa dữ liệu và phức tạp khi join các bảng mà dùng ORM.
Và code base ở các file service, controller, repository cũng sẽ trở nên phức tạp theo. Vì có nhiều use case mà như mình có nói hồi nảy.
Và khi đó, đôi khi chúng ta phải thực hiện query thuần cho những API bên read side.
DƯ THỪA DỮ LIỆU
Trước khi mình nói về vấn đề dư thừa dữ liệu. Thì mình muốn nói thêm một ý khá quan trọng:
Write side chúng ta thường tao tác với hầu hết các field (trường dữ liệu) trong model (entity). Ví dụ tạo một article thì chúng ta sẽ thao tác gần như tất cả các field trong một entity. Nhưng read side thì không như vậy, đôi khi chỉ thao tác với một vài trường dữ liệu trong một entity. Hoặc kết hợp nhiều entity với nhau (join nhiều bảng một lần) – như mình nói ở các use case read site ở trên.
Ngoài ra thì hầu hết các hệ thống web application, các request read side thường chiếm phần lớn trong hệ thống. Ví dụ một trang thương mại điện tử, người bán hàng tạo một sản phầm và publish nó (write side). Và người bán chỉ tạo một lần đó thôi, còn lại hầu hết các request từ user khác đến sản phẩm đó là read side, ví dụ xem chi tiết sản phẩm, sản phẩm tương tự, search, …
Cũng vì lý do read side và write side đều được xử lý thông qua một model duy nhất. Nên đôi khi sẽ làm chúng ta bị dư thừa dữ liệu, đặc biệt ở read side.
Giả sử ArticleEntity ở trên có khoảng vài chục trường dữ liệu (ở trên thì mình chỉ lấy ví dụ có vài trường dữ liệu thôi). Thì khi chúng ta thực hiện tạo mới một article, thì rõ ràng khá bình thường, chúng ta sẽ lưu cả entity xuống database (cùng với đó là thêm thông tin userId để biết đang của tác giả nào).
Nhưng khi ở read side, giả chúng ta chỉ lấy vài trường dữ liệu để hiển thị. Nhưng chúng ta phải query hết toàn bộ dữ liệu rồi map vào entity. Và rồi loại bỏ dữ liệu dư thừa rồi trả ra view cho user. Và như mình nói thì ở read side rất đa dạng các kiểu get dữ liệu lên. Mà mỗi lần get dữ liệu lên lại phải get hết thông qua entity thì bị dư thừa (còn tạo nhiều model khác ứng với mỗi trường hợp get dữ liệu thì codebase sẽ loạn lên). Hoặc là kiểu join nhiều bảng, ráp một ít dữ liệu của entity này và một ít dữ liệu của entity khác. Tình trạng dư thừa dữ liệu khi get data lên (rồi mapping thông qua ORM) là rất nhiều.
KHẢ NĂNG CHỊU TẢI KÉM
Chúng ta chỉ thao tác với một database duy nhất nên việc khả năng chịu tải kém lá đúng. Đột nhiên một lúc nào đó, hàng triệu request read side tới hệ thống thì hệ thống sẽ tạch ngay. Cho dù bạn có scale hệ thống bằng cách như tăng cấu hình phần cứng, load balancing, … thì request cao vẫn sẽ tạch, vì database connection lúc này rất nhiều. Database lúc này sẽ trở thành điểm bottlenecks (nút thắt cổ chai).
Ở ngay đây (hơi ngoài lề một chút), nhiều bạn sẽ cho rằng, chúng ta sẽ dùng một cache server để handle.
Và thông thường các bạn chưa có kinh nghiệm sẽ triển khai một ứng dụng với Cache database như thế này.
Ở read side, khi có request tới cache, nếu không có trong cache (miss cache) thì chui xuống database để lấy thông tin. Cách này, cũng tạm được, sẽ giảm tải được khá nhiều cho database, nhưng ở ngoài hệ thống lớn, người ta không làm như vậy.
Lý do là vì: khi hàng triệu use truy xuất tới một endpoint bị miss cache và đều chui xuống database thì trường hợp này cũng sẽ tạch. Ví dụ mấy sàn thương mại điện tử vào dịp 9/9 hay 10/10 tổ chức flash sale. Thì các trường hợp hàng triệu user truy cập vào một endpoint là điều bình thường.
Cho nên triển khai như vậy sẽ không hợp lý đối với một hệ thống real world.
Nói cách dễ hiểu hơn! Thì read side và write side được tách biệt hoàn toàn không còn dính tới nhau nữa. Khác hoàn toàn với cách xử lý truyền thống ở trên.
Và bên write side người ta gọi là command, bên read side người ta gọi là query.
Biết được CQRS là gì rồi, tiếp thôi nào!
LỢI ÍCH CỦA CQRS LÀ GÌ?
Tăng khả năng mở rộng: CQRS cho phép các command và query được xử lý riêng biệt. Điều này có thể giúp tăng khả năng mở rộng của hệ thống.
Tăng hiệu suất: CQRS có thể giúp tăng hiệu suất của hệ thống bằng cách cho phép các command và query được xử lý theo cách tối ưu nhất cho từng loại.
Tăng khả năng bảo trì: CQRS có thể giúp tăng khả năng bảo trì của hệ thống bằng cách tách biệt các command và query. Điều này có thể giúp các anh dev dễ dàng sửa đổi hoặc nâng cấp hệ thống hơn.
Tăng khả năng tái sử dụng: CQRS có thể giúp tăng khả năng tái sử dụng của hệ thống bằng cách tách biệt các command và query. Điều này có thể giúp các dev dễ dàng reuse lại các thành phần trong hệ thống
Những lợi ích ở trên đây mình chỉ tóm tắt thôi.
Để biết chi tiết hơn về lợi ích của CQRS là cái gì. Thì chúng ta đi sâu hơn một chút nhé.
ĐƠN GIẢN HOÁ CODEBASE GIÚP HỆ THỐNG DỄ BẢO TRÌ VÀ MỞ RỘNG
Nếu doanh nghiệp nghèo chỉ có tiền thuê được một server database. Thì chúng ta vẫn sẽ áp dụng CQRS để đơn giản hoá codebase trước.
Bây giờ chúng ta sẽ tổ chức cấu trúc thư mục lại áp dụng CQRS nha. Tổ chức cấu trúc thư mục là hoàn toàn linh hoạt đối với từng project và từng team. Nên đối với CQRS sẽ có khá nhiều cách tổ chức cấu trúc thư mục. Mình thì muốn tách ra hoàn toàn thành 2 phần khác nhau.
Các bạn có thể tách ra thành 2 repository (github) hoàn toàn tách biệt luôn.
com.lenhatthanh.blog.query/|-- controller/||--ArticleController.java (all get requests)| `--...|-- service/||--ArticleService.java
| `--...|-- repository/||--ArticleRepository.java (ở đây có thể sử dụng query thuần)| `--...
`-- model/|--Các Model tương ứng với từng use case
`--...
Khi apply CQRS, số lượng file có thể sẽ tăng lên, nhưng đó không phải là vấn đề. Vì lúc này code base đã được tách ra thành 2 phần riêng biệt. Code base lúc này rất dễ bảo trì và mở rộng.
TỐI ƯU HÓA CODE BASE WRITE SIDE
Đương nhiên rồi! chúng ta sẽ sử dụng ORM ở write side là quá tuyệt vời (dễ code, dễ bảo trì và cực kì tường minh).
Và đối với ứng dụng enterprise, chúng ta có thể dùng Domain Driven Design để có thể tập trung toàn bộ business phức tạp lại một chổ (ở core domain). Để ứng dụng có thể dễ dàng mở rộng về mặt codebase khi business thay đổi hoặc tăng lên phức tạp. Ngoài ra chúng ta có thể kết hợp thêm Clean Architecture để có một cấu trúc code base tốt hơn. Nhưng không phải bạ đâu cũng dùng nha các bạn.
Ngoài ra ORM còn giúp chúng ta nhiều thứ hơn như về ngăn chặn các vấn đề security (như SQL injection,…).
TỐI ƯU HÓA CODE BASE READ SIDE
Như mình đã nói thì số lượng request và use case ở đây cực kì phức tạp. Nên chúng ta có thể dùng query thuần (không dùng ORM) ở phần read side (trong trường hợp ứng dụng của bạn chỉ có một database ví dụ MySQL). Khi sử dụng query thuần thì code sẽ đở phức tạp đi rất nhiều so với dùng ORM ở read side. Bạn có thể thử dùng ORM và join nhiều bảng cùng với mỗi bảng thì lấy một ít dữ liệu sẽ thấy ngay vấn đề.
Sau khi tối ưu về mặt codebase như trên thì chúng ta sẽ có một thiết kế như sau:
Nhưng trên thực tế. Nếu đã apply CQRS thì ít ai chỉ tối ưu về mặt codebase mà không tối ưu về mặt database và server. Vì tối ưu về khả năng chịu tải là một vấn đề lớn nếu apply kiến trúc ở trên.
SCALE VỀ MẶT DATABASE VÀ SERVER KHI SỬ DỤNG CQRS
Khi sử dụng CQRS pattern, chúng ta có thể tách riêng hoàn toàn ra 2 server riêng biệt là Write side server và read side server. Và ứng với mỗi side, chúng ta sẽ có database riêng biệt.
SCALE ĐỘC LẬP VỚI NHAU NẾU LƯỢNG REQUEST TĂNG LÊN ĐỘT BIẾN
Bởi vì thông thường lượng request ở Read side chiếm phần lớn, nên bạn sẽ dễ dàng scale server (phần cứng hoặc instance nếu sử dụng cloud services) ở Read side. Ví dụ tăng cấu hình server lên hay tăng số lượng server lên. Hoặc sử dụng database chuyên dụng cho read data như Redis, mongoDB
Còn bên write side thì cũng có thể scale độc lập như vậy.
TỐI ƯU HOÁ PHẦN CỨNG CHO MỖI SERVER
Chúng ta có thể lắp các loại ổ cứng, ram chuyên cho đọc hay ghi tương ứng ở mỗi server read hay write (on-premise). Còn nếu chúng ta sử dụng cloud service có thể dễ dàng lựa chọn các service phù hợp cho mỗi side của ứng dụng.
SAU KHI SCALE XONG THÌ CHÚNG TA SẼ CÓ DESIGN SAU
Ở hình trên mình đã dùng Kafka (một message queue) để có thể sync data bất đồng bộ từ write side sang read side.
Và ví dụ bên dưới sẽ cho các bạn thấy mình sẽ dễ dàng scale hệ thống như thế nào. Ở design dưới thì mình scale về database là chính.
Mình có design một message queue (mình dùng Kafka) ngay trước khi ghi dữ liệu vào write database. Bởi vì lý do là nếu chạy đồng bộ (sync) thì ngay write database sẽ dễ trở thành một nút thắt cổ chai khi lượng request write tăng đột biến.
Ở write database thì mình dùng một Replica database. Và coi như là một backup trong trường hợp server bị chết.
Một message queue để đồng bộ dữ liệu từ write database sang read database.
Ở read database nếu lượng request quá nhiều và dữ liệu quá lớn. Thì mình có thể design nó thành một hệ thống database distributed (phân tán) để chịu tải tốt hơn và high availability hơn.
Ngoài ra chúng ta cũng có thể sử dụng K8S auto scaling instance cho ứng dụng của mình đặc biệt bên read side cho hệ thống ở trên.
Tới đây thì chúng ta đã biết CQRS là cái gì rồi. CQRS hay như thế đấy! Nhưng vẫn sẽ có một vài điểm đánh đổi (trade-off). Chúng ta cùng tìm hiểu tiếp.
VẤN ĐỀ KHI ÁP DỤNG CQRS
TÍNH NHẤT QUÁN VỀ DỮ LIỆU (DATA CONSISTENCY)
Đây chính là một loại đánh đổi (trade-off) khi chúng ta triển khai các hệ thống lớn.
Đây có thể xem là vấn đề lớn nhất khi áp dụng CQRS. Hay những pattern khác mà sử dụng event driven architecture (sử dụng Kafka đó bạn). Bởi vì data được sync bất đồng bộ từ write side lên read side nên dữ liệu sẽ không nhất quán (inconsistency).
Khi dữ liệu được tạo hoặc update bên write side. Thì đôi khi phải mất một khoảng thời gian nhất định thì dữ liệu đó mới sync qua được read side. Nhất là trong những lúc cao điểm, request tăng cao dẫn tới server chịu tải nhiều hơn.
BÂY GIỜ CHÚNG TA SẼ NÓI MỘT CHÚT VỀ TRADE-OFF
Nếu chúng ta muốn dữ liệu consistency (nhất quán). Nghĩa là dữ liệu bên write side update sao thì tất cả các request bên read side luôn trả về dữ liệu mới nhất. Thì ở đây chúng ta có thể sử dụng cơ chế sync (đồng bộ) thay vì async (bất đồng bộ). Ví dụ như sử dụng Rest API để sync dữ liệu luôn. Như vậy chúng ta sẽ có dữ liệu high consistency. Nhưng chúng ta sẽ phải đánh đổi thời gian response ở write side. Và khi lượng request tăng cao có thể dẫn tới server quá tải và không thể handle request. Dẫn tới tính availability sẽ thấp xuống.
Và đương nhiên ngược lại, nếu chúng ta muốn high availability thì phải đánh đổi với consistency.
Tùy vào business của hệ thống mà chúng ta (là những engineer) lựa chọn về việc đánh đổi khi triển khai hệ thống lớn.
Đối với một số hệ thống không cần yêu cầu về tính chất consistency tuyệt đối như báo điện tử (24h, VnExpress), media,… Chúng ta có thể đặt tính availability lên trên consistency và có thể sử dụng design ở hình trên. Lúc này chúng ta sẽ có khái niệm eventual consistency (nghĩa là không hoàn toàn nhất quán).
PHỨC TẠP HOÁ VÀ CHI PHÍ CAO
Câu mình muốn nói là: Không phải lúc nào cũng apply CQRS nếu không muốn tăng độ phức tạp của project.
Các bạn có thể thấy những hình ở trên, để triển khai một hệ thống lớn áp dụng CQRS và event driven architecture (và những pattern khác). Thì mọi thứ sẽ phức tạp hơn rất nhiều từ việc nhân sự, đội ngũ, đến cách tổ chức code. Và đặc biệt là hệ thống server sẽ nhiều hơn.
Hệ thống server nhiều (service nhiều) sẽ đi kèm với độ phức tạp cũng tăng lên rất nhiều. Như khi chúng ta monitor, xử lý, khôi phục hệ thống khi lỗi, … Vâng, và đó cũng là một kiểu trade-off cho chúng ta.
Thiết kệ hệ thống lớn, chúng ta phải đưa ra lựa chọn!
TÚM CÁI VÁY LẠI!
Trong hành trình khám phá CQRS là gì này, chúng ta đã thấy những ưu và nhược điểm của mô hình này. CQRS không phải là một giải pháp đối với tất cả các ứng dụng. Nhưng nó có thể là lựa chọn mạnh mẽ cho những ứng dụng đòi hỏi hiệu suất cao và tính mở rộng.
Nhớ rằng, quá trình triển khai CQRS có thể phức tạp, nhưng nếu được thực hiện đúng cách. Nó có thể đem lại lợi ích lâu dài cho sự phát triển và bảo trì của ứng dụng. Hãy cân nhắc kỹ lưỡng và kiểm soát mỗi quyết định để đảm bảo rằng CQRS phản ánh đúng nhu cầu của dự án của bạn.
Chúc các bạn thành công trong việc xây dựng các hệ thống mạnh mẽ và linh hoạt!
À còn một ý nữa. Khi bạn search những bài viết kiểu CQRS là gì trên google. Thì nó ra khá nhiều các title như Event sourcing, Event Driven Architecture, DDD… Các bạn có thời gian thì tìm hiểu thêm nha. Mình chỉ muốn trình bày only CQRS là cái gì trong bài viết này thôi.