Home Blog Page 192

7 thủ thuật bỏ túi cực hay cho các nhà tuyển dụng IT

7 thủ thuật bỏ túi cực hay cho các nhà tuyển dụng IT

Ở góc nhìn của nhiều nhà tuyển nhân viên IT đa phần luôn cố gắng đào sâu đúng nhu cầu của doanh nghiệp nhưng thường quên là “Muốn gặt trái ngọt, phải có công gieo trồng”. Chúng ta nên hiểu rằng để tìm kiếm được các ứng viên tiềm năng không đơn giản chỉ là phúc lợi và mô tả công việc đầy đủ, mà còn cần một số lưu ý. Bí kíp để mang một chuyên viên IT tài giỏi về đội là gì?

1. Tạo thiện cảm tốt từ lần tiếp xúc đầu tiên

Sự kết nối đầu tiên luôn mang lại ấn tượng mạnh mẽ nhất. Một Email tuyển dụng IT chuyên nghiệp phải thật sự thông minh và đúng “trình” của doanh nghiệp. Hãy cố gắng cá nhân hóa từng ứng viên của bạn để các Dev cảm nhận bạn khác biệt và họ cũng thế. Thay cho một lời chào quá đỗi bình thường như Thân gửi bạn/ Thân gửi ứng viên hãy gia vị thêm bằng cách “Thân gửi Dev nhà Top”.

Luôn thân thiện bằng cách ghi nhận năng lực và quá trình làm việc của các ứng viên. Cố gắng nói một cách ngắn gọn về những gì bạn biết về công việc trước đó của họ. Đừng quên giới thiệu về bản thân & công ty của bạn, sẽ không ai thích làm việc với một nhà tuyển dụng vô danh. Các Dev cần cân nhắc năng lực bản thân với công việc mới vì thế bạn cần sơ lược về các yêu cầu của vị trí ứng tuyển nhưng phải thật ngắn gọn.

VD: Thân gửi [Dev nhà Top],

Mình được biết [tên ứng viên] đang có nhiều kinh nghiệm trong lĩnh vực Data Science với nhiều dự án lớn. Hiện nay, [tên công ty của bạn] đang tìm kiếm ứng viên tiềm năng cho vị trí Data Scientist, rất mong được giới thiệu đến bạn [tên ứng viên] và mong có thể hợp tác với bạn trong thời gian tới.

[Đoạn giới thiệu ngắn về công ty và sản phẩm/dự án mà bạn đang tuyển]

[Đoạn giới thiệu về mô tả công việc và các phúc lợi liên quan]

*Lưu ý: Nhưng đừng quá kỳ vọng ứng viên sẽ đọc hết nội dung trong email của bạn, hãy chọn những ý nổi bật và khái quát nhất.

Nhà tuyển dụng IT mong chờ điều gì từ ứng viên? Có phải là năng lực chuyên môn, tinh thần và sự gắn kết bền vững? Ứng viên họ cũng vậy, lập trình viên luôn muốn tạo ra sự thay đổi. Hãy cho họ thấy những đổi mới khi đi cùng công ty bạn. Nếu đang có những dự án mới đừng ngần ngại chia sẻ ở mức độ vừa đủ vì đó sẽ là đòn tâm lý khơi gợi hứng thú và sự tò mò của ứng viên.

Kết thúc email bằng sự thân thiện và nhiệt tình của bạn điều đó thể hiện sự sẵn sàng chào đón một Dev mới về chung đội. Những từ liên quan đến Best – Best Wishes hoặc một câu kết đầy thiện cảm như “Sự phản hồi của bạn luôn là mong đợi của công ty chúng tôi.” nên được cân nhắc ở cuối email.

2. Làm sao để không lỡ mất ứng viên tiềm năng

“Khi nhìn thấy cá lớn, làm sao để câu cá lên bờ”

Nếu không muốn lỡ mất ứng viên tiềm năng thì lời khuyên cho bạn là hãy thực sự dành thời gian ra chăm sóc họ. Một HR “xịn” sẽ phản hồi thông tin ứng viên trong 3 ngày kể từ ngày nhận được CV và luôn giữ thông tin cập nhật không quá 5 ngày hoặc chủ động thông báo về sự kéo dài thời gian hơn dự định thay vì chọn cách im lặng. Thời gian là vàng vì vậy đừng để ứng viên phí thời gian chờ đợi quy trình của bạn. Việc giữ đúng lời hứa như đã trao đổi thể hiện sự chuyên nghiệp của nhà tuyển dụng. Ngoài ra, để thể hiện sự tôn trọng, bạn hãy cố gắng giúp đỡ với bất kỳ phản hồi nào từ các Developer.

3. Hãy là bạn & đồng nghiệp của ứng viên

Là một nhà tuyển dụng IT hơn ai hết ngoài chuyên môn nhân sự, bạn nên trang bị cho mình một số kiến thức cơ bản về ngành IT để hiểu những gì các ứng viên trao đổi và phản hồi họ một cách nhanh và chính xác nhất. Sự tương tác sẽ trở nên gắn kết hơn khi các lập trình viên nể trọng kiến thức và sự am hiểu của bạn dù bạn là HR hay IT Recruiter. Trong cuộc trò chuyện, bạn có thể đề cập đến kinh nghiệm làm việc của họ trong những công việc trước đó, điều này vừa giúp bạn có cơ hội khai thác được nhiều hơn thông tin về năng lực ứng viên cũng như rút ngắn được khoảng cách giữa nhà tuyển dụng và ứng viên tiềm năng.

tuyen-dung-IT

4. Cách viết mô tả công việc hấp dẫn

Kỹ thuật thiết kế Email cũng là một điểm nhấn cho nhà tuyển dụng IT. Những lưu ý bạn nên cân nhắc đầu tiên là không nên sử dụng quá nhiều gạch đầu dòng cho phần mô tả công việc điều này sẽ mất đi tính khoa học trong văn bản. Tiếp theo là tiêu đề (Title job) công việc rõ ràng, ngắn gọn và đầy đủ điều này mang lại hiệu quả vì ở giai đoạn “ mời gọi” này ứng viên đang ở thế thượng phong. Bạn cần phân loại và tách riêng những yêu cầu bắt buộc trong công việc với những yêu cầu có thể bỏ qua giúp ứng viên dễ dàng cân nhắc.

5. Bạn đã từng tự hỏi Developer họ muốn gì?

Theo nghiên cứu có đến 64% Developer nói rằng xây dựng một thứ gì đó mới mẻ rất quan trọng với họ. Điều này cũng nghĩa họ mong muốn được tư duy sáng tạo và sự độc lập trong từng dự án. Sức thuyết phục mạnh mẽ đối với ứng viên là nhìn được con đường sự nghiệp vẻ vang trong tương lai. Không ai khác nhiệm vụ này là ở cách công ty bạn đề ra lộ trình thăng tiến rõ ràng mang tính thỏa thuận thích đáng.

6. Nếu không muốn ăn “Bơ” phải có tuyệt chiêu liên lạc hiệu quả

Những con số không biết nói dối, nghiên cứu chỉ ra rằng hơn 52% Developer ghét khi bị liên lạc qua Facebook vì đây là kênh mạng xã hội và họ cảm thấy không thật sự nghiêm túc trong công việc, 22% Developer không có LinkedIn đặc biệt là các Developer tại Việt Nam cho biết LinkedIn còn khá mới mẻ với họ. Mặt khác,đến 65% Developer cho rằng gửi e-mail là một cách thức giao tiếp tốt để bắt đầu các mối quan hệ hợp tác. Hãy nhớ rằng có hơn 65% lập trình viên nói rằng mức lương là rất quan trọng với họ khi tìm một công việc mới. Gợi mở những cánh cửa tham vọng của từng ứng viên sẽ giúp họ phản hồi với công ty bạn một cách nhanh nhất có thể.  

  10 Bí quyết tuyển dụng giúp bạn tăng tỉ lệ nhận offer tức thì!

7. Cải tiến quy trình tuyển dụng dành cho IT

Để mang đến kết quả tuyển dụng nhanh nhất bạn cần tối ưu quy trình tuyển dụng bằng cách cố gắng linh hoạt thời gian và địa điểm phỏng vấn. Cung cấp các thông tin liên hệ với công ty trong ngày phỏng vấn. Giới thiệu ứng viên tiềm năng với đội ngũ hiện tại để làm quen và gây ấn tượng. Nếu được, hãy cho họ xem một vài dòng code của dự án điều này âm thầm nói với ứng viên “Cơ hội của bạn là công ty tôi”. Hạn chế đánh đố ứng viên những câu hỏi về trí tuệ vì nó không đồng nghĩa với việc ứng viên có thể viết Code tốt.

Xét về mặt hiệu quả, 7 bí kíp trên sẽ giúp ích cho hiệu suất làm việc của các cá nhân nhân sự và IT Recruiter trong việc tiếp cận và thu hút được ứng viên IT tiềm năng cho công ty. Nhưng để đẩy mạnh hiệu quả ở quy mô lớn hơn hoặc giúp cho cả quá trình tuyển dụng IT tại công ty bạn được thúc đẩy nhanh một cách rõ ràng, việc làm thương hiệu tuyển dụng – Employer Branding là điều bạn không nên bỏ qua. Một thương hiệu tuyển dụng tốt được phủ rộng tới cộng đồng luôn là một điểm mạnh với nhà tuyển dụng IT. Luôn truyền tải thông điệp, sứ mệnh và tầm nhìn của mình, đặc biệt trong ngành IT, việc thể hiện những khía cạnh lợi thế kỹ thuật, dự án độc đáo cũng như văn hóa độ Tech năng động sẽ là điểm cộng rất lớn khi ứng viên tìm kiếm thông tin trên Internet. Tham khảo 03 quy tắc vàng làm Employer Branding tại đây.

Đừng bỏ qua 10 xu hướng tuyển dụng IT hiệu quả, kết hợp với bí kíp trên, chúc bạn sớm tìm được ứng viên IT tiềm năng về đội của mình.

Xem thêm các việc làm ngành IT tại Topdev.vn

Để không trở thành 1 lập trình viên lỗi thời!

Giữ cho kĩ năng của bạn luôn được cập nhật xu hướng lập trình có thể là một điều không dễ, nhưng nó là vô cùng quan trọng đối với tất cả mọi người và nhất là những lập trình những người trong ngành công nghiệp công nghệ cao, nơi mọi thứ luôn thay đổi nhanh chóng.

Sarah Franklin, phó chủ tịch phụ trách quan hệ phát triển và tổng giám đốc của Salesforce, cho biết: “Tốc độ đổi mới đang gia tăng nhanh chóng trong hệ sinh thái kinh doanh ngày nay, do đó mà nhu cầu nhân lực có tay nghề luôn ở mức cao. Hơn nữa, giáo dục đại học truyền thống chỉ đơn giản là không đủ khi rất nhiều sinh viên dù đã tốt nghiệp nhưng vẫn không có các kỹ năng cần thiết trong thị trường công nghệ hiện nay”.

Todd Thibodeaux, chủ tịch và giám đốc điều hành của CompTIA, cho biết tốc độ đổi mới nhanh chóng cũng tạo ra những cơ hội nghề nghiệp mới: “Các công nghệ hiện tại và tương lai sẽ luôn cần những kỹ thuật viên và kỹ sư lành nghề để thực hiện chúng. Tuy nhiên, tốc độ thay đổi nhanh chóng làm cho hầu hết các ứng viên không thể nào theo kịp mọi thứ. Lựa chọn tốt nhất là chọn tập trung vào một vài lĩnh vực mà bạn quan tâm.”

Có thể bạn quan tâm:

  Lộ trình trở thành lập trình viên sau 9 tháng tự học (Phần 1)
  Lập trình viên và hội chứng Tự kỷ

Dưới đây là 10 lời khuyên để đảm bảo kỹ năng của bạn vẫn có luôn hữu ích đối với thị trường việc làm.

Lên kế hoạch rõ ràng

Sandy Carielli, giám đốc công nghệ an ninh của Entrust Datacard, nói: “Thật dễ dàng để bị mắc bẫy trong công việc hàng ngày và không dành thời gian cho những gì sắp xảy ra”.

Raphael Arar, nhà thiết kế và nghiên cứu của IBM Research, cho biết ông đã cố gắng dành thời gian, thậm chí chỉ cần 15 phút mỗi ngày để học một kỹ năng mới. Arar cho biết: “Đây có thể là hình thức xem các hướng dẫn trực tuyến, tham gia lớp MOOC hoặc một cuốn sách kỹ thuật.

Quan sát những con người dẫn đầu

Theo dõi blog, đăng ký bản tin, và facebook các nhà lãnh đạo ngành công nghiệp, Carielli nói. Nếu bạn không chắc chắn nơi để bắt đầu, hãy hỏi những người cố vấn hoặc những người khác trong lĩnh vực của bạn. Và nếu công ty của bạn đăng ký bất kỳ báo cáo phân tích nào, hãy xem liệu bạn có thể truy cập vào chúng hay không.

Carielli cho biết: “Không chỉ bạn sẽ tìm hiểu về các công nghệ và xu hướng mới, bạn sẽ thấy được cách suy nghĩ của họ”.

Tham gia các sự kiện, buổi meetup

Siddhartha Agarwal, phó giám đốc quản lý sản phẩm và chiến lược của Oracle, cho biết các sự kiện ngành công nghiệp thường là nguồn thông tin chi tiết nhất và là cơ hội học hỏi từ những chuyên gia công nghệ cao. Agarwal nói: “Các cuộc sự kiện công nghê là cách tuyệt vời để kết nối với cộng đồng các đồng nghiệp tương lai của bạn và thu thập các kỹ năng công nghệ mới”.

Mở rộng network của bạn

Nói chuyện với bạn bè và đồng nghiệp về những gì họ đang làm, Melnicki nói. Ông cho biết: “Những người có nhiều đặc điểm khác nhau mà tôi có thể liên lạc khi cần tìm kiếm hướng dẫn trong lĩnh vực chuyên môn của họ. ”

Phương tiện truyền thông xã hội là một cách khác để kết nối và theo xu hướng công nghệ, hãy tham gia các nhóm LinkedIn với các chuyên gia khác trong lĩnh vực của bạn, Agarwal nói.

Tìm kiếm các khóa học và thông tin trực tuyến

Dwayne Melancon, phó chủ tịch phụ trách sản phẩm tại iovation nói: “Có rất nhiều lớp học trực tuyến có thể giúp bạn không chỉ thuần thục kỹ thuật hiện nay, mà thậm chí còn đạt được các chứng chỉ cao cấp về kỹ thuật trong ngành công nghiệp”.

Andrew Selepak, giáo sư về viễn thông tại đại học Florida, cho biết việc tham gia một khóa học trực tuyến thông qua một trường đại học cũng có thể mang lại lợi ích cho việc hợp tác và học tập như một tập thể. Selepak nói: “Điều này đặc biệt quan trọng bởi vì công việc của chúng ta hiện nay đòi hỏi phải làm việc theo nhóm nhiều hơn với những người làm việc từ xa hoặc tổ chức các cuộc họp thông qua video hoặc Slack”.

Tham gia một tổ chức chuyên nghiệp

Các tổ chức chuyên nghiệp, chẳng hạn như ISACA và ISSA, thường xuyên tổ chức các tuần huấn luyện, Melancon nói. Bạn sẽ không chỉ được cung cấp các tài liệu đào tạo, mà còn cho phép tiếp cận với các cố vấn hoặc các chuyên gia giàu kinh nghiệm, giúp cho việc học tập và làm việc trở nên hiệu quả hơn.

Camp cho biết tất cả các thành phố lớn và nhiều trung tâm nhỏ hơn đều có một số hiệp hội về lập trình máy tính chuyên nghiệp gần đó. “Hãy tham gia những cuộc trò chuyện thú vị nhất với bạn để nâng cao kĩ năng của mình”

Đọc, đọc nữa, đọc mãi

Nghe có vẻ đơn giản, nhưng đọc tin tức và blog công nghệ mỗi ngày là một trong những cách tốt nhất để cập nhật những xu hướng và kỹ năng mới nhất và cần thiết trong công nghệ, Melnicki nói.

Robert Pryor, giám đốc dịch vụ của Key Information Systems cho biết: “Tìm một vài blogger có liên quan đến lĩnh vực chuyên môn của bạn và theo dõi họ”.

Thử nghiệm với các dự án cá nhân

Bạn có thể thường xuyên học hỏi nhiều nhất bằng cách cố gắng tự mình làm chúng, Melnicki nói. “Bạn sẽ nhận ra rằng một khi bạn có cơ hội để làm các dự án phụ cho chính mình, những gì được sử dụng sẽ trở nên vô cùng tuyệt vời”, ông nói thêm.

Peter Yang, đồng sáng lập của ResumeGo, cho biết nếu bạn là một lập trình viên, hãy viết code vào thời gian rảnh rỗi cho những dự án bạn yêu thích. Ông nói thêm: “Điều này sẽ giúp giữ trình độ của bạn trở nên sắc bén hơn bao giờ hết”.

Đánh giá nội bộ

Aniket Sharma, chuyên gia phân tích chất lượng tại Work & Co cho biết: “Đánh giá ngang hàng nội bộ – có thể là thông qua đánh giá code hoặc đánh giá hiệu suất – có thể là một cách tuyệt vời để hiểu được vị trí hiện tại của bạn”, Sharma nói. “Nó có thể mang lại lợi ích cho cả nhân viên và công ty vì không chỉ phát triển các kỹ năng công nghệ của nhân viên mà còn nâng cao chất lượng sản phẩm / công ty.”

Đa dạng hóa kiến ​​thức của bạn

Các mạng lưới trong tương lai sẽ bao gồm các thiết bị đầu cuối rất đa dạng, Thibodeaux nói. Eric M. Rintell, CEO & Co-founder của Rintell Technologies, nói – “Tôi đã học được sau nhiều thập kỷ trong ngành công nghiệp là những người thông minh luôn chấp nhận và thích nghi để thay đổi, trong khi những người ngu ngốc chống lại nó, thường dẫn đến mất việc làm”.

TopDev via techrepublic

Xem ngay những tin đăng tuyển dụng IT mới nhất trên TopDev

Lộ trình trở thành lập trình viên sau 9 tháng tự học (Phần hai)

Tiếp nối Phần 1.

Tháng thứ 4 – Hoàn thành khóa học backend tại FreeCodeCamp, Yeggle

Tôi đã tự học lập trình bao lâu qua và làm việc qua tất cả các dự án API trong freeCodeCamp, nhưng tôi bắt đầu tách ra khỏi freeCodeCamp

Với thôi thúc được thực hiện các full stack web application, vì vậy ngay khi tôi nhìn thấy tiêu đề của dự án Image Search Abstraction Layer, một một ý tưởng mới đã xuất hiện ra trong tôi. Tôi sẽ làm một node app chuyên lưu trữ các URL imgur ngẫu nhiên trong một cơ sở dữ liệu, và nó sẽ đánh số những hình ảnh ngẫu nhiên để khi user nhập vào thì sẽ truy xuất được chúng.

Bạn sẽ làm việc chăm chỉ và có nhiều thành công hơn khi bạn đang làm việc cho một dự án của chính mình.

Cuối cùng tôi cảm thấy đã sẵn sàng để bắt đầu làm cho các ứng dụng web full-stack cho riêng mình.

Khi tìm kiếm nhà hàng mới, tôi luôn thấy mình hay dùng Yelp để kiểm tra đánh giá và sau đó mở Maps để kiểm tra đánh giá của họ. Điều gì sẽ xảy ra nếu tôi tạo một ứng dụng kết hợp so sánh cả hai bên với nhau?

Và như vậy, tôi đã tạo ra Yeggle. Tôi sử dụng Node / Express / React cùng với Google Maps và Yelp API. Có một vài trở ngại mà tôi không nghĩ mình có thể vượt qua, nhưng cuối cùng tôi đã hoàn thành và rất tự hào về ứng dụng của mình. Sau đó, tôi up nó lên Reddit, nhưng không ai quan tâm. Đó là một sự thất vọng nhưng nó không làm tôi nản lòng.

Tháng thứ 5 – StockIT

Tôi bắt đầu với một kỳ nghỉ hai tuần tới Nhật Bản và Thái Lan!

Sau đó, tôi mới bắt tay vào dự án tiếp theo. Ý tưởng của tôi là làm một trò chơi về cơ hội để mua và bán cổ phiếu, nhưng bạn sẽ chơi với một thuật toán learning machine. Vì vậy, tôi tạo ra StockIT.

Tôi đã làm một video hướng dẫn về Pandas và Scikit Learn bao gồm nhiều kỹ thuật machine learning.

Sau khi hoàn thành và chia sẻ nó với Reddit, phản ứng từ mọi người khá là tích cực.

Hóa ra, giống như các nhà đầu tư, các redditors đều quan tâm về machine learning. Kết quả là mọi người đang chơi trò chơi của tôi và tận hưởng nó!

Tháng thứ 6 – jobSort (), Job Hunt Prep

Sau StockIT, tôi lăn ngay vào dự án cá nhân tiếp theo. Tôi muốn tổng hợp các trang web tuyển dụng công nghệ như Stack Overflow, Github và Hacker News. Để thêm tính độc đáo, tôi quyết định sắp xếp nó dựa trên bảng xếp hạng công nghệ đang được săn đón.

Tôi gặp nhiều trở ngại khác nhau trong dự án này và phải thay đổi kế hoạch một vài lần, nhưng tôi cũng đã hoàn thành nó tốt đẹp. Tech stack của tôi có sử dụng React/Node/Express/MySQL. Bạn có thể thấy là với stack như vậy thì jobSort () đã chiếm một phần kha khá thời gian của tôi trong tháng. Một người bạn đã khuyên tôi nên bắt đầu tìm kiếm việc làm ngay bây giờ.

Vốn ban đầu, tôi dự định sẽ tích lũy thêm nhiều kinh nghiệm nhưng người bạn này thuyết phục tôi bỏ kế hoạch đó và bắt đầu đi xin việc làm luôn. Vì vậy, tháng này tôi đã làm một portfolio và một sơ yếu lý lịch. Trong tháng tiếp theo tôi sẽ bắt đầu đi xin việc.

