
Vào tháng 2 năm 2026, METR công bố bài nghiên cứu về hiệu năng lập trình viên qua thời gian dài sử dụng công cụ AI. Qua 47 lập trình viên đa dạng kỹ năng và trình độ, và gần 500 tasks khác nhau, họ tìm ra rằng thời gian tiết kiệm được với AI assisted coding chỉ 4% so với lập trình viên bình thường. Trước đó, họ thực hiện nghiên cứu vào 2025 và kết luận rằng AI coding lại làm lập trình viên chậm hơn 19%. Sau khi cập nhật phương pháp nghiên cứu và tìm nhiều tình nguyện viên hơn thì những TNV cũ lại do dự hơn với các task mất thời gian nhiều hơn, ảnh hưởng work ethic của lập trình viên cả khi với công cụ AI.
Nhưng vấn đề không phải là thời gian - nhiều người cho rằng họ dùng AI mới dùng được công nghệ mới
Mình muốn prove a point:
Không có lý do gì để justify AI coding để dùng được công nghệ mới, và thậm chí thời gian dành ra để học còn tương đương.
Để thực hiện thí nghiệm này, mình đặt ra 2 tuần để release MVP bằng techstack lạ bằng ngôn ngữ lập trình ít quen thuộc (thiệt ra, mình thi Lập trình thi đấu hơn 3 năm và từng code C++ nhiều vào năm nhất).
Mục tiêu là để document lại những design choices, kiến thức mới, resources hay mà mình tìm được. Đồng thời, mình sẽ tạo cơ hội để chia sẻ quá trình thiết kế và giải quyết bài toán kỹ thuật dễ gặp nhiều nơi khác mà các bạn sẽ không được học trên trường.
Trong quá trình thực hiện project này, mình phụ thuộc rất nhiều vào rubber ducking trên Discord, vì tinh thần open source là cộng đồng là chính, và kinh nghiệm từ người đi trước là đáng quý. Cảm ơn ILikeFish, QB, strycks, MeanSoybean, bubu, tb-tian, nptloc21, leeminhduc, trthphc2003, demen100ns, steveonalex vì đóng góp ý kiến và thiết kế.
Source code và Windows binaries: YangTuanAnh/paragraph_cpp
Quay về lúc trước khi viết bài đầu, mình đã cân nhắc qua 1 vài cái tên, nhân tiện mình cũng muốn có mascot. Từ đây chúng ta thấy được quá trình tiến hóa tên: Paranote → pinyin/romanji/hangul related → Inkdrop (taken) → Scratchbox (taken) → CatWalk (cat + graph walk) → Paragraph (PARA + Graph walk).



Thiết kế software thì thường sẽ có functional requirement và non-functional requirements. Functional requirements là chi tiết ứng dụng cần thực hiện chức năng gì - chẳng hạn như “app phải có 4 page Tasks, Projects, Areas, Resources”. Còn non functional requirements, hoặc còn gọi là technical requirements, là những yêu cầu thỏa mãn tính chất khác như performance (hiệu năng), scalability, maintenance (khả năng duy trì), security, vv.
Kế hoạch vẵn là release 1 bản C++ và 1 bản web, và offline sync giữa web và desktop. Đây cũng là pain point của Notion, nên họ mất rất lâu để rollout chức năng offline syncing. Ý tưởng là lưu 1 db instance của user locally và force push-pull như Anki Web. Đây sẽ là functional requirement của mình.
Để tiện, mình sẽ đưa hết technical requirement sang cho Supabase quản lý. Đây là lơi thế của cloud và distributed systems khi thiết kế web applications - đảm bảo uptime và scaling nhanh, nhưng có thể đắt phí nếu quản lý không khéo.

Lấy cảm hứng từ Obsidian, mình cũng muốn người dùng có thể sync file đủ nhỏ lên Git/Syncthing. Một vấn đề với lưu json là load in memory và write lại toàn bộ file on disk mỗi khi có update. Có thể không quá tệ nếu dữ liệu nhỏ (Git file), nhưng lưu metadata dạng text.

