[{"data":1,"prerenderedAt":272},["ShallowReactive",2],{"list-\u002Fblog-vi":3},[4,85,204],{"entry":5,"writtenIn":84,"onlyIn":75},{"id":6,"title":7,"body":8,"date":69,"description":70,"extension":71,"meta":72,"navigation":73,"path":74,"role":75,"seo":76,"stack":77,"stem":78,"tags":79,"year":75,"__hash__":83},"content_vi\u002Fblog\u002Fthe-eight-hour-interface.md","Giao diện tám tiếng",{"type":9,"value":10,"toc":62},"minimark",[11,15,18,23,26,29,33,40,46,52,56,59],[12,13,14],"p",{},"Phần lớn lời khuyên về giao diện được viết cho loại phần mềm người ta gặp đúng một lần. Một trang giới thiệu có sáu giây để tự giải thích. Một luồng thanh toán được chấm điểm bằng việc người lạ có làm xong mà không phải suy nghĩ hay không.",[12,16,17],{},"Phần mềm doanh nghiệp thì ngược lại. Cùng một người mở cùng một màn hình mỗi sáng, suốt ba năm. Qua tuần đầu là họ thôi đọc nhãn. Thứ họ nhớ là vị trí của mọi thứ, không phải tên gọi.",[19,20,22],"h2",{"id":21},"cái-gì-thôi-quan-trọng","Cái gì thôi quan trọng",[12,24,25],{},"Phần onboarding bóng bẩy thôi quan trọng gần như ngay lập tức. Một tooltip giải thích ô nhập được đọc một lần rồi thôi, nhưng nó chiếm chỗ vĩnh viễn và phải bảo trì ở cả hai ngôn ngữ.",[12,27,28],{},"Cái mới lạ về thị giác cũng vậy. Một control thú vị ở lần đầu sẽ thành vướng víu ở lần thứ bốn trăm, vì thú vị là một khoảng ngắt nhỏ, mà ngắt thì cộng dồn.",[19,30,32],{"id":31},"cái-gì-bắt-đầu-quan-trọng","Cái gì bắt đầu quan trọng",[12,34,35,39],{},[36,37,38],"strong",{},"Vị trí ổn định."," Một nút đổi chỗ giữa hai bản phát hành là mọi người dùng đều trả giá. Sau tuần thứ hai, trí nhớ cơ bắp mới là giao diện thật, và nó không được ghi ở tài liệu nào.",[12,41,42,45],{},[36,43,44],{},"Mật độ."," Sáu giây chú ý thì thưởng cho khoảng trắng. Tám tiếng thì thưởng cho việc thấy thêm dòng mà không phải cuộn. Giá trị mặc định \"thoáng cho dễ nhìn\" thường là quá rộng với người làm việc này để kiếm sống.",[12,47,48,51],{},[36,49,50],{},"Lỗi phải đọc được."," Người lạ gặp lỗi thì bỏ luôn tác vụ. Người dùng hằng ngày phải gỡ được lỗi rồi đi tiếp. Nên câu hỏi không phải \"cái này trông đã đủ báo động chưa\" mà là \"nó có nói cho tôi biết bản ghi nào hỏng và giờ làm gì tiếp không\".",[19,53,55],{"id":54},"phần-khó-thuyết-phục","Phần khó thuyết phục",[12,57,58],{},"Không thứ nào ở trên lên ảnh đẹp cả. Một bảng dày, cột không xê dịch, chữ báo lỗi cộc lốc thì không thắng nổi một buổi trình bày thiết kế lại. Nó chỉ làm người dùng mất ít phút hơn mỗi ngày, và số ngày thì nhiều.",[12,60,61],{},"Đánh đổi đó nên nói thẳng ra từ sớm, vì nếu không thì cứ mỗi lần có người mới nhìn vào ảnh chụp màn hình là lại phải cãi lại từ đầu.",{"title":63,"searchDepth":64,"depth":64,"links":65},"",2,[66,67,68],{"id":21,"depth":64,"text":22},{"id":31,"depth":64,"text":32},{"id":54,"depth":64,"text":55},"2026-07-18","Phần mềm doanh nghiệp được dùng suốt một ngày làm việc, không phải liếc qua trong sáu giây. Chỉ một khác biệt đó thôi đã đổi gần như mọi quyết định thiết kế.","md",{},true,"\u002Fblog\u002Fthe-eight-hour-interface",null,{"title":7,"description":70},[],"blog\u002Fthe-eight-hour-interface",[80,81,82],"sapui5","ux","enterprise","Lk7BThvEXNP-tpJgPNE5cHkAipDCO5O6cfDZhjIpjOI","vi",{"entry":86,"writtenIn":84,"onlyIn":75},{"id":87,"title":88,"body":89,"date":194,"description":195,"extension":71,"meta":196,"navigation":73,"path":197,"role":75,"seo":198,"stack":199,"stem":200,"tags":201,"year":75,"__hash__":203},"content_vi\u002Fblog\u002Ftypescript-into-a-sapui5-codebase.md","Đưa TypeScript vào codebase SAPUI5",{"type":9,"value":90,"toc":189},[91,94,98,101,148,151,154,158,165,168,172,175,182,185],[12,92,93],{},"SAPUI5 có trước cái giả định rằng một codebase frontend thì phải có kiểu. Hệ module, cách khai báo class, và các view XML của nó đều được thiết kế cho một thế giới mà editor không giúp được gì. Kiểu thì thêm vào được, nhưng không đều — có phần của framework trả công ngay, có phần thì kháng cự.",[19,95,97],{"id":96},"chỗ-trả-công-ngay","Chỗ trả công ngay",[12,99,100],{},"Truy cập model là chỗ rõ nhất. Một dòng như thế này là nơi phần lớn bug lúc chạy trú ngụ:",[102,103,107],"pre",{"className":104,"code":105,"language":106,"meta":63,"style":63},"language-ts shiki shiki-themes henricus","const rate = model.getProperty(\"\u002Fheader\u002FexchangeRate\");\n","ts",[108,109,110],"code",{"__ignoreMap":63},[111,112,115,119,123,126,129,133,136,139,143,145],"span",{"class":113,"line":114},"line",1,[111,116,118],{"class":117},"s5glF","const",[111,120,122],{"class":121},"s6eg1"," rate ",[111,124,125],{"class":117},"=",[111,127,128],{"class":121}," model.",[111,130,132],{"class":131},"sNWak","getProperty",[111,134,135],{"class":121},"(",[111,137,138],{"class":121},"\"",[111,140,142],{"class":141},"s3_7c","\u002Fheader\u002FexchangeRate",[111,144,138],{"class":121},[111,146,147],{"class":121},");\n",[12,149,150],{},"Mọi thứ trong đó đều là phỏng đoán: đường dẫn có tồn tại không, giá trị có phải số không, sau một lần load hỏng nó có thành null không. Gắn kiểu cho model là chuyển cả ba phỏng đoán đó vào editor.",[12,152,153],{},"Formatter và các lời gọi OData theo sau. Cả hai đều là ranh giới — dữ liệu đi qua đó và đổi hình — mà ranh giới đúng là chỗ một dòng khai báo kiểu đáng công gõ.",[19,155,157],{"id":156},"chỗ-biến-thành-thủ-tục","Chỗ biến thành thủ tục",[12,159,160,161,164],{},"Các hàm vòng đời của controller gần như chẳng được gì. ",[108,162,163],{},"onInit"," không nhận tham số, không trả về gì; gắn kiểu cho nó là điền form.",[12,166,167],{},"View XML mới là giới hạn thật. View gọi hàm controller bằng tên dạng chuỗi, và không compiler nào kiểm chuỗi đó. Bạn có thể gắn kiểu hoàn hảo cho controller mà vẫn ship một view gọi hàm đã bị xoá từ ba commit trước. Kiểu cho ta cảm giác an toàn rất mạnh trên một khoảng trống mà nó chưa bao giờ bắc qua.",[19,169,171],{"id":170},"điều-tôi-sẽ-nói-với-chính-mình-lúc-bắt-đầu","Điều tôi sẽ nói với chính mình lúc bắt đầu",[12,173,174],{},"Gắn kiểu cho dữ liệu, đừng gắn cho framework. Hình dạng model, phản hồi service, đầu vào và đầu ra của formatter — đó là chỗ bắt được lỗi thật.",[12,176,177,178,181],{},"Cưỡng lại việc gắn kiểu cho những phần chỉ tồn tại để làm vừa lòng framework. Mỗi chữ ",[108,179,180],{},"any"," bị loại bỏ ở đó là một gánh nặng bảo trì mà không đổi lại được gì, và nó kéo diff dài đến mức người review chỉ lướt.",[12,183,184],{},"Mục tiêu chưa bao giờ là phủ kín kiểu. Mục tiêu là biết dữ liệu mang hình gì khi nó tới màn hình.",[186,187,188],"style",{},"html pre.shiki code .s5glF, html code.shiki .s5glF{--shiki-default:#E8D8A6}html pre.shiki code .s6eg1, html code.shiki .s6eg1{--shiki-default:#EDEBE3}html pre.shiki code .sNWak, html code.shiki .sNWak{--shiki-default:#C9A227}html pre.shiki code .s3_7c, html code.shiki .s3_7c{--shiki-default:#7BA697}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":63,"searchDepth":64,"depth":64,"links":190},[191,192,193],{"id":96,"depth":64,"text":97},{"id":156,"depth":64,"text":157},{"id":170,"depth":64,"text":171},"2026-05-02","Thêm kiểu cho một framework ra đời trước khi kiểu trở thành mặc định — chỗ nào đáng, và chỗ nào biến thành thủ tục.",{},"\u002Fblog\u002Ftypescript-into-a-sapui5-codebase",{"title":88,"description":195},[],"blog\u002Ftypescript-into-a-sapui5-codebase",[80,202],"typescript","OIeDH-qW-vhqwS3cXcdo_GHWdicI9-dK3p72yRE3gWw",{"entry":205,"writtenIn":271,"onlyIn":271},{"id":206,"title":207,"body":208,"date":260,"description":261,"extension":71,"meta":262,"navigation":73,"path":263,"role":75,"seo":264,"stack":265,"stem":266,"tags":267,"year":75,"__hash__":270},"content_en\u002Fblog\u002Fwhat-review-taught-me.md","What review taught me",{"type":9,"value":209,"toc":255},[210,213,217,220,223,227,233,239,245,249,252],[12,211,212],{},"I expected code review to make me better at spotting bugs in other people's work. It did, a little. What it actually changed was how I open a pull request.",[19,214,216],{"id":215},"reading-is-slower-than-writing","Reading is slower than writing",[12,218,219],{},"The author has the whole change loaded in their head. The reviewer has a diff, in file-alphabetical order, with no idea which part is the point.",[12,221,222],{},"That asymmetry is the entire problem, and almost every review frustration traces back to it. A reviewer who cannot find the point will either approve without reading or ask questions the author considers obvious. Both outcomes are the author's to prevent.",[19,224,226],{"id":225},"three-habits-that-came-out-of-it","Three habits that came out of it",[12,228,229,232],{},[36,230,231],{},"Separate the mechanical commit from the thinking commit."," A rename touching forty files and a logic change touching one should never arrive together. Reviewed separately, both are quick; reviewed together, the logic change is invisible.",[12,234,235,238],{},[36,236,237],{},"Say what you decided against."," \"I did not use a computed here because the value is read once during setup\" prevents the most common review comment before it is written. It costs one line.",[12,240,241,244],{},[36,242,243],{},"Point at the risky part."," Every change has one. Naming it is not an admission of weakness — it is directing the limited attention the reviewer has toward the place where it pays.",[19,246,248],{"id":247},"the-uncomfortable-part","The uncomfortable part",[12,250,251],{},"Most review comments I received were things I already knew and had skipped. Not knowledge gaps — attention gaps, at the end of a task when it felt done.",[12,253,254],{},"That is a harder problem than not knowing something, because you cannot read your way out of it. The only fix I have found is mechanical: read the diff yourself, in the review interface, before assigning anyone. Half the comments never get written.",{"title":63,"searchDepth":64,"depth":64,"links":256},[257,258,259],{"id":215,"depth":64,"text":216},{"id":225,"depth":64,"text":226},{"id":247,"depth":64,"text":248},"2026-03-09","Two years of reading other people's diffs, and the habit it changed most was how I write my own.",{},"\u002Fblog\u002Fwhat-review-taught-me",{"title":207,"description":261},[],"blog\u002Fwhat-review-taught-me",[268,269],"review","process","1TKANv2FoS2u9-WjFuhdsWOX7k8q2IOPrDwSSQn5uQ0","en",1788956392050]