Có thể bạn muốn xem:

  Con đường trở thành thực tập sinh tại Google
  Lộ trình trở thành lập trình viên sau 9 tháng tự học (Phần 1)

Tháng thứ 7 – Testing, tìm việc

Tháng này tôi tập trung vào học testing và Redux.

Tôi đã thêm flexbox vào CodeClub.Social để làm cho nó đáp ứng hơn. Tôi đã cải tiến UX di động trên jobSort (). Tôi thêm testing vào jobSort () với mocha / chai / enzyme vốn rất khó thiết lập.

Đến cuối tháng, tôi đã nộp đơn cho 63 chỗ việc làm. Trên Hacker News, tôi đã sử dụng jobSort () để xác định danh sách để đăng ký. Trên thực tế, tôi đã thử các công ty không phải là phần mềm để xem liệu tôi có thể nhận được cuộc gọi hoặc cuộc phỏng vấn ở bất cứ đâu.

Lúc đầu, tôi đã lựa chọn cách nộp hồ sơ thông thường. Sau đó, tôi quyết định cá nhân hóa thư xin việc và lý lịch của mình, và sau đó cố gắng gửi một email cho ai đó trong công ty. Phương pháp này rõ ràng là tốt hơn phương pháp cứ nhắm mắt gởi đại.

Tôi nhận được 5 cuộc gọi trong tháng đó – hai từ các công ty tuyển dụng và ba công ty phần mềm với những offer bao gồm:

  • DevOps /testing tại một công ty dotcom
  • Một công ty phân tích thực phẩm B,
  • Một startup khá lớn và thành công mà gần đây đã được mua bởi một công ty lớn

Tôi đã khá hài lòng với ba cuộc gọi phỏng vấn, và tôi đã học được rất nhiều từ họ. Tuy nhiên, những gì tôi học được từ những cuộc gọi này là không ai tìm kiếm một junior developer. Họ mong bạn biết bạn đang làm gì từ ngày đầu tiên.

Những cuộc gọi này đã dạy tôi rằng cần phải:

  • Giỏi để đủ để tăng giá trị từ ngày đầu tiên
  • Hãy tự tin để thuyết phục họ rằng bạn có giá trị cho công ty

Tháng thứ 8 – Ca đêm, Redux, Open source và các cuộc phỏng vấn 

Tôi bắt đầu tháng này làm việc ca đêm trong khoảng 40 ngày với 6 ngày một tuần, 12 giờ mỗi ngày, 5 giờ chiều đến 5 giờ sáng.

Tôi biết bản thân sẽ không thể làm được nhiều điều trong tháng này, nhưng tôi đã có một mục đích và tôi muốn đặt được nó.

Tôi đã tái cấu trúc lại jobSort () để sử dụng Redux, điều đáng ngạc nhiên là nó không hề khó khăn như tôi nghĩ. Tôi thực sự thích dòng dữ liệu với Redux. Thật thú vị khi nhìn thấy mọi người phàn nàn về Redux.

Đây cũng được coi là tháng của open source cho tôi. Và tất nhiên, React là lựa chọn để tôi đóng góp bởi nó đóng vai trò quan trọng của project mình.

Tại một trong những buổi họp mặt tôi đã tham dự, Anthony Ng đã khuyên tôi nên thử Downshift, một thư viện tự động hoàn chỉnh của Kent C. Dodds. Đây chính là thời điểm thay đổi mọi thứ. Downshift là một giải pháp hoàn hảo cho một số vấn đề tôi đã có với ứng dụng jobSort ().

Khoảng nửa tháng sau, tôi nhận được một email từ một trong những công ty tôi đã nộp đơn vào tháng trước. Các công nghệ họ đang tìm kiếm là chính xác những gì tôi đã học được – React, Redux, và D3. Tôi hầu như chỉ nói về các dự án của mình và tại sao tôi lại đưa ra những quyết định nhất định. Sau đó, họ yêu cầu tôi đến nơi để phỏng vấn. Cuộc phỏng vấn tại chỗ đầu tiên của tôi!

Thành thật mà nói, tôi không hề tự tin rằng mình sẽ nhận được công việc, nhưng ít ra thì bản thân sẽ có được kinh nghiệm phỏng vấn quí giá.

Lúc đầu, tôi đã lo lắng về việc đảm bảo rằng mình phải biết mọi thứ. Khi tôi nhận ra rằng mình sẽ không hoàn thành được thử thách, tôi nhận ra rằng bản thân cần phải ngừng lo lắng những gì người phỏng vấn nghĩ về tôi và chỉ cần google / stack overflow để tìm câu trả lời. Thành thật mà nói, tôi nghĩ rằng mình đã thất bại thảm hại.

Và cũng vì đó, tôi cảm thấy thư giãn trong suốt phần còn lại của cuộc phỏng vấn..

Tháng thứ 9 – Công việc

Tôi nhận được công việc đầu tiên của mình sau 9 tháng 7 ngày tự học lập trình. Tôi cảm thấy tự tin vì tôi nhận được đề nghị sau cuộc phỏng vấn đầu tiên và tôi hài lòng với quyết định của mình. Tôi muốn được trả tiền cho code mình viết ra.

Lời kết

Tôi hy vọng rằng những lời khuyên dưới đây sẽ giúp bạn phát triển một kế hoạch và đạt được mục tiêu của bạn.

  • Tìm ra những gì thúc đẩy bạn và sử dụng nó làm lợi thế cho bạn. 
  • Hãy đề ra mục tiêu và đáp ứng chúng.
  • Đi đến các cuộc gặp gỡ – meetup trước khi bạn nghĩ rằng mình đã sẵn sàng.
  • Đóng góp cho open source trước khi bạn nghĩ rằng bản thân đã sẵn sàng.

TopDev via Medium

Xem ngay những tin đăng tuyển dụng IT mới nhất trên TopDev

Con đường trở thành thực tập sinh tại Google

Tôi là sinh viên chuyên ngành môi trường tại đại học Yale-NUS (một trường cao đẳng nghệ thuật), có thể bạn thấy khá là bất ngờ khi tôi lại trở thành thực tập tại Google – kẻ khổng lồ về công nghệ. Bởi suy cho cùng thì việc nghiên cứu về môi trường cũng như là nghệ thuật thì có liên quan gì tới công nghệ hay kinh doanh?

Câu trả lời sẽ khiến bạn ngạc nhiên đấy! Đó là bởi bất cứ ai cũng có thể làm việc cho Google hay những kẻ khổng lồ công nghệ khác nếu họ thật sự cố gắng. Vì vậy, hãy để tôi giải thích và chia sẻ một số lời khuyên về những gì bạn có thể làm để có được một chuyến thực tập tại Google.