Hay là lưu hết vô sqlite? Mình chưa từng dùng sqlite bao giờ, mình còn tưởng nó là 1 cái server giống như Postgres. Hóa ra nó chỉ là 1 file .h và .c để đọc viết logic SQL đơn giản lên 1 file .db. Coi như có sẵn O(logn) operations và lưu object theo dạng binary thay text.

Mình lên dbdiagram nháp nhanh cái schema cho từng object. Tôi yêu Database Design, ngàn đời đội ơn cô Lê Thị Nhàn, mấy con gà thì biết gì?
Bài toán đặt ra 4 object để mô hình hóa: tasks, projects, areas, resources, archived. Yêu cầu rằng area thì có project và resources, projects thì có tasks và resources, khi nào hoàn thành thì area, projects, resources có thể được archived. Đó là yêu cầu cơ bản của hệ thống PARA. Các metadata khác như energy, urgency, type là qua cảm hứng từ workspace Notion của mình.

Ban đầu mình tính release bản web trước bản desktop vì techstack cho web đủ toolkit để draft nhanh MVP. Frontend thì React + Shadcn, nhìn chả khác gì Notion/Anytype clone. Backend thì NextJS và Supabase, mình hơi lười viết thêm service, cứ quăng lên Vercel thôi. Đội ơn Supabase vì lo hết Auth, database, storage, vv.

Nhưng mọi thứ bắt đầu khó chịu khi phải bật OAuth.

Hậu quả của việc cắt công đoạn: quay đi quay lại sửa UX và schema, và nó sẽ không phải là lần cuối. Tin vui là không phải nhét thêm DB nào nữa cả, cứ Postgres mà quất thôi. Everything is Postgres.

Khủng hoảng cắt bớt công đoạn. lần 2 và 3. Chưa suy nghĩ kỹ làm sao để truy xuất đồ thị và UX nên dùng sao cho hợp lý. Thôi, nản quả, viết blog và sang code C++ thôi!

Và đây là giây phút định mệnh khi quyết định viết desktop app bằng C++ thay Electron. Also mình cũng muốn thêm semantic search như SigLIP2 cho kế hoạch tương lai.

Bắt tay vô R&D cách viết desktop app bằng C++, vid này xuất hiện đầu và khuyên dùng luôn ImGui, tiện ghê.
Mình đã chốt dùng Imgui và Raylib để làm GUI sau khi tìm được bài này. Tình cờ thay, mình đã dùng Raylib để làm đồ án game hồi học nhập môn lập trình. Welcome back, old friend.
Why Raylib & Imgui?
First though, let’s answer the most obvious question: why Raylib and ImGui for building an app? Aren’t they for making games?
No! Okay, yes, Raylib was written for video game programming. But, it’s also a great multimedia toolkit. And, it’s both cross-platform and pretty easy to use.
Similarly, while Dear ImGui is popular in the game industry, it’s more than capable of building complex GUIs required for any app. Immediate Mode GUIs are popular in gaming because they’re easy to integrate into their real-time graphics engines. Buy, that does not mean you can’t use them for something else.
So, that’s why I’m using these libraries. Raylib provides the cross-platform ability, and a lot of low-level stuff such as loading images, drawing stuff, and playing sounds. Dear ImGui makes creating GUIs easy. At least, that’s what I’ve been told.
May là ImGui có cộng đồng OSS lớn và có đủ widget để chơi. Còn bài toán spring-based layout thì mình sẽ quay lại trong thời gian tới, hình như có sẵn thư viện để tính tọa độ rồi.

They’re gonna turn him into a marketable plushie tomorrow.

Khủng hoảng cắt bớt công đoạn, lần 4, mới self-aware.

Lần đầu trải nghiệm đắng cay với cmake. Chỉ cần bật developer mode trên Windows là được. Trong lúc tận dụng cơ hội học CMake, mình nhận ra nó chả khác gì viết Dockerfile, quá đã!

