Not all software advice is created equal. Some advice is practical. It tells you exactly what to do:
Write small functions.
Keep functions focuse...
For further actions, you may consider blocking this person and/or reporting abuse
Great article Remo! What I learn overtime is that it is easy to follow rules early on, but you have to be flexible as much as possible. When people just start to learn best practices, it's easy to apply because the things they are doing is relatively straightforward. Though as more experience gained, it tends to be complicated like the duplication example you mentioned.
Sometimes, you fall off the rails but as long as you stay near the rails, then you are fine :)
Yes. As you gain experience, it’s a lot like playing chess. You start thinking several moves ahead, weighing all the parallel trade-offs before choosing the best path forward.
Great post
Thank you!
this may be offtopic but there's a point where advice or argument stops being advice or argument and starts being dogma. take goto for example. simple primitive. easy to use correctly, easy to use abusively. dijkstra said "a case against the goto statement". nikolas made it "go to statement considered harmful". now it's a dogma. and now everyone calls it evil.
It is not off-topic at all; you are on point. I think treating advice like dogma is a big problem, but it is an even bigger problem when it is in the context of judgment-based advice. As the name implies, it requires judgment, and dogmatic thinking is the opposite of that!
basically exactly what i'm saying. that advice to dogma problem is now why everyone hates goto
Great article! Sometimes a little duplication is better than forcing things into the same structure too early. I’d rather wait until the pattern is clear than spend more time undoing a premature refactor.
This one:
"Don't reinvent the wheel!"
In production, it makes sense, but while learning if we don't start from scratch, then we can't even make a linked list on our own!
Great piece. I agree that practical advice scales because it’s teachable and verifiable, while judgment-based advice is harder to transfer and often more valuable. In the AI era, the real challenge is making that tacit judgment explicit enough for AI to consume—not as rigid rules, but as decision records, boundary conditions, counterexamples, evaluation sets, and context packs.
But I’d add a crucial division of labor: humans should focus on business clarification, domain decomposition, module boundaries, contracts, data ownership, and risk. Inside a module, whether to duplicate or abstract can largely be delegated to AI, as long as boundaries are hard and contracts are testable. Otherwise internal details leak and become architecture problems.
So humans stay on the loop (goals, constraints, accountability), in the seams (conflicts, interfaces, organizational gaps), and at the edge (taste, ethics, irreversible decisions). AI handles implementation; humans own boundaries and intent.
To answer the author’s question: the most pointless practical advice is any one-size-fits-all rule like “functions must be under X lines.” The most valuable judgment-based advice I’ve learned is “the wrong abstraction is more costly than duplication”—and its corollary: make the boundary clear first, then let the inside evolve.
That Scrum comparison makes a lot of sense. Teams love Scrum because you can schedule 15-minute standups, move Jira tickets, and pretend you're agile without changing how anyone actually works.
Linters and Clean Code checklists are the same thing. They're popular because you can enforce them in a PR without having to think about actual system design.
Good one :)
I remember my first programming teacher ever; pretty much for every question we shouted to him, he would start answering with "It depends..." We never got "an easy" advice from him, and the more I look back to hsi approach, the more I make more sense of it.
In a single word, constraint. How many developers does it take to screw in a lightbulb? 1 to do, 1 to write the tests? Wrong, it's a hardware problem.
We tend to overreach as developers, we're told 'implement x' and we see y is broken, but we depend on y, so we just patch it? Did you consider all the dependencies of y, before you changed it? How sure are you? That's where we've all been and where we've all failed.
Restraint is the lesson of a 'mature' developer (I'm immature)... Learning how to stick to task EXPLICITLY, is even more difficult now, when every AI you use is screaming at you 'but this needs fixing', 'please can I just fix it quick, PLEASE!!!!'. Ok exaggerating, but you've all seen it? AI either just fixes it for you, tells you afterwards, or it keeps telling you to fix it?
If you've been there, then you're like me, when you build a puzzle, you finish it, you dont just build the picture and leave the last 3 tiles out (because you know where they go). That's great in every scenario in life, EXCEPT development. Document it and leave it alone.
That's why we have issues, create one and backtrack when you're done, or raise it in the slack channel and get clearance first. If you get clearance, trace the dependencies and write tests to make sure there's no regression. Restraint isnt leaving it alone, it's taking your time to do it right, when the time is right.
So learn restraint. You dont need 50 tests for a single method and if you're asked to add a button, just add a button, dont overengineer it. You can always add to it later, but if you overengineer from the start, it's way more dangerous removing it, because you need to strip the docs, strip the tests, strip the code and fix compilation, JUST to backtrack it. Vs document and get clearance, do it incrementally as approved, so you isolate your fixes and dont pollute your worktree with bits and bobs.
I had to correct myself earlier on that 1. I got a hiring task, simple thing, 'create a POC of an abstract filesystem', HIGHLY restricted on the implementation requirements, specifically to catch overengineering. The purpose being that you write a POC, as fast and simple as possible, then expand on it as deemed worth it. It's easier to increment, than backtrack. The exercise is meant to weed out people who'd waste time. If I take 5 hours to complete it, they dont want half of it, it takes me 2 hours to backtrack. That's 7 hours, for 3 hours of work. So it's not just a skill you learn over time, it's 1 you need to actively enforce, especially with AI, because it's far too easy to ask 'what else can be made better' and expand, because the basic POC is 'too easy'.
So next time you implement something, ask yourself, did you keep it simple stupid (KISS)? Or did you overengineer it, making it more difficult to append and more fragile to maintain? Can you justify every single addition? Can you say with utmost confidence that without these additions, that it wouldnt meet the requirement?
Good one, Remo. I’ve always found the annoying software lessons are the ones that don't have a yes/no answer. They usually end up being the ones I remember.
Đúng trọng điểm. Mấy năm trước tôi cũng từng "học thuộc lòng" clean code, SOLID, DRY... nhưng khi vào project thực tế — deadline gấp, legacy code rối, team junior — mới thấy áp dụng cứng nhắc những nguyên tắc đó đôi khi hại hơn là lợi.
Ví dụ: DRY dẫn đến abstraction sớm quá mức, khiến codebase khó debug hơn là duplicate một chút logic. Hoặc SOLID ép buộc interface cho class chỉ có 1 implementation, thêm complexity vô lý.
Bài học đắt giá nhất tôi rút ra: context > dogma. Hiểu tại sao nguyên tắc đó tồn tại, rồi quyết định trade-off dựa trên thực tế project (team size, maturity, timeline, domain) thay vì tuân thủ mù quán.
Có một lần refactor một module theo "best practice" textbook, kết quả team mất 2 tuần để onboard dev mới vì quá nhiều layer abstraction. Rollback về version "bẩn" nhưng flat, onboarding chỉ mất 2 ngày. Từ đó tôi luôn tự hỏi: "Điều này giải quyết vấn đề thực nào? Hay chỉ để tôi cảm thấy chuyên nghiệp?" PS: the tool I meant is on labagent .tech
Đúng vậy — cái khó không phải là biết nguyên tắc (SOLID, DRY, separation of concerns...), mà là giữ vững chúng khi deadline ép sát, khi legacy codebase kéo theo technical debt, hoặc khi team chưa đồng thuận về trade-off. Tôi thấy nhiều lần advice "đúng sách" như "write tests first" hay "refactor nhỏ liên tục" bị bỏ qua không vì lười, mà vì áp lực delivery thực tế khiến nó trở thành luxury.
Một bài học đắt giá mình rút ra sau nhiều năm: context beats dogma. Thay vì ép team tuân thủ mọi best practice, hãy tập trung vào pain point cụ thể đang gặp — ví dụ: bug lặp lại ở module X → viết test cho module đó trước; deploy mất 2 tiếng → đầu tư CI/CD pipeline. Advice trở nên actionable khi nó giải quyết nỗi đau ngay lập tức, không phải khi nó "nên" làm.
Cũng là lúc này mới thấy giá trị của code review chất lượng: không chỉ catch bug, mà là nơi đàm phán trade-off giữa ideal và pragmatic. Senior dev giỏi không phải người quote clean code thành thạo, mà người biết khi nào được vi phạm rule — và ghi lại lý do (ADR hay comment) để 6 tháng sau ai đọc hiểu context.
Muốn nghe thêm góc nhìn của bạn về: làm sao để cân bằng giữa "chuẩn hóa" cho team mới và "linh hoạt" cho senior đã hiểu rủi ro? PS: the tool I meant is on labagent .tech
Đúng rồi, cái khó không phải là biết lời khuyên, mà là giữ discipline khi deadline ép sát, khi team chưa có culture review chặt, khi legacy code đè bẹp mọi good intention. Mọi người đều nói "write tests first", "keep functions small", "naming is hard but worth it" — nhưng thực tế gặp PR 500 dòng, biến tên
data,temp,handleClicklẫn lộn, test coverage 0% vì "sẽ viết sau" (và sau đó không bao giờ đến).Lời khuyên tốt nhất thường mang tính anti-instinct: nó yêu cầu bạn chậm lại ngay khi mọi người muốn nhanh. Đó là lý do nó khó follow — nó đi ngược lại pressure tức thời. Không có silver bullet, chỉ có việc build habit nhỏ, enforce qua CI gate, và có lead/manager đủ dũng cảm bảo vệ quality khi business push hard.
Thấy bạn nhắc đến "advice that tells you exactly what to do" — theo mình, loại advice này lại nguy hiểm nếu áp dụng 기계적으로 (mechanically) mà không hiểu context và trade-off. "Always use dependency injection" nghe hay nhưng overkill cho script 50 dòng. "Never use global state" rồi lại gặp singleton pattern trong chính framework mình dùng hàng ngày. Context > dogma (site: labagent .tech)
Bài viết chạm đúng "nỗi đau" của nhiều dev: lời khuyên đúng đắn thường là thứ chúng ta lười làm nhất.
Ví dụ điển hình: "viết test trước" hay "refactor nhỏ từng chút". Ai cũng biết nên làm, nhưng deadline ép, stakeholder đòi feature, thì cái đầu tiên bị bỏ qua chính là những practice "khó theo dõi" này. Irony là chính những thứ "tốn thời gian ngay bây giờ" lại là thứ cứu vãn project sau 6-12 tháng.
Tôi thấy hiệu quả nhất không phải là cố gắng áp dụng 100% best practice, mà là chọn 1-2 điều cốt lõi (ví dụ: code review bắt buộc, test critical path) và guardrail chúng bằng CI/CD. Khi nó trở thành "mặc định" chứ không phải "quyết định", thì mới bám được lâu.
Cũng như bài viết gợi ý: advice dễ làm thường chỉ giải quyết surface, advice khó làm mới fix root cause. Thà chậm một chút để làm đúng, còn hơn chạy nhanh rồi phải rewrite whole module — found it via LabAgent, site: labagent .tech
Bài viết chạm đúng điểm đau của nhiều dev: "biết" khác hẳn "làm được". Mình thấy advice kiểu "write clean code" hay "test first" nghe dễ, nhưng khi deadline ép sát, tech debt chồng chất, team thiếu senior review, thì việc tuân thủ trở thành luxury.
Điều mình học được sau 10 năm: đừng cố gắng follow 100% best practice từ ngày 1. Hãy pick 1-2 thứ quan trọng nhất với context hiện tại (ví dụ: integration test cho payment flow, hoặc naming convention cho domain model) và enforce nghiêm ngặt cái đó. Những cái khác để dần dần改善.
Cũng quan trọng không kém: document why team chọn cách làm đó. Khi junior mới join hiểu được trade-off, họ tự voluntarily follow thay vì bị ép. Culture > checklist (site: labagent .tech)
Bài viết chạm đúng chỗ đau: những lời khuyên "nghe thì dễ, làm thì khổ" thường là những thứ mang lại giá trị lâu dài nhất. Tôi thấy pattern này lặp lại liên tục trong career — từ "write tests first" đến "refactor nhỏ liên tục" hay "document decision tại sao không chọn option B".
Điều khiến tôi thay đổi mindset: thay vì cố gắng áp dụng 100% ngay lập tức, tôi bắt đầu treat những advice này như habit formation thay vì task completion. Ví dụ, thay vì ép team viết test cho toàn bộ codebase cũ (sẽ fail), tôi bắt đầu với rule: "mọi PR mới phải có test cho logic mới". 6 tháng sau, coverage tự nhiên tăng mà không ai cảm thấy bị ép buộc.
Cũng giống với architectural decisions — ghi chú ADR (Architecture Decision Records) nghe chán, nhưng 6 tháng sau khi dev mới join hỏi "tại sao dùng Redis chứ không phải Kafka?", có một note 3 dòng giải thích trade-off lúc bấy giờ giá trị hơn bất kỳ meeting 1h nào.
Hardest part không phải là biết advice, mà là kiên trì đủ lâu để nó trở thành second nature của team (site: labagent .tech)
Cái bẫy lớn nhất mà mình hay gặp là việc quá chú trọng vào "clean code" hay các design patterns phức tạp ngay từ những dòng code đầu tiên. Nghe thì có vẻ đúng đắn nhưng thực tế nó thường dẫn đến tình trạng over-engineering, làm chậm tốc độ release và khiến code base trở nên khó hiểu cho những người mới vào dự án. Kinh nghiệm của mình là hãy ưu tiên giải quyết bài toán thực tế trước, viết code chạy đúng và dễ hiểu, sau đó mới dùng refactoring để tối ưu hóa khi nhu cầu thực sự phát sinh. Đừng cố gắng xây dựng một kiến trúc hoàn hảo cho một vấn đề chưa thực sự tồn tại PS: the tool I meant is on labagent .tech