1. Tôi đã làm 9 công việc thực tập khác nhau

  • Trước khi học đại học: The Thought Collective (một doanh nghiệp xã hội.
  • Mùa hè đầu tiên: Philanthropy in Motion (một tổ chức phi lợi nhuận)
  • Học kỳ đầu tiên của năm thứ hai: Part-time intern tại Flare Communications (một công ty chuyên về huấn luyện nhân sự và điều hành)
  • Vào tháng 12, năm thứ hai: JFDI.Asia / QLC.io và Clickstream Ventures (nay là Cocoon Capital)
  • Học kỳ thứ hai của năm thứ hai: Tôi là một part-time intern tại Green Pea Cookie (một startup về đồ ăn)
  • Năm thứ ba: Thực tập sáu tháng tại Founder Institute (một công ty chuyên về vườn ươm) và thực tập thực tập năm tháng tại Tilt

Tôi biết bạn đang nghĩ gì: “Làm cách nào mà có thể làm ngần ấy việc?”. Đúng là nó nghe có vẻ điên cuồng, nhưng như bạn đã thấy, tôi đã làm nhiều việc thực tập khác nhau ngay từ khi còn đang đi học.

TIP: Hãy thực tập càng nhiều càng tốt để có thật nhiều kinh nghiệm làm việc. Bạn thậm chí có thể vừa làm part-time trong khi vẫn học bình thường. Startups có xu hướng giờ giấc linh hoạt hơn và sẵn sàng chấp nhận cho bạn vào làm thực tập viên ngay cả khi không có nhiều kinh nghiệm.

Hầu hết các chương trình thực tập tôi làm đều khá dễ tìm vì vậy hãy chăm đi đến các sự kiện, nói chuyện với startup founder và cho họ biết bạn đang quan tâm đến việc thực tập trong công ty của họ. Ngoài ra, bạn có thể đăng ký vào các cổng thực tập, chẳng hạn như Meetup, TopDev,…

>>> Xem thêm: Các level trong công việc trong ngành IT

2. Tôi đồng sáng lập một doanh nghiệp và giúp tăng vốn lên 70.000 đô la

Trong năm đầu tiên ở trường đại học, tôi đã đồng sáng lập một doanh nghiệp mạng xã hội – tên gọi là SDI Academy cùng với hai sinh viên khác. Mục tiêu của nó là để bảo vệ quyền lợi cho người lao động nhập cư thông qua việc dạy tiếng Anh cũng như giúp họ hòa nhập tốt hơn.

Với tư cách là đại diện cho SDI Academy, tôi tham gia vào hai chương trình gọi là Business Challenge và SIF Young Social Entrepreneurs Program. Hai sự kiện này đã dạy tôi rất nhiều về cách phát triển kinh doanh và cho phép tôi được gặp gỡ nhiều người tài năng trong startup cũng như là entrepreneur.

TIP: Mặt dù đây là một cơ hội học tập tuyệt vời, bạn không bắt buộc phải như tôi, làm một startup. Bởi vì nó cực kì khó nên tôi cũng khuyên bạn đừng nhảy vào nếu chỉ đơn thuần làm để thu lợi nhuận. Tuy nhiên, bạn có thể bắt đầu với các dự án nhỏ mà bạn thực sự đam mê, hoặc tham gia các cuộc thi về kinh doanh và cố gắng xây dựng một mạng lưới kết nối và quen biết.

>>> Xem thêm: Học như thế nào là đủ để có thể đi thực tập?

3. Tôi tham gia 10 cuộc thi hackathons và giành được chiến thắng 4 lần

Tôi sẽ thành thực: Tôi thực sự không giỏi code đến vậy. Do đó, làm thế nào để giành chiến thắng trong hackathons? Bí quyết nằm ở việc tìm được một đội ngũ có chung lý tưởng. Thường thì nó sẽ bao gồm hai developer, một designer và một người thật giỏi trong việc thuyết trình. Mặt khác, một ý tưởng hay là khi nó giải quyết đúng vấn đề, tương đối độc đáo, không quá phức tạp, và có thể cho ra lợi nhuận.

Nếu bạn không biết làm thế nào để code, thì nên cố gắng trở thành một người thuyết trình thực sự giỏi và hùng hồn. Với khả năng có thể viết một kịch bản mang tính thuyết phục cũng như thiết kế các slide thuyết trình bắt mắt. Bằng cách đó, bạn vẫn có thể đem lại giá trị cho đội. Đôi khi, việc thuyết trình cũng quan trọng như bản thân sản phẩm, do đó bạn phải đảm bảo rằng bạn có thể làm tốt. Tôi là người thuyết trình trong hầu hết các hackathon tôi tham gia, và tôi luôn luôn đảm bảo rằng mình luôn làm tốt nhất có thể.

TIP: Hãy cứ tham gia hackathons bất kể trình độ của bạn.Bởi chắc chắn là bạn sẽ học được điều gì đó. Ngoài ra, biết đâu may mắn lại mỉm cười và bạn có khả năng để giành chiến thắng.

4. Tôi tới làm ở Thung lũng Silicon trong vòng một năm

Tôi đã trải qua một năm làm việc tại Thung lũng Silicon thông qua chương trình NUS Overseas College. Tôi tham gia các lớp học kinh doanh tại Đại học Stanford, nơi mà tôi đã học được từ các nhà đầu tư mạo hiểm và các doanh nhân nổi tiếng, bao gồm Elon Musk!

TIP: Dành thời gian học tập hoặc làm việc thực tập ở nước ngoài. Nó sẽ cho phép bạn trải nghiệm sống trong một môi trường hoàn toàn mới. Rất nhiều công ty đa quốc gia tìm kiếm những người có kinh nghiệm này vì họ có thể dễ dàng thích ứng với các nền văn hoá khác nhau.

>>> Xem thêm: Đi thực tập vất vả và thậm chí không có lương: Câu chuyện của những người thành công trên toàn thế giới!

5. Tôi đã đi đến 300 sự kiện

Tôi là một kẻ cuồng các sự kiện lớn, nhưng đi đến nhiều công nghệ và sự kiện startup đã giúp tôi rất nhiều thứ:

  • Cải thiện những kỹ năng mềm của tôi: như tiếp cận mọi người và trò chuyện với họ một cách dễ dàng
  • Tìm hiểu thêm về xu hướng công nghệ mới nhất
  • Tiếp xúc với Startup
  • Kết bạn với rất nhiều người tài năng trong lĩnh vực công nghệ
  • Khám phá các cơ hội để trở thành một thực tập viên ở các công ty Startup.

TIP: Hãy dành thời gian đi các sự kiện! Nó có thể giúp bạn tìm hiểu những điều mới mẻ, mở rộng mạng lưới của bạn và nắm bắt cơ hội. Bạn có thể tìm kiếm sự kiện dễ dàng trên Facebook, Eventbrite và Meetup.

Tôi không thể chỉ liệt kê những kết quả mà không đề cập đến những thay đổi. Để có thể làm được nhiều việc thực tập, bắt đầu kinh doanh, tham gia nhiều cuộc thi, và tham dự hàng trăm sự kiện, tôi phải hy sinh những điều khác:

  • Tôi đã có ít thời gian hơn cho việc học của mình cũng như bạn bè.
  • Trong khi tôi là thành viên của một số câu lạc bộ trong năm đầu tiên, tôi đã phải giảm bớt thời gian tham gia các hoạt động ngoại khóa.
  • Tôi không thể dành nhiều thời gian đi chơi với bạn bè vì tôi thường phải làm rất nhiều việc.

TIP: Có thể bạn sẽ phải đối mặt với những điều, vì vậy hãy xem xét thật kĩ việc nếu nhận được một công việc thực tập hay làm việc trong một công ty công nghệ hàng đầu là điều bạn thật sự muốn. Nếu không, hoặc bạn chỉ thích một công việc ổn định và an toàn thì điểm số cao luôn là một trong những tiêu chí quan trọng.

***

Tôi khá chắc chắn rằng nếu bạn làm theo những gì tôi đã làm, bạn có thể có được một công việc thực tập hoặc làm việc tại một công ty công nghệ lớn. Bạn sẽ gặt hái nhiều lợi ích và thành công, nhưng hãy nhớ rằng bạn sẽ cần phải hy sinh một số thứ khác.

TopDev via techinasia

Có thể bạn muốn xem:

  Lộ trình trở thành lập trình viên sau 9 tháng tự học (Phần 1)

Lộ trình trở thành lập trình viên sau 9 tháng tự học (Phần 1)

Tác giả: Austin Tackaberry

 Bất cứ khi nào đọc được một câu chuyện về thành công, tôi sẽ ngay lập tức tìm kiếm thông tin của tác giả, hy vọng rằng nó cũng tương tự như mình. Và chưa bao giờ có một ai có quá khứ giống cuộc đời của tôi.

Tuy nhiên, tôi hy vọng rằng câu chuyện của mình sẽ truyền cảm hứng cho người khác và đóng vai trò là một bài học có giá trị cho thành công của bạn.

  6 điều tôi vỡ lẻ khi tự học code (P1)
  Lập trình viên tại Việt Nam cần ít nhất 5 năm để hiểu rõ một công nghệ!

Một chút về bản thân

Tôi tốt nghiệp từ một trường đại học tốt với bằng kỹ thuật hóa học và điểm trung bình tốt. Tuy nhiên, tôi không hề thực sự nghiêm túc theo đuổi lập trình cho đến năm ngoái.

Sau khi tốt nghiệp đại học, tôi đã được nhận vào làm dưới vai trò kỹ sư tại một nhà máy lọc dầu. Mãi đến 2017 thì tôi mới chuyển qua làm lập trình viên phần mềm.

Tại sao tôi muốn thay đổi sự nghiệp? 

Tôi thích giải quyết vấn đề liên quan tới kỹ thuật, nhưng đồng thời bản thân cũng muốn làm về kinh doanh.

Vào ngày 27 tháng 5 năm 2017, tôi nhận ra phát triển phần mềm có vẻ như là một sự phù hợp hoàn hảo.

Nhu cầu thì trường luôn cao, mức lương hấp dẫn và đó là ngành công nghiệp hoàn hảo để bắt đầu startup mà không cần nhiều vốn ban đầu. Tất cả bạn cần là một chiếc laptop, và cơ hội của bạn là vô hạn.

Mục tiêu

Bạn cần có một mục tiêu rõ ràng. Đặc biệt là nếu bạn đang cố gắng vừa học vừa làm. Mục tiêu của bạn nên đơn giản như sau:

Có được một công việc liên quan đến lập trình trong vòng một năm với mức lương tương đương hoặc tốt vị trí bây giờ.

Kế hoạch

Một khi bạn có một mục đích, ta sẽ cần một kế hoạch để giúp đạt được điều đó.

Vào ngày 27 tháng 5 năm 2017, tôi quyết định dành khoản 40 giờ mỗi tuần cho công việc của mình, để có thời gian để code sau khi làm việc và vào cuối tuần.

Kế hoạch của tôi khi đấy sẽ như thế này:

  • Tham gia khóa học về CS để có được sự hiểu biết cơ bản vững chắc về các khái niệm CS cốt lõi
  • Theo học tại freeCodeCamp cho đến khi tôi có thể xây dựng các full stack web app
  • Refactor để làm sạch mã, thêm testing, tập trung vào các khái niệm tiên tiến
  • Đóng góp vào nguồn mở
  • Chuẩn bị cho các cuộc phỏng vấn việc làm

Để bắt đầu, kế hoạch của tôi rất đơn giản. Vào thời điểm đó, tôi nghĩ là mình sẽ theo Google’s Technical Guide, vì vậy tôi bắt đầu với khóa học Udacity CS101.

Tháng 0 – Udacity CS101, Harvard CS50

Bước tiến đầu tiên này làm tôi vô cùng phấn khích. Việc học code được bắt đầu ngay khi tôi trở nhà sau giờ làm việc và sẽ không dừng lại cho đến khi tôi đi ngủ. Tôi đã hoàn thành 75% đầu tiên của khóa học Udacity CS101 trong 10 ngày. 25% cuối cùng là về đệ quy, và nó có một chút khó khăn hơn cho tôi. Do đó mà tôi mất tổng cộng 20 ngày để hoàn thành Udacity CS101.

Trong cùng lúc đó, tôi đã bắt đầu đọc các bài viết trên subreddit và điều quan trọng đối với các lập trình viên tự học cần phải có các tài khoản online. Tôi quyết định tạo các tài khoản mới trên Twitter, Reddit, Stack Overflow, Medium và Quora với tên họ đầy đủ.

Ngoài ra, tôi đã quyết định ngừng sử dụng các mạng xã hội dễ gây mất tập trung như Instagram, Facebook, …. Tôi chỉ kiểm tra điện thoại của mình về các tin tức và bài đăng liên quan đến lập trình. Điều này rất quan trọng trong việc đảm bảo rằng bản thân có thể tiếp cận với con đường học và tài liệu tốt nhất.

Và đó là nguyên cơ đã đưa tôi đến với khóa học Harvard CS50. Sinh viên CS ở các trường khác đã tham gia khóa học này và nói rằng họ đã học được nhiều thứ trong CS50 hơn cả một hai năm ngồi tại đại học.

Tháng thứ 1 – Harvard CS50, Linux, freeCodeCamp

Tôi đã hoàn thành CS50 trong vòng nửa tháng. Đó là một khóa học tuyệt vời mà bạn không thể bỏ qua. David Malan là một giảng viên xuất sắc, và có rất nhiều tài liệu để giúp bạn vượt qua nó. Bắt đầu với C, rồi chuyển sang Python, và sau đó kết thúc với việc phát triển web.

Sau khi CS50, tôi quyết định thiết lập XPS 15 cho dual boot Windows và Ubuntu. Đó là một ngày cuối tuần bực bội. Tôi làm rối tung các phân vùng và gần như biến chiếc máy của mình thành một cục gạch.

Tôi dần tách rời khỏi Windows và cuối cùng chỉ sử dụng mỗi Ubuntu.

Với kế hoạch coding 100 ngày do bản thân đề ra, bạn sẽ nhận ra rằng bản thân đã thực sự tiến bộ sau khi coi lại dữ liệu từ khoảng thời gian trên.

Mặt khác, tôi cũng chủ động mở rộng mối quan hệ trong thế giới của lập trình viên. Điều mà một người luôn ngại giao tiếp và chưa hề có tham gia buổi meetup sẽ gặp rất nhiều khó. Tôi có lúc đã định bỏ về khi chỉ còn vài bước đến buổi hẹn.

Tuy vậy, tôi nhanh chóng nhận ra rằng không có lý do để lo lắng. Không ai biết nhau, cũng như chả ai có quyền phán xét, và mọi người đều háo hức học hỏi. Và kể từ đó, tôi đã tham dự hơn 50 cuộc gặp gỡ trong 9 tháng.

Hầu hết mọi người chỉ bắt đầu tham dự buổi họp mặt khi họ đang tìm kiếm việc làm, nhưng vào thời điểm đó gần như là quá muộn. Có rất nhiều lý do để bắt đầu sớm, bao gồm:

  • Phát triển mối quan hệ mất rất nhiều thời gian. Bắt đầu sớm có nghĩa là bạn sẽ có kết nối để bảo đảm việc tìm kiếm một công việc trở nên dễ dàng.
  • Nói về lập trình với người lạ là một cách tuyệt vời để chuẩn bị cho các cuộc phỏng vấn
  • Bạn có thể tìm hiểu các framework, công cụ và tài nguyên học tập mới từ những người đi trước bạn. Điều này có thể ảnh hưởng đến kế hoạch học tập trong tương lai.

Cuối cùng, tôi đã chọn phát triển web vì nó có vẻ như có nhu cầu cao và cũng có rất dễ học trực tuyến. Một số người khuyên rằng ở giai đoạn này tôi nên suy nghĩ về các ứng dụng web tôi muốn xây dựng. Họ đã đề xuất Dự án Odin hoặc freeCodeCamp.

  • Ban đầu tôi quyết định tham gia Odin.
  • Và sau đó hai ngày, ý tưởng đó ngay lập tức bị bỏ qua.

Đây là một trong những nhược điểm của việc đi theo con đường tự học. Một phút bạn nghĩ rằng bạn biết con đường bạn nên đi, nhưng rồi vào ngày hôm sau bạn tự hỏi: liệu đó có phải là lựa chọn đúng !

Tháng thứ 2 – YDKJS, freeCodeCamp Front End, React

Tôi bắt đầu đọc You Don’t Know JavaScript, bởi vì tất cả mọi người khuyên bạn nên xem nó trên CodeCamp. Tôi đã phải mất một khoảng thời gian vì nó khá khó, nhưng đó là một nguồn tài liệu hoàn hảo để học về lexical scope, closures, promises, và tất cả các phần khác của JavaScript mà bạn muốn học nhưng không bao giờ làm vì chúng có vẻ khó khăn.

Tôi đã hoàn thành phần front-end của freeCodeCamp. Tuy vậy, kiến thức hiện tại chỉ tạm đủ cho những hiểu biết cơ bản. Do đó mà tôi quyết định học sâu hơn về CSS.

Bước tiếp theo là học React.

Tôi đã nghe rất nhiều về nó và giờ đây thì đã sẵn sàng. Tuy nhiên, tôi đã hơi do dự khi các vấn đề giấy phép bản quyền vào thời điểm đó. Thật may là Facebook đã nhanh chóng loại bỏ vấn đề trên.

Tôi đã cố gắng đọc các tài liệu và làm theo hướng dẫn của Tic-Tac-Toe của Facebook, nhưng vẫn không hiểu hết về nó. Tôi đã được bảo rằng đó là vì bản thân không hiểu JavaScript đủ. Vì vậy, sau đó tôi đã quyết định đọc lại cuốn You Don’t Know JavaScript.

-Còn tiếp-

Bài viết gốc được đăng tải tại Medium

Xem ngay những tin đăng tuyển dụng IT mới nhất trên TopDev

Modern C++ binary RPC framework gọn nhẹ, không cần code generation

Bài viết sẽ bàn về một framework C++ RP, ko yêu cầu bước code generation để glue code. Trước khi đi vào chi tiết, hãy tìm hiểu một số tính năng cơ bản của công cụ:

  • Mã nguồn tại https://bitbucket.org/ruifig/czrpc
    • Đoạn mã nguồn trên vẫn chưa hoàn chỉnh. Mục tiêu của nó chỉ là để thể hiện nền tảng xây dựng framework. Hơn nữa, để rút ngắn, đoạn code là hỗn hợp của code từ repo lúc viết và custom sample code, nên có thể sẽ xảy ra lỗi.
    • Một số đoạn trong mã nguồn (không liên quan trực tiếp đến nội dung ta đang bàn đến) sẽ chỉ được xem xét nhanh mà không quá quan tâm đến hiệu năng. Bất cứ cải tiến nào sẽ được thêm vào repo mã nguồn sau.
  • Modern C++ (C++11/14)
    • Yêu cầu ít nhất Visual Studio 2015. Clang/GCC cũng tạm ổn.
  • Type-safe
    • Framework xác định những RPC call không hợp lệ trong lúc compile, như tên RPC không rõ, số thông số sai, hoặc kiểu thông số sai.
  • API tương đối nhỏ, không dài dòng
  • Nhiều cách xử lý RPC reply
    • Bộ xử lý không đồng bộ (Asynchronous handler)
    • Futures
    • Client có thể xác định nếu một RPC gây exception bên phía server
  • Cho phép sử dụng (gần như) bất kỳ kiểu RPC parameter nào
    • Giả sử nguời dùng chạy đúng hàm để xử lý kiểu parameter đó
  • RPC hai chiều (server có thể call RPC trên client)
    • Thông thường, client code không thể tin tưởng được, nhưng vì framework nhằm sử dụng giữa các bên uy tín, nên đây không phải là vấn đề quá lớn
  • Không xâm nhập
    • Một object dùng cho RPC call không cần biết bất cứ thứ gì về RPC hoặc network cả.
    • Từ đó có thể bọc các class bên thứ ba cho RPC call.
  • Chi phí băng thông tối thiểu mỗi RPC call
  • Không phụ thuộc bên ngoài
    • Dù supplied transport (trong repo mã nguồn) dùng Asio/Boost Asio, nhưng bản thân framework không phụ thuộc vòa đó. Bạn có thể tự cắm transport của mình.
  • Không có tính năng bảo mật
    • Vì frame work được thiết kế sử dụng cho các bên tin tưởng lẫn nhau (như giữa server chẳng hạn).
    • Ứng dụng có thể tự chọn transport riêng, nên sẽ có cơ hội encrypt bất cứ thứ gì nếu cần thiết.

Có thể bạn muốn xem:

  Clean Code là cái của nợ gì?
  Tìm hiểu về nguyên lý "vàng" SOLID trong lập trình hướng đối tượng

Mặc dù mã nguồn vẫn chưa hoàn thiện, bài viết vẫn rất nặng về code. Code được tách thành nhiều phần nhỏ và một phần sau sẽ dựa theo phần trước. Để bạn hình dung được, sau đây là một mẫu code hoạt động hoàn chỉnh sử dụng repo mã nguồn tại thời điểm viết bài:

//////////////////////////////////////////////////////////////////////////
// Useless RPC-agnostic class that performs calculations.
//////////////////////////////////////////////////////////////////////////
class Calculator {
public:
    double add(double a, double b) { return a + b; }
};

//////////////////////////////////////////////////////////////////////////
// Define the RPC table for the Calculator class
// This needs to be seen by both the server and client code
//////////////////////////////////////////////////////////////////////////
#define RPCTABLE_CLASS Calculator
#define RPCTABLE_CONTENTS \
    REGISTERRPC(add)
#include "crazygaze/rpc/RPCGenerate.h"

//////////////////////////////////////////////////////////////////////////
// A Server that only accepts 1 client, then shuts down
// when the client disconnects
//////////////////////////////////////////////////////////////////////////
void RunServer() {
    asio::io_service io;
    // Start thread to run Asio's the io_service
    // we will be using for the server
    std::thread th = std::thread([&io] {
        asio::io_service::work w(io);
        io.run();
    });

    // Instance we will be using to serve RPC calls.
    // Note that it's an object that knows nothing about RPCs
    Calculator calc;

    // start listening for a client connection.
    // We specify what Calculator instance clients will use,
    auto acceptor = AsioTransportAcceptor<Calculator, void>::create(io, calc);
    // Start listening on port 9000.
    // For simplicity, we are only expecting 1 client
    using ConType = Connection<Calculator, void>;
    std::shared_ptr<ConType> con;
    acceptor->start(9000, [&io, &con](std::shared_ptr<ConType> con_) {
        con = con_;
        // Since this is just a sample, close the server once the first client
        // disconnects
        reinterpret_cast<BaseAsioTransport*>(con->transport.get())
            ->setOnClosed([&io] { io.stop(); });
    });

    th.join();
}

//////////////////////////////////////////////////////////////////////////
// A client that connects to the server, calls 1 RPC
// then disconnects, causing everything to shut down
//////////////////////////////////////////////////////////////////////////
void RunClient() {
    // Start a thread to run our Asio io_service
    asio::io_service io;
    std::thread th = std::thread([&io] {
        asio::io_service::work w(io);
        io.run();
    });

    // Connect to the server (localhost, port 9000)
    auto con =
        AsioTransport<void, Calculator>::create(io, "127.0.0.1", 9000).get();

    // Call one RPC (the add method), specifying an asynchronous handler for
    // when the result arrives
    CZRPC_CALL(*con, add, 1, 2)
        .async([&io](Result<double> res) {
            printf("Result=%f\n", res.get());  // Prints 3.0
            // Since this is a just a sample, stop the io_service after we get
            // the result,
            // so everything shuts down
            io.stop();
        });

    th.join();
}

// For testing simplicity, run both the server and client on the same machine,
void RunServerAndClient() {
    auto a = std::thread([] { RunServer(); });
    auto b = std::thread([] { RunClient(); });
    a.join();
    b.join();
}

Đây đa phần là setup code, vì transport được nhắc đến sử dụng Asio. Bản thân RPC có thể đơn giản như:

// RPC call using asynchronous handler to handle the result
CZRPC_CALL(*con, add, 1, 2).async([](Result<double> res) {
    printf("Result=%f\n", res.get());  // Prints 3.0
});

// RPC call using std::future to handle the result
Result<double> res = CZRPC_CALL(*con, add, 1, 2).ft().get();
printf("Result=%f\n", res.get());  // Prints 3.0

Tìm việc làm C++ nhanh chóng trên TopDev

Lý do tôi viết công cụ này

Tôi đang làm việc với một game có code named G4 cũng được vài năm rồi, trò chơi cung cấp cho player những chiếc máy tính mô phỏng nhỏ in-game mà thậm chí có thể code được. Điều này yêu cầu tôi phải cho nhiều server cùng chạy song song:

  • Gameplay Server(s)
  • VM Server(s) (mô phỏng máy tính ingame)
    • Để vẫn có thể mô phỏng máy tính ngay cả khi người chơi hiện không online
  • VM Disk Server(s)
    • Xử lý lưu trữ của máy tính ingame, như đĩa mềm hoặc ổ cứng.
  • Database server(s)
  • Login server(s)

Tất cả những server này cần trao đổi dữ liệu, vì vậy ta cần một RPC framework linh hoạt.

Ban đầu, giải pháp tôi tìm đến là tag method của một class với attribute cụ thể, rồi từ parser gốc Clang (clReflect) tạo bất cứ serialization cần thiết nào, và glue code.

Dù cách này vẫn áp dụng được, nhưng nhiều năm qua tôi vẫn luôn canh cánh làm cách nào tôi có thể dùng tính năng C++11/14 mới để tạo một minimal type safe C++ RPC framework, một phương pháp không cần bước code generation cho glue code, mà vẫn giữa một API chấp nhận được.

Với serialization của non-fundamental type, code genaration vẫn còn hữu dụng, vậy nên tôi không cần phải xác định thủ công làm sao để serialize tất cả các field của một struct/class cho trước. (dù khâu xác định thủ công này cũng chả phiền phức cho lắm).

RPC Parameters

Xét một function, để có type safe RPC call, ta phải làm được:

  • Xác định lúc compile nếu function là một RPC function hợp lệ (đúng số thông ố, đúng kiểu parameter,…)
  • Kiểm tra liệu các parameter được supply khớp (hoặc convert được) sang thứ mà function signature chỉ ra.
  • Serialize tất cả parameter.
  • Deserialize tất cả parameter.
  • Call những function mong muốn

Parameter Traits

Vấn đề đầu tiên bạn đối mặt là việc xác định kiểu parameter nào được chấp nhập. Một số RPC framework chỉ chấp nhật một số kiểu giới hạn, như Thrift. Hãy xem thử vấn đề.

Xét các function signature sau:

void func1(int a, float b, char c);
void func2(const char* a, std::string b, const std::string& c);
void func3(Foo a, Bar* b);

Vậy làm cách nào ta bắt compile kiểm tra theo parameter? Những kiểu fundamental type khá dễ và chắc chắn phải được framework hỗ trợ. Một bản copy dumb memory là đủ cho trường hợp như thế này, trừ khi bạn muốn cắt giảm số bit cần thiết để hy sinh một ít hiệu năng lấy bandwidth. Vậy chòn với các kiểu phức tạp như std::string, std::vector, hoặc chính class của mình? Còn pointer, reference, const reference, rvalue thì sao?

Ta có thể lấy một số ý tưởng từ những thứ C++ Standard Library đang làm trong type_traits header. Ta cần truy vấn một kiểu cho trước dựa theo tính chất RPC của nó. Hãy thử biến ý tưởng trên thành một template class `ParamTraits<T>`, với layout như sau:

Member constants
valid true if T is valid for RPC parameters, false otherwise
Member types
store_type Type dùng để giữ copy tạm thời cần cho deserializing
Member functions
write Ghi parameter đến một stream
read Đọc parameter vào một store_type
get Xét một store_type parameter, kết quả nó trả có thể được chuyển đến RPC function làm parameter

Trong ví dụ này,hãy thực thi `ParamTraits<T>` cho kiểu thuật toán, trong trường hợp ta có stream class với method `write` và `read`:

namespace cz {
namespace rpc {
// By default, all types for which ParamTraits is not specialized are invalid
template <typename T, typename ENABLED = void>
struct ParamTraits {
    using store_type = int;
    static constexpr bool valid = false;
};

// Specialization for arithmetic types
template <typename T>
struct ParamTraits<
    T, typename std::enable_if<std::is_arithmetic<T>::value>::type> {

    using store_type = typename std::decay<T>::type;
    static constexpr bool valid = true;

    template <typename S>
    static void write(S& s, typename std::decay<T>::type v) {
        s.write(&v, sizeof(v));
    }

    template <typename S>
    static void read(S& s, store_type& v) {
        s.read(&v, sizeof(v));
    }

    static store_type get(store_type v) {
        return v;
    }
};

}  // namespace rpc
}  // namespace cz

Và một ví dụ đơn giản:

#define TEST(exp) printf("%s = %s\n", #exp, exp ? "true" : "false");

void testArithmetic() {
    TEST(ParamTraits<int>::valid);         // true
    TEST(ParamTraits<const int>::valid);   // true
    TEST(ParamTraits<int&>::valid);        // false
    TEST(ParamTraits<const int&>::valid);  // false
}

`ParamTraits<T>` còn được sử dụng để kiểm tra nếu return type hợp lệ, và vì một void function hợp lệ, ta cần phải chuyên hóa `ParamTraits` cho void nữa.

namespace cz {
namespace rpc {

// void type is valid
template <>
struct ParamTraits<void> {
    static constexpr bool valid = true;
    using store_type = void;
};

}  // namespace rpc
}  // namespace cz

Một điều khá kỳ lạ là khi chuyên hóa cho `void` là còn chỉ định một `store_type` nữa. Chúng ta không thể sử dụng nó để lưu trữ bất cư thứ gì, nhưng sẽ làm một vìa đoạn template code về sau dễ dàng hơn.

Với những ví dụ `ParamTraits`, reference không phải là RPC parameter hợp lệ. Trong thực tế, bạn ít nhất sẽ muốn cho phép const reference, đặc biệt với fundamental type. Bạn có thể thêm một tweak để kích hoạt hỗ trợ cho `const T&` cho bất kỳ T hợp lệ nào khi ứng dụng của bạn cần đến.

// Make "const T&" valid for any valid T
#define CZRPC_ALLOW_CONST_LVALUE_REFS                                          \
    namespace cz {                                                             \
    namespace rpc {                                                            \
    template <typename T>                                                      \
    struct ParamTraits<const T&> : ParamTraits<T> {                            \
        static_assert(ParamTraits<T>::valid,                                   \
                      "Invalid RPC parameter type. Specialize ParamTraits if " \
                      "required.");                                            \
    };                                                                         \
    }                                                                          \
    }

Bạn cũng có thể thực hiện những tweak tương tự để kích hoạt hỗ trợ `T&` hoặc `T&&` nếu cần thiết, mặc dù nếu function thay đổi đến những parameter này, những thay đổi đó sẽ mất đi.

Hãy thử thêm hỗ trợ cho các kiểu phức tạp như `std::vector<T>`. Để hỗ trợ `std::vector<T>`, ta buộc phải hỗ trợ thêm cả `T`:

namespace cz {
namespace rpc {

template <typename T>
struct ParamTraits<std::vector<T>> {
    using store_type = std::vector<T>;
    static constexpr bool valid = ParamTraits<T>::valid;
    static_assert(ParamTraits<T>::valid == true,
                  "T is not valid RPC parameter type.");

    // std::vector serialization is done by writing the vector size, followed by
    // each element
    template <typename S>
    static void write(S& s, const std::vector<T>& v) {
        int len = static_cast<int>(v.size());
        s.write(&len, sizeof(len));
        for (auto&& i : v) ParamTraits<T>::write(s, i);
    }

    template <typename S>
    static void read(S& s, std::vector<T>& v) {
        int len;
        s.read(&len, sizeof(len));
        v.clear();
        while (len--) {
            T i;
            ParamTraits<T>::read(s, i);
            v.push_back(std::move(i));
        }
    }

    static std::vector<T>&& get(std::vector<T>&& v) {
        return std::move(v);
    }
};

}  // namespace rpc
}  // namespace cz

// A simple test
void testVector() {
    TEST(ParamTraits<std::vector<int>>::valid);  // true
    // true if support for const refs was enabled
    TEST(ParamTraits<const std::vector<int>&>::valid);
}

Để tiện hơn, chúng ta có thể dùng toán tử `<<` và `>>`  với class `stream` (không thế hiện ở đây). Với những toán tử này, hãy đơn thuần call các function `ParamTraits<T> read` và `ParamTraits<T> write` tương ứng.

Giờ đây ta đã có thể kiểm tra xem một type nhất định có được phép cho RPC parameter hay không, chúng ta có thể build trên đó kiểm tra xem một function có được dùng cho RPC hay không. Cách này được thực thi với variadic template.

Trước hết hãy tạo một template cho ta biết nếu nhóm parameter có hợp lệ hay không.

namespace cz {
namespace rpc {

//
// Validate if all parameter types in a parameter pack can be used for RPC
// calls
//
template <typename... T>
struct ParamPack {
    static constexpr bool valid = true;
};

template <typename First>
struct ParamPack<First> {
    static constexpr bool valid = ParamTraits<First>::valid;
};

template <typename First, typename... Rest>
struct ParamPack<First, Rest...> {
    static constexpr bool valid =
        ParamTraits<First>::valid && ParamPack<Rest...>::valid;
}

}  // namespace rpc
}  // namespace cz

// Usage example:
void testParamPack() {
    TEST(ParamPack<>::valid);  // true (No parameters is a valid too)
    TEST((ParamPack<int, double>::valid));  // true
    TEST((ParamPack<int, int*>::valid));    // false
}
Using ParamPack, we can now create a FunctionTraits template to query a function's properties.
namespace cz {
namespace rpc {

template <class F>
struct FunctionTraits {};

// For free function pointers
template <class R, class... Args>
struct FunctionTraits<R (*)(Args...)> : public FunctionTraits<R(Args...)> {};

// For method pointers
template <class R, class C, class... Args>
struct FunctionTraits<R (C::*)(Args...)> : public FunctionTraits<R(Args...)> {
    using class_type = C;
};

// For const method pointers
template <class R, class C, class... Args>
struct FunctionTraits<R (C::*)(Args...) const>
    : public FunctionTraits<R(Args...)> {
    using class_type = C;
};

template <class R, class... Args>
struct FunctionTraits<R(Args...)> {
    // Tells if both the return type and parameters are valid for RPC calls
    static constexpr bool valid =
        ParamTraits<R>::valid && ParamPack<Args...>::valid;
    using return_type = R;
    // Number of parameters
    static constexpr std::size_t arity = sizeof...(Args);

    // A tuple that can store all parameters
    using param_tuple = std::tuple<typename ParamTraits<Args>::store_type...>;

    // Allows us to get the type of each parameter, given an index
    template <std::size_t N>
    struct argument {
        static_assert(N < arity, "error: invalid parameter index.");
        using type = typename std::tuple_element<N, std::tuple<Args...>>::type;
    };
};

}  // namespace rpc
}  // namespace cz

// A simple test...
struct FuncTraitsTest {
    void func1() const {}
    void func2(int) {}
    int func3(const std::vector<int>&) { return 0; }
    int* func4() { return 0; }
};

void testFunctionTraits() {
    TEST(FunctionTraits<decltype(&FuncTraitsTest::func1)>::valid);  // true
    TEST(FunctionTraits<decltype(&FuncTraitsTest::func2)>::valid);  // true
    TEST(FunctionTraits<decltype(&FuncTraitsTest::func3)>::valid);  // true
    TEST(FunctionTraits<decltype(&FuncTraitsTest::func4)>::valid);  // false
}

`FunctionTraits` cho ta một vài tính chất sẽ được sử dụng sau. Lưu ý với ví dụ rằng `FunctionTraits::param_tuple` build trên `ParamTraits<T>::store_type`. Việc này cần thiết, vì ở một thời điểm nào đó, chung ta cần cách ể deserialize tất cả parameter thành một tuple trước khi call function.

Serialization

Vì chúng ta giờ đây có code cần thiết để truy vấn parameter, return type và validating function, chúng ta có thể gộp code lại để serialize một function call. Hơn nữa, đây chính là type safe. Nó sẽ không complie nếu nhập sai số hoặc kiểu parameter, hoặc nếu bản thân function không hợp lệ cho RPC (không hỗ trợ các return/parameter type).

namespace cz {
namespace rpc {

namespace details {

template <typename F, int N>
struct Parameters {
    template <typename S>
    static void serialize(S&) {}
    template <typename S, typename First, typename... Rest>
    static void serialize(S& s, First&& first, Rest&&... rest) {
        using Traits =
            ParamTraits<typename FunctionTraits<F>::template argument<N>::type>;
        Traits::write(s, std::forward<First>(first));
        Parameters<F, N + 1>::serialize(s, std::forward<Rest>(rest)...);
    }
};

}  // namespace details

template <typename F, typename... Args>
void serializeMethod(Stream& s, Args&&... args) {
    using Traits = FunctionTraits<F>;
    static_assert(Traits::valid,
                  "Function signature not valid for RPC calls. Check if "
                  "parameter types are valid");
    static_assert(Traits::arity == sizeof...(Args),
                  "Invalid number of parameters for RPC call.");
    details::Parameters<F, 0>::serialize(s, std::forward<Args>(args)...);
}

}  // namespace rpc
}  // namespace cz

//
// A simple test
// 
struct SerializeTest {
    void func1(int, const std::vector<int>) {}
    void func2(int*) {}
};

void testSerializeCall() {
    Stream s;
    serializeMethod<decltype(&SerializeTest::func1)>(s, 1,
                                                     std::vector<int>{1, 2, 3});

    // These fail to compile because of the wrong number of parameters
    // serializeMethod<decltype(&SerializeTest::func1)>(s);
    // serializeMethod<decltype(&SerializeTest::func1)>(s, 1);

    // Doesn't compile because of wrong type of parameters
    // serializeMethod<decltype(&SerializeTest::func1)>(s, 1, 2);

    // Doesn't compile because the function can't be used for RPCs.
    // int a;
    // serializeMethod<decltype(&SerializeTest::func2)>(s, &a);
}

Deserialization

Như đã đề cập ở trên, `FunctionTraits<F>::param_tuple` là type `std::tuple` ta dùng được để giữ tất cả parameter của function. Để có thể sử dụng tuple này để deserialize parameter, chúng ta phải chuyên hóa `ParamTraits` cho các tuple. Bên cạnh đó, ta còn có thể sử dụng `std::tuple` cho RPC parameter.

namespace cz {
namespace rpc {

namespace details {
template <typename T, bool Done, int N>
struct Tuple {
    template <typename S>
    static void deserialize(S& s, T& v) {
        s >> std::get<N>(v);
        Tuple<T, N == std::tuple_size<T>::value - 1, N + 1>::deserialize(s, v);
    }

    template <typename S>
    static void serialize(S& s, const T& v) {
        s << std::get<N>(v);
        Tuple<T, N == std::tuple_size<T>::value - 1, N + 1>::serialize(s, v);
    }
};

template <typename T, int N>
struct Tuple<T, true, N> {
    template <typename S>
    static void deserialize(S&, T&) {}
    template <typename S>
    static void serialize(S&, const T&) {}
};
}  // namespace details

template <typename... T>
struct ParamTraits<std::tuple<T...>> {
    using tuple_type = std::tuple<T...>;  // for internal use

    using store_type = tuple_type;
    static constexpr bool valid = ParamPack<T...>::valid;

    static_assert(
        ParamPack<T...>::valid == true,
        "One or more tuple elements is not a valid RPC parameter type.");

    template <typename S>
    static void write(S& s, const tuple_type& v) {
        details::Tuple<tuple_type, std::tuple_size<tuple_type>::value == 0,
                       0>::serialize(s, v);
    }

    template <typename S>
    static void read(S& s, tuple_type& v) {
        details::Tuple<tuple_type, std::tuple_size<tuple_type>::value == 0,
                       0>::deserialize(s, v);
    }

    static tuple_type&& get(tuple_type&& v) {
        return std::move(v);
    }
};

}  // namespace rpc
}  // namespace cz

// A simple test
void testDeserialization() {
    Stream s;
    serializeMethod<decltype(&SerializeTest::func1)>(s, 1,
                                                     std::vector<int>{1, 2});
    // deserialize the parameters into a tuple.
    // the tuple is of type std::tuple<int,std::vector<int>>
    FunctionTraits<decltype(&SerializeTest::func1)>::param_tuple params;
    s >> params;
};

Từ tuple đến function parameters

Sau khi deserialize tất cả parameter thành tuple, chúng ta giờ đâu phải tìm cách tháo tuble để call matching function. Một lần nữa, ta có thể dùng variadic template.

namespace cz {
namespace rpc {

namespace detail {
template <typename F, typename Tuple, bool Done, int Total, int... N>
struct callmethod_impl {
    static decltype(auto) call(typename FunctionTraits<F>::class_type& obj, F f,
                               Tuple&& t) {
        return callmethod_impl<F, Tuple, Total == 1 + sizeof...(N), Total, N...,
                               sizeof...(N)>::call(obj, f,
                                                   std::forward<Tuple>(t));
    }
};

template <typename F, typename Tuple, int Total, int... N>
struct callmethod_impl<F, Tuple, true, Total, N...> {
    static decltype(auto) call(typename FunctionTraits<F>::class_type& obj, F f,
                               Tuple&& t) {
        using Traits = FunctionTraits<F>;
        return (obj.*f)(
            ParamTraits<typename Traits::template argument<N>::type>::get(
                std::get<N>(std::forward<Tuple>(t)))...);
    }
};
}  // namespace details

template <typename F, typename Tuple>
decltype(auto) callMethod(typename FunctionTraits<F>::class_type& obj, F f,
                          Tuple&& t) {
    static_assert(FunctionTraits<F>::valid, "Function not usable as RPC");
    typedef typename std::decay<Tuple>::type ttype;
    return detail::callmethod_impl<
        F, Tuple, 0 == std::tuple_size<ttype>::value,
        std::tuple_size<ttype>::value>::call(obj, f, std::forward<Tuple>(t));
}

}  // namespace rpc
}  // namespace cz

// A simple test
void testCall() {
    Stream s;
    // serialize
    serializeMethod<decltype(&SerializeTest::func1)>(s, 1,
                                                     std::vector<int>{1, 2});
    // deserialize
    FunctionTraits<decltype(&SerializeTest::func1)>::param_tuple params;
    s >> params;
    // Call func1 on an object, unpacking the tuple into parameters
    SerializeTest obj;
    callMethod(obj, &SerializeTest::func1, std::move(params));
};

Vậy, giờ đây ta đã biết cách xác thực một function, serialize, deserialize, và call function đó. Vậy là đã xong bước code “level thấp” rồi. Lớp tiếp theo chúng ta sắp sửa phủ lên code này sẽ là RPC API thực sự.

The RPC API

Header

Header có chứa những thông tin sau:

Field
size Tổng dung lượng (byte) của RPC. Có dung lượng là một phần của header sẽ đơn giản hóa mạnh mẽ, vì chúng ta có thể kiểm tra nếu ta nhận tất cả dữ liệu trước khi thử xử lý RPC.
counter Số call. Mỗi lúc ta gọi RPC, một counter sẽ tăng và được chỉ định đến RPC call đó.
rpcid The function to call
isReply Nếu true, đây là reply đến RPC. Nếu false, đây là RPC call.
success Chỉ áp dụng cho reply (isReply==true). Nếu true, call thành công và data là reply. Nấu false, data là exception information

Counter và rpcid hình thành một key xác định RPC call instance, cần khi ghép một incoming RPC reply đế RPC call gây ra nó.

// Small utility struct to make it easier to work with the RPC headers
struct Header {
enum {
kSizeBits = 32,
kRPCIdBits = 8,
kCounterBits = 22,
};
explicit Header() {
static_assert(sizeof(*this) == sizeof(uint64_t),
“Invalid size. Check the bitfields”);
all_ = 0;
}

struct Bits {
uint32_t size : kSizeBits;
unsigned counter : kCounterBits;
unsigned rpcid : kRPCIdBits;
unsigned isReply : 1;  // Is it a reply to a RPC call ?
unsigned success : 1;  // Was the RPC call a success ?
};

uint32_t key() const {
return (bits.counter << kRPCIdBits) | bits.rpcid;
}
union {
Bits bits;
uint64_t all_;
};
};

inline Stream& operator<<(Stream& s, const Header& v) {
s << v.all_;
return s;
}

inline Stream& operator>>(Stream& s, Header& v) {
s >> v.all_;
return s;
}

}  // namespace rpc
}  // namespace cz

Table

Chúng ta đã serialize và deserialize một RPC, nhưng không phải là cách map một RPC đã serialize đến đúng function bên phía server. Để giải quyết vấn đề này, chúng ta cần chỉ định một ID đến mỗi function. Client sẽ biết nó muốn call function nào, và điền đúng ID vào header. Server kiểm tra header, và khi biết được ID, nó sẽ gửi đến đúng handler. Hãy tạo một số ID cơ bản để xác định bảng phân phối tương tự.

namespace cz {
namespace rpc {

//
// Helper code to dispatch a call.
namespace details {
// Handle RPCs with return values
template <typename R>
struct CallHelper {
    template <typename OBJ, typename F, typename P>
    static void impl(OBJ& obj, F f, P&& params, Stream& out) {
        out << callMethod(obj, f, std::move(params));
    }
};

// Handle void RPCs
template <>
struct CallHelper<void> {
    template <typename OBJ, typename F, typename P>
    static void impl(OBJ& obj, F f, P&& params, Stream& out) {
        callMethod(obj, f, std::move(params));
    }
};
}

struct BaseRPCInfo {
    BaseRPCInfo() {}
    virtual ~BaseRPCInfo(){};
    std::string name;
};

class BaseTable {
public:
    BaseTable() {}
    virtual ~BaseTable() {}
    bool isValid(uint32_t rpcid) const {
        return rpcid < m_rpcs.size();
    }
protected:
    std::vector<std::unique_ptr<BaseRPCInfo>> m_rpcs;
};

template <typename T>
class TableImpl : public BaseTable {
public:
    using Type = T;

    struct RPCInfo : public BaseRPCInfo {
        std::function<void(Type&, Stream& in, Stream& out)> dispatcher;
    };

    template <typename F>
    void registerRPC(uint32_t rpcid, const char* name, F f) {
        assert(rpcid == m_rpcs.size());
        auto info = std::make_unique<RPCInfo>();
        info->name = name;
        info->dispatcher = [f](Type& obj, Stream& in, Stream& out) {
            using Traits = FunctionTraits<F>;
            typename Traits::param_tuple params;
            in >> params;
            using R = typename Traits::return_type;
            details::CallHelper<R>::impl(obj, f, std::move(params), out);
        };
        m_rpcs.push_back(std::move(info));
    }
};

template <typename T>
class Table : public TableImpl<T> {
    static_assert(sizeof(T) == 0, "RPC Table not specified for the type.");
};

}  // namespace rpc
}  // namespace cz

The Table template cần phải được chuyên hóa cho class mà ta muốn dùng cho RPC call. Giả sử ta có một Calculator class muốn dùng cho RPC calls:

class Calculator {
public:
    double add(double a, double b) {
        m_ans = a + b;
        return m_ans;
    }
    double sub(double a, double b) {
        m_ans = a - b;
        return m_ans;
    }
    double ans() {
        return m_ans;
    }

private:
    double m_ans = 0;
};

Ta có thể chuyên hóa Table template cho Calculator, để cả client và server đều có thể tham gia:

// Table specialization for Calculator
template <>
class cz::rpc::Table<Calculator> : cz::rpc::TableImpl<Calculator> {
public:
    enum class RPCId { add, sub, ans };

    Table() {
        registerRPC((int)RPCId::add, "add", &Calculator::add);
        registerRPC((int)RPCId::sub, "sub", &Calculator::sub);
        registerRPC((int)RPCId::ans, "ans", &Calculator::ans);
    }

    static const RPCInfo* get(uint32_t rpcid) {
        static Table<Calculator> tbl;
        assert(tbl.isValid(rpcid));
        return static_cast<RPCInfo*>(tbl.m_rpcs[rpcid].get());
    }
};

Với một ID,function `get` sẽ trả dispatcher về đúng method `Calculator`. Rồi ta có thể chuyển `Calculator` instance,  input và output streams sang dispatcher để xử lý mọi thứ còn lại.

Quá trình chuyên hóa khá dài dòng và không thể tránh khỏi sai sót, vì enum và `registerRPC` call phải khớp. Nhưng chúng ta có thể rút ngắn rõ rệt quá trình với một vài macro. Đầu tiên, hãy xem thử một ví dụ dài dòng để thấy cách dùng bảng này:

void testCalculatorTable() {
    // Both the client and server need to have access to the necessary table
    using CalcT = Table<Calculator>;

    //
    // Client sends RPC
    Stream toServer;    
    RPCHeader hdr;
    hdr.bits.rpcid = (int)CalcT::RPCId::add;
    toServer << hdr;
    serializeMethod<decltype(&Calculator::add)>(toServer, 1.0, 9.0);

    //
    // Server receives RPC, and sends back a reply
    Calculator calc;  // object used to receive the RPCs
    toServer >> hdr;
    auto&& info = CalcT::get(hdr.bits.rpcid);
    Stream toClient;  // Stream for the reply
    // Call the desired Calculator function.
    info->dispatcher(calc, toServer, toClient);

    //
    // Client receives a reply
    double r;
    toClient >> r;
    printf("%2.0f\n", r);  // Will print "10"
}

Một lần nữa, ví dụ này khá dài dòng, chỉ để thể hiện code flow. Chúng ta sẽ có ví dụ tối ưu hơn sau.

Vậy, chúng ta đơn giản quá trình chuyên hóa bảng như thế nào? Nếu ta đặt gist của chuyên hóa bảng trong một header không được guard, bạn chỉ cần một vài define theo sau là một include của header (không guard) đó để tạo tương tự.

Ví dụ:

#define RPCTABLE_CLASS Calculator
#define RPCTABLE_CONTENTS \
    REGISTERRPC(add) \
    REGISTERRPC(sub) \
    REGISTERRPC(ans)
#include "RPCGenerate.h"

Quá đơn giản, đúng không?

`RPCGenerate.h` là một header không được guard, trông như sau:

#ifndef RPCTABLE_CLASS
    #error "Macro RPCTABLE_CLASS needs to be defined"
#endif
#ifndef RPCTABLE_CONTENTS
    #error "Macro RPCTABLE_CONTENTS needs to be defined"
#endif

#define RPCTABLE_TOOMANYRPCS_STRINGIFY(arg) #arg
#define RPCTABLE_TOOMANYRPCS(arg) RPCTABLE_TOOMANYRPCS_STRINGIFY(arg)

template<> class cz::rpc::Table<RPCTABLE_CLASS> : cz::rpc::TableImpl<RPCTABLE_CLASS>
{
public:
    using Type = RPCTABLE_CLASS;
    #define REGISTERRPC(rpc) rpc,
    enum class RPCId {
        RPCTABLE_CONTENTS
        NUMRPCS
    };

    Table()
    {
        static_assert((unsigned)((int)RPCId::NUMRPCS-1)<(1<<Header::kRPCIdBits),
            RPCTABLE_TOOMANYRPCS(Too many RPCs registered for class RPCTABLE_CLASS));
        #undef REGISTERRPC
        #define REGISTERRPC(func) registerRPC((uint32_t)RPCId::func, #func, &Type::func);
        RPCTABLE_CONTENTS
    }

    static const RPCInfo* get(uint32_t rpcid)
    {
        static Table<RPCTABLE_CLASS> tbl;
        assert(tbl.isValid(rpcid));
        return static_cast<RPCInfo*>(tbl.m_rpcs[rpcid].get());
    }
};

#undef REGISTERRPC
#undef RPCTABLE_START
#undef RPCTABLE_END
#undef RPCTABLE_CLASS
#undef RPCTABLE_CONTENTS
#undef RPCTABLE_TOOMANYRPCS_STRINGIFY
#undef RPCTABLE_TOOMANYRPCS

Thêm vào đó, việc chuyên hóa bảng thế này giúp ta hỗ trợ inheritance dễ dàng hơn. Hãy tưởng tượng chúng ta có một `ScientificCalculator` kế thừa từ Calculator:

class ScientificCalculator : public Calculator {
public:
    double sqrt(double a) {
        return std::sqrt(a);
    }
};

Khi đã define riêng biệt các content của Calculator, chúng ta có thể tái sử dụng define này:

// Separately define the Calculator contents so it can be reused
#define RPCTABLE_CALCULATOR_CONTENTS \
    REGISTERRPC(add) \
    REGISTERRPC(sub) \
    REGISTERRPC(ans)

// Calculator table
#define RPCTABLE_CLASS Calculator
#define RPCTABLE_CONTENTS \
    RPCTABLE_CALCULATOR_CONTENTS
#include "RPCGenerate.h"

// ScientificCalculator table
#define RPCTABLE_CLASS ScientificCalculator
#define RPCTABLE_CONTENTS \
    RPCTABLE_CALCULATOR_CONTENTS \
    REGISTERRPC(sqrt)
#include "RPCGenerate.h"

Transport

Chúng ta phải xác định dữ liệu được chuyển đổi như thế nào giữa client và server. Hãy đặt dữ liệu đó vào `Transport` interface class. Interface bị cố ý để rất đơn giản để ứng dụng xác định một custom transport. Tất cả chúng ta cần là method để gửi, nhận, và đóng.

namespace cz {
namespace rpc {

class Transport {
public:
    virtual ~Transport() {}

    // Send one single RPC
    virtual void send(std::vector<char> data) = 0;

    // Receive one single RPC
    // dst : Will contain the data for one single RPC, or empty if no RPC
    // available
    // return: true if the transport is still alive, false if the transport
    // closed
    virtual bool receive(std::vector<char>& dst) = 0;

    // Close connection to the peer
    virtual void close() = 0;
};

}  // namespace rpc
}  // namespace cz

Result

Kết quả RPC phải trông như thế nào? Mỗi khi chúng ta tạo một RPC call, kết quả có thể đi theo 3 hình thức.

Form Meaning
Valid Nhận reply từ server với giá trị kết quả của RPC call
Aborted Connection failed hoặc bị đóng, và chúng ta không nhận được giá trị kết quả
Exception Nhận reply từ server với exception string (RPC call gây exception bên server side)
namespace cz {
namespace rpc {

class Exception : public std::exception {
public:
    Exception(const std::string& msg) : std::exception(msg.c_str()) {}
};

template <typename T>
class Result {
public:
    using Type = T;

    Result() : m_state(State::Aborted) {}

    explicit Result(Type&& val) : m_state(State::Valid), m_val(std::move(val)) {}

    Result(Result&& other) {
        moveFrom(std::move(other));
    }

    Result(const Result& other) {
        copyFrom(other);
    }

    ~Result() {
        destroy();
    }

    Result& operator=(Result&& other) {
        if (this == &other)
            return *this;
        destroy();
        moveFrom(std::move(other));
        return *this;
    }

    // Construction from an exception needs to be separate. so
    // RPCReply<std::string> works.
    // Otherwise we would have no way to tell if constructing from a value, or
    // from an exception
    static Result fromException(std::string ex) {
        Result r;
        r.m_state = State::Exception;
        new (&r.m_ex) std::string(std::move(ex));
        return r;
    }

    template <typename S>
    static Result fromStream(S& s) {
        Type v;
        s >> v;
        return Result(std::move(v));
    };

    bool isValid() const {
        return m_state == State::Valid;
    }
    bool isException() const {
        return m_state == State::Exception;
    };
    bool isAborted() const {
        return m_state == State::Aborted;
    }

    T& get() {
        if (!isValid())
            throw Exception(isException() ? m_ex : "RPC reply was aborted");
        return m_val;
    }

    const T& get() const {
        if (!isValid())
            throw Exception(isException() ? m_ex : "RPC reply was aborted");
        return m_val;
    }

    const std::string& getException() {
        assert(isException());
        return m_ex;
    };

private:
    void destroy() {
        if (m_state == State::Valid)
            m_val.~Type();
        else if (m_state == State::Exception) {
            using String = std::string;
            m_ex.~String();
        }
        m_state = State::Aborted;
    }

    void moveFrom(Result&& other) {
        m_state = other.m_state;
        if (m_state == State::Valid)
            new (&m_val) Type(std::move(other.m_val));
        else if (m_state == State::Exception)
            new (&m_ex) std::string(std::move(other.m_ex));
    }

    void copyFrom(const Result& other) {
        m_state = other.m_state;
        if (m_state == State::Valid)
            new (&m_val) Type(other.m_val);
        else if (m_state == State::Exception)
            new (&m_ex) std::string(other.m_ex);
    }

    enum class State { Valid, Aborted, Exception };

    State m_state;
    union {
        Type m_val;
        std::string m_ex;
    };
};

// void specialization
template <>
class Result<void> {
public:
    Result() : m_state(State::Aborted) {}

    Result(Result&& other) {
        moveFrom(std::move(other));
    }

    Result(const Result& other) {
        copyFrom(other);
    }

    ~Result() {
        destroy();
    }

    Result& operator=(Result&& other) {
        if (this == &other)
            return *this;
        destroy();
        moveFrom(std::move(other));
        return *this;
    }

    static Result fromException(std::string ex) {
        Result r;
        r.m_state = State::Exception;
        new (&r.m_ex) std::string(std::move(ex));
        return r;
    }

    template <typename S>
    static Result fromStream(S& s) {
        Result r;
        r.m_state = State::Valid;
        return r;
    }

    bool isValid() const {
        return m_state == State::Valid;
    }
    bool isException() const {
        return m_state == State::Exception;
    };
    bool isAborted() const {
        return m_state == State::Aborted;
    }

    const std::string& getException() {
        assert(isException());
        return m_ex;
    };

    void get() const {
        if (!isValid())
            throw Exception(isException() ? m_ex : "RPC reply was aborted");
    }

private:
    void destroy() {
        if (m_state == State::Exception) {
            using String = std::string;
            m_ex.~String();
        }
        m_state = State::Aborted;
    }

    void moveFrom(Result&& other) {
        m_state = other.m_state;
        if (m_state == State::Exception)
            new (&m_ex) std::string(std::move(other.m_ex));
    }

    void copyFrom(const Result& other) {
        m_state = other.m_state;
        if (m_state == State::Exception)
            new (&m_ex) std::string(other.m_ex);
    }

    enum class State { Valid, Aborted, Exception };

    State m_state;
    union {
        bool m_dummy;
        std::string m_ex;
    };
};

}  // namespace rpc
}  // namespace cz