Hans de Ruiter đã giúp mình rất nhiều trong công đoạn tìm hiểu. Ổng có nguyên bộ sách và series về CMake và GUI development bằng C++. Ông hiện tại đang code SaaS bằng C++ (Drogon) và làm ongoing series về chủ đề này, cảm hứng rất lớn cho project này.
https://keasigmadelta.com/blog/making-a-ridiculously-basic-paint-app-with-raylib-imgui/
https://keasigmadelta.com/blog/everyone-told-me-not-to-build-a-saas-in-c/
https://keasigmadelta.com/blog/make-tutorial-getting-started/
https://keasigmadelta.com/blog/people-think-cpp-is-hard-it-isnt/
https://keasigmadelta.com/blog/sqlite-in-c-how-to-add-and-query-data-insert-select-tutorial/

Không thể tin nỗi mình chưa dùng phải dùng assert cho C++. Sau khi có functional và technical requirements, và trước khi bắt tay code, thì chúng ta nên viết test, mỗi functional và technical requirement nên có ít nhất 1 test. Test Driven Development khuyến khích mình viết test trước khi viết code, và đảm bảo test phải đúng trước khi implement chức năng tiếp theo.

Baby learns to use cmake to import sqlite, and a decision he’s going to regret later. Baby learns about the chrono header. sqlite3 ko có uuid engine, nên phải tự chế, chắc là ko sao đâu

Baby also learns about the smart pointer.
A smart pointer is a class that wraps a 'raw' (or 'bare') C++ pointer, to manage the lifetime of the object being pointed to. There is no single smart pointer type, but all of them try to abstract a raw pointer in a practical way.
Smart pointers should be preferred over raw pointers. If you feel you need to use pointers (first consider if you really do), you would normally want to use a smart pointer as this can alleviate many of the problems with raw pointers, mainly forgetting to delete the object and leaking memory.
With raw pointers, the programmer has to explicitly destroy the object when it is no longer useful.
A smart pointer by comparison defines a policy as to when the object is destroyed. You still have to create the object, but you no longer have to worry about destroying it.
Baby also learns about the Google C++ Style Guide. Mới nhớ ra là struct là để chứa attributes, class là để chứa attributes và methods. Và tập dần snake_case thay camelCase. Và dùng nullptr thay NULL.

Việc chọn design pattern để thiết kế GUI bàng C++ lại quan trọng vì rất nặng OOP. Có 2 architecture chính khi thiết kế full-stack application:
Model — View — Controller (MVC)
Model — View — ViewModel (MVVM)
Modern desktop applications often contain dozens of UI components and large volumes of business logic. Without a clear architecture, the code quickly becomes difficult to test, modify, or extend.
The Model — View — Controller (MVC) pattern separates application responsibilities into three components:
Model — application data and business logic
View — the user interface
Controller — handles user interaction and updates the model
This separation helps prevent UI code from becoming tightly coupled with business logic.
Another popular architectural pattern for UI development is MVVM (Model — View — ViewModel).
MVVM separates responsibilities into:
Model — application data
View — UI components
ViewModel — presentation logic and data binding
MVVM is particularly useful when the UI must reflect dynamic data changes such as dashboards or analytics applications.
Tất cả implementation của mình đều chon MVVM thay MVC vì dữ liệu update liên tục giữa memory và disk. Ví dụ từ code mình cho object Area và view model cho Area:


Mình sẽ phải có 1 translation layer giữa sqlite statement và parse thành C++ object. Lấy ví du hàm std::vector<Area>& Backend::get_areas(int archived) của mình để trích object từ table Areas trong sqlite.
Recap lại, sqlite chỉ là 2 file sqlite3.h và sqlite3.c . Tụi nó chỉ có vai trò thực hiện logic của 1 SQL Database ngay trên ứng dụng, thay vì phải dựng hẵn 1 server riêng như Postgres. Từ sqlite, 1 cái query theo thứ từ sqlite3_open() → sqlite3_prepare() → sqlite3_step() → sqlite3_column() → sqlite3_finalize() → sqlite3_close()

