Giới thiệu đến bạn công nghệ JSF – một hợp phần trong hệ sinh thái JavaEE [1]. Phiên bản mới nhất hiện tại của JavaEE là phiên bản 7, phiên bản mới nhất của JSF (JavaServer Faces) là 2.2, được phát triển theo JSR344 [2]. JSF 1.0 được phát hành lần đầu vào ngày 11/03/2004, dùng để xây dựng các hợp phần (component) để xây dựng giao diện đồ họa người dùng (GUI) cho ứng dụng Java web. Các công nghệ WinForm, ZK Framework, GWT, Vaadin, JavaFX đều sử dụng cơ chế hợp phần (component) lắp ghép lại với nhau, JSF cũng vậy. Khi sử dụng JSF, bạn có thể khai thác tính năng AJAX phong phú, đơn giản, dễ dùng.
JavaEE là bộ đặc tả do Oracle cùng cộng đồng cùng xây dựng, JSF là công nghệ nằm trong bộ JavaEE do đó JSF là một công nghệ tiêu chuẩn và được hỗ trợ chính thức. JSF chạy được trên các JavaEE-compliance application server ( máy chủ tuân thủ bộ tiêu chuẩn JavaEE, như Oracle WebLogic, IBM WebSphere, GlassFish, v.v..).
Có nhiều JSF framework cho bạn lựa chọn
Hiện tại công nghệ JSF có rất nhiều framework, đó là:
Mojarra JavaServer Faces (cung cấp bởi Oracle [3])
PrimeFaces ( cung cấp bởi PrimeTek – một công ty phần mềm Brazil [4])
RichFaces (cung cấp bởi JBOSS – Redhat [5])
MyFaces Trinidad (cung cấp bởi Apache Software Foundation [6])
OmniFaces (được phát triển chính bởi Arijan Tijims, Bauke Scholtz, Jan Beernink [7])
ICEfaces (phát triển bởi ICEsoft [8])
Có vài framework nữa tuy nhiên không phổ biến nên chúng tôi không liệt kê thêm. Các JSF framework kể trên đều có phiên bản miễn phí. Thực tế sử dụng trên thế giới, cũng như tại Việt Nam, trong các JSF framework kể trên, thì phổ biến hơn cả là PrimeFaces, sau đó đến RichFaces.
Ứng tuyển các vị trí việc làm Java lương cao trên TopDev
JSF là ca nhạc, PrimeFaces là ca sỹ
Tác giả bài viết cố gắng trả lời cho bạn đọc một câu hỏi phổ biến khiến lập trình viên bối rối. Sự khác nhau giữa JSF và RichFaces là gì? Sự khác nhau giữa ICEfaces và JSF là gì?
Câu nói trên là cách dễ nhất để giải thích cho bạn về sự khác nhau giữa JSF và hàng loạt framework JSF đang tồn tại. JSF là công nghệ, còn PrimeFaces là sản phẩm cụ thể hóa công nghệ JSF. Bạn hãy liên tưởng, có công nghệ sản xuất động cơ đốt trong 4 thì, và Honda Air Blade là một sản phẩm cụ thể hóa công nghệ đó. Nếu JSF là ca nhạc, thì RichFaces, ICEfaces cũng là ca sỹ, v.v..
JSF khá dễ dùng, linh hoạt. Giả sử SmartJob sử dụng PrimeFaces để làm biểu mẫu nhập thông tin về công việc (job), các trường và loại component tương ứng sẽ là:
Các nhãn: label
Tên công việc: textbox
Công ty tuyển dụng: drop-down list (combo box)
Nội dung tuyển dụng: textbox
Ngày bắt đầu đăng tuyển: calendar
Ngày kết thúc đăng tuyển: calendar
Nút bấm gửi thông tin lên server: Button (type = submit)
Công nghệ JSF có nhiều ưu điểm: thời gian phát triển ứng dụng nhanh mà vẫn hỗ trợ AJAX. JSF có nhược điểm, JSF dựa trên cơ chế stateful component (tương tự cơ chế của ASP.NET WebForm) nên có nhược điểm là dung lượng request, response gửi qua lại giữa client-server lớn, dẫn đến thời gian giao tiếp giữa 2 phía sẽ lâu. Bản chất của HTTP là stateless , trong khi JSF cố giữ trạng thái các component trong ứng dụng bằng cơ chế stateful của component.
Ghi chú
[1] http://www.oracle.com/technetwork/java/javaee/overview/index.html
[2] JSR (Java Specification Request): Yêu cầu đặc tả (công nghệ cụ thể nào đó trong) Java
[3] https://javaserverfaces.java.net/
[4] http://www.primetek.com.tr/
[5] http://richfaces.jboss.org/
[6] http://myfaces.apache.org/trinidad/
[7] http://omnifaces.org/
[8] http://www.icesoft.org/java/home.jsf
Bài viết được sự cho phép của tác giả Nguyễn Văn Trọng
Dạo gần đây mình hay nhận được nhiều câu hỏi của các bạn sinh viên và một số bạn trẻ đang theo nghề lập trình. Nội dung xoay quanh chuyện cơm áo. Vì thấy nó rất chính đáng nên mình mới viết bài này để giải đáp thắc mắc. Trên thực tế, sau khi ra trường đi làm thì ai cũng vậy, theo đuổi đam mê là một chuyện nhưng ít ra phải đủ ăn. Đi làm vài năm dành dụm chút tiền, chưa tính đến chuyện phụng dưỡng cha mẹ nhưng chí ít cũng phải đủ cưới vợ.
Mình vẫn còn nhớ như in hồi 2012, mới bập bẹ bước vào nghề BrSE thì cũng là thời điểm lên xe bông. Vì chưa tích cóp được gì nhiều nên ngửa tay xin ông anh trai. Cũng may anh mình lúc đó còn đang độc thân, đi xkld Hàn nên có dư được chút. Nghĩ lại thì hơi kỳ kỳ, các bạn đừng có vậy nghen, tự thân tự lực vẫn tốt hơn “gọi trợ giúp” người thân.
Quay lại chủ đề chính, xu hướng gia công phần mềm cho Nhật. Những ai đang hoặc có ý định học tiếng Nhật thì đây là bài viết đáng để các bạn bỏ 5 phút ra nghiền ngẫm.Lịch sử gia công phần mềm
Thời điểm những năm 80-90 Các doanh Nghiệp của Nhật như Hitachi, Fujitsu, Toshiba, NEC, Sony … đều có bộ phận Software riêng. Họ tự xây dựng hệ thống thông tin cho mình. Sau này đến khoảng đầu 2000 trở đi thì xu hướng khoán ngoài (outsource) mới bắt đầu nở rộ. Lý do chính là Nhật vào thời kỳ này đã qua thời hoàng kim, các doanh nghiệp không còn giữ mức lợi nhuận khủng bố để nuôi bộ máy IT riêng. Họ buộc phải giữ lại những nhân sự chủ chốt chỉ để nghiên cứu giải pháp. Phần việc triển khai thì có 2 hướng. Đối với các dự án liên quan tính bảo mật cao, quyết định thành bại của tập đoàn thì thông thường sẽ do nội bộ làm từ A->Z. Hoặc có thể thuê các công ty uy tín trong nước để đảm bảo an toàn, không bị rò rỉ tài liệu hay ý tưởng. Còn những việc khác sẽ khoán ngoài mà chủ yếu là Ấn Độ, Trung Quốc. Điều này nhằm mục đích tiết kiệm chi phí tối đa, đồng thời mang lại hiệu quả cao.
Thời gian đầu thì Ấn độ là mạnh nhất, sau đấy Trung Quốc mới nhảy vào và dần chứng minh được tuyệt chiêu “lấy thịt đè người”. Đến giữa những năm 2000 thì Trung Quốc vượt lên thành đối tác số 1, thời điểm này Việt Nam mới manh nha nhúng tay vào. Bạn sẽ khá bất ngờ khi 1 người Ấn hay Việt lấy N2 trong vòng 1 năm, nhưng với dân Tàu thì chuyện N2 trong 6 tháng không có gì ghê ghớm. Bởi vậy có những dự án yêu cầu vài nghìn người N2-N3 thì các doanh nghiệp ở Đại Liên, Thượng Hải hay Thiên Tân đều có thể chiến được.
Những năm 2010 thì bắt đầu xu hướng dịch chuyển từ TQ sang các nước đông nam á trong đó Việt Nam được ưu tiên. Ngoài Việt ra còn có Myanma, Thái, Phi, Bangladesh. Nhưng các bác JP vẫn like dân ta, 1 phần vì rẻ, 2 là dễ bảo, thứ 3 code trâu. Còn về lý do dịch chuyển này nói ra đây hơi dài dòng, hẹn 1 bài khác vậy.
Thị Trường Gia Công Phần Mềm Nhật Hiện Nay
Vì tính tiết kiệm cộng với hiệu quả nên thì trường outsource vẫn phát triển mạnh trong vài năm gần đây. Các công ty liên tục mọc lên như nấm sau mưa. Có rất nhiều BrSE mà mình biết sau vài năm chinh chiến đã ra lập công ty riêng và khá thành công. Các bạn tham khảo danh sách Outsource Company List bên dưới.
Mình sẽ cắt nghĩa một chút vì sao lại tiết kiệm, vì sao lại hiệu quả. Về chi phí, nếu nuôi bộ phận IT đông người thì việc trả lương thưởng sẽ ngốn kha khá, trung bình 1 người tầm 50 man (~100 triệu)/tháng, 1000 người thì … nhiều quá tính không nổi. Chưa kể cơ chế tuyển dụng trọn đời ở Nhật, tức là chỉ có tuyển chứ hiếm khi sa thải, nghĩa là nuôi cả đời. Trong khi outsource thì hết dự án là thôi, trả 1 cục tiền lấy sản phẩm cái rẹc … xong.
Còn về tính hiệu quả, người chưa làm với Nhật nhiều mà chỉ “nghe nói” thường hay thần thánh hoá kỹ sư JP. Thực ra họ có các kỹ sư rất giỏi, rất sáng tạo nhưng tỉ lệ chỉ chiếm phần nhỏ. Hầu hết các kỹ sư IT đều ở mức biết việc, cẩn thận tỉ mỉ chứ rất yếu khoản học cái mới. Vậy nên đối với các dự án công nghệ tiên tiến thường họ chỉ nghiên cứu giải pháp, còn việc coding giao cho các doanh nghiệp Việt Nam sẽ hiệu quả hơn. Dân IT của nước mình kỹ thuật được đánh giá khá cao, chỉ thua Ấn vs Trung chút xíu.
Xu Hướng Tương Lai (5 – 10 Năm Nữa)
Có 3 xu hướng chính :
Tuyển người giỏi vào làm SE. Hiện có nhiều công ty như Rakuten, Line (chat), Microsoft JP, Recruit jp… hay tuyển kỹ sư chất lượng cao từ các nước khác. Yêu cầu là giỏi tiếng Anh/Nhật và kỹ thuật đỉnh. Profile họ sẽ refer từ nhiều nguồn, trong đó có Github hay các dự án mã nguồn mở. Ngoài ra sẽ test lúc phỏng vấn qua Skype.
Tiếp tục khoán ngoài các dự án phần mềm. Những công ty IT liên doanh Việt Nhật hoặc VN mà làm chuyên cho thị trường Nhật sẽ tiếp tục mở rộng. Hiện tại theo ước tính thì số lượng nhân viên mảng JP đang khoảng trên dưới 20 ngàn người và sẽ tiếp tục tăng.
Mua bán – sát nhập : Ngoài việc tự mở các chi nhánh tại Việt Nam thì các công ty JP để tránh rủi ro sẽ bỏ tiền ra mua đứt luôn các công ty đang hoạt động. Như vậy sẽ đỡ mất công xây dựng, tránh được các sự cố không đáng có hoặc khó khăn trong việc hoà nhập văn hoá. Vì thông thường sau khi mua/sát nhập, bộ máy cũ hầu như giữ nguyên. Vẫn hoạt động trên nguyên tắc mẹ con nhưng việc điều hành hoạt động vẫn theo tập quán như trước và chỉ thay đổi về mặt chiến lược.
Kết
Các bạn đang theo mảng JP hoặc sắp sửa học tiếng Nhật đọc xong hy vọng là đỡ hoang mang. Thêm 1 yếu tố nữa mà các bạn sẽ yên tâm là dân số Nhật hiện đang già dần. Số liệu dự đoán trong vài chục năm nữa sẽ giảm kinh khủng. Các tập đoàn lớn dày công gây dựng lên nên không dễ gì họ để suy yếu chỉ vì thiếu nhân lực trong nước.
Ngày nay các bác đều có xu hướng toàn cầu hoá. Không chỉ là trong việc kinh doanh mà ngay cả nhân sự cũng vậy. Chuyện cán bộ cấp cao họ có tỉ lệ ngầm 20 – 50 % buộc phải là người nước ngoài. Cái này hơi xa vời so với tầm hiểu biết hạn hẹp của mình. Còn riêng chuyện dev thì hiện tại ngồi quanh mình không ít người Trung Quốc, Myanma, Bangladesh, Ấn độ.
Thêm cái cuối cùng, nghành công nghiệp phim *** của Nhật thì khỏi nói rồi, nên lỡ học JP mà không dùng được cho công việc thì chí ít là xem phim không cần sub . Mấy đoạn hội thoại đầu phim cũng hay và “nhân bản” lắm.
Bài viết được sự cho phép của tác giả Nguyễn Hữu Khanh
Khi làm việc với switch statement, các bạn sẽ hay gặp những vấn đề sau:
Vấn đề thứ nhất là chúng ta quên khai báo break statement: điều này có nghĩa code chúng ta sẽ không thoát khỏi switch statement sau khi match case đầu tiên mà sẽ thực thi tiếp những case tiếp theo, ví dụ như:
package com.huongdanjava;
public class SwitchExample {
public static void main(String[] args) {
int a = 1;
int b = 2;
switch (a + b) {
case 2:
System.out.println("Khanh");
break;
case 3:
System.out.println("Huong Dan Java");
case 4:
System.out.println("Lap Trinh Java");
default:
System.out.println("Hello");
break;
}
}
}
Kết quả:
Vấn đề thứ hai là nếu các bạn muốn trong trường hợp giá trị của biến với các giá trị khác nhau a và b thì xử lý cùng một đoạn code. Chúng ta sẽ viết code như sau:
package com.huongdanjava;
public class SwitchExample {
public static void main(String[] args) {
int a = 1;
int b = 2;
switch (a + b) {
case 2:
System.out.println("Khanh");
break;
case 3:
System.out.println("Huong Dan Java");
break;
case 4:
System.out.println("Huong Dan Java");
break;
default:
System.out.println("Hello");
break;
}
}
}
Hoặc:
package com.huongdanjava;
public class SwitchExample {
public static void main(String[] args) {
int a = 1;
int b = 2;
switch (a + b) {
case 2:
System.out.println("Khanh");
break;
case 3:
case 4:
System.out.println("Huong Dan Java");
break;
default:
System.out.println("Hello");
break;
}
}
}
Những cách viết này bất tiện vì duplicate code hoặc chúng ta đôi khi không control được nên đặt break statement ở chỗ nào.
Vấn đề thứ ba là nếu các bạn muốn sử dụng switch statement để determine giá trị của một biến nào đó, các bạn phải viết như sau:
package com.huongdanjava;
public class SwitchExample {
public static void main(String[] args) {
String message = "";
int n = 1;
switch (n) {
case 1:
message = "Khanh";
break;
case 2:
message = "Huong Dan Java";
break;
case 3:
message = "Lap Trinh Java";
break;
default:
break;
}
System.err.println(message);
}
}
Để giải quyết những vấn đề trên, từ Java 12, các bạn có thể viết switch statement với những cải tiến như sau:
Đối với vấn đề thứ nhất, Java 12 hỗ trợ chúng ta cách viết switch statement giống như lambda expression, ví dụ như:
package com.huongdanjava;
public class SwitchExample {
public static void main(String[] args) {
int a = 1;
int b = 2;
switch (a + b) {
case 2 -> System.out.println("Khanh");
case 3 -> System.out.println("Huong Dan Java");
case 4 -> System.out.println("Lap Trinh Java");
default -> System.out.println("Hello");
}
}
}
Kết quả:
Đối với vấn đề thứ 2 thì các bạn có thể viết lại switch statement như sau:
package com.huongdanjava;
public class SwitchExample {
public static void main(String[] args) {
int a = 1;
int b = 2;
switch (a + b) {
case 2 -> System.out.println("Khanh");
case 3, 4 -> System.out.println("Huong Dan Java");
default -> System.out.println("Hello");
}
}
}
Kết quả:
Còn đối vấn đề thứ ba thì bây giờ Java 12 đã hỗ trợ chúng ta return lại value với switch statement như sau:
package com.huongdanjava;
public class SwitchExample {
public static void main(String[] args) {
int n = 1;
var message = switch (n) {
case 1 -> "Khanh";
case 2 -> "Huong Dan Java";
case 3 -> "Lap Trinh Java";
default -> "Hello";
};
System.out.println(message);
}
}
Kết quả:
Nếu các bạn muốn thêm code để xử lý business logic trong mỗi case của switch statement thì các bạn cần upgrade lên Java 13, sau đó thêm code và sử dụng từ khóa yield để return lại giá trị mà mình mong muốn. Ví dụ như sau:
package com.huongdanjava;
public class SwitchExample {
public static void main(String[] args) {
int n = 1;
var message = switch (n) {
case 1 -> "Khanh";
case 2 -> "Huong Dan Java";
case 3 -> "Lap Trinh Java";
default -> "Hello";
};
System.out.println(message);
}
}
Những kinh nghiệm từ quá trình tự phát triển của một Developer (Phần 1)
Tác giả: Victor Cassone
Con đường tự học và trưởng thành của một nhà phát triển luôn khó khăn và đầy bất trắc. Không có một đường thẳng nào từ người mới thành lập trình viên đến nghề nghiệp. Vì điều này, tôi tin rằng tất cả các nhà phát triển tự học đều có những câu chuyện hay ho để chia sẻ đến với mọi người.
1. Tập trung vào quy trình học tập và làm việc
Khi mới bắt đầu học lập trình, tôi chưa bao giờ có ý định lập nghiệp từ nó. Tôi chỉ muốn tạo ra một ứng dụng cụ thể và chân thật mà thôi. Cuộc hành trình của tôi bắt đầu khi tôi là sinh viên năm cuối đại học. Tôi vừa đọc xong tiểu sử của Richard Branson và có lẽ đã đọc quá nhiều TechCrunch. Tôi được bơm đầy năng lượng về mảng kinh doanh.
Một ngày nọ, một ý tưởng về việc xây dựng ứng dụng đã xuất hiện trong đầu tôi. Tôi nhanh chóng vẽ ra viễn cảnh về những thứ lớn lao tiếp theo mà mình có thể làm được. Tôi bị cuốn hút bởi ý tưởng và ngừng chú ý đến các bài giảng. Sự hào hứng của tôi đối với ý tưởng ứng dụng nhanh chóng phát triển đến mức tôi cảm thấy mình cần phải hành động.
Nhưng có một vấn đề lớn, ý tưởng của tôi là một ứng dụng dành cho thiết bị di động và tôi không biết bất kỳ ai có thể xây dựng các ứng dụng dành cho thiết bị di động. Tôi là một sinh viên đại học không có kinh nghiệm lập trình, kinh doanh hoặc thiết kế đang cố gắng học Android để có thể xây dựng một ứng dụng di động phức tạp. Tôi đoán đó là những gì bạn nhận được khi kết hợp một ý tưởng lớn với cỗ máy cường điệu của Thung lũng Silicon.
Tôi đã mua một vài cuốn sách về phát triển Android và giành vô số giờ trong phòng để cố gắng dán các ứng dụng của mình lại với nhau. Tôi không quan tâm ứng dụng hoạt động như thế nào, tôi chỉ muốn có một sản phẩm hoàn chỉnh.
Thời gian trôi qua và ứng dụng biến thành một Frankenstein của mã sao chép và dán. Ứng dụng không có nhiều tính năng và nó hầu như không chạy mà không gặp sự cố. Cho đến khi vô tình tham gia một lớp Khoa học Máy tính, tôi mới nhận ra rằng mình nên tập trung hơn vào việc thực sự cố gắng học phát triển phần mềm.
Không có khả năng lập trình khiến tôi từ bỏ ý tưởng ứng dụng ban đầu. Tôi nhận ra rằng tôi sẽ không làm nên điều lớn lao tiếp theo, ít nhất là hiện tại thì vẫn chưa. Theo thời gian, tôi quyết tâm và lên kế hoạch cho bản thân để có thể học tập nghiêm túc hơn. Tôi bắt đầu thích lập trình và cuối cùng bắt đầu sự nghiệp của một nhà phát triển phần mềm.
Ý tưởng về việc xây dựng ứng dụng lớn của tôi đã làm lãng phí một khoảng thời gian dài. Nhưng may mắn là tôi đã tìm thấy được sự tập trung ở những phút cuối. Khi bạn quá tập trung vào kết quả cuối cùng, bạn sẽ bắt đầu đi những con đường tắt. Các lối tắt có thể dẫn đến một số tiến bộ trong ngắn hạn nhưng về lâu dài, việc thiếu sót các kiến thức chuyên môn cơ bản sẽ xảy ra.
Điều quan trọng cần nhớ là khi bắt đầu học bất cứ điều gì mới đều sẽ yêu cầu bạn cần hoàn chỉnh ngay từ những bước nhỏ nhất. Mỗi bước đều sẽ đòi hỏi bạn phải cẩn thận và chuyên tâm nhiều hơn. Học những điều mới cũng giống như xây một ngôi nhà. Bạn bắt đầu với nền tảng trước khi bắt đầu quá trình xây dựng. Nếu nền móng bị lỗi, tất cả mọi thứ sớm muộn sẽ sụp đổ.
Đôi khi việc xây dựng một nền tảng vững chắc đòi hỏi bạn phải chậm lại. Không có gì phải xấu hổ khi đi chậm. Những người hiểu những điều cơ bản lần đầu tiên sẽ đi trước những người phải quay lại và học lại chúng. Thật sự tập trung vào quá trình làm việc sẽ giúp ích rất nhiều cho quá trình làm việc của bạn.
Khi tôi đang xây dựng ứng dụng của mình, Stack Overflow đã trở thành “người bạn” đồng hành xuyên suốt với tôi. Bất cứ khi nào tôi gặp khó khăn, tôi sẽ cố gắng cùng nhau tạo ra một câu hỏi hoàn hảo để hỏi cộng đồng Stack Overflow. Tôi trả lời trung bình một vài câu hỏi mỗi ngày.
Tôi sẽ dành một khoảng thời gian để rà soát trang web và cố gắng tìm một đoạn code chính xác có thể khắc phục các vấn đề mà tôi gặp phải. Khi tôi tìm thấy câu trả lời phù hợp, tôi sẽ sao chép và dán vào codebase của mình và cố gắng làm cho nó hoạt động với code hiện có của tôi. Tôi dành một ít thời gian để cố gắng hiểu code mà tôi đã thêm.
Quá trình này diễn ra trong một thời gian cho đến khi tôi tìm hiểu và nhận ra những sai sót trong phương pháp của mình. Stack Overflow vừa là cơ hội nhưng thực tế cũng là thách thức. Nó rất tốt trong việc giúp bạn giải quyết vấn đề, tuy nhiên, nếu không cẩn thận, bạn có thể nhanh chóng trở nên phụ thuộc vào trang web.
Đôi khi trang web giải quyết vấn đề quá tốt. Nó tạo ra một cảm giác tự tin sai lầm có thể dẫn đến đau đầu hơn trên đường. Stack Overflow chỉ cho bạn cách bắt một thứ gì đó hoạt động nhưng nó thường không cho bạn biết rõ ràng tại sao nó hoạt động. Hiểu được vấn đề như thế nào là quan trọng. Các lỗi cần được sửa và code cần chạy là những gì… Hiểu rõ lý do tại sao điều gì đó hoạt động sẽ giúp bạn áp dụng lại kiến thức trong tương lai.
Sao chép và dán code từ Stack Overflow giống như có ai đó cho bạn một con cá. Trong khi đó, việc hiểu rõ lý do tại sao một đoạn code hoạt động giống như dạy chúng câu cá. Không có gì sai khi sao chép và sử dụng code đó. Tất cả chúng ta đều làm được. Nó chỉ là một vấn đề khi nó cản trở sự phát triển của bạn với tư cách là một nhà phát triển.
Điều tôi đã phải học một cách khó khăn là không thể học được bất cứ điều gì nếu các câu trả lời liên tục được đưa ra cho bạn. Không có đường tắt trong quá trình học tập. Khi bạn gặp khó khăn, hãy cố gắng giải quyết vấn đề mã hóa ít nhất một vài lần trước khi truy cập Google. Khi sử dụng những đoạn code có sẵn, hãy dành một chút thời gian để cố gắng hiểu đoạn code trước khi tiếp tục.
3. Tìm hiểu sự trợ giúp từ những người có kinh nghiệm
Điều đầu tiên tôi làm sau khi quyết định học lập trình là mua hai cuốn sách phát triển Android. Khi bắt đầu, tôi theo sát các bài tập trong sách và làm việc thông qua các dự án ví dụ. Tuy nhiên, tôi nhanh chóng thất vọng với tiến độ mà tôi đang đạt được sau cuốn sách và quyết định bắt đầu tìm hiểu cách lập trình cho riêng mình.
Tôi đã giành vô số giờ ngồi một mình trong phòng để cố gắng tìm ra các vấn đề lập trình đơn giản. Tôi bị mắc kẹt với các dòng code mới và không hề cảm thấy mình đang tiến bộ. Tôi bế tắc một cách bất lực và cuộc sống của tôi là một hỗn hợp của sự nghi ngờ, thất vọng và cảm giác lạc lõng.
Để làm cho vấn đề tồi tệ hơn, tôi có một ý tưởng sáng tạo là bắt đầu sử dụng một C library khổng lồ có tên là FFMPEG. Ứng dụng của tôi cần để chỉnh sửa video, vì vậy tôi nghĩ rằng việc sử dụng chức năng mạnh mẽ của thư viện là một ý tưởng hay.
Đó không phải là bước đi thông minh nhất của tôi, vì vào thời điểm đó, tôi hầu như không thể làm cho ứng dụng Android của mình hoạt động. Tôi đã lãng phí rất nhiều thời gian để cố gắng đọc code C và tìm ra cách tôi có thể sử dụng nó trong ứng dụng của mình. Tôi đã đấu tranh để thậm chí nhập thư viện vào dự án Android của mình.
Sau nhiều giờ không đi đến đâu, cuối cùng tôi đã chán nản và từ bỏ thư viện. Cùng khoảng thời gian với sự cố FFMPEG, tôi đã đăng ký một lớp Lập trình hướng đối tượng. Nhiệm vụ đầu tiên là xây dựng một chương trình Blackjack. Tôi đã tự học lập trình được 5 – 6 tháng cho đến thời điểm này và tôi cảm thấy tự tin với kỹ năng của mình. Tôi đã hoàn thành bài tập và cảm thấy hài lòng về công việc của mình. Nhưng, không mất nhiều thời gian để cảm giác đó biến mất. Toàn bộ chương trình của tôi được viết theo một phương pháp rất lớn. Mọi người khác trong lớp đều có thể nhận ra rằng chương trình cần được tách thành các lớp. Nó không thật sự tốt!
May mắn thay, bài tập trên lớp và sự hướng dẫn từ giáo viên đã cho phép tôi bước ra khỏi ứng dụng Android và suy ngẫm về khả năng lập trình của mình. Tôi bắt đầu coi trọng việc học và khắc phục mong muốn của mình là tạo ra một ứng dụng hoàn chỉnh.
Bây giờ tôi nhận ra rằng nếu tôi chỉ nói chuyện với một nhà phát triển có kinh nghiệm trong những ngày đầu đó, họ sẽ thấy những gì tôi đang làm. Tôi có thể đã thiết lập các ưu tiên của mình một cách thẳng thắn, và nói lên một điều gì đó có ý nghĩa với tôi. Họ sẽ giúp tôi sửa chữa con đường của mình khi tôi đi vào những ngõ cụt vô ích (như cố gắng làm việc với FFMPEG).
Có rất nhiều cách tôi có thể làm để tìm kiếm sự trợ giúp. Tôi cố gắng tìm một giáo sư / sinh viên tại trường đại học có kinh nghiệm về Android hoặc tìm đến cộng đồng địa phương để được giúp đỡ. Tôi cũng có thể đã thử tìm một cộng đồng Android trực tuyến.
Các nhà phát triển có kinh nghiệm giống như một chiếc la bàn. Họ sẽ không đưa bạn đến đích nhưng họ sẽ đảm bảo bạn được chỉ dẫn đúng hướng. Sự giúp đỡ của họ có thể sẽ tạo nên sự khác biệt giữa thành công và thất bại. Đảm bảo rằng bạn tìm kiếm hướng dẫn ở bất cứ nơi nào bạn có thể tìm thấy. Nó sẽ giúp bạn tiết kiệm thời gian và hạn chế sự thất vọng trên chặng đường làm việc của mình.
Hi vọng những thông tin trên đây sẽ giúp bạn nắm được các vấn đề cơ bản khi bắt quá trình tự phát triển của bản thân. Đón xem phần tiếp theo cũng TopDev nhé!
Bài viết được sự cho phép của blogchiasekienthuc.com
Các tùy chỉnh mặc định của GeoGebra phần lớn thì đã phù hợp với đại đa số người dùng rồi. Tuy nhiên, vẫn có một số trường hợp ngoại lệ, ví dụ như là:
Mỗi lần dựng lên một đối tượng mới (điểm, đoạn thẳng, đường thẳng, …) thì tên của đối tượng này sẽ tự động được tạo và hiển thị trên Graphics.
Tên điểm có kích thước khá nhỏ.
Giao diện sử dụng là tiếng Anh …
Và mỗi lần như vậy, chúng ta đều phải tùy chỉnh lại => điều này tốn không ít thời gian, công sức và còn nhàm chán nữa. Chính vì vậy mà trong bài viết này, mình sẽ hướng dẫn cho các bạn cách thiết lập lại GeoGebra để sử dụng được hiệu quả hơn.
Các thao tác tùy chỉnh thông thường chỉ tự động lưu lại trong cửa sổ hiện tại. Vậy nên, nếu bạn muốn lưu lại cho các lần sử dụng tiếp theo thì bạn cần phải vào Options => chọn Save Settings để cài đặt.
#1. Không hiển thị tên đối tượng mới
Như đã giới thiệu ở trên, mỗi lần dựng một đối tượng mới (điểm, đoạn thẳng, đường thẳng, …) thì tên của đối tượng này sẽ tự động được tạo và hiển thị trên Graphics.
Thường thì chúng ta rất ít sử dụng những tên này, vì vậy nên tùy chỉnh ẩn ngay ban đầu chứ không phải tạo xong rồi mới ẩn một cách thủ công nữa.
Thực hiện: Chọn Options => chọn Labeling => chọn No New Objects để ẩn tên của các đối tượng.
#2. Cách chỉnh cỡ chữ lớn hơn trong GeoGebra
Theo mặc định thì tên của điểm có cỡ chữ khá là nhỏ, vì vậy nếu bạn trình chiều bằng Smart Tivi thì nhiều khả năng là học sinh ngồi ở những dãy bàn cuối lớp sẽ không thể nhìn thấy rõ được.
Chúng ta có hai phương pháp để giải quyết được vấn đề này, đó là:
2.1. Phương pháp thứ nhất
Thay đổi Font Size lớn hơn: Chọn Options => chọn Font Size => chọn 28pt
Phương pháp này có ưu điểm là rất nhanh chóng và áp dụng cho được cho tất cả các điểm hiện tại, nhưng nhược điểm của nó là toàn bộ cửa sổ GeoGebra cũng đều bị to lên theo.
2.2. Phương pháp thứ 2
Gán tên bằng công cụ Text và định dạng lại cỡ chữ, các bước thực hiện như sau:
+ Bước 1: Chọn công cụ Text
+ Bước 2: Nháy chuột trái vào điểm cần tạo tên => đánh dấu chọn vào LaTeX formula => nhập tên => và chọn OK
+ Bước 3: Nháy vào tên vừa tạo => chọn Graphics => chọn Large hoặc Very Large hoặc Extra Large
Phương pháp này tuy phải thực hiện qua nhiều bước, nhưng tên của điểm được tạo ra bằng phương pháp này khá đẹp mắt và có độ tùy biến khá cao.
Có một lưu ý dành cho các bạn khi sử dụng phương pháp này là trong trường hợp: tên ban đầu và tên tạo bằng công cụ Text là khác nhau – thì tên mà GeoGebra đọc sẽ được hiểu là tên ban đầu.
#3. Thiết lập ngôn ngữ tiếng Việt cho GeoGebra
GeoGebra không chỉ là một phần mềm đa nền tảng, mà nó còn là một phần mềm đa ngôn ngữ nữa. Cụ thể là phần mềm này hỗ trợ rất nhiều ngôn ngữ khác nhau trên thế giới, trong đó có tiếng Việt 🙂
Cá nhân mình thường không sử dụng tiếng Việt đối với các phần mềm nước ngoài. Bởi vì có khá nhiều từ khi được dịch sang tiếng Việt thì nghĩa của nó không còn được sát với nghĩa ban đầu.
Thậm chí trong một số trường hợp còn không thể dịch được mà phải mô tả. Cái thứ 2 nữa là khi gặp lỗi thì việc tìm kiếm cách sửa lỗi cũng khó khăn hơn, vì đa số các bài hướng dẫn đều là tiếng Anh.
Vì vậy cá nhân mình không khuyến khích các bạn sử dụng tiếng Việt với phần mềm GeoGebra. Mình chỉ sử dụng tiếng Việt khi phần mềm là thuần Việt mà thôi..
Trường hợp bạn muốn sử dụng giao diện tiếng Việt cho phần mềm GeoGebra thì bạn thực hiện theo các tao tác sau:
Chọn Options => chọn Language => chọn R – Z => chọn Tiếng Việt như hình bên dưới là được.
#4. Tùy chỉnh hộp thoại Preferences trong GeoGebra
Hộp thoại Preferences cho phép chúng ta tùy chỉnh các tùy chọn mà chúng ta yêu thích. Chẳng hạn bạn có thể tùy chỉnh màu sắc và kích thước của điểm, hoặc đoạn thẳng, hoặc…
Từ đây về sau mỗi khi tạo điểm, điểm sẽ tự động có màu sắc và kích thức như chúng ta đã tùy chỉnh trước đó.
Để mở hộp thoại Preferences bạn có thể vào Options => chọn Advanced …
4.1. Màu sắc và kích thước của điểm
Trong hộp thoại Advanced => bạn hãy chọn Defaults
GeoGebra hiện tại đang cung cấp cho chúng ta 5 loại điểm:
Free điểm tự do.
Dependent điểm phụ thuộc.
On Path điểm nằm trên đường.
In Region điểm nằm trong một miền nào đó (miền đa giác, hình tròn, …).
Complex Number điểm biểu diễn một số phức.
Chẳng hạn ở đây chúng ta cần tùy chỉnh lại màu sắc và kích thước của điểm tự do thì bạn hãy chọn Free => chọn
Color để tùy chỉnh màu sắc.
Style (Point Sixe) để tùy chỉnh kích thước của điềm tự do..
4.2. Màu sắc và kích thước của đoạn thẳng
Trong hộp thoại Advanced => bạn hãy chọn Defaults
Color để tùy chỉnh màu sắc.
Style (Line Thickness) để tùy chỉnh độ dày của đoạn thẳng.
Các đối tượng còn lại như DeLine, Ray, Polyline, Vector, Conic, Sector, … các bạn cũng tùy chỉnh tương tự nếu muốn nhé. Tùy thuộc vào đối tượng được chọn mà các tùy chọn trong hộp thoại Preferences sẽ có khác nhau đôi chút.
#5. Lời kết
Okay, trên đây là những bước tùy chỉnh lại GeoGebra trước khi sử dụng – giúp bạn làm việc hiệu quả hơn với phần mềm này.
Những tùy chỉnh bên trên là tương đối phù hợp với đại đa số người dùng Việt Nam chúng ta. Tất nhiên là bạn có thể tùy chỉnh khác theo sở thích cá nhân của bạn, miễn sao là nó phù hợp với bạn nhất là được.
Ngoài các tùy chỉnh cơ bản bên trên, GeoGebra còn cung cấp cho chúng ta rất nhiều tùy chỉnh khác, chẳng hạn như: đơn vị của góc, hiển thị/ không hiển thị (thanh nhập lệnh, thanh công cụ, thanh phụ, lưới, trục trung, trục hoành, …), kiểu lưới, màu lưới, …
Các bạn có thể tự tìm hiểu và khám phá thêm nhé. Xin chào tạm biệt và hẹn gặp lại các bạn trong những bài viết tiếp theo !
Bài viết được sự cho phép của tác giả Lê Xuân Quỳnh
Hello guys! Chúng ta code ios mỗi ngày, và chắc hẳn nhiều thanh niên giống mình ít khi check memory của app khi sử dụng để tìm hiểu những vấn đề bất thường của app. Trong ví dụ hôm nay chúng ta sẽ tự tay mình tạo ra leak để hiểu bản chất nó là gì, thay vì nghe các ông chém gió suông đọc xong rồi không hiểu gì cả.
Code ở branch bai1 nên bạn cần chuyển source về nhánh này nha. Cách chuyển xem phần mình pink sau:
Tuy nhiên tôi khuyên bạn chơi với terminal của MacBook cho nó pro nhé. Vì nếu bạn tải chay về bằng trình duyệt thì không thấy source code ở đâu đâu :v
Chơi với terminal đơn giản như sau:
Bạn gõ lệnh cd vào source code hôm trước bạn tải về: cd LeakMemorySample
2. Tiếp theo là gõ fetch để lấy source code mới nhất của tôi về: git fetch
3. Tiếp theo là bạn show toàn bộ branch trên repo của tôi bằng lệnh: git branch -a
Ở đây bạn sẽ thấy các branch sau: bai1 * bai2 master
Ví dụ bài này, tôi để hết source vào branch tên là bai2. Bạn chuyển sang source code bài 2 như sau: git checkout bai1
Sau khi branch bai1 được bôi đậm chuyển màu nghĩa là bạn đã thành công rồi đó. Còn nếu nó báo không thấy thì chứng tỏ bạn làm sai, hãy làm lại!
Tôi sẽ giải thích từng phần 1.
Màn hình thứ nhất: ViewController.
Tôi lười nên tôi lấy mặc định file do Xcode tạo luôn. Bạn vào file Main.storyboard để xem cách tôi layout kéo thả các thứ
Màn hình này chả có gì cả, ngoài cái ảnh logo web, 1 cái nút để mở sang màn thứ 2, nơi chứa leak memory.
2. Màn hình thứ 2: SecondViewController.
Màn này code như sau:
Tôi giải thích đoạn code trên:
Dòng 19 – tôi gọi hàm bindViewModel để kết nối viewmodel với view. Tôi thích làm MVVM cho code clean. Nên nếu bạn không hiểu về MVVM là cái gì, thì vui lòng tìm tag MVVM ở web này để hiểu thêm.
Từ dòng 23:
Tôi tạo viewModel ở đây. Ở dòng 27, tôi có 1 hàm closure. Hàm này làm cái gì? Nó chứa đoạn code 28(hoặc 29) set lại màu của background. Như ảnh thì tôi đang comment cái dòng 27 đằng sau, và dòng 28. Nếu bạn không biết gì về closure, thì hiểu nôm na nó là cái hàm chạy vào 1 thời điểm nào đó từ view model phát ra. Nó không chạy luôn ngay lúc khai báo đâu nha. Giống như bạn tạo 1 cái lịch biểu, rằng nếu như người yêu mà hôn bạn thì bạn làm cái gì đó Hôn ở đây là hàm needClosure, còn làm gì đó là dòng 28(hoặc 29).
Dòng 33, tôi tạo 1 hàm fake request API, có thời gian delay, và cuối cùng giả sử khi có kết quả thì nó sẽ call vào closure tôi nói ở trên.
Rồi vậy viewModel tôi có gì nào? Theo dõi code sau:
Ở dòng 11, tôi tạo 1 biến closure để bắn sự kiện ra khi API có kết quả trả về.
Dòng 12 tôi tạo 1 timer fake timeout của API.
Dòng 13 tôi tạo 1 biến lưu mảng số nguyên data.
Dòng 18 tới 21, tôi cố gắng tạo ra nhiều dữ liệu chiếm memory để cho bạn thấy sẽ tai hại thế nào khi ta không giải phóng nó. Tôi đẩy nó vào background thread để không block main thread khi bạn cố gắng mở SecondViewController.
Dòng 25 tới 30, tôi tạo fake 1 hàm call API. Hàm này sau 1 giây sẽ trả về kết quả. Khi có kết quả, nó sẽ gọi vào biến closure để bắn ra cho thằng SecondViewController xử lý. SecondViewController sẽ thực hiện việc đổi màu.
OK vậy là bạn đã hiểu luồng của app rồi. Vậy việc tiếp theo là theo dõi nó đã leak memory như nào.
Đầu tiên bạn chạy app và xem memory sử dụng như hình:
Bấm như hình để xem memory khi chạy app
Bạn bấm vào nút Open Second screen, sau đó đợi 1 lát cho SecondViewController tạo data rồi bấm back ra màn 1. Cứ làm như vậy dăm bảy lần, bạn sẽ thấy 1 điều là bộ nhớ chỉ có tăng mà không có giảm.
Chúc mừng bạn đã tạo leak memory thành công.
Thế tại sao nó lại leak memory?
Quay lại với đoạn code closure dưới đây:
Đoạn code gây leak memory
Ở đây tôi sử dụng liên kết mạnh từ viewModel sang view controller, bằng việc lấy self.view. Nghĩa là thằng viewModel này chỉ thực sự bị hủy khi thằng SecondViewController bị hủy. Tuy nhiên, thằng ViewModel này là con của thằng SecondViewController. Mà thằng SecondViewController muốn hủy thì nghĩa là con nó phải hủy trước. Ồ, vậy là bố bảo là con hủy thì bố mới hủy, con thì nói bố ơi bố hủy đi thì con mới hủy nè. Cuối cùng 2 thằng chả ai chịu ai, bộ nhớ vẫn cứ ở đấy, chả bao giờ giải phóng được.
Rất simple đúng không?
Vậy cách khắc phục thế nào? Chúng ta tiến hành sửa lại đoạn code trên như sau:
Tiến hành capture cho closure
Tôi tiến hành capture cho hàm closure trên. Ồ thế capture là gì? Đơn giản là thằng con nói bố ơi bố khi con có closure trả về, nếu như bố vẫn sống ở đó thì con sẽ set màu cho bố nhé! Mà bố ngỏm rồi(đã bị giải phóng khi bấm back) thì thôi con không gán nữa. Tôi tạo capture [weak self] để tạo liên kết yếu cho điều đó. Khi đó đoạn code ở closure sẽ là self?. thay vì self. Thật là đơn giản đúng không nào
Khi đó hàm sau sẽ in ra dòng chữ “free memory SecondViewController”
Từ nay về sau mỗi khi bạn tạo 1 view controller mới, bạn hãy thêm hàm deinit vào và đặt debug vào đó, xem nó đã được giải phóng chưa? Nếu như bạn đã hủy view controller rồi thì không có leak memory ở nó. Còn mà không in ra thì chắc chắn leak memory rồi bạn nhé.
Vậy là tôi đã trình bày xong trường hợp leak memory ở closure. Qua ví dụ hi vọng bạn sẽ cẩn thận hơn trong việc code của mình. App chạy được nhưng phải chạy mượt.
Phần mở rộng tôi viết gồm hubUrl, hubName là trỏ đến địa chỉ Hub cần kết nối tới. this.onOk là phương thức đảm bảo đang ở trạng thái kết nối mới thực hiện func (một hành động nào đó). Với classHub tôi đã xử lý hết các sự kiện khi mất kết nối, đang kết nối và kết nối
// Nếu bị mất kết nối thì 2s sau sẽ kết nối lại
$this.hub.connection.disconnected(function ()
{
console.log("disconnected");
state = 1;
var t = setInterval(function () { $this.doHubStart(true); clearInterval(t); }, $this.timeout);
});
$this.hub.connection.reconnecting(function ()
{
state = 1;
console.log("reconnecting");
});
// Đã được kết nối lại
$this.hub.connection.reconnected(function ()
{
console.log("reconnected");
var t = setInterval(function () { $this.doHubStart(true); clearInterval(t); }, $this.timeout);
});
Nếu bị mất kết nối, thì sau khoảng this.timeout (mặc định 2000ms) sẽ thực hiện kết nối lại.
Hub.ActionRegistry = function ()
{
this.registry = function (func) { /* Code code */ }
/* Code Code */
}
Với Hub.ActionRegistry được dùng để đăng ký các hành động sau khi nhận dữ liệu được push từ server gửi về. Tại sao tôi lại viết class này, vì tôi hướng tới có thể nhiều nơi, nhiều module mà cũng muốn nhận dữ liệu push về mà không phải lúc nào cũng phải sửa lại phương thức nhận dữ liệu tại client của Hub. Điều này cũng đồng nghĩa với việc lúc nào module nào dùng thì có thể đăng ký nhận dữ liệu.
Sau đây là code mẫu tôi viết cho chương trình chat đơn giản về sử dụng thư viện mở rộng trên
Tán gẫu nào
Trước tiên bạn cũng cài đặt SignalR và gắn thẻ script ở client
<scriptsrc='/signalr/hubs'></script> <!-- file js mà hub sinh ra --><scriptsrc='Hub.js'></script> <!-- Thư viện mở rộng của tôi -->
Code server để định nghĩa Hub
public class ExampleHub : Hub
{
public void ClientSendMessage(string userName, string msg, string color)
{
userName = userName.Trim();
if (userName.IsNull()) return;
msg = msg.Trim();
if (msg.IsNull()) return;
var data = new { userName, avatar = userName.First(), msg, time = DateTime.Now.ToString("HH:mm dd/MM/yyyy"), color };
Clients.AllExcept(Context.ConnectionId).receiveMessage(data, false);
Clients.Client(Context.ConnectionId).receiveMessage(data, true);
}
}
Code javascript tại client để kết nối tới Hub
function ExampleHub()
{
$.extend(this, new Hub("", "exampleHub"));
this.actionReceiveMessage = new Hub.ActionRegistry();
this.onBeforeStart = function ()
{
var $this = this;
// Client
this.hub.client.receiveMessage = function (data, right)
{
$this.actionReceiveMessage.do(function (func) { func(data, right); });
};
// Server
this.startDone = function () { };
this.clientSendMessage = function (userName, message, color)
{
this.onOk(function ()
{
$this.hub.server.clientSendMessage(userName, message, color);
});
}
};
}
Ở đây các bạn có thể thấy ExampleHub kế thừa tới Hub và gán 2 giá trị: hubUrl = “” (Do server của hub nằm ngay tại server web của tôi) và hubName = “exampleHub”.
Ở phía xử lý client có this.actionReceiveMessage là đối tượng đăng ký nhận hành động muốn xử lý data gửi về từ server. Client muốn giao tiếp gửi request tới server thì thông qua this.onOk để luôn đảm bảo chỉ thực hiện request khi ở trạng thái kết nối ổn định.
Và cuối cùng là giao diện hiển thị tôi định nghĩa một BoxChat như sau
function BoxChat()
{
this.ws = null; // là một instance của ExampleHub
this.area = null;
var template = null;
var colors = { 0: "bg-danger text-white", 1: "bg-success text-white", 2: "bg-primary text-white", 3: "bg-warning text-white", 4: "bg-secondary text-white", 5: "bg-dark text-white", 6: "bg-orangered text-white" };
var names = { 0: "Sơn20", 1: "Dũng", 2: "Việt Anh", 3: "TuhiKing", 4: "Tàn Long", 5: "ButaKing", 6: "Osoft" };
var color = null;
this.start = function ()
{
color = colors[Core.random(6)];
var $this = this;
template = this.area.find("[data-form=template] .direct-chat-msg");
var userNameInput = this.area.find("[name=userName]");
var messageInput = this.area.find("[name=message]").focus();
var butonSend = this.area.find("[data-form=button]");
userNameInput.val(names[Core.random(6)] + "." + Core.random(20)); // Tạo một tên ngẫu nhiên
butonSend.click(function ()
{
var userName = userNameInput.val();
if (userName == "") { Core.alert("Vui lòng nhập tên tài khoản để tán gẫu"); return; };
var message = messageInput.val();
if (message == "") { Core.alert("Vui lòng nhập nội dung để tán gẫu"); return; };
$this.ws.clientSendMessage(userName, message, color);
});
messageInput.keypress(function (event)
{
if (event.keyCode == "13")
{
butonSend.click();
return false;
}
});
var formChat = this.area.find("[data-form=chat]");
this.ws.actionReceiveMessage.registry(function (data, right)
{
var chatItem = template.clone();
chatItem.find("[data-chat]").each(function () { $(this).html(data[$(this).attr("data-chat")]); });
chatItem.find("[data-chat=avatar]").addClass(data.color);
if (right) chatItem.addClass("right");
formChat.append(chatItem);
formChat.scrollTop(formChat[0].scrollHeight);
messageInput.val("").focus();
});
}
}
Tại hàm start của BoxChat tôi đăng ký hành động khi có message gửi về thì hiển thị nội dung tin nhắn lên. Như đã nói ở trên, actionRegistry giúp đăng ký hành động nhận push từ server ở mọi module mà mình muốn mà không cần phải sửa lại hàm ở client trong hub.
Cuối cùng là khai báo và sử dụng
var ws = new ExampleHub();
ws.timeout = 2000; // Mặc định
ws.start();
var boxChat = new BoxChat();
boxChat.ws = ws;
boxChat.area = $("article");
boxChat.start();
// Ví dụ viết code đăng ký thêm một hành động nhận dữ liệu từ server push về
// Thì hiển thị một notify bên góc phải trên cùng màn hình
// Để cho thấy là có thể đăng ký sự kiện nhận dữ liệu ở mọi nơi.
ws.actionReceiveMessage.registry(function (data, right)
{
new PNotify({ target: $("article"), text: data.userName + ":" + data.msg, type: "success", delay: 1000 });
});
Tại bài viết này tôi vẫn đang sử dụng asp.net SignalR mà chưa chuyển sang asp.net Core SignalR. Do chưa đủ điều kiện về vật chất, hạ tầng và nhất là hệ thống phần mềm hiện tại của tôi vẫn đang viết trên asp.net. Tuy nhiên với cách làm này của tôi thì chuyển sang asp.net Core tôi cũng sẽ làm tương tự. Và đây là chia sẻ nhỏ mà tôi đã làm trong thực tế.
Gửi cảnh báo khi nhân viên thêm phiếu chi với một mục đích chi nào đó bị quá giới hạn cho phép mà quản lý đã cấu hình
Click vào cảnh báo ở góc phải bên dưới màn hình xem ngay được nội dung cảnh báo
Tại nội dung cảnh báo. Click xem chi tiết phiếu để xem nhân viên đã thêm chi cho những mục đích gì
Chúc các bạn lập trình ngày càng tốt hơn. Nếu có thắc mắc các bạn hãy để lại bình luận nhé. Xin cảm ơn
Bài viết được sự cho phép của tác giả Kien Dang Chung
Các lập trình viên gạo cội chắc không lạ gì với JasperReport, nhưng với một số người mới JasperReport có thể là một khái niệm rất mới. Vậy JasperReport là gì? nó được ứng dụng như thế nào trong các hệ thống phần mềm?
Trong các hệ thống ứng dụng, các mẫu biểu chứng từ, các loại tài liệu báo cáo… là rất cần thiết. Ví dụ, với một hệ thống quản lý bán hàng chúng ta có các mẫu chứng từ như phiếu xuất kho, phiếu bảo hành… Hệ thống ngân hàng có thể có các chứng từ như các mẫu thu chi, phiếu tạo khách hàng, phiếu gửi tiền… JasperReport là bộ máy tạo các báo cáo mã nguồn mở được viết bằng ngôn ngữ Java giúp tích hợp vào các hệ thống nhằm tạo ra các mẫu biểu báo cáo, chứng từ phức tạp một cách dễ dàng.
JasperReport là một thư viện viết bằng Java, vậy tại sao Allaravel một website chuyên về framework Laravel lại giới thiệu JasperReport? Bạn có thể tham khảo bài viết Xây dựng báo cáo với JasperReport trong Laravel để hiểu hơn. Quay lại với bài viết, giờ thì bạn có thể đã hiểu sơ bộ được JasperReport là gì?
JasperReport có rất nhiều ưu điểm để trở thành một lựa chọn tốt nhất của bạn cho việc xây dựng hệ thống xuất chứng từ:
JasperReport là phần mềm mã nguồn mở được phát triển từ năm 2001, do vậy nó hoàn toàn miễn phí và có một cộng đồng sử dụng rất lớn.
Có rất nhiều các công cụ được phát triển giúp cho việc xuất báo cáo với JasperReport đơn giản:
iReport là công cụ giao diện trực quan GUI giúp tạo ra các mẫu chứng từ chính xác dễ dàng. Phiên bản cuối cùng 5.5.0 phát hành ngày 31/12/2015 và sau đó chuyển sang công cụ mới là JasperSoft Studio.
JasperSoft Studio công cụ thế hệ mới của iReport. Trong các bài viết của loạt bài về JasperReport chúng ta sẽ chỉ sử dụng JasperSoft Studio thay cho iReport.
JasperStarter là công cụ giúp xuất báo cáo thông qua các câu lệnh trong màn hình dòng lệnh (console).
JasViewer công cụ giúp chạy các file jasper, hiển thị người dùng tham số đầu vào chứng từ, hiển thị nội dung chứng từ.
JasperReport Server là máy chủ chạy báo cáo có thể tích hợp cho phép chạy các báo cáo thời gian thực hoặc theo lịch trình dựa trên nền tảng web. Khi xây dựng các file báo cáo bằng iReport hoặc JasperSoft Studio, các file này có thể triển khai lên máy chủ và được thực hiện, xuất ra các định dạng khác nhau.
2. So sánh JasperReport với các hệ thống báo cáo khác như Crystal Report, KoolReport
Crystal Report là công cụ báo cáo rất quen thuộc với các lập trình viên .NET do nó tích hợp sâu vào Visual Studio .NET. Microsoft sau đó cũng phát triển riêng một hệ thống báo cáo là Microsoft Report. Sự khác biệt lớn là Crystal Report là phần mềm có phí do vậy để sử dụng bạn phải mua bản quyền.
KoolReport là hệ thống báo cáo viết bằng ngôn ngữ PHP, nó cũng là lựa chọn tốt khi sử dụng framework Laravel, đặc biệt nó có một gói tích hợp giúp phát triển báo cáo KoolReport trong Laravel nhanh chóng mà không cần phải viết nhiều code. Tuy nhiên, để thiết kế được báo cáo chúng ta cần có KoolReport Pro với giá 129$.
Qua rất nhiều so sánh chúng ta có thể thấy JasperReport là lựa chọn tốt với chi phí thấp (gần như không mất phí) cho phép tạo ra các chứng từ phức tạp nhất với các thành phần báo cáo hiện đại như biểu đồ, các loại mã vạch, QR code, bản đồ…
3. Cấu trúc thư viện JasperReport
net.sf.jasperreports.engine.JasperCompileManager: sử dụng để biên dịch JRXML report template.
net.sf.jasperreports.engine.JasperFillManager: Sử dụng để fill report với dữ liệu từ data source.
net.sf.jasperreports.engine.JasperPrintManager: Sử dụng để in tài liệu được sinh ra từ các thư viện JasperReports.
net.sf.jasperreports.engine.JasperExportManager: Sử dụng để xuất ra các định dạng PDF, HTML, hoặc XML.
net.sf.jasperreports.view.JasperViewer: Hiển thị một ứng dụng Java Swing đơn giản có thể hiển thị các báo cáo.
net.sf.jasperreports.view.JasperDesignViewer: Sử dụng để thiết kế và xem trước report templates.
4. Lời kết
JasperReport với bề dầy về phát triển gần 20 năm và một cộng đồng lớn kế thừa từ Java, Eclipse… đã tạo nên một hệ sinh thái xoay quanh các báo cáo, các hệ thống phân tích ra quyết định BI cực phong phú với giá thành cực thấp. JasperReport là lựa chọn tốt khi bạn muốn triển khai các hệ thống báo cáo, hệ thống in ấn chứng từ… tích hợp với các ứng dụng trên nền tảng Laravel. Chúng tôi mong muốn được đem đến cho các bạn các kiến thức cơ bản nhất giúp ích cho công việc các bạn sau này, các kiến thức có thể chưa được biên tập bài bản và đôi khi đưa ra theo những suy nghỉ cảm tính rất mong nhận được sự góp ý từ các bạn bè.
Trước khi bắt đầu tìm hiểu Mybatis separator là gì?. Nếu yếu sinh lí, ý lộn, nếu chưa biết về Mybatis thì các ông có thể đọc bài giới thiệu về Mybatis ở Kieblog.
Trong quá trình làm việc tại công ty, gặp phải một dự án sử dụng Mybatis. Va vấp khá nhiều, rút ra không ít kinh nghiệm khi làm việc với chim nhỏ, nên viết ra bài này chia sẻ chút kinh nghiệm nhỏ nhoi khi làm việc với Mybatis separator.
Mong anh em đón đọc, có gì chưa đúng xin vui lòng bình luận phía dưới. Đm, tôi thề là ông nào chửi tôi cũng rep lại lịch sự nhé!.
Nhìn quen không?. Dynamic với Mybatis rõ ràng render động giấu phẩy giữa các điều kiện IN
Trong trường hợp này seperator giúp thêm các dấu phẩy giữa các điều kiện IN. SQL sẽ generate ra như sau:
SELECT * FROM COURSES
WHERE tutor_id IN (1,2,3)
Không phải chỉ mỗi cho các điều kiện WHERE IN, seperator còn hỗ trợ loop đối với các câu SQL cơ bản. các cặp value phức tạp hơn
SELECT * FROM COURSES
WHERE (tutor_id, lession_id) IN ((1, 2), (2,3), (3,4))
2. Kinh nghiệm xương máu
Sử dụng Mybatis separator thật sự tiện lợi. Nói thẳng ra nhắc tới Dynamic SQL mà không có ví dụ về Mybatis separator thì cũng hết cả vui, hết cả sinh động.
Tuy nhiên, thực tế cho thấy, cần phải thật sự cẩn thận khi sử dụng separator.
Chuyện thế này, ở công ty, khi cả team tôi đang phát hục mặt làm task cho kịp tiến độ thì QC hét lên.
Ủa, sao cái bảng ABC này có tới cả triệu dòng thế?. Ai đã làm gì?. Mới chạy test lòng vòng có vài lần. Sao dữ liệu đâu ra lắm thế?.
Chả có gì để nói nếu tôi không phải là người vô tình tìm ra cái bug do vô tình mà có này.
Nói là bug vô tình nhưng nếu cứ để vậy mà release thì bug này không phải chuyện nhỏ. Tất cả các câu SQL viết cho dự án đều được yêu cầu tối ưu triệt để. (Anh em nào chưa biết thì đọc qua bài viết này Tối ưu SQL, jouneu to the west)
Dự án thì đang dùng Amazon EC3, nếu mới trong một thời gian ngắn sử dụng mà đã có tới mấy chục triệu row trong table thì thật là lạ (dự án đâu làm về big data)
Cắm đầu nhìn code, ban đầu thì chả có gì đặc biệt, anh đồng nghiệp insert một list data. Tất nhiên ảnh sẽ dùng foreach của separator, truyền vào một list, rồi cứ loop trên list đó để insert vào DB thôi.
Đại khái viết thế này:
// Tôi viết đại khái thôi nhé!.
// Chứ theo policy công ty không thể public source được.
<foreach collection="danh_sach_group" item="group" separator="UNION">
<foreach collection="group" item="model">
insert into BANG_NAO_DO
(ten, tuoi)
values
(#{model.ten}, #{model.tuoi})
</foreach>
</foreach>
3. Vấn đề ở đâu?
Như cái ví dụ phía trên tôi post lên thì các ông thấy vấn đề nằm ở đâu không?.
Vấn đề nằm ở chỗ cái separator=”UNION” đấy, thật sự tai hại. Nói không chắc một số anh em chưa hiểu được (cái Dynamic SQL thật sự cần trí tưởng tượng một chút).
Tôi lấy ví dụ nhé:
// Công ty có 2 group (A và B)
Group A - 2 thằng (Tên TÈO - 11 tuổi, tên TỒ - 12 tuổi)
Group B - 3 thằng (Tên TÍ - 13 tuổi, tên TỦN - 14 tuổi, tên TỊT - 15 tuổi)
// Với 2 group này, ta cần 5 câu insert vào BANG_NAO_DO
INSERT INTO BANG_NAO_DO (ten, tuoi) VALUES (TEO, 11)
INSERT INTO BANG_NAO_DO (ten, tuoi) VALUES (TO, 12)
INSERT INTO BANG_NAO_DO (ten, tuoi) VALUES (TI, 13)
INSERT INTO BANG_NAO_DO (ten, tuoi) VALUES (TUN, 14)
INSERT INTO BANG_NAO_DO (ten, tuoi) VALUES (TIT, 15)
Đấy, yêu cầu dùng Mybatis separator chỉ có thế. Insert 5 thằng trong 2 group vào là ổn, nhưng đây lại insert tới gấp đôi, gấp ba. Bảo sao database không quá tải!.
Ra production kiểu này là toang, dễ đi lắm, dễ ăn chửi lắm!. Nên anh em nhớ cẩn thận nha
4. Kết luận
Dynamic SQL thật sự rất tiện lợi, từ khóa Separator cũng vậy. Trường hợp sử dụng lịnh hoạt, SQL sẽ làm rất nhiều việc, bớt phải code ở phần logic Java.
Tuy nhiên, trường hợp sử dụng cần đặc biệt chú ý các từ khóa foreach, separator by UNION, OR, AND. Trước khi viết cần lường được trước câu SQL sẽ render như thế nào?.
Nhớ lường trước cái nguy hiểm của seperator UNION trước khi bắt đầu nha!
Nếu không thể lường trước, phải cố gắng show log câu SQL được render ra như thế nào rồi run thử. Tránh các trường hợp SQLException khi chạy production thực tế.
Bài viết được sự cho phép của tác giả Nguyễn Văn Trọng
Làm việc với vai trò của BrSE là người trung gian thực sự không phải việc đơn giản, vì 1 quyết định đưa ra sẽ có ảnh hưởng đến cả nhiều phía. Dưới đây là 1 số tình huống dở khóc dở cười mà mình đã từng gặp phải.
Khi phát hiện ra hệ thống cần có thêm chức năng abc, nếu nói cho khách hàng biết thì offshore sẽ nổi khùng vì phải overtime, mà không nói thì khách hàng cũng không biết, vậy nên nói không ?
Khi khách hàng không chấp nhận estimate vì các bác nghĩ quá cao, còn nếu giảm xuống thì bên phía nhà mình cho rằng quá thấp, thì phải làm thế nào ?
Khi khách hàng yêu cầu điều tra 1 lỗi hệ thống mà phần này mình chưa từng làm bao giờ, từ chối thì sẽ bị đánh giá thấp, mà nhận nếu làm không được thì gay go nữa ? vậy phải làm sao ?
Để trở thành 1 BrSE tốt thì trong quá trình làm việc hay mỗi lần đưa ra quyết định phải dựa trên nhiều nguyên tắc. Quyết định thì có thể đúng hoặc sai, nhưng 1 khi đã làm thì hãy dứt khoát, có sai thì mới có đúng – có dở thì sau này mới hay được.Có 3 nguyên tắc chính mà bản thân mình cho là quan trọng nhất.
Mình sẽ đưa dẫn chứng cụ thể để giải thích, mời các bạn cùng tham khảo nhé.
Tại sao lại vậy ? đơn giản “khách hàng là thượng đế”, còn nếu bạn muốn làm thượng đế thì … cái này mình bó tay thật.
Các cụ có câu (các cậu có …) “Thương nhau cau sáu bổ ba, ghét nhau cau sáu bổ ra làm mười” – cái ni là mạ miềng (mẹ mình) dạy. Khi mà các bác khách hàng thương mình rồi mọi việc sau này nó dễ dàng vô cùng, làm đúng thì bị khen lấy khen để, mà sai nhỏ nhỏ thì cũng … xí xóa cho qua. Để các bác ấy thương thì phải chiều, chiều sao cho đúng ? thì đơn giản là nhìn mọi việc trên view khách hàng sẽ hiểu ra được những mong muốn sâu xa mà các bác ko nói, hoặc đôi khi chưa nghĩ ra để nói.
Dạo trước, có lần mình phát hiện ra 1 lỗi khá to trong FW của khách hàng (FW này phát triển dựa trên Struts và Spring), lúc này cực kỳ khó xử vì nếu sửa 1 phát thì sẽ phải test lại toàn bộ, anh em offshore Over Time vỡ mật luôn. Mình quyết định nói nhưng kèm theo điều kiện là xin cho mọi người nghỉ vài ngày xả hơi sau khi xong cái ấy. Kết quả là giai đoạn đó tuy căng thẳng nhưng đổi lại từ đó về sau FW chạy rất mượt mà, khách hàng hài lòng còn anh em ở nhà cũng thoải mái hơn rất nhiều.
Đứng về phía khách hàng nhưng cũng phải nghĩ cho bên mình sao cho hài hòa
Nguyên Tắc 2 : Khách Hàng Không Phải Lúc Nào Cũng Đúng
Hãy luôn đặt nghi vấn
Đối với người Nhật, họ luôn chỉn chu trong mọi quyết định, thường sẽ họp hành bàn bạc rất kỹ lưỡng, làm gì cũng có lý do hết. Nên nói họ sai thì rất hiếm khi, nhưng có hoàn toàn hợp lý chưa thì chưa chắc. Hãy luôn đặt ra nghi vấn với yêu cầu. Tại sao họ là yêu cầu làm vậy ? có cách nào hay hơn không ?
Cách đây 3 năm, trong 1 dự án mà các bác giao cho team BrSE mình làm từ design (basic + detail) đến test nghiệm thu. Thì ở giai đoạn design các bác muốn tụi mình làm = WORD. Thiệt sự mình cực kỳ ghét làm với word mà chỉ thích exel, 2 anh trong team cũng vậy vì nó rất khó format, bể tùm lum, ko thích hợp để làm design với đặc thù dự án lúc đó. Rất may là dự án trước đó mình cũng từng dùng word để làm rồi nên hiểu được những khó khăn khi phải chỉnh sửa nhiều, khi mình kể chuyện này cho các bác ấy nghe thì được chấp nhận chuyển đổi yêu cầu. Và sau này viết thêm mấy cái tool macro (word thì bótay) Gen tài liệu nữa nên năng suất vù vù mới thấy quyết định đó quá đúng đắn.
Nguyên Tắc 3 : Không Nhất Thiết Phải Nói Thật Nhưng Tuyệt Đối KHÔNG ĐƯỢC NÓI DỐI
Luôn tôn trọng sự thật
Đây là kinh nghiệm mình được 1 anh senpai truyền lại, trải nghiệm và thấy nó rất cần thiết. Người Nhật ghét nhất 2 thứ : ăn cắp và dối trá. Dân tộc Nhật, nền kinh tết Nhật dựa trên niềm tin, làm giàu bằng niềm tin từ khách hàng nên dối trá đối với họ là cái gì đó rất kinh khủng.
Khi khách hàng hỏi “tiến độ coding đến đâu rồi mậy”, còn 1 tuần nữa release mà đang cháy bùng bùng, cái module to nhất thì chú dev phụ trách lại nghỉ phép giữa chừng vì cô người yêu bị … sổ mũi. Tình hình có vẻ căng ! trả lời sao đây ?
“dạ bác ok lắm ah” – vừa nói xong bỗng nhận được mail xin dời deadline của PM bên offshore => vỡ mặt.
“Dạ Function xxxOzawa do chú Kim phụ trách nhưng chú ấy xin nghỉ phép rồi ah, chắc là sẽ bị trễ … vài ngày” => vỡ đầu.
Lúc này tốt nhất là xin trả lời sau, trước mắt phải bàn bạc cụ thể với PM để bàn đối sách giải quyết, đưa ra giả thuyết cho trường hợp tốt nhất – xấu nhất, rồi sau đó mới thành thật với khách hàng (thành thật về tiến độ – kèm đối sách, nhưng mấy cái lý do lãng xẹt liên quan chú dev tên Kim thì ko nên nói ra).
Tổng Kết
Đánh giá sự việc trên view khách hàng
Khách hàng không phải lúc nào cũng đúng.
Không nói dối nhưng không nhất thiết phải nói thật.
Trên đây là bộ 3 nguyên tắc cơ bản của bản thân mình, tất nhiên là cũng có vài nguyên tắc khác nhưng mình thấy nó không quan trọng lắm nên ko liệt kê. Các bạn có thể áp dụng theo nhưng đừng cứng nhắc quá nhé, nếu có gì hay cứ share cho mình học hỏi thêm ờ comment bên dưới nhé.
Bấm tổ hợp phím Ctrl+Shift+A (Windows) hoặc Cmd + Shift + A (MacOS), tìm Registry. Khi hộp thoại Registry hiển thị thì kích hoạt (enable) cấu hình compiler.automake.allow.when.app.running
Bước 3 — Khởi động lại IntelliJ và thưởng thức
Chú ý:
Một số tình huống có thể xảy ra khi sử dụng tính năng Hot Reload là lỗi chạy lại nếu có sử dụng@Autowired trong mã nguồn Spring. Cách giải quyết: dùng phương pháp inject qua contructor thay vì @Autowired .
Bài viết được sự cho phép của tác giả Nguyễn Hữu Khanh
Java cho phép chúng ta có thể định nghĩa một class nằm trong một class khác, ví dụ như:
package com.huongdanjava.javaexample;
public class Application {
class Builder {
}
}
Class được định nghĩa bên trong một class khác, chúng ta gọi nó là nested class còn class khác này được gọi là outer class. Trong ví dụ của mình ở trên thì Builder là một nested class còn Application là một outer class. Nested class còn được chia thành 2 loại khác nhau là Non-static nested class và Static nested class. Cụ thể như thế nào? Trong bài viết này, chúng ta sẽ tìm hiểu về các nested class trong Java các bạn nhé!
Non-static nested class hay còn gọi là những Inner class. Chúng ta có thể declare những Inner class này bên trong một class khác như class Builder ở trên, bên trong một phương thức nào đó (Method-local Inner Class), ngoài ra chúng ta còn có những class không có tên gọi là Anonymous Inner Class.
Khác với việc khai báo một class, chúng ta không thể declare nó với private access modifier, đối với Inner class chúng ta có thể làm điều này. Như ví dụ trên, mình có thể declare class Builder là private như sau:
package com.huongdanjava.javaexample;
public class Application {
private class Builder {
}
}
Mà khi đã declare một Inner class là private thì nó chỉ có thể được access bên trong class nó được khai báo thôi các bạn nhé!
Để khởi tạo một Inner class, các bạn cần khởi tạo đối tượng cho outer class trước. Ví dụ như:
package com.huongdanjava.javaexample;
public class Application {
private class Builder {
}
public static void main(String[] args) {
Application app = new Application();
Builder builder = app.new Builder();
}
}
Khi chúng ta khai báo một class bên trong một method nào đó, gọi là Method-local Inner Class, thì scope của class này chỉ thuộc về method đó mà thôi. Các bạn không thể khai báo class này với access modifier trong trường hợp này. Ví dụ như:
package com.huongdanjava.javaexample;
public class Application {
public void print() {
class Builder {
}
}
}
Và do đó, chúng ta chỉ có thể khởi tạo đối tượng của class này bên trong method:
package com.huongdanjava.javaexample;
public class Application {
public void print() {
class Builder {
}
Builder builder = new Builder();
}
}
Các bạn có thể khai báo final class hoặc abstract class bên trong một method các bạn nhé:
package com.huongdanjava.javaexample;
public class Application {
private void print() {
abstract class Builder {
}
}
}
Hay:
package com.huongdanjava.javaexample;
public class Application {
private void print() {
final class Builder {
}
}
}
Anonymous Inner Class thường được khai báo khi chúng ta muốn override một phương thức nào đó của một class hoặc interface. Chúng ta sẽ khai báo Anonymous Inner Class và khởi tạo đối tượng cho class này cùng một thời điểm. Ví dụ như:
package com.huongdanjava.javaexample;
public class Application {
public void print() {
Comparable<String> comparable = new Comparable<String>() {
public int compareTo(String o) {
return 0;
}
};
}
}
Các bạn cũng có thể declare Anonymous Inner Class bên ngoài method như sau:
package com.huongdanjava.javaexample;
public class Application {
Comparable<String> comparable = new Comparable<String>() {
public int compareTo(String o) {
return 0;
}
};
public void print() {
}
}
Static nested class là những class được định nghĩa với từ khoá static. Nó là static member của outer class và do đó chúng ta không cần phải khởi tạo đối tượng của outer class và chính class này để access tới nó luôn. Ví dụ như:
package com.huongdanjava.javaexample;
public class Application {
static class Builder {
private static void print() {
}
}
public static void main(String[] args) {
Application.Builder.print();
}
}
Static nested class sẽ không access được tới các field, methods non-static của outer class. Ví dụ như:
Chúng ta không thể declare Static nested class bên trong method các bạn nhé!
Bữa rồi rảnh rỗi ngồi lướt Facebook thấy có status đăng như sau “Phỏng vấn Vuejs một bạn làm Vuejs 2 năm nhưng không rõ về mounted, computed và created“.
Nghĩ mà buồn thay, nên tiếp sau bài viết về Vuejs life cycle – hiểu sao cho đúng. Mình quyết định viết thêm bài viết một số câu hỏi phỏng vấn Vuejs cơ bản. Hy vọng sẽ giúp đỡ các bạn khi try hard phỏng vấn Vuejs nha.
v-if only renders the element to the DOM if the expression passes whereas v-show renders all elements to the DOM and then uses the CSS display property to show/hide elements based on expression.
v-if chỉ render các element tới cây DOM nếu biểu thức bên trong dấu bằng đúng (true). Trong khi đó, v-show render tất cả các element đó trên DOM và sử dụng CSS để ẩn hiện thông qua tính đúng sai trong biểu thức
Rõ ràng mà nói, sự khác biệt cơ bản và chuẩn chỉnh nhất giữa v-if và v-show là có render lên DOM tree hay không?. V-if rõ ràng không hề render object đó nếu không thỏa điều kiện, còn v-show thì có.
v-if has higher toggle costs while v-show has higher initial render costs
v-if có chi phí render khá cao, chắc chắn là cao hơn v-show, thoải điều kiện thì v-show sẽ render lại object đó trên DOM. Nên cẩn thận khi dùng
// v-show render trên DOM, nhưng display: none để ẩn đi
<h1 v-show="ok">Hello!</h1>
Nên một số đối tượng thường xuyên ẩn hiện nên sử dụng v-show. Bạn nào chưa biết về cây DOM có thể tham khảo bài viết này tại Kieblog.
Đấy, đọc thì tưởng là câu hỏi phỏng vấn Vuejs dễ. Trả lời cho đúng với bài bản thì không phải dễ nha.
2. Tại sao khi bind v-for thường phải mapping key
Hơi kì kì nhưng câu trả lời cũng có ý đúng là v-if thì có v-else còn v-show thì không
3. Có thể for loop một range với v-for được không?
Căng, trước giờ v-for với index cho từng item thì thấy nhiều, chứ for cho range thì hơi lạ. Tuy nhiên đây không phải câu phỏng vấn Vuejs khó. Chỉ là một câu hỏi mẹo thôi
v-for với kiểu mapping idx kiểu này thì bình thường, xài rất nhiều.
<div
class="message-content"
v-for="(msg, idx) in message.contents"
v-bind:key="idx"
v-html="msg"
></div>
Tuy nhiên, vẫn có một kiểu khác ít biết hơn có thể for trong range như sau:
// Sử dụng từ khóa in, loop trong dãy từ 1->10
<div>
<span v-for="n in 10">{{ n }} </span>
</div>
Đù, hỏi thách đố vl, nhưng không sao, biết thêm chừng nào tốt chừng đó. Code kiểu này trong pro ra hẳn.
4. Dynamic route matching là gì?
Dynamic route cũng là một câu hỏi phỏng vấn khá hay. Tất nhiên đã làm nhiều với Vuejs sẽ trả lời hoặc đưa ra được ví dụ về câu hỏi này. Đã làm Vue thì mình nghĩ trong quá trình phỏng vấn Vuejs chắc chắn sẽ có một câu liên quan tới Router. Nên ôn kĩ nha!.
Tuy nhiên, chính xác mà nói:
Dynamic route matching to map routes to the same component based on a pattern.
Dynamic route matching giúp ta map các route tới component với các pattern khác nhau
Cùng xem xét ví dụ dưới đây:
// Sử dụng từ khóa in, loop trong dãy từ 1->10
const Aritcle = {
template: '<div>Aritcle {{ $route.params.subjectId }}, PostId: {{ route.params.postid }}</div>'
}
const router = new VueRouter({
routes: [
// dynamic segments start with a colon
{ path: '/subject/:subjectId/post/:postid', component: Aritcle }
]
})
Khi sử dụng, các URL mapping với router param sẽ cho các kết quả như sau:
/subject/vuejs/post/123 hoặc /subject/react/post/234
5. Mục đích của keep alive tag?
Câu hỏi phỏng vấn Vuejs về keep-alive thường không hỏi nhiều. Tuy nhiên khá dễ để nắm bắt, bạn nào có đọc qua chắc chắn trả lời được.
Keep-alive tag is an abstract component used to preserve component state or avoid re-rendering. When you wrapped tag around a dynamic component, it caches the inactive component instances without destroying them.
Keep-alive tag sử dụng để giữ trạng thái hiện có của component và tránh việc render lại component quá nhiều lần. Các đối tượng được đóng ở trong tag keep-alive sẽ được giữ instances
Keep-alive component đặc biệt hữu ích ở các component stepper. Một khi đã lưu các thông tin ở step này, back lại sẽ có ngay, không phải render.
<!-- Inactive components will be cached! -->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
Ngoài việc không phải render lại (về perfomance), sử dụng keep alive tag còn tránh việc sử dụng Store vô tội vạ để lưu trữ khi back đi back lại
<!-- basic -->
<keep-alive>
<component :is="view"></component>
</keep-alive>
<!-- multiple conditional children -->
<keep-alive>
<comp-a v-if="a > 1"></comp-a>
<comp-b v-else></comp-b>
</keep-alive>
<!-- used together with `<transition>` -->
<transition>
<keep-alive>
<component :is="view"></component>
</keep-alive>
</transition>
Trong bài viết này, Smartjob sẽ chia sẻ cho các bạn một câu chuyên khá hi hữu về chàng trai hiện đang là lập trình viên của gã khổng lồ Google khi anh ấy bằng một cách thần kỳ đã bảo toàn được tới 90% lương của mình. Quả là một câu chuyện đáng học hỏi cho những ai muốn tiết kiệm thật nhiều cho tương lai của mình.
Chàng trai đó tên là Brandon, 24 tuổi, trở thành kỹ sư phần mềm của Google từ tháng 5/2015. Anh ấy đã có ý tưởng sống trong một chiếc xe tải để thắt chặt chi tiêu của mình khi bắt đầu thực tập tại Google giữa năm 2014. Vào thời điểm đó, Brandon cùng 3 người bạn của anh đã thuê một căn hộ 2 giường ngủ với mức giá rẻ nhất San Francisco (khoảng 65USD/đêm tương đương 2000 USD/tháng).
Chiếc xe của Brandon tại khuôn viên Google
Sau kì thực tập, Brandon đã thuyết phục hoàn toàn được các sếp trong Google và trở thành kỹ sư phần mềm tại đây. Ngay sau đó, anh đã chi khoảng 10.000USD để mua chiếc xe tải cũ sản xuất năm 2006, nhãn hiệu Ford cỡ nhỏ. Chiếc xe này đã chạy được hơn 252.000km nhưng vẫn còn rất tốt đối với Brandon. Thực chất số tiền 10.000USD là số tiền thưởng mà Google đã dành cho Brandon khi anh ký hợp đồng với hãng này.
Chiếc xe tải của Brandon rộng gần 12m2, không quá lớn nhưng đủ để anh đặt 1 chiếc giường, 1 tủ quần áo, 1 giá treo đồ và 1 lồng thú cưng :D. Theo lời anh, trước đó dù được ở trong một căn hộ nhưng anh hoàn toàn không thoải mái và không có cảm giác như ở nhà. Với Brandon đó chẳng khác gì ném tiền qua cửa sổ. Brandon chia sẻ thêm: “Thật khó để biện mình cho hành động ném tiền qua cửa sổ như thế. Về cơ bản, tôi đang lãng phí tiền bạc. Số tiền ấy chẳng để góp vốn cho cái gì hay xây dựng tương lai vì thế tôi không đành lòng tiếp tục thuê căn hộ.”
Tại San Francisco, số tiền mà chàng trai này phải bỏ ra cho việc mua bảo hiểm xe tải là 121USD/tháng. Bên cạnh đó, hóa đơn tiền điện thoại anh dùng sẽ do Google chi trả. Để tiết kiệm hơn, anh cũng ăn 3 bữa và tắm giặt ngay tại văn phòng. Và đặc biệt, anh không mua thêm bất cứ vật dụng nào liên quan đến điện. Anh chia sẻ: “Tôi không sở hữu bất cứ vật gì dùng đến điện. Chiếc xe tải của tôi có sẵn đèn. Tôi có một chiếc đèn pin để sử dụng vào ban đêm. Ngoài ra, tôi chỉ có một cục pin nhỏ để dự phòng, vài ngày mới sạc một lần trên công ty. Tôi dùng cục pin này để sạc tai nghe và điện thoại di động. Còn laptop của tôi có thể chạy qua đêm sau mỗi lần sạc đầy tại văn phòng.”
Brandon đặt ra mục tiêu là tiết kiệm 90% thu nhập sau khi đã trừ các khoản thuế. Anh sẽ dùng số tiền đó để trả nợ đã vay từ thời còn sinh viên và đầu tư cho các kế hoạch của riêng mình. Anh cũng tiết lộ rằng anh đã vay số tiền 22.434USD để trang trải cho việc học đại học và với việc tiết kiệm triệt để trong vòng 4 tháng, anh đã trả được 16.449USD và theo như các tính toàn của anh, tất cả số tiền anh đã vay sẽ được hoàn trả trong vòng 6 tháng tới. Trong 10 đến 20 năm nữa, anh sẽ có trong tay hàng trăm ngàn USD với chính sách thắt chặt chi tiêu như hiện nay.
Chia sẻ thêm về cuộc sống của Brandon: anh luôn là người có mặt sớm nhất công ty bởi mỗi sáng sớm anh chỉ tốn vài bước chân đi bộ từ oto đến nơi làm việc trong khi đồng nghiệp của anh phải chờ hàng giờ trên tàu điện hoặc chết đứng trong đám kẹt xe. Anh nghĩ anh vẫn còn trẻ và lựa chọn cuộc sống như vậy tuy có hơi kì cục nhưng cũng chẳng ảnh hưởng đến ai.
Nhận xét của Smartjob:
Brandon là một chàng trai thực sự giỏi dù có hơi “dị”. Từ khi học đại học cho đến khi thi tuyển và làm việc tại Google anh hoàn toàn biết cách “quản trị cuộc đời” mình. Việc vay tiền, đi học, học thật giỏi, thi vào Google tất cả đã nằm trong tính toán của anh. Với cuộc sống đắt đỏ tại San Francisco, với khoản nợ khá lớn như thế, tiết kiệm là cách tốt nhất giúp anh rút ngắn thời gian để thực hiện những ước mơ của mình. Các bạn có thể cười chê chàng trai này, cho rằng anh ấy mặt dày, hà tiện,… nhưng theo Smartjob, Brandon là người đáng để học hỏi. Ngay cả Google cũng tôn trọng cách sống của anh thì chúng ta chẳng có lý do gì để cười nhạo một người như thế. Mỗi ước mơ khi đã thành hiện thực đều có những sự đánh đổi và Brandon đã lựa chọn cách đánh đổi như thế.
Bài viết được sự cho phép của tác giả Nguyễn Hữu Khanh
Trong bài viết trước, mình đã giới thiệu với các bạn về Liquibase, một thư viện giúp chúng ta hiện thực việc database migration cho một ứng dụng Java bất kỳ. Trong bài viết này, mình sẽ hướng dẫn các bạn hiện thực database migration sử dụng Liquibase với Spring MVC các bạn nhé!
Mặc định, Liquibase đã hỗ trợ việc integrate với Spring framework. Bằng cách khai báo bean của class SpringLiquibase trong Spring container, khi chạy ứng dụng có sử dụng Spring framwork lên, Liquibase sẽ tự động thực hiện việc database migration cho chúng ta các bạn nhé!
Các bạn có thể cấu hình bean của class SpringLiquibase trong Spring container sử dụng XML configuration (trong tập tin /src/main/webapp/WEB-INF/spring/root-context.xml) như sau:
Từ value của changeLog trong cấu hình của SpringLiquibase ở trên, các bạn có thể hiểu là mình cần tạo tập tin changelog của Liquibase db-changelog.xml trong thư mục src/main/resources.
Mình sẽ định nghĩa nội dung tập tin changelog để thêm bảng clazz như sau:
React Router Cheatsheet và mọi thứ bạn cần biết (Phần 2)
Tác giả: Reed Barger
Thành phần liên kết
Giả sử với NavBar, chúng tôi muốn tạo một số liên kết để có thể di chuyển xung quanh ứng dụng của mình dễ dàng hơn thay vì phải thay đổi URL theo cách thủ công trong trình duyệt.
Chúng ta có thể làm như vậy với một thành phần đặc biệt khác từ React Router DOM là Link component. Nó chấp nhận prop to, chỉ định nơi muốn liên kết điều hướng người dùng đến. Trong trường hợp này có thể có một liên kết về:
import{ BrowserRouter as Router, Switch, Route, Link }from"react-router-dom";exportdefaultfunctionApp(){return(<Router><Navbar /><Switch><Route path="/" component={Home}/><Route path="/about" component={About}/></Switch></Router>);}functionNavbar(){return(<nav><Link to="/">Home</Link><Link to="/about">About</Link></nav>)}
Thành phần liên kết cho phép cung cấp một số inline styles giống như bất kỳ thành phần React tiêu chuẩn nào. Nó cũng cung cấp một prop component hữu ích, vì vậy chúng tôi có thể đặt liên kết của mình làm thành phần tùy chỉnh riêng để tạo kiểu dễ dàng hơn.
Ngoài ra, React Router DOM cũng cung cấp NavLink Component rất hữu ích trong trường hợp muốn áp dụng một số kiểu đặc biệt.
Nếu bạn đang ở trên đường dẫn mà liên kết trỏ đến, điều này cho phép tạo một số kiểu liên kết hoạt động để cho người dùng biết, bằng cách xem liên kết đang ở trang nào.
Ví dụ, nếu người dùng đang ở trên trang chủ, chúng tôi có thể cho họ biết càng nhiều bằng cách sử dụng phần mềm activeStyle hỗ trợ để làm cho liên kết được in đậm và có màu đỏ khi họ ở trên trang chủ:
import{
BrowserRouter as Router,
Switch,
Route,
NavLink
}from"react-router-dom";exportdefaultfunctionApp(){return(<Router><Navbar /><Switch><Route path="/" component={Home}/><Route path="/about" component={About}/></Switch></Router>);}functionNavbar(){return(<nav><NavLink
activeStyle={{
fontWeight:"bold",
color:"red"}}
to="/">
Home
</NavLink><NavLink activeClassName="active" to="/about">
About
</NavLink></nav>);}
Ngoài ra còn có prop activeClassName có thể được thiết lập nếu bạn không muốn bao gồm các inline styles hoặc muốn nhiều kiểu có thể tái sử dụng hơn để thực hiện cùng một chức năng activeStyle.
Một thành phần rất hữu ích khác mà React Router DOM cung cấp là thành phần chuyển hướng. Điều này có vẻ lạ khi có một thành phần thực hiện chức năng chuyển hướng người dùng khi nó được hiển thị, nhưng lại rất hữu ích.
Bất cứ khi nào bạn sử dụng một cái gì đó như một private route và có điều kiện trong đó người dùng không được xác thực, bạn có thể chuyển hướng họ trở lại trang đăng nhập.
Dưới đây là một ví dụ về việc triển khai các thành phần về private route đảm bảo rằng người dùng được xác thực trước khi nó hiển thị cho họ một route cụ thể đã được khai báo với thành phần này.
Ngược lại, nếu chúng không được xác thực, chúng sẽ được chuyển hướng đến một public route (có thể là một route để đăng nhập) khi thành phần chuyển hướng được hiển thị:
import{
BrowserRouter as Router,
Switch,
Route,
Redirect
}from"react-router-dom";exportdefaultfunctionApp(){return(<Router><Switch><Route exact path="/" component={Home}/><PrivateRoute path="/hidden" component={Hidden}/></Switch></Router>);}functionPrivateRoute({ component: Component,...rest }){// useAuth is some custom hook to get the current user's auth stateconst isAuth =useAuth();return(<Route
{...rest}
render={(props)=>
isAuth ?<Component {...props}/>:<Redirect to="/"/>}/>);}functionHome(){return<>home</>;}functionHidden(){return<>hidden</>;}
useHistory Hook
Trên tất cả các thành phần đầy chất lượng này, có một số hook rất hữu ích mà React Router DOM cung cấp thêm cho chúng ta.
Chúng chủ yếu hữu ích bằng cách cung cấp thông tin bổ sung mà bạn có thể sử dụng trong các thành phần của mình. Chúng có thể được gọi là các React hook bình thường mà chúng ta có thể sử dụng các giá trị của chúng một cách chính xác theo ý muốn.
Có lẽ một trong những hook hữu dụng nhất chính là useHistory hook. Có thể sử dụng nó ở đầu bất kỳ thành phần nào được khai báo trong thành phần bộ định tuyến của chúng tôi và lấy lại các dữ liệu history, bao gồm thông tin như vị trí được liên kết với thành phần chẳng hạn.
Điều này cho chúng ta biết tất cả về vị trí hiện tại của người dùng, chẳng hạn như tên đường dẫn mà họ đang truy cập, cũng như bất kỳ tham số truy vấn nào có thể được thêm vào URL của chúng tôi. Tất cả dữ liệu vị trí đều có thể truy cập được từ history.location:
import{ useHistory }from"react-router-dom";functionAbout(){const history =useHistory();
console.log(history.location.pathname);// '/about'return(<><h1>The about page is on:{history.location.pathname}</h1></>);}
Ngoài ra, các thông tin lịch sử trực tiếp bao gồm các phương pháp hữu ích cho phép hướng người dùng theo chương trình đến các trang khác nhau trong ứng dụng của bạn.
Điều này rất hữu ích, ví dụ, để chuyển hướng người dùng sau khi đăng nhập hoặc trong bất kỳ tình huống nào mà cần phải đưa người dùng từ trang này sang trang khác.
Chúng tôi có thể đẩy người dùng từ trang này sang trang khác bằng cách sử dụng history.push. Khi sử dụng phương pháp đẩy, bạn chỉ cần cung cấp đường dẫn mà chúng tôi muốn đưa người dùng của mình đến bằng cách sử dụng phương pháp này. Nó thêm trang mới này vào stack về lịch sử:
import{ useHistory }from"react-router-dom";functionAbout(){const history =useHistory();
console.log(history.location.pathname);// '/about'return(<><h1>The about page is on:{history.location.pathname}</h1><button onClick={()=> history.push('/')}>Go to home page</button></>);}
Cũng có thể chuyển hướng người dùng bằng history.replace, giá trị này cũng chấp nhận giá trị đường dẫn, nhưng xóa mọi thứ trong lịch sử, sau khi điều hướng được thực hiện. Điều này rất hữu ích cho những trường hợp không cần quay lại lịch sử, chẳng hạn như sau khi người dùng đã đăng xuất.
useLocation Hook
Các useLocation hook bao gồm tất cả các thông tin tương tự mà useHistory hook làm.
Điều quan trọng cần lưu ý là nếu bạn cần cả dữ liệu vị trí và sử dụng lịch sử để điều hướng người dùng của mình theo chương trình, hãy đảm bảo sử dụng History. Tuy nhiên, nếu bạn chỉ muốn dữ liệu vị trí, tất cả những gì bạn cần làm là sử dụng useLocation hoặc lấy lại tất cả dữ liệu vị trí trên một đối tượng giống với dữ liệu được cung cấp trên history. location:
import{ useLocation }from"react-router-dom";functionAbout(){const location =useLocation();
console.log(location.pathname);// '/about'return(<><h1>The about page is on:{location.pathname}</h1></>);}
useParams Hook + Dynamic Routes
Một điều mà chúng tôi không đề cập đến khi nói đến các routes là chúng tôi có thể tạo ra các dynamic routes một cách tự nhiên. Điều này có nghĩa là các tuyến không cố định và được xác định, nhưng có thể là bất kỳ số ký tự nào.
Các dynamic routes rất hữu ích trong các tình huống mà chúng tôi có, chẳng hạn như một bài đăng trên blog với một slug duy nhất. Làm cách nào để đảm bảo rằng chúng tôi hiển thị dữ liệu và các thành phần một cách thích hợp, vì slug bài đăng trên blog của chúng tôi có thể hoàn toàn khác?
Để khai báo một tham số route trên một route nhất định, nó phải được đặt trước bằng dấu hai chấm :. Nếu muốn tạo một dynamic route, “/ blog /: postSlug”, cho một thành phần bài đăng trên blog, nó có thể giống như sau:
Nhưng trong thành phần BlogPost, làm cách nào để nhận được dữ liệu slug của bài đăng đó? Chúng ta có thể truy cập bất kỳ thông số tuyến nào của một tuyến đã khai báo với thành phần liên kết của nó bằng cách sử dụng useParams hook.
useParams sẽ trả về một đối tượng sẽ chứa các thuộc tính phù hợp với các tham số tuyến của chúng ta (trong trường hợp này là postSlug). Chúng ta có thể sử dụng cấu trúc đối tượng để truy cập ngay lập tức và khai báo dưới dạng một biến với tên postSlug:
Nếu muốn biết liệu thành phần đã cho có nằm trên một trang nhất định hay không, chúng ta có thể sử dụng useRouteMatch hook.
Ví dụ: trong bài đăng trên blog, để xem liệu trang chúng tôi đang truy cập có khớp với tuyến đường “/ blog /: postSlug” hay không, chúng tôi có thể lấy lại giá trị boolean sẽ cho chúng tôi biết liệu tuyến đường mà chúng tôi đang truy cập có khớp với mà chúng tôi đã chỉ định:
import{ useRouteMatch }from"react-router-dom";functionBlogPost(){const isBlogPostRoute =useRouteMatch("/blog/:postSlug");// display, hide content, or do something else}
Sau đó bạn cd vào thư mục code MVVMSample. Mẹo là gõ cd MV, nhấn nút tab trên bàn phím nó cũng ra lệnh như sau:
cd MVVMSample
Tiếp theo gõ để show toàn bộ branch:
git branch -a
Bạn sẽ thấy branch master, bai1… Mỗi branch sẽ chứa code của 1 bài. Ở đây ta chỉ quan tâm code bài 1 nên bạn cần chuyển qua code của bài 1 bằng cách gõ như sau:
git checkout bai1
Như vậy là bạn đã có code của bài hôm nay rồi đấy. Đơn giản đúng không?
Để triển khai mô hình MVVM, chúng ta cần nắm qua mô hình này xem nó hoạt động như thế nào.
Hãy tiếp cận 1 cách đơn giản nhất nhé. Mặc định khi bạn tạo 1 project thì Xcode nó tự render các file theo tiêu chuẩn MVC của họ. M là Model, V là View, còn C là Controller. Tuy nhiên, ở đây chúng ta sẽ hay xử lý logic để lấy dữ liệu cho view và hiển thị view chung vào ViewController.
Nếu tiếp cận cách đơn thuần như thế, thì cái table của bạn muốn có dữ liệu thì bạn phải tạo được data cho nó ở trong ViewController luôn. Rồi ngày qua ngày, cái table view này nó thêm nhiều tính năng mới. Ví dụ như khi chạm vào từng cell trên table thì phải kiểm tra xem cell đó là con gì, mở màn hình detail tương ứng. Hoặc trong cái view controller của bạn sếp yêu cầu phải lấy data từ trên server về thay vì tạo data set cứng trong nó. Bạn phải xử lý khi có data thì hiển thị như nào, khi không có data thì hiển thị như nào. Rồi một ngày đẹp trời nữa sếp bạn yêu cầu tao muốn hiển thị nhiều loại cell khác nhau, mỗi loại cell 1 loại data khác nhau… Code cứ dài càng dài, và càng khó chỉnh sửa sau này.
Vấn đề của MVC ở đây là xử lý hết logic trong View, thì code nhiều. Mà nhiều thì khó đọc, cáu, chửi thề, và cuối cùng là đấm sếp rồi nghỉ việc
Có rất nhiều developer đã căng thẳng như thế, tóc bạc đi nhiều sau nhiều năm code MVC thuần. Rồi họ bắt đầu bực bội và muốn thay đổi điều gì đó để cho công việc đơn giản hơn. Họ nghĩ ngay “tại sao không đưa các xử lý logic về data sang 1 file khác, còn view chỉ đơn thuần là find data vào thôi?” Và rồi họ quyết định làm điều đó. Ra ban công làm khói, ngắm hàng xóm và tiếp tục đọc nhé.
Thành phần mới này họ đặt tên là View Model – nghĩa là nó giao thông qua lại giữa view và model. Cái tên rất sát nghĩa đúng không?
Thôi nói lan man. Ở đây tôi sẽ trình bày cách triển khai từng phần 1 cho dễ hiểu nha.
Đầu tiên bạn tạo mới 1 project bằng Xcode, đặt tên nó là gì cũng được. Vào file Main.storyboard và kéo thả 1 cái tableview, 1 cái title như hình:
Kéo thả title và tableview
Bước tiếp theo bạn tạo mới 1 file AnimalViewController kế thừa từ UIViewController, chọn nó quản lý cái file giao diện bạn vừa làm. Tiến hành kéo thả các liên kết từ view vào UIViewController này.
Giả sử tôi kéo thả tableview và được code như sau:
@IBOutlet weak var tableView: UITableView!
Chưa xong, đã có tableview rồi thì phải tạo cell cho nó. Vậy bạn hãy tạo 1 cái lớp MyTableViewCell kế thừa từ UITableViewCell. Và lại kéo thả! Việc nhẹ lương cao nhỉ
Hình nó như này:
Bên trái là label, bên phải là image.
Nhớ auto layout cho nó nhé. Không biết auto layout thì tôi đấm luôn. Chú ý cái khoanh đỏ tôi lười nên hay lấy cái identifier này trùng với tên class cell. Để cho dễ nhớ và copy cho tiện. Mục đích của nó là sau tái sử dụng.
Code như sau:
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var animalImageView: UIImageView!
Rồi, và bây giờ để code clean thì bạn tạo cho tôi 1 cái hàm ở AnimalViewController có nội dung như sau:
Rất basic, ở đây nó có ý nghĩa là table đăng ký cell để tí dùng. Kế thừa các hàm delegate và datasource. Những công việc mà newbie nào cũng cần biết nhỉ. Sau đó ném cái hàm vào trong hàm viewDidLoad nhé. Vẫn chưa thấy view model đâu. Bây giờ là chuyên mục chính này.
Đầu tiên là bạn phải tạo cái model để chứa dữ liệu animal của tôi, bao gồm 1 cái tên và 1 cái hình. Tôi tạo code bằng 1 cái struct như sau:
struct Animal {
let name: String
let image: String
}
Struct vì nó là giá trị, không có dùng class à nha. Class để giành cho View model.
Rồi, bây giờ ta sẽ nghĩ ngay đến việc là cần 1 cái view model để xử lý các công việc sau:
Tạo datasource cho tableview, chắc chắn phải ném nó vào hàm init của view model
Tạo các hàm trả về cho delegate và datasource của tableview.
Cụ thể bạn tạo file AnimalViewModel và code như sau:
Code thì dài dòng, nhưng để đơn giản thì tôi tạo data ở local, bài sau tôi sẽ hướng dẫn lấy data từ đâu đó trên mạng nha. Các công việc như sau:
Hàm init sẽ tạo datasource cho tableview
Hàm numberOfRowsInSection tôi đặt giống hàm của table datasource, mục đích trả số row trên 1 section
Hàm cellForRowAt cũng vậy, mục đích là để bind data cho cell nhá
Và quay lại file AnimalViewController, bạn viết thêm cho tôi dòng này:
var animalViewModel: AnimalViewModel!
Khi tạo xong view model rồi, thì bạn phải ném nó vào view controller. Sau đó, lại để cho code clean, bạn tạo 1 hàm xử lý view model cho nó. Code như sau:
Ở trên chỉ độc 1 dòng code là tạo view model. Tuy nhiên các dự án thực tế nó có nhiều setting khác, như các closure của view model. Nhưng khoan quan tâm, tôi sẽ trình bày ở các bài sau nhé.
Và bây giờ là cần xử lý cho table view, bạn viết đoạn code sau:
Đoạn code trên khá sạch sẽ, view controller sẽ lấy data thông qua view model, và bạn méo cần quan tâm trong đó có gì, chỉ cần view model mày trả data cho tao là được. Do vậy, view controller đã không phải xử lý các logic xử lý model nữa. Khỏe hẳn ra, cứ như là lấy vợ về có người nấu cơm cho ăn ấy.
Tuy nhiên, ở ví dụ này tôi có 1 loạt bức ảnh màu trắng, bạn có thể xem trong mục Assets như hình:
Ảnh tôi chôm trên mạng
Do vậy để cho ứng dụng màu mè thì tôi viết thêm các extension tạo màu từ ảnh trắng này. Các bạn vào file Extensions để xem.
Trong UIImageView+Extensions tôi có code:
extension UIImageView {
func setImageColor(color: UIColor) {
let templateImage = self.image?.withRenderingMode(.alwaysTemplate)
self.image = templateImage
self.tintColor = color
}
}
Đoạn này là tạo màu cho ảnh.
Trong UIColor+Extensions, là tạo màu random cho ảnh nó khác màu nhau nhá:
Bài viết được sự cho phép của tác giả Nguyễn Văn Trọng
Dạo này có nhiều bạn hay hỏi han mình về 1 số kinh nghiệm trong nghề, hầu hết đều là các bạn trẻ đang học đại học hoặc ra trường 1 vài năm nhưng chưa xác định hướng đi rõ ràng. Khi nói chuyện thì mình phát hiện ra là còn nhiều ngộ nhận về nghề này nên mình mạo muội kể ra 5 điểm cho anh chị em tham khảo. Thiệt sự còn nhiều điều các bạn trẻ chưa hình dung hết trong con đường phía trước nhưng mà mình cực kỳ vui khi biết nhiều thanh niên thanh nữ đang rất máu lửa dấn thân vào nghề “code dạo cầu vinh”, và mình cảm nhận được sự cố gắng của các bạn từng ngày, ham học ham làm chứ không ham chơi như mình hồi xưa
1. BrSE Chỉ Làm Việc Communication (Làm Cầu Nối Liên Lạc)
Thực tế kết nối thông tin chỉ là 1 phần trong chuỗi công việc hàng ngày mà BrSE phải đảm nhận. Ngoài việc truyền đạt yêu cầu khách hàng cho team offshore/onsite cũng còn rất nhiều task thú vị khác như đi xin lỗi … giỡn chứ cũng ngồi mày mò code với tìm hiểu công nghệ, support kỹ thuật…
2. BrSE Không Cần Giỏi Công Nghệ
Có nhiều bạn thắc mắc rằng “code không cứng có làm được BrSE không ? “, giờ mình hỏi lại “không cao to đẹp trai, không dẻo miệng có cưa được gấu không ? “, thực tế các bạn thấy mấy cu cậu xấu hoắc mà gấu xinh như tiên thường có 1 thế mạnh đặc biệt khác như có oto hay SH chẳng hạn :D. Câu trả lời là vẫn OK nhưng phải có cái yếu tố khác cực mạnh để bù vào, như tiếng nhật lưu loát chẳng hạn. Nếu đem so sánh BrSE vs FA về 3 khía cạnh thì nó sẽ tương đương như sau :
Đẹp trai, cao to = code cứng
Dẻo mỏ = kỹ năng mềm tốt
Đô la thần chưởng = JP thần chưởng
Vậy nên nếu muốn cưa đổ gái ngon thì phải làm gì các bạn biết rồi. Tương tự như vậy, không cần giỏi code cũng làm được Br nhưng mà đối tượng khách hàng sẽ bị thu hẹp lại, chỉ tán được khách hàng dễ, chứ gặp khách to vs khó tính cũng mệt. May mắn cho anh em là “cao to đẹp trai” do bẩm sinh – khó cải thiện còn code cứng dễ ợt, làm nhiều học nhiều đọc nhiều tự khắc lên thôi.
3. Cứ Qua Nhật Onsite Là Thành BrSE
Cái này không hề đúng nhé, đó chỉ đơn thuần là SE thôi. Ở đây mình không có ý so sánh BrSE vs SE cái nào cao hơn, vì có những SE làm trong dự án khủng khách hàng to, được giao các task khó mà ngay cả những chuyên gia Nhật không dám nhận, ở Nhật hiện tại có rất nhiều SE khủng như vậy, có điều họ không muốn lên tiếng thôi 1 mặt họ quá bận, 1 mặt khác là những tập đoàn lớn sercurity rất chặt nên các anh không được phép chia sẻ thông tin.
4. Không Có Tiếng Nhật Thì Sẽ Không Được Đi Nhật
Cái này cũng không đúng luôn nhé, Có những team mấy chục người nhưng chỉ cần 1 vài người cầu nối còn lại các anh em khác chỉ việc rung đùi ngồi vọc code vs tìm hiểu công nghệ mới thôi. Lý do là đặc thù dự án ưu tiên kỹ năng lập trình chứ không cần các yếu tố khác vì đã có BrSE lead lo hết rồi. Hoặc cũng có 1 số công ty Nhật nhưng mà Global, toàn dùng tiếng anh trong mail – tài liệu – họp … nên 1 số BrSE chỉ cần tiếng Anh lưu loát là có thể sang làm Bridge hoặc SE được. Đợt trước có mấy anh bạn JP bẻ đôi không biết, tình cờ thấy Rakuten tuyển SE chỉ cần tiếng anh, apply thử. Kết quả là giờ mang cả gia đình qua Nhật định cư luôn rồi, lương cực khủng, công việc toàn liên quan công nghệ sở trường nên tha hồ vùng vẫy. Các bạn muốn đi Nhật nhưng ghét tiếng Nhật có thể chọn khe cửa hẹp này để lách sang.
5. BrSE Là Việc Dành Cho Con Trai
Lại bậy nữa, tuy tỉ lệ ít hơn nam nhưng BrSE nữ không hiếm, đôi khi còn lợi thế hơn trong việc thuyết phục khách hàng (mỹ nhân kế). Mình đã từng gặp nhiều chị em trong nghề này, đa số đều là những người thành công, có người code cứng – có người không nhưng tựu chung lại là hầu hết đều giỏi JP và giỏi kỹ năng mềm. Còn gì tuyệt hơn khi những bống hồng IT làm việc tại Nhật, du ngoạn trên những con đường phủ đầy hoa anh đào trong tà áo dài thướt tha !
Lombok là một thư viện Java giúp sinh các mã getter & setter tự động. Bên cạnh đó còn hỗ trợ sinh các hàm khởi tạo (constructor) với tham số, hoặc không có tham số.
Nếu là lập trình viên Java, chắc hẳn ai cũng biết getter/setter, constructor. Với những lớp mô tả dữ liệu (Entity class), chúng ta thường lặp lại các thao tác tạo mã getter/setter và constructor một cách nhàm chán. Lombok giúp mã ngắn gọn hơn trong những trường hợp này.
Giả sử, chúng ta xây dựng chương trình quản lý danh sách việc cần làm (Todo List). Lớp mô tả dữ liệu Todo sẽ có 2 thuộc tính: title, complete.
public class Todo {
private String title;
private boolean complete;
public Todo() { }
public Todo(String title, boolean complete) {
this.title = title;
this.complete = complete;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public boolean isComplete() {
return complete;
}
public void setComplete(boolean complete) {
this.complete = complete;
}
}
Nếu sử dụng Lombok, đoạn mã trên có thể được viết lại như sau:
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Todo {
private String title;
private boolean complete;
}
Ở trên sử dụng 4 anotation:
– @Setter để thay thế các phương thức: setTitle, setComplete
– @Getter để thay thế các phương thức: getTitle, isComplete
– @NoAgrsConstructor thay thế phương thức khởi tạo không có tham số Todo()
– @AllArgsConstructor thay thế phương thức khởi tạo có tham số Todo(…)
Nếu muốn gọn hơn nữa, chúng ta có thể viết như sau:
Ở trên sử dụng 4 anotation:
– @Setter để thay thế các phương thức: setTitle, setComplete
– @Getter để thay thế các phương thức: getTitle, isComplete
– @NoAgrsConstructor thay thế phương thức khởi tạo không có tham số Todo()
– @AllArgsConstructor thay thế phương thức khởi tạo có tham số Todo(…)
Nếu muốn gọn hơn nữa, chúng ta có thể viết như sau:
Nếu xây dựng một chương trình có nhều đối tượng dữ liệu cần mô tả thì việc áp dụng Lombok sẽ giảm đi một khối lượng lớn mã đáng kể, tiết kiệm nhiều thời gian và nâng cao hiệu suất công việc. Quả thật hữu ích phải không các bạn?
Tiếp theo, chúng ta sẽ tìm hiểu cách cài đặt Lombok vào dự án Java.
Các bước cài đặt
Nếu sử dụng Gradle, các bạn có thể thực hiện qua các bước sau:
Bước 1: Bổ sungbuild.gradle
Nếu xây dựng một chương trình có nhều đối tượng dữ liệu cần mô tả thì việc áp dụng Lombok sẽ giảm đi một khối lượng lớn mã đáng kể, tiết kiệm nhiều thời gian và nâng cao hiệu suất công việc. Quả thật hữu ích phải không các bạn?
Bước 2: Cấu hình IDE để có thể xử lý annotation trong Lombok.
Với IntelliJ IDEA, có thể thiết lập bằng cách vào “Settings > Build > Compiler > Annotation Processors”, chọn “Default”, tick chọn “Enable annotaion processing”.
Qua nội dung trên, chúng ta đã biết được tác dụng của Lombok và cách cài đặt để sử dụng. Nếu có góp ý hoặc bổ sung, bạn có thể bình luận trực tiếp trên bài viết này.
Bài viết được sự cho phép của tác giả Nguyễn Hữu Khanh
Database migration hay chúng ta còn có thể gọi là version control cho database, là một công việc quản lý thông tin, cấu trúc database theo kiểu versioning. Lấy ví dụ như bạn đang phát triển một ứng dụng quản lý sinh viên có sử dụng database, release đầu tiên của ứng dụng này các bạn cần 2 table để quản lý thông tin là student và clazz, lần release thứ 2 thì chúng ta cần chỉnh sửa thông tin table student hoặc clazz, hoặc có thể là thêm mới table subject để quản lý môn học, … Cho mỗi lần release chúng ta sẽ có một version liên quan đến database structure cho ứng dụng của mình. Để hiện thực nhu cầu này, chúng ta có nhiều cách khác nhau trong Java như sử dụng Flyway hoặc Liquibase, … Trong bài viết này, mình sẽ giới thiệu với các bạn về Liquibase để hiện thực database migration các bạn nhé!
Khi làm việc với Liquibase, các bạn cần nắm 3 khái niệm cơ bản của nó là changelog, changeset và changetype.
Một changelog có thể sẽ chứa nhiều changeset và một changeset có thể chứa nhiều changetype. Nói nôm na cho các bạn hiểu thì changelog là một tập tin này định nghĩa tất cả các version của database structure theo cách của Liquibase để nó có thể execute việc thay đổi database theo version cho chúng ta được. Các bạn có thể định nghĩa tập tin changelog này theo nhiều định dạng khác nhau bao gồm SQL, XML, JSON, YAML, …
Nếu các bạn định nghĩa bằng XML thì nội dung của tập tin changelog sẽ có nội dung cơ bản như sau:
Bên trong tag <databaseChangeLog> đại diện cho changelog, chúng ta sẽ định nghĩa các changeset. Các bạn có thể hiểu mỗi changeset là một version của database structure của ứng dụng. Có 2 thuộc tính bắt buộc cho mỗi changeset mà các bạn phải khai báo là id và author.
Mỗi changeset sẽ hỗ trợ cho các bạn rất nhiều changetype. Nói nôm na changetype là đại diện cho các câu lệnh SQL để các bạn thực hiện việc thêm, mới, xoá, sửa database structure. Cho changeLog với XML format, các bạn có thể thấy các changetype như sau:
Ví dụ bây giờ mình cần tạo mới table name student với thông tin về tên và tuổi của sinh viên thì mình sẽ định nghĩa changeset với changetype như sau:
Bây giờ, mình sẽ viết code để xem Liquibase hoạt động như thế nào các bạn nhé!
Mình sẽ lưu nội dung changelog ở trên vào tập tin db-changelog.xml trong thư mục src/main/resources của project:
Mình sẽ tạo mới main class để làm ví dụ:
package com.huongdanjava.liquibase;
public class Application {
public static void main(String[] args) {
}
}
Để làm việc với Liquibase, các bạn cần sử dụng đối tượng của class Liquibase. Chúng ta sẽ cần truyền cho đối tượng này connection tới database và đường dẫn tới tập tin cấu hình của changelog. Có 3 constructor trong class Liquibase cho phép chúng ta truyền các thông tin này.
Mình sử dụng một trong 3 constructor này như sau:
package com.huongdanjava.liquibase;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import liquibase.Liquibase;
import liquibase.database.Database;
import liquibase.database.DatabaseFactory;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.LiquibaseException;
import liquibase.resource.ClassLoaderResourceAccessor;
public class Application {
public static void main(String[] args) throws SQLException, LiquibaseException {
Connection c = DriverManager.getConnection("jdbc:postgresql://localhost:5432/liquibase_example",
"postgres", "123456");
Database database = DatabaseFactory.getInstance()
.findCorrectDatabaseImplementation(new JdbcConnection(c));
Liquibase liquibase = new Liquibase("classpath:db-changelog.xml", new ClassLoaderResourceAccessor(), database);
}
}
Tham số thứ 2 trong constructor của Liquibase trong ví dụ trên chỉ định cách mà Liquibase sẽ đọc tập tin cấu hình changelog. Nó implement interface ResourceAccessor của Liquibase đó các bạn!
Các bạn sử dụng cách nào cũng được tùy theo project của mình nhé.
Sau khi đã có đối tượng Liquibase thì các bạn có thể gọi method update() với một tham số mang ý nghĩa Context mà chúng ta đang chạy database migration. Các bạn có thể để empty hoặc null cũng được nếu muốn. Mình sẽ để empty:
package com.huongdanjava.liquibase;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import liquibase.Liquibase;
import liquibase.database.Database;
import liquibase.database.DatabaseFactory;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.LiquibaseException;
import liquibase.resource.ClassLoaderResourceAccessor;
public class Application {
public static void main(String[] args) throws SQLException, LiquibaseException {
Connection c = DriverManager.getConnection("jdbc:postgresql://localhost:5432/liquibase_example",
"postgres", "123456");
Database database = DatabaseFactory.getInstance()
.findCorrectDatabaseImplementation(new JdbcConnection(c));
Liquibase liquibase = new Liquibase("classpath:db-changelog.xml", new ClassLoaderResourceAccessor(), database);
liquibase.update("");
}
}
OK, giờ thì chạy ứng dụng thôi các bạn.
Để kiểm tra kết quả, các bạn hãy vào database của mình. Các bạn sẽ thấy Liquibase tạo ra 3 bảng trong ví dụ trên của mình:
Ngoài bảng student với cấu trúc database mà chúng ta đã định nghĩa trong changeset:
như các bạn thấy, chúng ta còn có thêm 2 bảng là databasechangelog và databasechangeloglock.
Mục đích của bảng databasechangelog là keep track lại tất cả các changeset mà Liquibase đã chạy. Nếu các bạn query table này, các bạn sẽ thấy kết quả như sau:
Thứ tự execute cho mỗi changeset sẽ phụ thuộc vào việc các bạn định nghĩa changeset đó trước hay sau trong tập tin db-changelog.xml các bạn nhé! Id của mỗi changeset cùng với author và filename chỉ để cho Liquibase biết là nó đã execute changeset đó chưa dựa vào bảng databasechangelog này.
Bảng databasechangeloglock chỉ để cho Liquibase make sure là chỉ có một tiến trình thực hiện việc database migration được thực hiện tại cùng một thời điểm thôi các bạn nhé!
Bây giờ, nếu mình thêm một changeset mới để thêm column address trong bảng student: