Hôm nay tôi tình cờ quan sát công trường xây dựng tại ga trung tâm Ljubljana . Trước đây tôi luôn xem công việc của họ là điều hiển nhiên, nhưng hôm nay tôi nhìn sâu hơn. Nhà ga không thể đóng cửa, vì điều đó sẽ ảnh hưởng nghiêm trọng đến du lịch, việc vận chuyển con người và hàng hóa, cùng nhiều thứ khác. Công việc được chia nhỏ một cách cẩn thận, các phương án thay thế được triển khai, và lịch trình tàu được điều phối linh hoạt để giảm thiểu tác động đến mọi người. Đây là những thực hành kỹ thuật “kinh điển”, và chúng có thể áp dụng ở mọi nơi.
Khi quan sát cách các công nhân phối hợp, một số duy trì hạ tầng hiện có trong khi những người khác xây dựng các nền tảng mới, tôi nhanh chóng nhận ra: đây chính xác là những gì chúng ta làm trong software engineering. Chúng ta đối mặt với cùng một bài toán cốt lõi: giữ hệ thống hoạt động ổn định trong khi đồng thời cải tiến nó. Khi xây dựng tính năng mới, chúng ta phải đảm bảo tác động tối thiểu đến người dùng và khách hàng—dù là migration database, refactor service hay cập nhật thư viện để xử lý lỗ hổng bảo mật.
Ở những năm đầu sự nghiệp, tôi từng ngây thơ nghĩ rằng kỹ sư được đánh giá qua số dòng code viết ra. Giờ đây tôi theo đuổi điều ngược lại: làm được nhiều hơn với ít hơn. Tôi vẫn nhớ đã từng so sánh số dòng code với bạn bè, xem 10.000 dòng là một cột mốc—một thước đo giờ đây trở nên khá vô lý trong thời đại AI.
Giờ tôi hiểu rằng luôn tồn tại trade-off. Bạn cần cân nhắc khách hàng, ý thức về khả năng hệ thống crash giữa đêm, và phản biện lại một số ý tưởng thay vì code một cách máy móc. Những phẩm chất này thậm chí còn quan trọng hơn trong thời đại AI. Việc tạo code được khuếch đại, nhưng các thực hành engineering tốt là thứ giúp kiểm soát lượng output khổng lồ của AI. Code do AI tạo ra có ích gì nếu tính năng không được sử dụng hoặc ứng dụng liên tục crash? Theo tôi là không có giá trị.
Qua nhiều năm, tôi đã xác định một số kỹ năng cốt lõi định nghĩa một software engineer. Chúng không phải về việc viết code—mà là về tư duy, giao tiếp và tạo ra giá trị. Đây là những điều tôi học được.
Tư duy hệ thống
Là một engineer, bạn kết nối các điểm và khiến các hệ thống “nói chuyện” với nhau. Điều này nghĩa là hiểu cách các thành phần tương tác, dự đoán tác động downstream, và thiết kế giải pháp hoạt động trong một hệ sinh thái lớn hơn. Trong sự nghiệp, tôi đã tích hợp nhiều dịch vụ bên thứ ba, nhưng thành tựu đáng tự hào nhất là xây dựng một app store hoàn toàn mới dựa trên marketplace sản phẩm của chúng tôi.
Dự án này đòi hỏi tư duy hệ thống thực sự. Tôi không chỉ phải hiểu API của marketplace, mà còn phải hiểu cách app store sẽ tiêu thụ dữ liệu, xử lý lỗi và hiển thị cho người dùng. Đây là lần đầu tôi làm việc với REST API—trước đó tôi chỉ có kinh nghiệm với SOAP. Nó rất thử thách: đọc tài liệu, kiểm tra payload, debug liên tục. Khi đó Swagger và OpenAPI chưa phổ biến, nên việc tích hợp thủ công là lựa chọn duy nhất.
Giao tiếp
Tư duy hệ thống thôi là chưa đủ—bạn cần truyền đạt rõ ràng ý định của mình. Ban đầu tôi không có code review đúng nghĩa, nhưng khi trải nghiệm, tôi nhận ra giao tiếp—hay chính xác hơn là “ý định”—quan trọng đến mức nào. Code được đọc nhiều lần mỗi ngày bởi các engineer khác, và mục tiêu là giúp họ hiểu nó làm gì.
Tôi nhớ lần code review đầu tiên: cảm giác như bị “mổ xẻ”, nhưng nhanh chóng nhận ra feedback có giá trị lớn thế nào trong việc nâng cao chất lượng code.
Việc đặt tên hàm và biến rất quan trọng. Test cần có ý nghĩa, không chỉ để đạt coverage. Các nguyên tắc như DRY và KISS nên được áp dụng khi phù hợp, không phải bằng mọi giá.
Nhưng giao tiếp không chỉ nằm trong code. Bạn cần viết tài liệu, ghi lại quyết định kỹ thuật bằng RFC hoặc ADR—vì code chỉ giải thích “cái gì”, không phải “tại sao”. Một khía cạnh khác là đặt câu hỏi đúng—đôi khi giúp bạn loại bỏ cả một ý tưởng và tiết kiệm rất nhiều thời gian.
Debugging
Ngay cả khi giao tiếp rõ ràng, bug vẫn xảy ra. Việc tìm bug không phải lúc nào cũng đơn giản. Khả năng gắn debugger và nhìn vào những gì diễn ra “phía sau hậu trường” đã cứu tôi rất nhiều lần.
Tôi nhớ có lần kiểm tra code, đi qua toàn bộ method từ đầu đến cuối mà không tìm ra vấn đề. Sau đó mới nhận ra một điều kiện không được thỏa mãn, nên method đó chưa bao giờ được gọi. Tôi đã chắc chắn rằng state change chỉ xảy ra trong method đó, và điều này khiến tôi đi sai hướng. Debugger đã giúp tôi nhận ra sai lầm của mình.
Quản lý thời gian và độ phức tạp
Kỹ năng debug quan trọng, nhưng biết khi nào nên dừng việc hoàn thiện và bắt đầu “ship” còn quan trọng hơn. Tôi hiếm khi thấy dự án hay tính năng nào hoàn thành đúng hạn trong ngành phần mềm—đây gần như là một trò đùa phổ biến. Điều này cho thấy việc quản lý thời gian và độ phức tạp là cực kỳ cần thiết, nhưng không hề đơn giản—bạn phải dựa vào kinh nghiệm.
Tôi nhớ khi làm một hệ thống rewards cho thẻ tín dụng với deadline gấp. Mọi thứ không hề suôn sẻ. Khi gặp trở ngại, chúng tôi hoặc đơn giản hóa hướng tiếp cận, hoặc đánh dấu một số tính năng là “nice to have” để kịp ra MVP. Dĩ nhiên, technical debt xuất hiện, nhưng đó là cái giá chấp nhận được. Chính lúc đó tôi học được tính thực tế: điều quan trọng là phản hồi nhanh từ người dùng, không phải hoàn hảo tuyệt đối.
Thoải mái với sự không chắc chắn
Quản lý thời gian và độ phức tạp đồng nghĩa với việc đối mặt với điều chưa biết. Software development luôn tồn tại nhiều ambiguity, và bạn cần xử lý nó. Thước đo thực sự của seniority là khả năng xử lý sự mơ hồ này.
Tôi nhận ra điều này khi ở level intermediate. Tôi từng nhận một yêu cầu tính năng khiến mình choáng ngợp—không biết bắt đầu từ đâu. May mắn là tôi trao đổi với một đồng nghiệp, và anh ấy chia sẻ: hãy chia nhỏ vấn đề lớn thành các phần nhỏ. Khi làm vậy, mọi thứ trở nên rõ ràng hơn. Từ đó, mỗi khi giải quyết vấn đề, tôi luôn breakdown nó—giúp nhìn rõ từng phần, đặt câu hỏi đúng và giảm bớt sự không chắc chắn.
Học cách học
Chia nhỏ vấn đề là một chuyện, nhưng bạn cũng cần liên tục học các domain, công cụ và kỹ thuật mới. Trong ngành phần mềm, bạn phải hiểu bài toán trước khi giải nó. Vì vậy engineer luôn cố gắng nắm domain knowledge trước—sau đó code và tài liệu mới trở nên có ý nghĩa. Và kỳ vọng là bạn phải học nhanh.
Tôi nhớ một cuộc trò chuyện với sếp sau khi kết thúc thời gian thử việc. Ông nói rằng ban đầu tôi học khá chậm, nhưng sau đó nhận ra sản phẩm rất phức tạp và khó nắm bắt. Vì vậy, việc tôi mất thời gian để hiểu là điều hoàn toàn hợp lý.
Tương tự, khi học ngôn ngữ, framework hay công cụ mới, cách tiếp cận cũng vậy. Các nguyên tắc nền tảng không thay đổi—chỉ có syntax hoặc API khác đi. Nhưng bạn vẫn cần thích nghi và học hỏi.
Bạn chỉ có thể làm điều này khi biết cách học—và không có công thức chung. Mỗi người có cách tiếp nhận kiến thức riêng. Theo thời gian, bạn liên tục tinh chỉnh quá trình học—một vòng lặp không bao giờ kết thúc.
Kết luận
Như các ví dụ trên cho thấy, công việc của một software engineer vượt xa việc chỉ viết code. Đây là điểm khác biệt giữa engineer và developer. Software engineering không chỉ là code, mà là về constraint, trade-off và giải quyết vấn đề—không phải bạn chọn ngôn ngữ nào.
Giống như những công nhân tại ga Ljubljana phải cân bằng giữa tiến độ và việc giảm thiểu gián đoạn, software engineer cũng phải điều hướng sự phức tạp, giao tiếp hiệu quả và tạo ra giá trị trong các ràng buộc.
Đó là lý do tôi không sợ AI—tôi tận dụng nó. Tất cả những kỹ năng trên vẫn sẽ có giá trị rất cao, bất kể người ta nói gì. Một software engineer không được định nghĩa bởi số dòng code, mà bởi cách họ hoàn thành công việc trong những điều kiện cụ thể.