Theo thứ tự đó, mình đã khai báo sqlite3_open() từ đầu chương trình. Tiếp đến, mình đưa query cho db bằng sqlite3_prepare() và check xem query có hợp lệ không. Nếu không trả SQLITE_OK thì đóng db và finalize statement. Còn hợp lệ thì chúng ta sẽ iterate lần lượt các row bằng sqlite3_step() khi còn SQLITE_ROW và trích dữ liệu trong stmt bằng hàm riêng của mình read_stmt_area . Khi đọc xong rồi thì step sẽ trả về SQLITE_DONE và chúng ta finalize statement.


Và cuối cùng có cái GUI đầu tiên, sau đó là phải refactor khá nhiều lần và đọc design patterns. Guideline của mình thường sẽ là viết tối đa 100-200 dòng mỗi file trước khi tối ưu, vì khá sure mình sẽ rất lười. Một vấn đề với AI assisted coding là lạm phát OOP abstraction và viết thừa. Keep It Simple, Stupid (KISS).

Đọc DDIAv2 về làm sao để mô hình hóa đồ thị trên database, dạng mình dùng gọi là property graph model, as opposed to triple storage model. Các bài toán về lưu trữ và mô hình hóa đồ thị rất hay khi làm việc với dữ liệu có nhiều mối quan hệ many-to-many.

Cho đến lúc này, mình đang dùng node render bằng imnodes và tất cả các node đều vẽ ở tọa độ (0, 0), muốn có spring layout thì phải R&D thêm xí. Mình cũng có tìm được vài thư viện C++ implement thuật toán networks nhưng chưa thử nghiệm.

Last sprint, cảm giác chả khác gì làm đồ án môn học.

Các tasks đều có assigned string id nên để truy tìm nhanh để lưu thêm 1 cái hash table để O(1) access thay vì O(n) hết vector.

Graph view bắt đầu ra dạng, đây cũng là imnodes. Mình chỉ dùng heuristic để vẽ tạm graph theo dạng tree, sẽ dùng spring layout trong thời gian tới.

Một số GUI optimization khác vào phút cuối: dùng static attributes, UX design, dirty flag

Và cách chia trường hợp OS.
#include "imgui.h"
#include "gui/pages.h"
#include <filesystem>
#ifdef _WIN32
#include <windows.h>
#elif __APPLE__
#include <cstdlib>
#else
#include <cstdlib>
#endif
void IUIPages::draw_page_settings()
{
constexpr const char* dataFolder = "data";
ImGui::Text("Data Folder");
ImGui::TextDisabled("%s", std::filesystem::absolute(dataFolder).string().c_str());
if (ImGui::Button("Open Data Folder"))
{
std::filesystem::create_directories(dataFolder);
#ifdef _WIN32
ShellExecuteA(
0,
"open",
std::filesystem::absolute(dataFolder).string().c_str(),
0,
0,
SW_SHOWNORMAL);
#elif __APPLE__
std::string cmd = "open \"" +
std::filesystem::absolute(dataFolder).string() + "\"";
std::system(cmd.c_str());
#else
std::string cmd = "xdg-open \"" +
std::filesystem::absolute(dataFolder).string() + "\"";
std::system(cmd.c_str());
#endif
}
}
Để release cmake thành install thì 1 cách là zip self-contained folder với binary lại 1 chỗ bằng cmake --build build --config Release --target package
Để chạy Github Actions cho project, mình dùng windows-latest và install mingw để build cmake trên CI
jobs:
build:
runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v4
- uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
update: true
install: >
mingw-w64-x86_64-gcc
mingw-w64-x86_64-cmake
mingw-w64-x86_64-make
- name: Verify compiler
run: |
which gcc
gcc --version
cmake --version
Thời gian dành ra cho project này được thống kế đầy đủ trên app Forest. Để hoàn thành project tương đối lớn với một techstack lạ, học từ đầu mà không dùng AI, mình dành ra trung bình 2 tiếng một ngày. Đỉnh điểm vào 3 ngày cuối vì đánh lẽ sẽ release sớm hơn. 2 tiếng 1 ngày là một lượng thời gian hợp lý trong ngày mà hầu hết mọi người dùng để học - số liệu không quá bất ngờ, thậm chí expected.
Downside rõ nhất của người dùng AI là chi phí và giới hạn sử dụng. Khi đạt giới hạn sử dụng, nhiều người sẽ “cụt tay”. Mặc dù rõ là giỡn, nhưng nó đang reinforce kết luận của bài nghiên cứu của METR rằng work ethic của lập trình viên dùng AI đang tệ đi.
Với thời gian tiết kiếm được chỉ trung bình 4%, nhưng không dành bất cứ thời gian nào để luyện tập. Thành ra các bạn lại phải dành thêm thời gian để học lại những kỹ thuật trong codebase. Với người có kinh nghiệm và không phải học, thì lack of engagement sẽ dẫn thành skill atropy, dẫn đến kỹ năng đưa ra quyết định bị lệ thuộc.
Kể cả economic costs cũng phải justify được thời gian tiết kiệm được, và rõ ràng đồ án NMLT của các bạn chưa làm ra tiền. Hoặc có, thì 4% của 2h chỉ là 5m, nên cách dễ nhất là tự hỏi xem 5m ấy có đáng số tiền token đó không.
Tất cả kết luận trên đều đặt giả thuyết rằng AI luôn đúng và không bao giờ hiểu sai ý người dùng. Nhưng thực tế như thế nào thì sẽ "leave as an exercise to the reader". Còn political và existential risks về AI sẽ để dành cho bài sau.