Chuyên hóa `Result<void>` rất cần thiết, vì trong trường hợp đó không có kết quả, nhưng caller vẫn muốn biết neus RPC call có được xử lý đúng cách hay không. Mới đầu, tôi từng xem xét sử dụng `Expected<T>` cho RPC reply. Nhưng `Expected<T>` về cơ bản có hai trạng thái (Value hoặc Exception), mà chúng ta lại cần tới 3 (Value, Exception, và Aborted). Ta hay cho rằng Aborted có thể được xem là exceptiom, nhưng từ góc nhìn của client, điều này không phải lúc nào cũng đúng. Trong một số trường hợp bạn sẽ muốn biết một RPC fail vì đóng kết nối, mà không phải vì server phản hồi exception.

Video: Sendo.vn xây dựng kiến trúc hệ thống mở rộng để đáp ứng tăng trưởng 10x mỗi năm như thế nào?

OutProcessor

Chúng ta cần phải theo dõi những RPC call đang diễn ra để user code nhận kết quả khi chúng đến. Ta có thể handle một kết quả theo hai cách. Thông qua handle không đồng bộ (tương tự Asio), hoặc với một future.

Ta cần hai class cho việc này: một outgoing processor và một wrapper cho một RPC call duy nhất. Một class khác (Connection) để buộc các outgoing processor và incoming processor với nhau. Class này sẽ được giới thiệu sau.

namespace cz {
namespace rpc {

class BaseOutProcessor {
public:
    virtual ~BaseOutProcessor() {}

protected:
    template <typename R>
    friend class Call;
    template <typename L, typename R>
    friend struct Connection;

    template <typename F, typename H>
    void commit(Transport& transport, uint32_t rpcid, Stream& data,
                H&& handler) {
        std::unique_lock<std::mutex> lk(m_mtx);
        Header hdr;
        hdr.bits.size = data.writeSize();
        hdr.bits.counter = ++m_replyIdCounter;
        hdr.bits.rpcid = rpcid;
        *reinterpret_cast<Header*>(data.ptr(0)) = hdr;
        m_replies[hdr.key()] = [handler = std::move(handler)](Stream * in,
                                                              Header hdr) {
            using R = typename ParamTraits<
                typename FunctionTraits<F>::return_type>::store_type;
            if (in) {
                if (hdr.bits.success) {
                    handler(Result<R>::fromStream((*in)));
                } else {
                    std::string str;
                    (*in) >> str;
                    handler(Result<R>::fromException(std::move(str)));
                }
            } else {
                // if the stream is nullptr, it means the result is being aborted
                handler(Result<R>());
            }
        };
        lk.unlock();

        transport.send(data.extract());
    }

    void processReply(Stream& in, Header hdr) {
        std::function<void(Stream*, Header)> h;
        {
            std::unique_lock<std::mutex> lk(m_mtx);
            auto it = m_replies.find(hdr.key());
            assert(it != m_replies.end());
            h = std::move(it->second);
            m_replies.erase(it);
        }

        h(&in, hdr);
    }

    void abortReplies() {
        decltype(m_replies) replies;
        {
            std::unique_lock<std::mutex> lk(m_mtx);
            replies = std::move(m_replies);
        }

        for (auto&& r : replies) {
            r.second(nullptr, Header());
        }
    };

    std::mutex m_mtx;
    uint32_t m_replyIdCounter = 0;
    std::unordered_map<uint32_t, std::function<void(Stream*, Header)>>
        m_replies;
};

template <typename T>
class OutProcessor : public BaseOutProcessor {
public:
    using Type = T;

    template <typename F, typename... Args>
    auto call(Transport& transport, uint32_t rpcid, Args&&... args) {
        using Traits = FunctionTraits<F>;
        static_assert(
            std::is_member_function_pointer<F>::value &&
                std::is_base_of<typename Traits::class_type, Type>::value,
            "Not a member function of the wrapped class");
        Call<F> c(*this, transport, rpcid);
        c.serializeParams(std::forward<Args>(args)...);
        return std::move(c);
    }

protected:
};

// Specialization for when there is no outgoing RPC calls
// If we have no outgoing RPC calls, receiving a reply is therefore an error.
template <>
class OutProcessor<void> {
public:
    OutProcessor() {}
    void processReply(Stream&, Header) {
        assert(0 && "Incoming replies not allowed for OutProcessor<void>");
    }
    void abortReplies() {}
};

}  // namespace rpc
}  // namespace cz

Nên nhớ `OutProcessor<T>` không cần một reference/pointer đến object của T. Nó chỉ cần biết type mà ta gửi RPC đến, để biết được phải dùng `Table<T>`.

Đây là ví dụ sử dụng OutProcessor:

//
// "trp" is a transport that sends data to a "Calculator" server
void testOutProcessor(Transport& trp) {
    // A processor that calls RPCs on a "Calculator" server
    OutProcessor<Calculator> outPrc;

    // Handle with an asynchronous handler
    outPrc.call<decltype(&Calculator::add)>(
              trp, (int)Table<Calculator>::RPCId::add, 1.0, 2.0)
        .async([](Result<double> res) {
            printf("%2.0f\n", res.get());  // prints '3'
        });

    // Handle with a future
    Result<double> res = outPrc.call<decltype(&Calculator::add)>(
                        trp, (int)Table<Calculator>::RPCId::add, 1.0, 3.0)
                  .ft().get();
    printf("%2.0f\n", res.get());  // prints '4'
}

Một lần nữa, hơi quá dài dòng, vì tôi chưa giới thiệu tất cả code để áp dụng tối ưu. Nhưng chí ít, ta đã thấy được interface `OutProcessor<T>` và `Call` làm việc như thế nào. Thực thi `std::future` build đơn giản trên thực thi không đồng bộ.

InProcessor

Giờ đây ta có thể gửi một RPC và đợi kết quả, hãy xem thử ta cần gì ở bên kia. Phải làm gì khi server nhận được một RPC call.

Hãy tạo class `InProcessor<T>`. Trái với `OutProcessor<T>`, `InProcessor<T>` cần phải giữ một reference tới một object thuộc kiểu T. Điều này sảy ra khi nhận được RPC, nó có thể gọi requested method lên object đó, và gửi kết quả trở lại client.

namespace cz {
namespace rpc {

class BaseInProcessor {
public:
    virtual ~BaseInProcessor() {}
};

template <typename T>
class InProcessor : public BaseInProcessor {
public:
    using Type = T;
    InProcessor(Type* obj, bool doVoidReplies = true)
        : m_obj(*obj), m_voidReplies(doVoidReplies) {}

    void processCall(Transport& transport, Stream& in, Header hdr) {
        Stream out;
        // Reuse the header as the header for the reply, so we keep the counter
        // and rpcid
        hdr.bits.size = 0;
        hdr.bits.isReply = true;
        hdr.bits.success = true;

        auto&& info = Table<Type>::get(hdr.bits.rpcid);

#if CZRPC_CATCH_EXCEPTIONS
        try {
#endif
            out << hdr;  // Reserve space for the header
            info->dispatcher(m_obj, in, out);
#if CZRPC_CATCH_EXCEPTIONS
        } catch (std::exception& e) {
            out.clear();
            out << hdr;  // Reserve space for the header
            hdr.bits.success = false;
            out << e.what();
        }
#endif

        if (m_voidReplies || (out.writeSize() > sizeof(hdr))) {
            hdr.bits.size = out.writeSize();
            *reinterpret_cast<Header*>(out.ptr(0)) = hdr;
            transport.send(out.extract());
        }
    }

protected:
    Type& m_obj;
    bool m_voidReplies = false;
};

template <>
class InProcessor<void> {
public:
    InProcessor(void*) {}
    void processCall(Transport&, Stream&, Header) {
        assert(0 && "Incoming RPC not allowed for void local type");
    }
};

}  // namespace rpc
}  // namespace cz

Define `CZRPC_CATCH_EXCEPTIONS` cho phép chúng ta tweak nếu ta muốn exception phía server được chuyển sang phía client.

Nếu việc sử dụng `InProcessor<T>` (và `Table<T>`) cho phép call RPC lên object (không biết gì về RPC hoặc netwrk). Ví dụ như, hay xem xét giả dụ sau:

oid calculatorServer() {
    // The object we want to use for RPC calls
    Calculator calc;
    // The server processor. It will call the appropriate methods on 'calc' when
    // an RPC is received
    InProcessor<Calculator> serverProcessor(&calc);
    while (true) {
        // calls to serverProcessor::processCall whenever there is data
    }
}

Object `Calculator` được dùng cho RPC không biết gì về RPC. `InProcessor<Calculator>` sẽ đảm nhiệm mọi nhiệm vụ liên quan. Từ đó ta không thể sử dụng các class bên thức ba cho PRC. Trong một số trường hợp, chung ta muốn class dùng cho RPC biết biết về RPC và/hoặc network. Ví dụ như, nếu bạn đang tạo một chat system, bạn sẽ khiến client gửi message (RPC call) đến server. Server cần biết client được kết nối đến thứ gì, để có thể truyền phát message.

Connection

Ta giờ đây đã có thể gửi nhận RPC. dù API có hơi dài một chút. Những template class `OutProcessor<T>` và `InProcessor<T>` sẽ xử lý những sự kiện sảy ra với data tại cả hai đầu kết nối. Vậy, hiện ta cần chính điều này. Một `Connection` để buộc mọi thức cần để gửi nhận dữ liệu, và đơn giản là API, về một chỗ.

namespace cz {
namespace rpc {

struct BaseConnection {
    virtual ~BaseConnection() {}

    //! Process any incoming RPCs or replies
    // Return true if the connection is still alive, false otherwise
    virtual bool process() = 0;
};

template <typename LOCAL, typename REMOTE>
struct Connection : public BaseConnection {
    using Local = LOCAL;
    using Remote = REMOTE;
    using ThisType = Connection<Local, Remote>;
    Connection(Local* localObj, std::shared_ptr<Transport> transport)
        : localPrc(localObj), transport(std::move(transport)) {}

    template <typename F, typename... Args>
    auto call(Transport& transport, uint32_t rpcid, Args&&... args) {
        return remotePrc.template call<F>(transport, rpcid,
                                          std::forward<Args>(args)...);
    }

    static ThisType* getCurrent() {
        auto it = Callstack<ThisType>::begin();
        return (*it) == nullptr ? nullptr : (*it)->getKey();
    }

    virtual bool process() override {
        // Place a callstack marker, so other code can detect we are serving an
        // RPC
        typename Callstack<ThisType>::Context ctx(this);
        std::vector<char> data;
        while (true) {
            if (!transport->receive(data)) {
                // Transport is closed
                remotePrc.abortReplies();
                return false;
            }

            if (data.size() == 0)
                return true;  // No more pending data to process

            Header hdr;
            Stream in(std::move(data));
            in >> hdr;

            if (hdr.bits.isReply) {
                remotePrc.processReply(in, hdr);
            } else {
                localPrc.processCall(*transport, in, hdr);
            }
        }
    }

    InProcessor<Local> localPrc;
    OutProcessor<Remote> remotePrc;
    std::shared_ptr<Transport> transport;
};

}  // namespace rpc
}  // namespace cz

Từ đây ta có output processor, the input processor, và the transport. Để user code có thể xác định liệu mó có đang phục vụ RPC không. nó sử dụng một class Callstack. Class cho phép tạo RPC/network aware code nếu cần thiết, giống server class.

Vậy, ta đơn giản hóa API bằng cách nào? Vì `Connection<T>` có mọi thứ ta cần, một macro dưới dạng paramether của connection object, một function name và parameter, xử lý mọi thứ, bao gồm cả type check để nó không compile nếu là call không hợp lệ.

