GoF gặp một mâu thuẫn kinh điển: thêm operation mới mà không sửa class cũ, hoặc thêm class mới mà không sửa operation cũ — mày chỉ được chọn một. Visitor giải nửa bài đó.
Lập trình
Lập trình
74 bài viết
GoF gặp bài toán: cùng một traversal logic phải chạy trên array, linked list, tree, graph — mà không thay đổi code duyệt. Iterator tách 'cách duyệt' ra khỏi 'cấu trúc chứa data'. Java đã bake nó vào ngôn ngữ.
CSS variables, prefers-color-scheme, OS integration, và tại sao dark mode đúng cách phức tạp hơn mày nghĩ.
GoF gặp bài toán: một request cần được xử lý bởi nhiều handler theo thứ tự, nhưng sender không nên biết handler nào sẽ thật sự xử lý nó. Mày đang dùng pattern này mỗi khi gửi request qua Spring Security.
GoF gặp cấu trúc dạng cây — menu có submenu, category có subcategory, permission có group. Composite cho phép xử lý một node đơn hay toàn bộ cây bằng cùng một cách.
Con số 0–100 của Lighthouse không phải cảm tính — nó là weighted average của các metric đo chính xác trải nghiệm người dùng. LCP, INP, CLS, và lý do con số đó ảnh hưởng đến SEO.
Tải landing page mà phải kéo theo code của admin dashboard — đó là vấn đề bundle khổng lồ. Code splitting, dynamic import, và content hash giải quyết nó như thế nào.
Cùng một ảnh, WebP nhỏ hơn JPEG 30%, AVIF nhỏ hơn 50% — mà mắt người không phân biệt được. Đây là ba lớp kỹ thuật làm cho web năm 2024 load nhanh hơn mà không mờ hơn.
GoF gặp vấn đề với code legacy và third-party library có interface không khớp. Adapter là cái adapter phích cắm của lập trình — không thay đổi gì ở hai đầu, chỉ làm cho chúng vừa nhau.
Trace ID theo request qua nhiều service, Micrometer Tracing với Spring Boot 3, đọc waterfall diagram để debug.
GoF gặp vấn đề với object có quá nhiều optional field. Builder tách quá trình tạo object thành từng bước. Lombok @Builder thì sao?
GoF gặp vấn đề với `new` keyword — nó buộc code phải biết cụ thể class nào sẽ được tạo. Factory cắt đứt sự phụ thuộc đó. Abstract Factory đi thêm một bước.
GoF tạo ra Singleton để giải quyết một vấn đề cụ thể. Junior dùng nó để giải quyết mọi thứ — và đó là lý do nó nằm trong danh sách anti-pattern.
Reverse proxy, HTTPS, gzip, rate limit ở tầng infra — những thứ Nginx phải làm trước khi request chạm Spring Boot.
Alert 5xx spike: correlation ID, timeline, thay đổi gần đây — trước khi grep exception ngẫu nhiên. Checklist junior vào on-call rotation.
Flyway ADD COLUMN NOT NULL một lần = deploy đứng. Thêm nullable → deploy code → backfill → enforce — schema và app lệch pha có kiểm soát.
SIGTERM, server.shutdown=graceful, readiness fail khi DB down — K8s không route traffic vào pod đang chết.
@Scheduled trên hai pod Spring = email nhắc lịch gửi đôi. Distributed lock Redis/DB — chỉ một instance chạy job mỗi lần.
CompletableFuture trên ThreadPoolTaskExecutor, @EnableAsync, self-invocation, và vì sao @Async trong @Transactional thường không còn transaction.
Đổi field response là đổi contract — versioning, deprecation có hạn, và phân biệt breaking vs non-breaking change.
Alert phải báo user đang đau, có hành động rõ, và page ít — không phải mọi log ERROR đều ping Slack lúc 3h sáng.
Rolling update mặc định trên K8s, blue-green đổi traffic một phát, và vì sao zero-downtime không có nghĩa zero-risk.
On-call đã stabilize (bài 121). Postmortem blameless: timeline, root cause hệ thống, action item có owner — không phải biên bản kỷ luật.
Staging mirror prod về topology và config shape — data masked, secret riêng, gateway sandbox. Integration test 103 fail vì staging khác local, không phải vì test dở.
Merge liên tục lên main, ship binary hàng ngày, bật tính năng bằng flag — rollout 5% trước khi mở cho toàn bộ clinic.
Gọi this.save() trong cùng class không qua proxy — transaction không mở. Rollback chỉ với RuntimeException. Bug thầm lặng khiến data half-committed.
Password trong application.yml commit lên Git là incident chờ xảy ra. Config theo môi trường, Spring profiles, và cách HMS tách secret khỏi codebase.
Optional không phải cách viết null an toàn cho mọi chỗ. Return type có lý, field và parameter thì gần như luôn sai — và senior reject vì lý do đó.
Unit test kiểm tra logic trong isolation. Integration test kiểm tra các layer phối hợp đúng không. @DataJpaTest, @WebMvcTest, @SpringBootTest — khi nào dùng cái nào và tại sao cần cả hai.
Log quá nhiều không giúp debug nhanh hơn. Log đúng level, đúng context, đúng thông tin — và biết những gì tuyệt đối không được log. MDC correlation ID để trace request xuyên suốt.
Constructor có 8 tham số không tên — đây là dấu hiệu Builder Pattern cần xuất hiện. Từ Java thuần đến Lombok @Builder, cách tổ chức object creation đúng trong Spring Boot.
Bài 6 trong series này nói về technical debt từ góc độ kỹ thuật — mày đang nợ ai, nợ cái gì. Bài này nói về nó từ góc độ product: khi nào nên chủ động...
Tao từng rất tự hào về một cái booking flow tao viết. Clean architecture đúng sách, separation of concerns rõ ràng, unit test coverage cao, zero redun...
Developer hiểu business model sẽ ưu tiên đúng hơn, estimate chính xác hơn, và đưa ra đề xuất kỹ thuật có business context. Đây là thứ phân biệt senior và staff engineer.
Mỗi tính năng là gánh nặng: cần maintain, cần test, cần document, cần support. Tính năng tốt nhất đôi khi là tính năng bạn quyết định không build.
MVP không phải là product tệ. MVP là version nhỏ nhất có thể validate được hypothesis. Build đúng thứ trước khi build đúng cách.
"Chúng ta cần cache" — thực ra là query đang thiếu index. "Cần thêm field vào DB" — thực ra là UI đang hiển thị sai. Hiểu đúng vấn đề trước khi giải nó.
Developer implement ticket. Product-minded engineer hỏi: ticket này giải quyết vấn đề gì, có cách nào đơn giản hơn không, và đây có phải vấn đề đúng cần giải quyết không?
Khi review PR, senior không chỉ đọc code — họ hỏi: change này có đúng direction không? Có tạo ra dependency mới không? Có làm hệ thống khó thay đổi hơn không?
Command đóng gói một yêu cầu thành object — cho phép queue, log, undo, và retry. Đây là nền tảng của event sourcing và nhiều hệ thống phức tạp.
Proxy kiểm soát access đến object thật. Logging, caching, security check, lazy loading — tất cả đều có thể implement qua Proxy mà không sửa code gốc.
Kế thừa mở rộng là static và compile-time. Decorator mở rộng là dynamic và runtime. Khi behavior cần thay đổi linh hoạt — Decorator là lựa chọn đúng.
Facade đơn giản hóa interface phức tạp. Thay vì để mọi nơi gọi Keycloak SDK trực tiếp — một Facade che đi sự phức tạp và là điểm thay đổi duy nhất.
Observer Pattern giải coupling giữa event publisher và subscriber. Nhưng gọi notification bên trong database transaction là một trong những lỗi kiến trúc phổ biến nhất.
Strategy và State trông giống nhau về cấu trúc nhưng giải quyết vấn đề khác nhau. Hiểu sai là dùng sai — và code sẽ không express đúng intent.
Bất cứ khi nào bạn có một quy trình cố định nhưng các bước có thể thay đổi — Template Method đang ở đó. Abstract class trong Spring Boot là ví dụ điển hình.
Pattern là giải pháp cho vấn đề đã biết — không phải template để áp vào mọi nơi. Dùng pattern không có vấn đề để giải quyết là over-engineering.
Áp dụng SOLID quá sớm tạo ra over-engineering. Áp dụng quá muộn tạo ra debt. Biết khi nào cần dùng mới là kỹ năng thật sự.
Dependency Inversion: code cấp cao không phụ thuộc code cấp thấp. Cả hai phụ thuộc abstraction. Đây là nền tảng của Dependency Injection trong mọi framework.
Interface Segregation: đừng ép class implement những method nó không cần. Interface phình to là dấu hiệu của coupling ẩn và design thiếu suy nghĩ.
Liskov Substitution Principle: subclass phải thay thế được superclass mà không làm hỏng chương trình. Vi phạm LSP là nguồn gốc của những bug khó tìm nhất.
Open/Closed Principle: mở để mở rộng, đóng để sửa đổi. Khi thêm feature mới mà phải sửa code đang chạy tốt — đó là dấu hiệu cần abstraction.
Single Responsibility không có nghĩa là class chỉ có một method. Có nghĩa là class chỉ thay đổi vì một lý do — một actor duy nhất yêu cầu nó thay đổi.
SOLID không phải checklist để tick vào. Đó là tư duy về cách tổ chức code để nó không sụp đổ khi requirement thay đổi.
Deadline không phải lý do để viết code xấu — đó là lý do để viết code đơn giản hơn. Hai thứ này khác nhau hoàn toàn.
Refactor không phải là viết lại, không phải là thêm feature. Đó là cải thiện cấu trúc code mà không thay đổi behavior — và có test để chứng minh.
Khả năng test được là thuộc tính thiết kế, không phải afterthought. Code khó test là code có quá nhiều coupling và quá nhiều responsibility.
catch (Exception e) {} là một trong những đoạn code nguy hiểm nhất có thể tồn tại. Lỗi bị ẩn đi, hệ thống tiếp tục chạy sai mà không ai hay.
Con số 86400 xuất hiện ở 12 chỗ khác nhau trong codebase. Đó là 12 chỗ có thể sai khi business rule thay đổi — và không ai biết.
Boolean parameter trong function signature là mùi code rõ ràng nhất. Nó làm function làm hai việc và caller không hiểu đang gọi cái gì.
Comment không phải lúc nào cũng tốt. Khi bạn cần comment để giải thích code — thường đó là dấu hiệu code cần được viết lại.
SRP nghe có vẻ đơn giản — nhưng hầu hết dev định nghĩa "một việc" sai. Đây là cách hiểu đúng để áp dụng thật sự.
Một cái tên tốt loại bỏ nhu cầu comment. Một cái tên xấu là nguồn gốc của mọi hiểu lầm trong codebase.
Clean Code không phải là code đẹp hay code ngắn. Đó là code mà người khác có thể đọc, hiểu, và thay đổi mà không cần hỏi tác giả.
Debug không phải là đoán mò. Có một quy trình tư duy để tìm lỗi nhanh hơn — và hầu hết dev không ai dạy họ cách đó.
Code phức tạp thường là dấu hiệu của sự chưa chín — không phải sự tinh tế. Người giỏi nhất thường viết code đơn giản nhất.
Mọi quyết định kỹ thuật đều có cái giá. Kỹ năng của senior không phải là tìm giải pháp hoàn hảo — mà là chọn đúng cái phải đánh đổi.
Hệ thống tốt không phải là hệ thống không bao giờ lỗi — mà là hệ thống lỗi một cách có kiểm soát và tự phục hồi được.
Technical debt là công cụ, không phải tội lỗi. Vấn đề là khi bạn nợ mà không biết mình đang nợ — đến lúc trả giá mới hay.
Không phải lúc nào đơn giản cũng là đúng. Under-engineering tạo ra technical debt ngay từ ngày đầu mà không ai nhận ra.
Xây dựng hệ thống phức tạp cho bài toán đơn giản không phải là giỏi — đó là dấu hiệu bạn chưa hiểu vấn đề thật sự.
Viết code nhanh không phải là viết ngay. Senior dành thời gian hỏi đúng câu hỏi — và đó là lý do code của họ ít phải sửa hơn.
Sự khác biệt không nằm ở kỹ thuật — mà ở cách nhìn vào một yêu cầu: bạn đang giải quyết hôm nay hay đang thiết kế cho tương lai?
Viết code pass test chưa đủ. Senior reject PR không phải vì code sai — mà vì code không thể sống cùng hệ thống theo thời gian.