Sau một tuần design và một tuần speedrun code full stack application bằng C++, mình lại học được nhiều hơn về system design, data modelling, UI/UX và những tidbit về modern C++ development. It's kinda expected to see someone with C/C++ experience rage out about my baby code, which is necessary for development. You don’t get that anymore from the expected perfectionism with AI code. Và kể cả khi làm project nghiêm túc thì kỹ năng học, tiếp thu kiến thức mới, và thiết kế ý tưởng vẫn quan trọng và đáng duy trì.
https://github.com/YangTuanAnh/paragraph_cpp
https://rubberduckdebugging.com/
https://www.yangtuananh.dev/posts/im_releasing_paragraph
https://www.altexsoft.com/blog/functional-vs-technical-requirements/
https://pl-rants.net/posts/when-not-json/
https://keasigmadelta.com/blog/building-a-cross-platform-c-gui-app-with-cmake-raylib-and-dear-imgui/
https://ko-fi.com/yangtuananh/goal?g=1
https://github.com/Schniz/fnm/issues/338
https://keasigmadelta.com/blog/making-a-ridiculously-basic-paint-app-with-raylib-imgui/
https://keasigmadelta.com/blog/everyone-told-me-not-to-build-a-saas-in-c/
https://keasigmadelta.com/blog/cmake-tutorial-getting-started/
https://keasigmadelta.com/blog/people-think-cpp-is-hard-it-isnt/
https://keasigmadelta.com/blog/sqlite-in-c-how-to-add-and-query-data-insert-select-tutorial/
https://viblo.asia/p/tong-quan-ve-tdd-gDVK2WzeZLj
https://stackoverflow.com/questions/6012663/get-unix-timestamp-with-c
https://google.github.io/styleguide/cppguide.html
https://stackoverflow.com/questions/106508/what-is-a-smart-pointer-and-when-should-i-use-one
https://sqlite.org/quickstart.html
https://zylinski.se/posts/know-why-you-dont-like-oop/
https://linuxvox.com/blog/c-compiling-on-windows-and-linux-ifdef-switch/
https://gameprogrammingpatterns.com/dirty-flag.html
https://github.com/ptsouchlos/CMakeInstallExample
https://gordonyfg.github.io/blog/post-gtest_mingw64_vscode_win_ci_w_githubaction/