#define CZRPC_CALL(con, func, ...)                                        \
    (con).call<decltype(&std::decay<decltype(con)>::type::Remote::func)>( \
        *(con).transport,                                                 \
        (uint32_t)cz::rpc::Table<                                         \
            std::decay<decltype(con)>::type::Remote>::RPCId::func,        \
        ##__VA_ARGS__)

Khi dùng marcro này, cú pháp RPC call sẽ trở nên cực kỳ đơn giản. Hãy xe, thử client code ví dụ dưới đây:

// Some class to use for RPC calls
class MagicSauce {
public:
    int func1(int a, int b) {
        return a + b;
    }

    int func2(int a, int b) {
        return a + b;
    }
};

// Define RPC table for MagicSauce
#define RPCTABLE_CLASS MagicSauce
#define RPCTABLE_CONTENTS REGISTERRPC(func1)
#include "RPCGenerate.h"

// 'trp' is a fully functional transport
void test_Connection(std::shared_ptr<Transport> trp) {
    Connection<void, MagicSauce> con(nullptr, trp);

    // Doesn't compile : Invalid number of parameters
    // CZRPC_CALL(con, func1, 1);

    // Doesn't compile : Wrong type of parameters
    // CZRPC_CALL(con, func1, 1, "hello");

    // Doesn't compile: func3 is not a MagicSauce method
    // CZRPC_CALL(con, func3, 1, 2);

    // Doesn't compile: func2 is a method of MagicSauce, but not registered as
    // RPC
    // CZRPC_CALL(con, func2, 1, 2);

    // Compiles fine, since everything is valid
    CZRPC_CALL(con, func1, 1, 2).async([](Result<int> res) {
            printf("%d\n", res.get());  // print '3'
        });
}

Bạn có để ý là `void` và `nullptr` được sử dụng khi tạo kết nối với `Connection<void`, `MagicSauce> con(nullptr, trp);`? Thao tác này sẽ điều tiết bidirectional RPC (server cũng có thể call RPC lên một client). Trong trường hợp này, chúng ta không trong đợi vào client side RPC, vậy nên client side `Connection` object không có một local object để call RPC.

Một ví dụ đơn giản (nhưng không hoạt động) của bidirectional RPC:

class ChatClient;

class ChatServer {
public:
    // Called by clients to post new messages
    void msg(const char* msg);
    void addNewClient(std::shared_ptr<Transport> trp);

private:
    // Connection specifies both a LOCAL, and REMOTE object types
    std::vector<std::unique_ptr<Connection<ChatServer, ChatClient>>> m_clients;
};

#define RPCTABLE_CLASS ChatServer
#define RPCTABLE_CONTENTS REGISTERRPC(msg)
#include "RPCGenerate.h"

class ChatClient {
public:
    void onMsg(const char* msg);
};

#define RPCTABLE_CLASS ChatClient
#define RPCTABLE_CONTENTS REGISTERRPC(onMsg)
#include "RPCGenerate.h"

void ChatServer::msg(const char* msg) {
    // Simply broadcast the message to all clients
    for (auto&& c : m_clients) {
        CZRPC_CALL(*c, onMsg, msg);
    }
}
void ChatServer::addNewClient(std::shared_ptr<Transport> trp) {
    auto con = std::make_unique<Connection<ChatServer, ChatClient>>(this, trp);
    m_clients.push_back(std::move(con));
}

void ChatClient::onMsg(const char* msg) {
    printf("%s\n", msg);
}

void test_ChatServer() {
    ChatServer server;
    while (true) {
        // Wait for connections, and call ChatServer::addClient
    }
}

// 'trp' is some fully functional transport connected to the ChatServer
void test_ChatClient(std::shared_ptr<Transport> trp) {
    ChatClient client;
    // In this case, we have a client side object to answer RPCs
    Connection<ChatClient, ChatServer> con(&client, trp);
    while (true) {
        // call the 'msg' RPC whenever the user types something, like this:
        CZRPC_CALL(con, msg, "some message");

        // The server will call our client 'onMsg' when other clients send a
        // message
    }
}

Các parameter của `Connection` template trên server và client được đảo ngược. Khi nhận được dữ liệu, object `Connection` sẽ forward xử lý đến `InProcessor` nếu là một incoming RPC call, hoặc đến `OutProcessor` nếu đó là reply đến outgoing RPC call trước đó. Data flow của bidirectional RPC trông như thế này:

Improvements

Có một số phần được cố ý không đưa vào framework, để từ đó ứng dụng có thể quyết định thế nào là tốt nhất. Ví dụ:

  • Transport initialization
    • Giao diện transport rất đơn giản, nên nó không quá chú trọng vào hướng khởi tạo cụ thể hoặc xác định incoming data. Việc cung cấp một transport hiệu quả đến class `Connecttion` hoàn toàn phụ thuộc vào ứng dụng. Đây cũng là lý do tại sao tôi tránh hiển thị transport initialization, vì tôi phải trình bày một transport implementation hoạt động hoàn chỉnh.
    • Tại thời điểm viết bài, repo mã nguồn có một transport implementation dùng Boost Asio (hoặc Asio biệt lập)
  • Xác định disconnection
    • Như với initialization, có những đoạn code để xử lý và xác định shutdown.Ứng dụng sẽ có nhiệm vụ quyết định thao tác như thế nào với bất cứ custom implementation nó cung cấp.
  • Và nhiều hơn nữa

TopDev via gamasutra

Xem ngay những tin đăng tuyển dụng IT mới nhất trên TopDev

Flutter khác biệt gì so với những công nghệ đương thời?

flutter-khac-biet-gi-so-voi-nguoi-anh-android

Flutter là gì?

Flutter là mobile UI framework của Google để tạo ra các giao diện native chất lượng cao trên iOSAndroid trong khoảng thời gian ngắn. Flutter hoạt động với source code có sẵn, được sử dụng bởi các nhà phát triển và các tổ chức trên khắp thế giới, đồng thời nó open-source và miễn phí.

Clip giới thiệu về Flutter:

Sự vượt trội của Flutter

Flutter là một công cụ mới được cung cấp bởi Google cho phép các nhà phát triển xây dựng các ứng dụng đa nền tảng có thể được thực hiện trong các hệ thống khác nhau chẳng hạn như Android hay iOS chỉ với một codebase chung.

Công cụ này được được xây dựng trong C và C ++ và cung cấp một cơ chế rendering 2D, một funtional-reactive framework là React-inspired, và một tập hợp các Material Design widget. Nó hiện đang được distribute bản alpha: version 0.0.20, tuy vậy giai đoạn đầu của nó đã được cho phép để tạo ra interfacing phức tạp, thực hiện kết nối mạng và thậm chí quản lý tập tin.

Cách tiếp cận Flutter’s là khác nhau từ các solution khác, ví dụ Cordova chạy trên một WebView là HTML, CSS và Javascript. Không giống như những công cụ này, nó chỉ sử dụng Dart như một ngôn ngữ lập trình duy nhất. Dart là khá dễ dàng để tìm hiểu và nếu bạn có kiến thức Java, 75% của công việc được gần như hoàn tất và làm quen với Dart sẽ chỉ mất một vài ngày.

  Flutter Vs. React Native - Nên chọn framework nào?

Tại sao nên sử dụng Flutter?

Phát triển ứng dụng nhanh chóng: Tính năng hot reload của Flutter giúp bạn nhanh chóng và dễ dàng thử nghiệm, xây dựng giao diện người dùng, thêm tính năng và sửa lỗi nhanh hơn. Trải nghiệm tải lại lần thứ hai, mà không làm mất trạng thái, trên emulator, simulator và device cho iOS và Android.

UI đẹp và biểu cảm: Thỏa mãn người dùng của bạn với các widget built-in đẹp mắt của Flutter theo Material Design và Cupertino (iOS-flavor), các API chuyển động phong phú, scroll tự nhiên mượt mà và tự nhận thức được nền tảng.

flutter

Framework hiện đại và reactive: Dễ dàng tạo giao diện người dùng của bạn với framework hiện đại, reactive của Flutter và tập hợp các platform, layout và widget phong phú. Giải quyết các thách thức giao diện người dùng khó khăn của bạn với các API mạnh mẽ và linh hoạt cho 2D, animation, gesture, hiệu ứng và hơn thế nữa.

Truy cập các tính năng và SDK native: Làm cho ứng dụng của bạn trở nên sống động với API của platform, SDK của bên thứ ba và native code. Flutter cho phép bạn sử dụng lại mã Java, Swift và ObjC hiện tại của mình và truy cập các tính năng và SDK native trên iOS và Android.

Phát triển ứng dụng thống nhất: Flutter có các công cụ và thư viện để giúp bạn dễ dàng đưa ý tưởng của mình vào cuộc sống trên iOS và Android. Nếu bạn chưa có kinh nghiệm phát triển trên thiết bị di động, thì Flutter là một cách dễ dàng và nhanh chóng để xây dựng các ứng dụng di động tuyệt đẹp. Nếu bạn là một nhà phát triển iOS hoặc Android có kinh nghiệm, bạn có thể sử dụng Flutter cho các View của bạn và tận dụng nhiều code Java / Kotlin / ObjC / Swift hiện có của bạn.

Mẹo quy ước tên cho CSS giúp bạn rút ngắn 2/3 thời gian khi debug!

meo-quy-uoc-ten-cho-css-giup-ban-rut-ngan-23-thoi-gian-khi-debug

Tác giả: Emmanuel Ohans

Tôi đã nghe không ít developer nói rằng họ ghét CSS. Với kinh nghiệm của mình, đây là kết quả cho việc không dành thời gian để học CSS.

CSS không phải là “ngôn ngữ” đẹp nhất, nhưng nó đã thành công trong việc làm front-end cho các website trong hơn 20 năm nay. Tuy nhiên, khi bạn viết nhiều CSS, bạn sẽ nhận ra được thấy một nhược điểm lớn. Quá khó để developer có thể maintain CSS.

Các CSS tệ sẽ nhanh chóng trở thành một cơn ác mộng đối với các lập trình viên. Dưới đây là một số quy ước tên sẽ giúp bạn giảm vô số giờ để debug.

Có thể bạn muốn xem:

  Responsive HTML bằng một dòng CSS
  Roadmap Frontend Developer - "Con đường tắt" để trở thành cao thủ Frontend Developer

Sử dụng dấu phân cách “-“

Nếu bạn sử dụng nhiều JavaScript

var redBox = document.getElementById('...')

Vấn đề là cách đặt tên này không phù hợp với CSS.

Đừng bao giờ:

.redBox {
  border: 1px solid red;
}

Thay vì vậy, hãy làm như thế này:

.red-box {
   border: 1px solid red;
}

Đây là một tiêu chuẩn đặt tên cho CSS. Và điều này được cho là dễ đọc hơn.

Ngoài ra, nó cũng phù hợp với thuộc tính của CSS.

// Correct
.some-class {
   font-weight: 10em
}
// Wrong
.some-class {
   fontWeight: 10em
}

Qui ước đặt tên BEM

Các team có cách tiếp cận khác nhau để viết CSS selector. Một số nhóm sử dụng dấu phân cách, trong khi một số khác lại thích sử dụng một cách đặt tên có cấu trúc hơn gọi là BEM.

Nhìn chung, có 3 vấn đề mà qui tắc này giải quyết:

  1. Để biết 1 selector làm cái gì, chỉ cần nhìn vào tên của nó
  2. Để có một ý tưởng về nơi mà một selector được sử dụng, chỉ cần nhìn vào nó
  3. Để biết mối quan hệ giữa các các class name, chỉ cần nhìn vào chúng

Bạn đã bao giờ nhìn thấy class name được viết như sau:

.nav--secondary {
  ...
}
.nav__header {
  ...
}

Đó là quy tắc BEM.

Video: Tối ưu Front-end để web của bạn load dưới 5 giây

Giải thích BEM

BEM cố gắng phân chia giao diện người dùng tổng thể thành các component nhỏ có thể tái sử dụng.

Hãy quan sát hình ảnh dưới đây:

Hình này biểu diễn một component, chẳng hạn như một khối thiết kế.
Bạn có thể dễ đoán được B trong BEM là viết tắt của ‘Block’.
Trong thực tế, ‘block’ này đại diện cho điều hướng trang, tiêu đề, chân trang hoặc bất kỳ khối thiết kế khác.
Theo thực tiễn được giải thích ở trên, một class name lý tưởng cho component này sẽ là stick-man.
Component nên theo kiểu sau:
.stick-man {
  
 }

Chúng ta đã sử dụng các chuỗi giới hạn ở đây. Thật tuyệt vời!

E là viết tắt của “Elements”

E trong ‘BEM’ là viết tắt của Elements.

Khối thiết kế hiếm khi bị cô lập.

Ví dụ, stick-man phải có đầu, hai cánh tay và chân.

head, feetarms là tất cả các element bên trong component. Chúng có thể được xem như các component con, nghĩa là con của component bố mẹ nó.
Sử dụng quy tắc BEM, các element  của class name được lấy ra bằng cách thêm hai dấu gạch dưới, tiếp theo là tên phần tử.
Ví dụ:
.stick-man__head {
}
.stick-man__arms {
}
.stick-man__feet {
}

M là “Modifiers”

M trong ‘BEM’ viết tắt của Modifiers.

Điều gì sẽ xảy ra nếu stick-man ông bị sửa đổi và chúng ta có một stick-man có màu xanh hoặc đỏ?

Thực tế, đây có thể là nút đỏ hoặc nút màu xanh. Đây là những sửa đổi của component được đề cập.

Sử dụng BEM, các modifier class name được lấy ra bằng cách thêm hai dấu nối sau tên của phần tử.

Ví dụ:

.stick-man--blue {
}
.stick-man--red {
}

Ví dụ cuối cùng cho thấy các component cha mẹ đang được sửa đổi. Nhưng không phải lúc nào cũng như vậy.

Điều gì sẽ xảy ra nếu chúng ta có những stick-man có kích cỡ đầu khác nhau?

Lúc này các element đã được sửa đổi. Hãy lưu ý, element là một component con trong khối  tổng thể.

Các .stick-man đại diện cho Block , .stick-man__head element

Như đã thấy trong ví dụ ở trên, dấu gạch nối đôi cũng có thể được sử dụng như sau:

.stick-man__head--small {
}
.stick-man__head--big {
}

Hãy lưu ý việc sử dụng các dấu gạch nối đôi trong ví dụ trên. Điều này biểu thị một modifier.

Đây là điều cơ bản về cách quy tắc BEM hiệu quả như thế nào.

Cá nhân tôi có xu hướng chỉ sử dụng các class name dùng phân cách gạch ngang cho các dự án đơn giản và BEM cho các giao diện người dùng có liên quan.

Tại sao sử dụng các quy ước đặt tên?

Đặt tên là một việc cực kì khó. Chúng tôi đang cố gắng làm mọi việc dễ dàng hơn và tiết kiệm thời gian cho chúng tôi trong tương lai với code có thể duy trì nhiều hơn.

Đặt tên đúng cách trong CSS sẽ làm cho code của bạn dễ đọc và dễ dàng hơn.

Đặt đúng tên trong CSS sẽ giúp bạn dễ đọc và duy trì code hơn.

Nếu bạn chọn sử dụng quy tắc BEM, nó sẽ trở nên dễ dàng hơn để xem mối quan hệ giữa các component của bạn/khối chỉ bằng cách nhìn vào đánh dấu.

Đặt tên CSS với JavaScript Hooks

VD ta được chuyển giao code HTML trông như sau:

<div class="siteNavigation">
</div>

ta đã đọc bài này và nhận ra rằng đây không phải là cách tốt nhất để đặt tên cho những thứ trong CSS. Vì vậy, ta tiếp tục code và tái cấu trúc các codebase trước:

<div class="site-navigation">
</div>

Một nơi nào đó trong JavaScript code, có sử dụng class mà ta chưa rename, siteNavigation:

//the Javasript code
const nav = document.querySelector('.siteNavigation')

Vì vậy, với sự thay đổi class name, biến nav biến thành null.

Để ngăn chặn các trường hợp này, developer đã đưa ra các chiến lược khác nhau.

1.Sử dụng js- class names

Một cách để giảm thiểu các lỗi như vậy là sử dụng class name js-* để biểu thị mối quan hệ với DOM element đang được đề cập.

Ví dụ:

<div class="site-navigation js-site-navigation">
</div>

Và JavaScript code:

//the Javasript code
const nav = document.querySelector('.js-site-navigation')

Theo một quy ước, bất kỳ ai nhìn thấy js-site-navigation class name sẽ hiểu rằng có mối liên hệ với DOM element đó trong mã JavaScript.

2. Sử dụng thuộc tính Rel

Bạn có nhận ra điều này?

<link rel="stylesheet" type="text/css" href="main.css">

Về cơ bản, thuộc tính rel xác định mối quan hệ mà tài nguyên đã liên kết đến resource mà nó được tham chiếu.

Trong ví dụ trước, những người đề xướng kỹ thuật sẽ làm việc này:

<div class="site-navigation" rel="js-site-navigation">
</div>

Và với JavaScript:

const nav = document.querySelector("[rel='js-site-navigation']")

Tôi có nghi ngờ về kỹ thuật này, nhưng bạn có thể bỏ qua nó trong một số codebases. Yêu cầu ở đây là “có một mối quan hệ với Javascript, vì vậy tôi sử dụng thuộc tính rel để biểu thị điều đó”.

Web là một nơi lớn với rất nhiều “phương pháp” khác nhau để giải quyết cùng một vấn đề.

3. Không sử dụng data attribute

Một số developer sử dụng các data attributes như JavaScript Hooks. Điều này là không đúng. Theo định nghĩa, data attributes được sử dụng để lưu trữ dữ liệu tùy chỉnh.

Tip: Thêm các CSS comments

Điều này không liên quan gì đến các quy ước đặt tên, nhưng cũng sẽ giúp bạn tiết kiệm được nhiều thời gian.

Trong khi rất nhiều nhà phát triển web cố gắng không viết các bình luận JavaScript hoặc viết đại loại, tôi nghĩ bạn nên viết comment cho CSS.

Vì CSS không phải là “ngôn ngữ” thân thiện nhất, nếu có comment chi tiết rõ ràng thì bạn sẽ tiết kiệm thời gian khi bạn hoặc ai đó sau này xem lại code của mình.

Tham khảo thêm các vị trí tuyển dụng lập trình CSS lương cao cho bạn

Bài viết gốc được đăng tải tại Freecodecamp

10 tài liệu lập trình Android miễn phí từ cơ bản đến nâng cao

TopDev chọn lọc và giới thiệu các tài liệu lập trình Android miễn phí từ cơ bản đến nâng cao cùng những công cụ dành cho các bạn muốn tìm hiểu và bắt đầu lập trình Android, cũng như muốn nâng cao “tay nghề” và dấn thân vào con đường lập trình Android chuyên nghiệp.

Khám phá Top các vị trí lập trình Android hấp dẫn

  1. Android Programming for Beginners
Đây là tài liệu Android dành cho những người mới sử dụng JAVA và lập trình Android. Tác giả cung cấp hơn 40 ứng dụng nhỏ trong suốt cuốn sách để đi cùng với lời giải thích đơn giản và rõ ràng về các chủ đề. Từ Android Studio đến JAVA đến vòng đời sản phẩm, cuốn sách này bao gồm tất cả các khái niệm cơ bản bạn cần nắm để bắt đầu xây dựng ứng dụng Android đầu tiên của mình. https://topdev.vn/s/LOwqpcUg

2. Head First Android Development

Tuyển tập seri Head First đã mang đến cuốn sách tuyệt vời cho các lập trình viên. Head First Android Development thể hiện cách tiếp cận độc đáo, hướng dẫn bằng hình ảnh để việc học lập trình Android trở nên thú vị và hấp dẫn. Ngay cả đối với người mới bắt đầu làm quen Android, cuốn sách này sẽ giúp bạn nắm bắt cách xây dựng ứng dụng Android đầu tiên của mình một cách nhanh chóng. https://topdev.vn/s/ImtUrCX0

3. The Android Developer’s Cookbook – Building Applications with the Android SDK

Quyển sách dành cho các bạn muốn bắt tay vào xây dựng app trên nền tảng Android. Cập nhật các chương mới về phát triển giao diện người dùng và luồng nâng cao, thanh toán trong ứng dụng,…cùng với các kỹ thuật mới truy cập phần cứng NFC đến sử dụng Google Cloud Messaging. Bạn sẽ học các kỹ thuật thực hành tốt nhất để giải quyết hiệu quả các vấn đề phổ biến và để tránh những cạm bẫy trong toàn bộ vòng đời phát triển https://topdev.vn/s/cZtmq7hu

4. Android Programming: The Big Nerd Ranch Guide (2nd Edition)

Nếu bạn là người đã có kinh nghiệm lập trình JAVA, đây là một cuốn sách tuyệt vời cho việc học lập trình Android. Dựa trên khóa học Android Bootcamp trong một tuần được tài trợ bởi Big Nerd Ranch, cuốn sách này cung cấp những giải thích một cách toàn diện, thực tiễn và súc tích về các khái niệm, API trong lập trình Android.

https://topdev.vn/s/0arpSF9J

5. Android Programming: Pushing the Limits

Đây là một lựa chọn tuyệt vời khác cho lập trình viên Android đang tìm kiếm những thứ “nặng đô” hơn. Cuốn sách nhằm mục đích thúc đẩy các ranh giới lập trình Android với nhiều mẹo, thủ thuật và các kỹ thuật mà đa số chưa biết hoặc chưa được tận dụng. Đây sẽ giúp bạn trở thành lập trình viên Android cao cấp hơn. android-programing

6. Advanced Android Application Development

Đối với lập trình viên Android dày dạn kinh nghiệm đang tìm kiếm các chủ đề nâng cao và chuyên sâu hơn, đây là một lựa chọn tuyệt vời. Cuốn sách cung cấp các kỹ thuật lập trình Android cao cấp để giúp các lập trình viên xây dựng các ứng dụng ở mức chuyên nghiệp hơn. Ngoài ra, Advanced Android Application Development cung cấp cho nhà phát triển một bộ hướng dẫn toàn diện chắc chắn sẽ có ích. https://topdev.vn/s/Hy3s6oPv

7. Raspberry Pi Android Projects

Quyển sách cung cấp các kiến thức quản lý hầu hết các chức năng cơ bản của Raspberry Pi từ điện thoại Android của bạn. Sử dụng các dự án được tạo trong cuốn sách này để phát triển các dự án thú vị hơn nữa trong tương lai. Trải nghiệm học tập dựa trên dự án để giúp bạn khám phá những cách tuyệt vời để kết hợp sức mạnh của Android và Raspberry Pi.

https://topdev.vn/s/Io76Eeju

8. Android Recipes

Trong Android Recipes, bạn sẽ tìm thấy các ví dụ mã trực tiếp. Nó cung cấp lời khuyên thực tế sẽ giúp bạn hoàn thành công việc một cách nhanh chóng và tốt đẹp. Điều này có thể giúp bạn tiết kiệm rất nhiều công việc so với việc tạo một dự án từ đầu! https://topdev.vn/s/6m5t6pHd

9.30+ công cụ phát triển ứng dụng Android chuyên nghiệp ( Phần 1)

Tập hợp một số công cụ tốt nhất để nâng cao hiệu suất phát triển và xây dựng các ứng dụng Android. Những công cụ này đã giúp các nhà lập trình tiết kiệm rất nhiều thời gian quý giá từ cuộc sống hàng ngày và cũng như tạo các ứng dụng tốt hơn và chất lượng được nâng lên đáng kể.

https://topdev.vn/s/3qp9C65s

10. 30+ công cụ phát triển ứng dụng Android chuyên nghiệp ( Phần 2)

Là sự tiếp nối của phần 1, ở phần 2 tác giả đề cập những công cụ tiếp theo nhằm hỗ trợ đắc lực trong nâng cao hiệu suất phát triển và xây dựng các ứng dụng Android

Tìm việc làm các vị trí lập trình Android hấp dẫn tại TopDev.

Cách viết CV giúp lập trình viên ghi điểm với nhà tuyển dụng

Cách viết CV dành cho Software Developer

Một CV tốt, đồng nghĩa với cơ hội bạn được nhà tuyển dụng để mắt đến càng cao. Để nâng cao khả năng trúng tuyển thì một bản CV chuyên dụng của từng ngành là điều không thể thiếu, nhất là đối với ngành đòi hỏi chuyên môn cao như công nghệ thông tin. Để viết CV xin việc IT thì có rất nhiều cách, và một CV IT gây ấn tượng thông thường có các đề mục cơ bản sau:

  1. Thông tin cá nhân: Liệt kê đơn giản họ tên, năm sinh, địa chỉ, email, số ĐT kèm ảnh đại diện nên rõ mặt, nghiêm túc, chất lượng rõ nét, không nên là ảnh selfie. Email cũng cần nghiêm túc và tốt nhất là bằng tên thật của bạn để thể hiện sự chuyên nghiệp.
  2. Mục tiêu nghề nghiệp: Nêu rõ định hướng của bạn trong con đường nghề nghiệp của mình. Hãy tóm tắt trong vòng 2-3 câu mục tiêu ngắn hạn/dài hạn của bạn đối với công việc đang ứng tuyển. Với mục này nhà tuyển dụng có thể đánh giá được phần nào năng lực cũng như tầm nhìn, và quyết định xem có nên đọc tiếp CV của bạn hay không.
  3. Kỹ năng: Liệt kê những gì bạn biết và/hoặc có kinh nghiệm và nêu chính xác mức độ hiểu biết của bạn trong từng mục. Phần này cũng quan trọng không kém trong CV ngành công nghệ thông tin.
  4. Kinh nghiệm làm việc: Tóm tắt, liệt kê những dự án, công ty mà bạn đã từng làm việc, kèm chức vụ và sắp xếp theo trình tự thời gian từ mới nhất đến cũ nhất. Đây có thể nói là phần quan trọng nhất của một CV IT.
  5. Thành tựu: Liệt kê thành tựu cá nhân trong các dự án đó. Thành tựu ở đây có thể là những gì bạn tự hào rằng mình đã làm được cho dự án, những điều bạn nhận ra và cải thiện được cho bản thân nhờ dự án.
  6. Thông tin khác: Bằng cấp, giải thưởng, dự án riêng (nếu có)

Tạo CV IT online, chuẩn ATS miễn phí trên TopDev

Tìm việc chỉ phụ thuộc vào may mắn là một quan niệm sai lầm. Sự chuẩn bị cẩn thận và đánh giá đúng khả năng của bản thân quyết định rất lớn đến việc thành công ứng tuyển của bạn. Để bạn không phải lo lắng quá nhiều về câu hỏi làm sao để có 1 CV IT chuẩn? TopDev đã tạo sẵn cho bạn những mẫu CV IT cực đẹp mà đơn giản kèm theo thông tin đầy đủ theo tiêu chuẩn. Bạn chỉ cần đơn giản là vào công cụ tạo CV online đáng tin cậy của TopDev.

Xem ngay những tin đăng tuyển dụng IT mới nhất trên TopDev

Tổng hợp các mẫu CV IT đẹp và cách viết CV xịn dành cho lập trình viên

Mẫu CV

Tổng hợp các mẫu CV IT đẹp – chuẩn dành cho các bạn lập trình viên Backend, Frontend, Fullstack, iOS, Android, PHP, .NET, Java… từ level Fresher cho đến Senior, Leader.

Giới thiệu

Viết CV luôn là một vấn đề nan giải cho bất kỳ ai. Tuy nhiên bạn chỉ cần dành ra 5 phút để tham khảo các mẫu CV IT chuẩn được TopDev thiết kế dành riêng cho lập trình viên, dựa trên hơn 1.000 mẫu CV Developer nổi bật trên thế giới, ứng tuyển thành công các vị trí tại các tập đoàn công nghệ lớn Microsoft, Google, Amazon…

Bạn sẽ biết cách làm thế nào để có một CV đúng, chuẩn và phù hợp với từng vị trí công việc cụ thể đối với ngành công nghệ thông tin, chọn lọc các thông tin tiêu chuẩn và cấu trúc thống nhất giúp Nhà tuyển dụng dễ dàng đánh giá kinh nghiệm và Tech stack của bạn.

  Cách viết CV giúp lập trình viên ghi điểm với nhà tuyển dụng

Hiện tại các mẫu cv này có hỗ trợ cả tiếng Anh lẫn tiếng Việt với các format chuẩn cho bạn sự lựa chọn tốt nhất.

>>> Xem thêm: Mẫu CV IT tiếng Anh hấp dẫn nhà tuyển dụng

Ngoài ra, việc có CV tốt cũng giúp cho bạn có được nhiều cơ hội tốt hơn trong tương lai, hãy nhớ rằng, trong CV phải luôn có đủ những thông tin sau đây.

  • Thông tin cá nhân: Liệt kê đơn giản họ tên, năm sinh, địa chỉ, email, số điện thoại kèm ảnh đại diện nên rõ mặt, nghiêm túc, chất lượng rõ nét, không nên là ảnh selfie. Email cũng cần nghiêm túc và tốt nhất là bằng tên thật của bạn để thể hiện sự chuyên nghiệp.
  • Mục tiêu nghề nghiệp: Nêu rõ định hướng của bạn trong con đường nghề nghiệp của mình. Hãy tóm tắt trong vòng 2-3 câu mục tiêu ngắn hạn/dài hạn của bạn đối với công việc đang ứng tuyển.

Với mục này nhà tuyển dụng có thể đánh giá được phần nào năng lực cũng như tầm nhìn, và quyết định xem có nên đọc tiếp CV của bạn hay không.

  • Kỹ năng: Liệt kê những gì bạn biết và/hoặc có kinh nghiệm và nêu chính xác mức độ hiểu biết của bạn trong từng mục. Phần này cũng quan trọng không kém trong CV ngành công nghệ thông tin.
  • Kinh nghiệm làm việc: Tóm tắt, liệt kê những dự án, công ty mà bạn đã từng làm việc, kèm chức vụ và sắp xếp theo trình tự thời gian từ mới nhất đến cũ nhất. Đây có thể nói là phần quan trọng nhất của một CV IT. Lưu ý hãy trình bày kinh nghiệm làm việc theo thứ tự thời gian ưu tiên những vị trí gần đây nhất và những kinh nghiệm liên quan đến vị trí mà bạn đang ứng tuyển.
  • Thành tựu/ Dự án: Liệt kê vai trò của cá nhân trong các thành tựu/dự án đó. Thành tựu ở đây có thể là những gì bạn tự hào rằng mình đã làm được cho dự án, những điều bạn nhận ra và cải thiện được cho bản thân nhờ dự án.
  • Thông tin khác: Bằng cấp, giải thưởng, dự án riêng (nếu có)
  5 mẹo và mẫu CV IT để gây ấn tượng với nhà tuyển dụng!

Các mẫu CV trên TopDev

Mẫu tham khảo 1

Mẫu CV IT

Mẫu tham khảo 2

Mẫu CV IT

Mẫu tham khảo 3

cv lập trình viên

Tạo CV IT online, chuẩn ATS miễn phí trên TopDev

Có thể bạn quan tâm:

Xem thêm việc làm ngành it hàng đầu tại TopDev

Lập trình viên tại Việt Nam cần ít nhất 5 năm để hiểu rõ một công nghệ!

                                                                                                       TopDev via Noria

Xem ngay những tin đăng tuyển dụng IT mới nhất trên TopDev

5 mẹo và mẫu CV IT để gây ấn tượng với nhà tuyển dụng!

CV của bạn chỉ có 5 giây để gây ấn tượng với nhà tuyển dụng!

“Rất nhiều CV của ứng viên ngành công nghệ thông tin đều viết quá dài và trình bày không được mạch lạc. Họ luôn cố gắng chia sẻ nhiều thông tin để có một hồ sơ thật ấn tượng. Tuy nhiên, trong một đợt tuyển dụng có rất nhiều CV được gửi về, nhà tuyển dụng chỉ có thời gian rất ngắn lướt qua và lựa chọn hồ sơ phù hợp để cân nhắc tiếp theo. Vì vậy để được nhận lời mời phỏng vấn, những gợi ý và mẫu CV IT dưới đây có thể giúp bạn nhanh chóng lọt vào mắt của nhà tuyển dụng” chia sẻ từ Mr. Xuân Sơn, Product Manager của Tuổi Trẻ Online.

1. ĐỘ DÀI

CV IT của bạn chỉ nên gói gọn trong 1 trang giấy khổ A4, nếu quá trình công tác của bạn lâu năm có thể kéo dài lên thành 2 trang để thể hiện rõ chi tiếp hơn.

2. CẤU TRÚC

Để thuận tiện cho nhà tuyển dụng, hãy đặt phần thông tin liên lạc ở phần đầu của hồ sơ, kế đến là tóm tắt về chuyên môn, những kỹ năng và kinh nghiệm của bạn, cuối cùng là phần học vấn và bằng cấp.  Hiện nay, bạn có thể tham khảo một số mẫu CV IT Tiếng anh và tiếng Việt phổ biến để tiết kiệm thời gian hơn cho việc thiết kế của bạn.

3. NỘI DUNG VÀ TỪ NGỮ

Phần tóm tắt chuyên môn hãy mô tả ngắn gọn về định hướng công việc và vị trí mong muốn sắp tới. Trong mục kỹ năng, chỉ liệt kê từ 4 đến 6 kỹ năng quan trọng nhất mang đến thành công trong công việc của bạn. Khi mô tả về kinh nghiệm làm việc, hãy sử dụng những động từ diễn tả thành công như “đạt được”, “gia tăng”… kèm theo một vài số liệu ấn tượng, điều này giúp bạn nổi bật hơn so với các CV khác.

Tạo CV IT online, chuẩn ATS miễn phí trên TopDev

4. ĐỊNH DẠNG

Hãy định dạng đơn giản và thông dụng nhất, để nhà tuyển dụng dễ dàng thấy được những điểm bạn muốn nhấn mạnh. Tránh dùng nhiều màu sắc, chữ in đậm hoặc viết hoa quá nhiều, chỉ gây khó chịu khi nhìn vào CV của bạn. Những thông tin quan trọng bạn nên đặt ở nữa trên hồ sơ và ở những khu vực nổi bật để gây chú ý nhà tuyển dụng khi họ lia mắt.

5. KIỂM TRA NHIỀU LẦN

Điều này giúp bạn tránh những sai sót lỗi chính tả, và đảm bảo bạn cung cấp đầy đủ thông tin để giới thiệu bản thân và thuyết phục nhà tuyển dụng hẹn bạn một buổi phỏng vấn. Đừng nghĩ tìm việc chỉ phụ thuộc vào may mắn. Sự chuẩn bị cẩn thận và đánh giá đúng khả năng của bản thân quyết định rất lớn đến việc thành công ứng tuyển của bạn. Bạn nên nhớ, nhà tuyển dụng chỉ có 5 giây để quyết định có xem tiếp hay không!

Đừng quên tham khảo nhiều mẫu CV đẹp dành cho lập trình viên, viết xong CV thì mạnh dạn apply vào những công việc IT với mức lương cực hấp dẫn ở TopDev nhé.

THÔNG BÁO THAM DỰ Sự kiện Thương mại điện tử LỚN NHẤT năm 2019!

Tháng 3 này, Hiệp Hội Thương mại điện tử Việt Nam – VECOM sẽ tổ chức sự kiện LỚN NHẤT mở đầu năm 2019:

DIỄN ĐÀN TOÀN CẢNH THƯƠNG MẠI ĐIỆN TỬ VIỆT NAM

Vietnam Online Bussiness Forum 2019 (VOBF 2019)

>>> Xem chi tiết: http://vobfhcm.vecom.vn/

Sự kiện với quy mô toàn quốc, quy tụ cộng đồng thương mại điện tử trong và ngoài nước.

Với sự góp mặt đến từ các đại diện: VECOM, NIELSEN, AMAZON, TIKI, SHOPEE và FACEBOOK, GOOGLE.

Chỉ diễn ra 1 lần duy nhất trong năm, đây là cơ hội lớn cho các doanh nghiệp tiến lên bước mở rộng quy mô với chủ đề “Scaling Up Your Bussiness – Vươn ra toàn cầu”.

Bạn sẽ được nghe và cũng tham gia trao đổi với các chuyên gia hỗ trợ mở rộng quy mô kinh doanh, giúp bạn tiếp cận khách hàng cả nước, hoặc khách hàng toàn cầu.

Nội dung chi tiết, bạn có thể tham khảo tại đây!

200 người đăng ký sớm nhất sẽ được ƯU ĐÃI mức vé EARLY BIRD – Giá thấp nhất

* Thời gian: Ngày 28 Tháng 03 năm 2019, 8.30AM – 5PM
* Địa điểm: Trung tâm Hội nghị Capella Parkview, Số 3 Đặng Văn Sâm, Phường 9, Phú Nhuận, Tp. Hồ Chí Minh

Lưu ý: Số lượng chỗ ngồi có hạn, BTC chỉ ưu tiên giữ chỗ cho những ai đăng ký và hoàn tất thanh toán sớm nhất!

Hotline: 0902940969 – Ms. Thạch

Stateless là gì? Stateful là gì?

stateful vs stateless là gì

Stateless

Trong lập trình web, chúng ta có sự tương tác giữa client với server. Phần mềm gồm 2 thành phần chính: phần mềm và data. Như vậy, một phần mềm được thiết kế theo tương tác client – server thì phần nhiều tập lệnh sẽ nằm phía server. Client có nhiệm vụ gửi dữ liệu lên để xử lý sau đó nhận kết quả trả về. Vậy stateful vs stateless là gì?

Để hiểu khái niệm stateful vs stateless là gì chúng ta cần phải biết rằng, Stateless là thiết kế không lưu dữ liệu của client trên server. Có nghĩa là sau khi client gửi dữ liệu lên server, server thực thi xong, trả kết quả thì “quan hệ” giữa client và server bị “cắt đứt” – server không lưu bất cứ dữ liệu gì của client. Như vậy, khái niệm “trạng thái” ở đây được hiểu là dữ liệu.

  Cách làm HTTPS hoạt động trên local trong 5 phút
  Responsive HTML bằng một dòng CSS

Stateful

Stateful là một thiết kế ngược lại, chúng ta cần server lưu dữ liệu của client, điều đó đồng nghĩa với việc ràng buộc giữa client và server vẫn được giữ sau mỗi request (yêu cầu) của client. Data được lưu lại phía server có thể làm input parameters cho lần kế tiếp.

Tổng kết và ví dụ

HTTP là một Application Protocol dạng stateless, tương tác client-server theo HTTP thì phần server sẽ không lưu lại dữ liệu của client. HTTP ban đầu chỉ được dùng đơn thuần cho website, client gửi request, server nhận request xử lý rồi trả về lại cho client hiển thị. Sau đó thì kết thúc 1 quy trình. Sau này người ta mới bắt đầu nâng cấp cho phép website giống như một ứng dụng stateful bao gồm html, database (mysql, mongodb…), transaction…

Có 4 cách lưu data của client khi xây dựng Web Application bao gồm: URL Rewriter, Form, Cookie, HTTP Session.

TopDev

Có thể bạn muốn đọc thêm:

Xem thêm IT Jobs for Developer hấp dẫn lương cao tại TopDev!

Kinh nghiệm xương máu sau 9 tháng làm Kỹ sư phần mềm (Phần 1)

kinh-nghiem-xuong-mau-sau-9-thang-lam-ky-su-phan-mem-phan-1

Tác giả: Benjamin Schachter

Sau 9 tháng làm việc tại Dexter với tư cách là một lập trình viên, tôi đã học được rất nhiều điều.  Cũng vì vậy, tôi đã quyết định viết một bài đăng blog chia sẽ về những trải nghiệm, cũng như một bài đăng một bài kĩ thuật về Self Positioning React Component mà tôi đã thực hiện trong vài tháng làm việc tại đây. Nhận được công việc mới chỉ là bước khởi đầu, làm thật tốt công việc được giao lại là một câu chuyện hoàn toàn khác, là một lập trình viên tôi hoàn toàn hiểu điều đó.

Có thể bạn muốn xem thêm:

  Cách tôi nhân rộng một dự án trị giá 86 triệu đô la chỉ với 57 dòng code!

  Đi phỏng vấn vị trí React Native cần trang bị những gì?

Những suy nghĩ của tôi về vai trò của mình đã thay đổi đáng kể kể từ khi tôi bắt đầu. Tôi đã nghĩ rằng trở thành một nhà phát triển phần mềm thì phải “xử lý” đám code càng nhanh càng tốt. Tuy nhiên, điều đó có vẻ không đúng thực tế lắm, như chuyện bạn viết code nhanh nhưng không chất lượng, buộc phải thay đổi liên tục sẽ không phải là cách giúp cho doanh nghiệp của bản có thể bị trì trệ hơn. May mắn thay, tôi đã gặp một người sếp có suy nghĩ tương tự, anh ấy cũng là dân phần mềm như tôi.

Mục tiêu của bạn sẽ là: Viết code chất lượng tốt và giao tiếp tốt với đồng nghiệp. Bạn không phải được trả tiền chỉ để code, bạn được trả tiền để suy nghĩ và tìm ra vấn đề. Sản phẩm đi kèm là tư duy tinh tể và chỉ dẫn máy làm theo dưới dạng code. Tôi muốn giải quyết vấn đề trong một dòng code dễ đọc hơn 10 dòng code khó hiểu. Tôi muốn giải quyết vấn đề trong 5 dòng code có thể đọc được so với một dòng code phức tạp, nested code kèm theo nhiều loại toán tử phức tạp. Tôi nghĩ bạn sẽ hiểu ý tôi muốn nói gì.

Muốn biết phải hỏi muốn giỏi phải học.

Những ngày đầu, tôi đã được sếp gửi cho một đường link để tham khảo, sau khi đọc tôi thật sự lo lắng về khả năng của mình. Tôi vẫn luôn rất ý thức trước khi đặt bất kỳ câu hỏi nào.

Tôi có hẳn riêng mình một check list trước khi nhờ người khác hỗ trợ điều gì:

  • Đây có phải là một câu hỏi mà tôi đã hỏi trước đó, và nếu có, tôi đã ghi lại câu trả lời ở đâu?
  • Đây có phải là điều tôi có thể Google không?
  • Điều này đã được ghi chép lại ở đâu đó trong nội bộ?
  • Chuyện gì đang xảy ra ở đây? Nguyên nhân sâu xa của lỗi hoặc các phản ứng bất ngờ mà tôi đang gặp phải là gì?
  • Tôi có thực sự hiểu câu hỏi tôi đang cố gắng để trả lời? Bạn có thể dành thời gian để đọc lại vấn đề một lần nữa thay vì đưa ra câu trả lời nửa vời hoặc câu trả lời hấp tấp.

Sau khi làm theo các bước này, tôi sẽ thử giải quyết vấn đề một mình, tìm một giải pháp đã được từng được document lại, hoặc hỏi một câu hỏi với ngữ cảnh tốt hơn và chi tiết hơn để giúp người khác dễ dàng trả lời hơn. Thậm chí, tốt hơn nữa, nếu tôi có thể đặt một câu hỏi hay và được trả lời qua đoạn chat, đồng đội của tôi không cần phải bỏ mọi thứ để giúp tôi.

Nếu tôi đã đi hết 90% con đường để giải quyết vấn đề thì thật ra chỉ cần 10% sự trợ giúp nữa thôi, một nhà phát triển cấp cao sẽ rất vui khi giúp bạn vì biết rằng bạn đã cố gắng hết mức có thể. Tìm kiếm người khác để giải quyết vấn đề của bạn không phải là một cách tuyệt vời để xây dựng lòng tin trong nhóm của bạn.

Những người thông minh thích những câu hỏi hay – vì vậy đừng ngại khi hỏi họ.

Video: Trọn bộ các chủ đề nâng cao của DevOps

Tránh những sai lầm cũ và hỏi những câu hỏi mà ai cũng đã biết câu trả lời.

Điều này nói dễ hơn là thực hiện, bạn có thể ứng dụng nó vào bất cứ ngành nào, không chỉ lập trình. Rất nhiều khái niệm và thông tin mới sẽ khiến mắc phải những sai lầm không đáng có, và là không thể tránh khỏi. Để hạn chế việc này, hãy nghiên cứu nhiều hơn, Google có đầy đủ thông tin. Xem kĩ các tài liệu, chúng là bạn của bạn. Hãy hỏi nếu như sau khi tìm hiểu bạn vẫn không có trả lời. Sau đó hãy viết chúng ra thành tài liệu, và đặt cho mình những mục tiêu để cải thiện nó.

Hãy đảm bảo rằng lần tiếp theo bạn gặp vấn đề tương tự, bạn biết phải làm gì. Tất cả chúng ta đều mắc sai lầm, nhưng việc tự ý thức và nỗ lực thay đổi là cách để mọi người trở nên tốt hơn.

Luôn xem lại những gì mình đã làm

Không ai thích đi qua PR và bảo bạn gỡ bỏ console.logs,  debuggers hoặc bảo bạn sửa lỗi linting. Tôi sẽ không xuất bản bài đăng này mà không đọc qua một vài lần và nhờ một người bạn xem qua nó trước.

Nhìn kĩ vào code của bạn và tự hỏi những câu hỏi sau:

  • Tôi đã viết một đoạn logic phức tạp. Có chức năng tương tự nào trong ứng dụng có thể giải quyết vấn đề này theo cách dễ đọc hơn, linh hoạt hơn hay chung chung không?
  • Nếu không, tôi có nhớ lý do tại sao tôi đã viết code này trong một tuần hay không? Nếu câu trả lời là không, tôi muốn đổi code hoặc bình luận nó. Người duyệt PR nên có một số lí do giải thích tại sao tôi lại đưa ra quyết định đó.
  • Hãy chắc chắn rằng code của bạn đang đi qua linting và được kiểm tra trước khi đưa nó cho bất kì ai khác.
  • Tôi có đang lặp lại hay không? Tôi có thể làm gọn logic bị lặp bằng một hàm hay không ?
  • Nếu đây là code của người khác mà tôi đang xem, tôi sẽ đưa ra những bình luận nào? Tôi muốn thay đổi gì để nó rõ ràng hơn?

Hãy thử nhìn vào code của bạn vào ngày hôm sau, bạn sẽ nhìn thấy rất nhiều sự khác biệt. Liệu có gì không ổn trong logic của các đoạn code? Liệu các component của bạn có xử lý logic đúng hay không?

Ngoài ra, việc tự đánh giá code tốt tiết kiệm thời gian và tiền bạc cho công ty. Đó là cách tốt nhất để bạn tìm ra bug của bạn và tự khắc phục chúng thay vì phải nhờ người khác tìm ra chúng sau hai ngày.

Điều cuối cùng nói về việc xem lại code của bạn. Test tất cả vào MỌI THỨ bạn đã làm. Tôi muốn code của tôi gửi cho bất cứ ai cũng phải tốt đến mức không thể chỉnh sửa. Nếu họ bấm vào một trang mới và thấy được một lỗi lớn hoặc màn hình trắng, điều đó chứng minh tôi đã không thực sự xem xét lại công việc của mình. Grep cho code bạn đã chỉnh sửa và đảm bảo rằng bạn không làm hỏng bất cứ thứ gì bằng việc bổ sung thêm vào component.

Nghe có vẻ ngớ ngẩn, nhưng những sản phẩm có quá nhiều dòng code lớn phức tạp và bạn có thể không nhận ra cho đến khi bạn đã làm hỏng một cái gì đó.

Tôi chắc rằng, bạn sẽ không muốn xem bản draft đầu tiên của bài blog này 🙂

Bài viết gốc được đăng tải tại Medium

Cách tôi nhân rộng một dự án trị giá 86 triệu đô la chỉ với 57 dòng code!

Tác giả: Tait Brown

Cảnh sát Victoria là cơ quan thực thi pháp luật chính của Victoria, Úc. Với hơn 16.000 xe bị mất cắp ở Victoria trong năm qua – tổn thất khoảng 170 triệu đô – cơ quan này đang thử nghiệm nhiều giải pháp kỹ thuật nhằm giải quyết nạn trộm xe. Họ gọi open source project – hệ thống này là BlueNet.

Để giúp ngăn chặn việc bán xe ăn cắp, đã có một dịch vụ dựa trên web tên VicRoads để kiểm tra tình trạng đăng ký xe. Bộ cũng đã đầu tư vào một máy quét tấm giấy cố định – một camera cố định quét qua lưu lượng để tự động xác định các xe bị đánh cắp.

Đừng hỏi tôi tại sao, nhưng một buổi chiều tôi đã có mong muốn mẫu thử nghiệm một chiếc máy quét nhãn đĩa xe gắn máy sẽ tự động thông báo cho bạn nếu một chiếc xe đã bị đánh cắp hoặc đã không đăng ký. Hiểu rằng những thành phần cá nhân này tồn tại, tôi tự hỏi làm thế nào để kết hợp chúng với nhau khó khăn đến thế nào.

Cảnh sát Victoria vừa mới tung ra bản thử nghiệm về một phần mềm tương tự, và chi phí phát hành ước tính đã ở đâu đó trong khoảng 86.000.000 đô. Một người bình luận đã chỉ ra rằng với 86 triệu đô để trang bị cho 220 chiếc xe thì sẽ tính ra trung bình 390,909 đô mỗi chiếc.

Chắc chắn chúng ta có thể làm tốt hơn một chút.

Các tiêu chí làm nên thành công

Trước khi bắt đầu, tôi lên kế hoạch về yêu cầu chính cho thiết kế sản phẩm:

#1: Việc xử lý hình ảnh phải được thực hiện local

Streaming video trực tiếp đến nơi xử lí trung tâm dường như là cách tiếp cận hiệu quả nhất để giải quyết vấn đề này. Bên cạnh tuyệt vời cho lưu lượng truy cập dữ liệu, bạn cũng giới thiệu độ trễ mạng vào một quá trình có thể đã được khá chậm.

Mặc dù một thuật toán về centralized machine learning chỉ có thể có được sự chính xác hơn theo thời gian, chúng ta muốn tìm hiểu nếu một local thực hiện trên thiết bị sẽ là “đủ tốt”.

#2: Phải làm việc với hình ảnh có chất lượng thấp

Bởi vì tôi không có Raspberry Pi hoặc webcam USB nên tôi sẽ sử dụng footage của dashcam – nó có sẵn và là nguồn lý tưởng cho dữ liệu mẫu. Giống như added bonus, dashcam video đại diện cho chất lượng chung của footage mà bạn mong muốn từ các camera gắn trên xe.

#3: Cần được xây dựng dựa trên công nghệ mã nguồn mở 

Dựa trên một phần mềm độc quyền có nghĩa là bạn sẽ get stung mỗi khi bạn yêu cầu thay đổi hoặc tăng cường – và tiếp tục stinging cho mỗi yêu cầu được thực hiện sau đó. Sử dụng công nghệ mã nguồn mở là không cần suy nghĩ.

Relying upon a proprietary software means you’ll get stung every time you request a change or enhancement — and the stinging will continue for every request made thereafter. Using open source technology is a no-brainer.

Kết luận

Ở level cao, giải pháp của tôi lấy một hình ảnh từ dashcam video, đưa nó qua qua một hệ thống nhận dạng tấm giấy phép mã nguồn mở được cài đặt cục bộ trên thiết bị, truy vấn dịch vụ kiểm tra đăng ký, và sau đó trả về kết quả.

Dữ liệu được trả lại cho thiết bị được cài đặt trong xe thực thi pháp luật bao gồm xe và mô hình (mà nó chỉ sử dụng để xác minh các xe đã bị đánh cắp), tình trạng đăng ký và bất kỳ thông báo nào của chiếc xe được báo cáo bị đánh cắp.

Nghe có vẻ đơn giản, đó là bởi vì nó thực sự là. Ví dụ, xử lý hình ảnh tất cả có thể được xử lý bởi thư viện openalpr.

Tất cả được dùng để nhận diện các ký tự trên license plate:

openalpr.IdentifyLicense(imagePath, function (error, output) {
   // handle result
});

Đây là những điều của tôi chứng minh khái niệm như sau:

// Open form and submit enquire for `rego`
function getInfo(rego) {
	horseman
	  .userAgent('Mozilla/5.0 (Windows NT 6.1; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0')
	  .open(url)
	  .type('#registration-number-ctrl input[type=text]', rego)
	  .click('.btn-holder input')
	  .waitForSelector('.ctrl-holder.ctrl-readonly')
	  .html()
	  .then(function(body) {
	  	console.log(processInfo(body, rego));
	    return horseman.close();
	  });
}

// Scrape the results for key info
function processInfo(html, rego) {
	var $ = cheerio.load(html);
	var vehicle = $('label.label').filter(function() {
	  return $(this).text().trim() === 'Vehicle:';
	}).next().text().trim();

	var stolen = $('label.label').filter(function() {
	  return $(this).text().trim() === 'Stolen status:';
	}).next().text().trim();

	var registration = $('label.label').filter(function() {
	  return $(this).text().trim() === 'Registration status & expiry date:';
	}).next().text().trim();

	return {
		rego,
		vehicle,
		stolen,
		registration
	};
}

Kết quả

Sự công nhận về giấy phép mã nguồn mở được không được công nhận. Ngoài ra, các thuật toán nhận dạng hình ảnh có thể không được tối ưu hóa cho tấm giấy phép của Úc.

Giải pháp đã có thể nhận diện tấm giấy phép trong một lĩnh vực rộng.

Các chú thích được thêm vào có hiệu lực. Biển số được nhận dạng mặc dù reflection và lens distortion.

Mặc dù, giải pháp này đôi khi gặp vấn đề với các chữ cái trên biển số.

Sai lầm trên biển số, nhầm M thành H

Nhưng … các giải pháp cuối cùng sẽ có được sự chính xác.

Một vài khung hình sau đó, M được xác định chính xác và ở mức độ tin cậy cao hơn

Như bạn thấy trong hai hình ảnh trên, việc xử lý hình ảnh một vài khung hình sau đó đã nhảy lên từ mức độ tự tin là 87% đối với tóc trên 91%.

Với tính chính xác có thể được cải thiện bằng cách tăng tỷ lệ mẫu và sau đó sắp xếp theo thứ hạng độ tin cậy cao nhất. Ngoài ra, một ngưỡng có thể được đặt chỉ chấp nhận sự tự tin lớn hơn 90% trước khi tiếp tục xác nhận số đăng ký.

Đây là các bản sửa lỗi code đầu tiên và không loại trừ việc đào tạo phần mềm nhận dạng license plate với bộ dữ liệu cục bộ.

Còn về 86,000,000 đô

Để công bằng, tôi hoàn toàn không biết có bao nhiêu con số 86 triệu đô – và tôi cũng không thể nói về tính chính xác của công cụ mã nguồn mở của tôi mà không cần đào tạo so với hệ thống BlueNet.

Tôi hy vọng phần của ngân sách đó bao gồm việc thay thế một số cơ sở dữ liệu và các ứng dụng phần mềm để hỗ trợ truy vấn các cấp phép có tần số cao, độ trễ thấp trên mỗi xe.

Mặt khác, chi phí khoảng 391k đô mỗi xe dường như khá tốn kém – đặc biệt nếu BlueNet không chính xác và không có các dự án công nghệ thông tin quy mô lớn để ngừng hoạt động hoặc nâng cấp các hệ thống bị phụ thuộc.

Các ứng dụng tương lai

Mặc dù rất dễ bị cuốn vào bản chất Orwellian của một mạng lưới những người tán thành tấm giấy phép luôn có nhiều ứng dụng tích cực của công nghệ này. Hãy tưởng tượng một hệ thống thụ động quét người cùng đi xe và tự động cảnh báo các nhà chức trách và thành viên gia đình đến vị trí hiện tại của họ.

Xe của Teslas đã được làm đầy bằng camera và cảm biến với khả năng nhận được các bản cập nhật OTA – hãy tưởng tượng biến những chiếc xe này thành một đội quân tốt. Các tài xế của Ubers và Lyft cũng có thể được trang bị các thiết bị này để tăng đáng kể phạm vi bảo hiểm.

Sử dụng công nghệ mã nguồn mở và các component hiện có, có vẻ như có thể cung cấp một giải pháp cung cấp một tỷ lệ lợi nhuận cao hơn nhiều – cho một khoản đầu tư ít hơn 86 triệu đô.

Có thể bạn muốn xem:

  Top 5 công cụ mã nguồn mở dành cho MySQL administrator
  Những lập trình viên phiên bản X-men: Những code project "dị" nhất trên GitHub

Bài viết gốc được đăng tải tại Freecodecamp

AI đi vào thực tiễn (Phần 2): Trí tuệ nhân tạo đang xâm nhập và định hình tương lai

Ngày nay, chúng ta đã quen thuộc với các khái niệm như Trí tuệ nhân tạo (artificial intelligence). Điển hình như các gợi ý, đề xuất sách và phim trên Amazon và Netflix hay danh sách phát nhạc trên Spotify, Youtube, … Nhưng năm 2019 sẽ mang lại cho chúng ta những gì có thể xem là “siêu cá nhân hóa” (hyper-personalization) cho người tiêu dùng thông qua một loạt các ứng dụng về sức khỏe, tài chính, mua sắm và mọi thứ liên quan. Điều này là một bước tiến mới của các ứng dụng AI khi khả năng dự đoán chính xác ngày càng tăng cao, ít tốn chi phí hơn cho dù dữ liệu cá nhân người dùng thì ngày càng dày đặc. Dưới đây là một số lĩnh vực mà chúng ta có thể mong đợi “siêu cá nhân hóa” dựa trên AI vào năm nay.

1. Thể dục thể chất (Physical Fitness)

Sức khỏe là một trong những lĩnh vực hot của các ứng dụng AI. Các ứng dụng thể dục và thiết bị đeo như Fitbit và Apple Watch và thậm chí một số tai nghe VR có thể tự động thu thập dữ liệu của người dùng – chẳng hạn như bạn đã đi bao nhiêu bước hay leo bao nhiêu nấc thang – và tổng hợp với tất cả các dữ liệu của toàn bộ người dùng để tạo ra hồ sơ thể hình riêng biệt dành cho từng người và lên các kế hoạch theo đó, giống như bộ điều chỉnh nhiệt thông minh Nest sử dụng dữ liệu từ khắp các gia đình để tối ưu hóa các kế hoạch sử dụng năng lượng. Những gợi ý như “Giảm lượng thức ăn của bạn 210 calo / ngày để tăng tuổi thọ thêm 1,5 năm” sẽ không còn xa.

Physical Fitness

Bên cạnh đó, vấn đề thường xuyên ở hầu hết các ứng dụng tập thể dục – sức khỏe – thể hình là việc người dùng mệt mỏi khi nhập các loại thực phẩm họ đã ăn – hoặc gian lận và chọn không nhập chúng. Điều đó dẫn đến nếu không có dữ liệu chính xác thì các phân tích là vô nghĩa. Tuy nhiên, AI đã có giải pháp khắc phục: Khi AI trở nên tiên tiến hơn, bạn chỉ cần chụp ảnh những gì bạn ăn hoặc ứng dụng sẽ theo dõi lượng thức ăn thông qua camera của smartphone và tự động hóa quy trình thu thập dữ liệu. Một số ứng dụng như Lose It đã sử dụng công nghệ này.

2. Sức khỏe tâm thần (Mental Heath)

*Là một trạng thái không chỉ không có rối loạn hay dị tật tâm thần mà còn là một trạng thái tâm thần hoàn toàn thoải mái, cần phải có chất lượng nuôi sống tốt, có được sự cân bằng và hòa hợp giữa cá nhân, người xung quanh và môi trường xã hội.

Bạn có muốn biết thời gian nào trong ngày là lúc tâm trạng bạn suy sụp nhất? Hay cách mà mọi người tác động đến bạn sẽ có phản ứng với mức năng lượng như thế nào? Sẽ sớm có một ứng dụng cho điều đó. Các ứng dụng có thể tự thực hiện cảm biến, sử dụng cảm biến sinh học để theo dõi nhịp tim / nhịp thở, phản ứng ngoài da và các chỉ số khác để đánh giá phản ứng của chúng ta đối với mọi thứ từ buổi sáng đi làm đến bữa tối với luật pháp. Sau đó, chúng ta có thể sử dụng các báo cáo toàn diện được tạo ra cho lối sống thoải mái hơn, ít căng thẳng hơn, hoặc ít nhất là nhận thức được con người và các tình huống có nhiều khả năng làm cho lòng bàn tay đổ mồ hôi.

Mental Heath

Công nghệ nhận dạng khuôn mặt dựa trên AI cũng có ứng dụng rộng rãi trong sức khỏe tâm thần. Affectiva, một doanh nghiệp xuất phát từ MIT, cung cấp các sản phẩm ứng dụng “kích hoạt cảm xúc” bằng cách đọc và phân tích biểu cảm khuôn mặt với độ chính xác tốt hơn con người. Vì vậy, các ứng dụng sẽ đánh giá mức độ căng thẳng hoặc các triệu chứng lo âu / trầm cảm của bạn và đưa ra gợi ý phù hợp: “Thực hiện hai phút thở sâu” hoặc “Hẹn gặp bác sĩ trị liệu”. Công ty cũng đã phát triển một sản phẩm trong xe hơi để đánh giá trạng thái cảm xúc trong khi lái xe từ biểu cảm khuôn mặt và giọng nói, cải thiện an toàn khi trên đường.

  AI đi vào thực tiễn (Phần 1): Những biến động của năm 2018

3. Tài chính cá nhân (Personal Finance)

Có rất nhiều câu hỏi đặt ra trong lĩnh vực này: Làm thế nào để chúng ta biết những khoản đầu tư nào phù hợp nhất với sự tăng trưởng, rủi ro và nhu cầu thanh khoản của từng cá nhân? Và khi nào nên đầu tư vào chúng? Và đầu tư bao nhiêu? Và khi nào bán?

Câu trả lời ngắn gọn: “Chúng tôi không rõ. Nhưng AI thì có thể”

Các ứng dụng AI sẽ ngày càng cải thiện khả năng dự đoán rủi ro của chúng ta và các tính năng của hồ sơ đầu tư khác bằng cách “đọc giữa các dòng” theo nghĩa đen. Các nhà nghiên cứu đã phát triển một thuật toán deep learning có thể phân tích lời nói hoặc văn bản để đánh giá mức độ tin cậy của người nói / người viết về bất kỳ điều gì về hiệu quả của sản phẩm nào đó và xác định tính chính xác của các dự báo thu nhập được.

Personal Finance

Một ứng dụng tài chính cá nhân kết hợp công nghệ như vậy có thể yêu cầu bạn viết phản hồi cho một số tình huống chấp nhận rủi ro khác nhau, sau đó đánh giá mức độ tự tin của bạn về mỗi kế hoạch để tạo ra một kế hoạch đầu tư được cá nhân hóa cao trong ngắn hạn hoặc dài hạn. Trên thực tế, công ty Narrative Science ở giai đoạn đầu đang sử dụng xử lý ngôn ngữ tự nhiên để phát triển các ứng dụng loại này và có khả năng giúp người dùng hiểu được mức độ chịu đựng của họ đối với các lớp đầu tư như cổ phiếu hòa vốn.

4. Mua sắm (Shopping)

Là người mua sắm, chúng ta muốn được cá nhân hóa trong các lựa chọn của mình, từ phụ kiện, quần áo cho đến ô tô; nhưng bên cạnh đó tôi cũng muốn mua những thứ mà những người giống tôi mua hoặc những thứ tôi muốn giống như vậy. Các ứng dụng AI tập trung vào bán lẻ có thể giúp xác định sở thích sản phẩm của chúng ta bằng cách không chỉ nhìn vào các hành vi mua hàng trong quá khứ, mà cả những người giống chúng ta, để đưa ra các đề xuất dựa trên hình thức đám đông hậu trường (behind-the-scenes crowdsourcing). Và điều này đã xảy ra khi Amazon đã tổng hợp dữ liệu cá nhân để phát triển các đề xuất sản phẩm và các dự đoán khác trong một thời gian trước đó. Nhưng cụ thể nó sẽ xuất hiện vào năm 2019 – trên các ngành và loại sản phẩm – và mức độ cá nhân hóa để mong đợi sẽ tăng lên.

Shopping

Nhận dạng khuôn mặt nâng cao đồng nghĩa với việc trải nghiệm tại cửa hàng được cá nhân hóa hơn nhiều. Ví dụ, các nhà bán lẻ sẽ có thể nhận ra các khách hàng của chương trình khách hàng thân thiết đã bước vào và đẩy các giao dịch, các đề xuất được cá nhân hóa cho họ thông qua điện thoại của họ, cùng với việc cung cấp dịch vụ ở mức cao hơn (như tư vấn mua sắm). Nhận dạng khuôn mặt cũng sẽ cho phép doanh nghiệp phân tích phong cách mua sắm của khách hàng hoặc phân khúc hoặc đường dẫn trong cửa hàng chặt chẽ hơn.

Nhìn chung, siêu cá nhân hóa dựa trên AI mang lại lợi ích tiềm năng rất lớn nhưng nó cũng kèm theo một lời cảnh báo: Các doanh nghiệp càng sở hữu nhiều dữ liệu cá nhân của chúng ta, nguy cơ lạm dụng và mất cắp dữ liệu càng tiềm ẩn. Tuy nhiên, các vấn đề bảo mật đang được đẩy mạnh cùng với AI để nhằm đảm bảo độ an toàn và sự trải nghiệm để giúp cuộc sống chúng ta ngày một tốt hơn. Hiện nay TopDev đang có sự kiện “AI và NLP – Ứng dụng thực tiễn và cách bắt đầu” được mở rộng hơn từ chuỗi sự kiện “AI – Ứng dụng thực tiễn và cách bắt đầu” với nhiều cái nhìn đa chiều về AI và NLP trong thực tế xung quanh chúng ta.

AI&NLP

THÔNG TIN CHI TIẾT

Thời gian & địa điểm: 17:30 – 21:00 ngày 19/03/2019

Link đăng ký: https://meetup.vn/e/EL2?src=a

ĐỘC GIẢ TOPDEV DÙNG NGAY CODE MARCH2019 ĐỂ NHẬN ƯU ĐÃI 50.000Đ CHO SỰ KIỆN TRÊN.

AI đi vào thực tiễn (Phần 1): Những biến động của năm 2018

Năm 2018 có thể coi là một dấu mốc lớn trong lĩnh vực trí tuệ nhân tạo (AI), dù có cả những nốt thăng và trầm.

Các tin đáng chú ý về AI trong năm 2018

Xe tự động: Bất kể những diễn biến không mấy lạc quan, rõ ràng năm 2018 là một năm quan trọng trong việc thúc đẩy ngành công nghiệp sản xuất xe tự vận hành đi lên. Hàng loạt cuộc thử nghiệm được tiến hành của Uber, Waymo (của Google), Lyft cùng nhiều công ty khác được diễn ra và đã có những thành quả đầu tiên sau giai đoạn kiện tụng về quyền sở hữu trí tuệ giữa các ông lớn. Có thể nói xe tự lái đã chính thức có mặt dù vẫn còn nhiều vấn đề phải giải quyết trước khi các phương tiện có khả năng tự chủ hoàn toàn.

Xe tự động

Trợ lý ảo, ứng dụng tại các doanh nghiệp: Các thiết bị tích hợp trợ lý “ảo” và được điều hành thông qua giọng nói đã được đầu tư về hình ảnh, tính năng một cách đáng kể và có một vị trí lớn trong xu hướng AI trong năm 2018. Các trợ lý ảo có thể gọi điện, trò chuyện với người thật hoàn hảo tới mức không thể hình dung là họ đang nói chuyện với máy. Chatbot trở nên phổ biến tại lĩnh vực dịch vụ chăm sóc khách hàng, trong khi những công nghệ tự quản lý thông tin tích hợp AI đã dần hoàn thiện hơn. Nhiều công ty cũng đang nỗ lực đưa ra những bước tiến mới trong quá trình xử lý ngôn ngữ tự nhiên, giúp doanh nghiệp có thể tìm hiểu rõ hơn các luồng dữ liệu phi cấu trúc của họ. Hoạt động marketing có sử dụng hỗ trợ từ AI cũng là một trong những xu hướng nổi bật trong năm 2018, với khái niệm siêu cá nhân hóa được chú ý hơn khi các công ty nhận ra lợi ích của AI trong hoạt động này.

 

trợ lý ảo

Chương trình AI cấp quốc gia: Rất nhiều nước trên thế giới đều đẩy mạnh và xem việc phát triển AI là một trong những nhiệm vụ hàng đầu. Điển hình như lộ trình ba bước của Trung Quốc với nguồn quỹ đầu tư 5 tỷ USD cùng một công viên công nghệ trị giá 2,1 tỷ USD để tạo điều kiện phát triển công nghệ, Mỹ và Pháp với kế hoạch đầu tư lần lượt là 2 tỷ USD và 1,5 tỷ Euro cho các sáng kiến liên quan đến AI, …

Chương trình AI cấp quốc gia

Riêng ở Việt Nam, “Kế hoạch phát triển trí tuệ nhân tạo ở Việt Nam đến năm 2025” đã được ban hành và sẽ tập trung phát triển các công nghệ nền tảng về xử lý và nhận dạng âm thanh, tiếng nói, hình ảnh, thị giác máy, tích hợp dữ liệu, bản đồ số, quản lý dây chuyền sản xuất. Nhiều trường đại học cũng đã bổ sung và cập nhật các kiến thức liên quan đến AI vào chương trình đào tạo cũng như một số trường đào tạo miễn phí ngành Robot và Trí tuệ nhân tạo cho sinh viên.

Bên cạnh đó, các mảng khác của trí tuệ nhân tạo cũng thu được những kết quả rất tích cực: nhận diện khuôn mặt ngày càng phát triển giúp tăng cường khả năng bảo mật và quản lý, quá trình tự động hóa robot (RPA) và học máy ngày càng được đẩy mạnh, các nguồn vốn được rót vào cũng như bộ luật AI được ban hành giúp cho AI ngày càng lan rộng và phát triển mạnh mẽ hơn.

AI một trong những công nghệ đang xâm nhập vào thực tiễn, góp phần định hướng tương lai đang tạo ra nhiều cơ hội cho các doanh nghiệp trong thời kỳ chuyển đổi số. Nhưng để đi đến những điều lớn lao, AI là gì và đang được ứng dụng xung quanh chúng ta như thế nào? bạn nên nắm rõ. Một trong những tech-meetup đáng chú ý vào tháng 03/2019 là “AI và NLP – Ứng dụng thực tiễn và cách bắt đầu”

Độc giả TopDev Blog có thể tham gia sự kiện theo thông tin bên dưới:

AI&NLP

Persol – Quy mô tầm cỡ và cơ hội rộng mở chào đón Technical Project Manager

Persol - Quy mô tầm cỡ và cơ hội rộng mở chào đón Technical Project Manager

Với sự lớn mạnh hơn 90 công ty trong và ngoài nước, Persol vẫn đang tiếp tục phát triển thêm nguồn lực để mở rộng quy mô. Với Persol, con người chính là nền tảng, mỗi cá nhân cần được đầu tư cho sự phát triển. Đó cũng là lý do tại sao môi trường việc làm tại Persol lại có thể thu hút được nhiều nhân tài.

Persol: “Work and Smile”!

Thương hiệu Persol có từ tháng 7 năm 2016, đến nay tập đoàn Persol đã lớn mạnh với hơn 90 công ty trong và ngoài nước, tham gia vào nhiều lĩnh vực: cung cấp các giải pháp về System, Outsourcing, Product, Global team system và Tư vấn công nghệ. Là một đối tác phát triển cá nhân, Tập đoàn Persol hoạt động để rút ra tiềm năng cá nhân và tối đa hóa các cơ hội sống. Persol mong muốn giúp tạo ra một xã hội trong đó tất cả những người làm việc có thể trải nghiệm niềm vui của Tagline nhóm của Persol: “Work and Smile”.

Song song với việc phát triển quy mô, Persol luôn luôn chú trọng đến phát triển nhân lực và thu hút nguồn lao động chất lượng. Bởi vì, Persol bắt nguồn từ “Person- con người” với tâm niệm: sự phát triển của mỗi cá nhân chính là cốt lõi sự phát triển của công ty.

5 yếu tố tạo nên giá trị của Persol Process & Technology Vietnam:

➤ Your job – your joy: niềm vui đến từ chính công việc bạn đang làm

➤ Work & smile: hăng say lao động và thư giãn khi cần

➤ When you like your work everyday is a holiday: được làm điều mình thích mỗi ngày, đó là sự tận hưởng.

➤ Stay hungry- Stay foolish: hãy cứ khao khát, hãy cứ dại khờ

➤ Hiring the right person: tuyển đúng người, dùng đúng lúc.

Persol không ngừng chiêu mộ nhân tài với nhiều đãi ngộ xứng đáng cả vật chất lẫn tinh thần. Đặc biệt hiện tại, Persol đang mở ra cơ hội cho những tài năng muốn thử sức ở vai trò lãnh đạo – vị trí Technical Project Manager (PHP, Java, C#) với mức lương lên đến 3,000 USD.

Persol Việt Nam tìm bạn đồng hành, vạch lối đi mới cho nền công nghệ Việt Nam

Persol luôn mong mỏi kết nối được với những nhà quản lý tài năng, cùng theo đuổi đam mê, cùng gặt hái cái kết ngọt cho những người trẻ đầy ý chí. Technical Project Manager (PHP, Java, C#) chính là tấm vé mà Persol muốn gửi đến các kỹ sư công nghệ tại Việt Nam với những quyền lợi đặc biệt:

  • Mức lương hậu hĩnh lên tới $3,000;
  • Được review hiệu quả công việc nâng mức thu nhập 2 lần/ năm;
  • Nghỉ phép thả ga tận 24 ngày lương vẫn về đều đặn;
  • Luôn có cà phê, trà và bia miễn phí giải tỏa “cơn say” công việc;
  • Có đãi ngộ riêng dành cho nhân viên có trẻ nhỏ;
  • Được chi thoải mái trong những ngày đặc biệt của nhân viên và gia đình;Vi vu du ngoạn với company trip, team building hàng tháng;
  • Được làm việc với đối tác người Nhật, rèn luyện kỹ năng chuyên môn trong môi trường toàn cầu;
  • Nhận sự hỗ trợ nhiệt tình từ đồng nghiệp, kề vai sát cánh chinh chiến các dự án lớn nhỏ.

Tìm việc làm Project Manager tại các doanh nghiệp hàng đầu trên TopDev

Mô tả công việc:

  • Làm việc trực tiếp với khách hàng Nhật Bản;
  • Thành thạo tiếng Nhật để hiểu rõ những yêu cầu từ phía khách hàng;
  • Quản lý thay đổi phạm vi dự án, tiến độ dự án;
  • Quản lý lịch trình để đảm bảo công việc được xác định, phân công và hoàn thành đúng hạn;
  • Phân tích, làm rõ và chuyển giao yêu cầu cho các thành viên;
  • Tham gia vào việc design documents, test cases.

Yêu cầu:

  • Bằng N2 tiếng Nhật;
  • Kỹ năng viết và giao tiếp Tiếng Anh tốt;
  • Ít nhất 4 năm kinh nghiệm trong lĩnh vực phát triển phần mềm;
  • Có kiến thức vững chắc về JAVA hoặc PHP hoặc C;
  • Ưu tiên ứng viên đã làm việc tại Nhật Bản hoặc có kinh nghiệm làm việc với các đối tác Nhật Bản.

Để có thể cùng nhau đi trên chặng đường dài phía trước, Pesol rất mong tìm được người phù hợp để tiếp tục đồng hành chinh phục những giấc mơ lớn. Với vị trí Technical Project Manager (PHP, Java, C#) tại Persol, con số $3,000 không chỉ là mức thu nhập xứng đáng mà còn là niềm tự hào cho sự nỗ lực và ý chí của chính bạn!

Xem ngay những tin đăng tuyển dụng IT mới nhất trên TopDev