Việc học giải quyết vấn đề một cách có quy trình đã được thảo luận từ rất lâu. Những lần gặp khó khăn khi code là những lần ta tích lũy kinh nghiệm, nhận ra được các mẫu có tính lặp lại và khám phá những chiến lược nhất định. Nếu phân loại được đoạn code và những vấn đề tương tự sẽ giúp ích rất nhiều cho lập trình viên.
Một trong những nhà toán học tuyệt vời đã chia sẻ về vấn đề này, George Polya, qua cuốn sách nổi tiếng How to solve it, a new aspect of mathematical method xuất bản lần đầu năm 1945. Không rõ George Polya có mong đợi lập trình viên thế kỷ 21 đọc được tác phẩm toán của mình hay không, nhưng tôi mong các bạn nhận ra được những góc nhìn của ông có giá trị đến thế nào. Giải quyết vấn đề không phải là một năng khiếu Giải toán là một kỹ năng thực tế như bơi lội. Chúng ta thành thục được bất kỳ kỹ năng nào bằng cách bắt chước và thực hành. Khi tập bơi, bạn bắt chước theo cách mọi người dùng tay và chân để giữ cho đầu ở trên mặt nước và cuối cùng, bạn biết bơi nhờ tập luyện. Khi tập giải toán, bạn quan sát và bắt chước những gì người khác làm khi giải toán và cuối cùng, bạn biết giải toán nhờ thực hành.
George Polya, How to solve it Giải quyết vấn đề không chỉ là về “trí óc” Dạy giải toán chính là sự rèn luyện ý chí. Để giải quyết những bài khó, người học sẽ học cách kiên trì vượt qua thất bại, trân trọng những tiến bộ nhỏ, chờ đợi ý tưởng cần thiết và tập trung hết sức khi ý tưởng xuất hiện.
George Polya, How to solve it Quyết tâm và cảm xúc đóng một vai trò quan trọng khi giải quyết các vấn đề khó.
Quy trình giải quyết vấn đề Bất cứ khi nào thực hiện quá trình giải quyết vấn đề, hãy ghi nhớ bốn bước sau:
- Hiểu vấn đề
- Lập kế hoạch
- Thực hiện kế hoạch
- Nhìn lại và đánh giá
Hiểu vấn đề
Thật ngớ ngẩn khi trả lời một câu hỏi bạn không hiểu. Thật đáng buồn khi làm việc để ra một kết quả bạn không mong muốn.. - George Polya, How to solve it
Trước khi đi tìm giải pháp, cần phải trình bày được tất cả các yếu tố của vấn đề để hiểu nó rõ hơn. Bạn có thể nêu vấn đề trong một câu không? Bài tập nhỏ này rất hữu ích: thuyết phục bản thân rằng bạn đã hiểu mục tiêu và không tốn quá nhiều năng lượng để hiểu nó nữa khi đang tập trung giải quyết vấn đề. Như được đề cập trong sách The Pragmatic Programmer, khi lập trình viên debug với “vịt cao su” chính là buộc bản thân phải hiểu rõ vấn đề để có thể dạy nó cho người khác.
Có những ràng buộc nào cần phải đáp ứng không? Trước hết, hãy hiểu vấn đề một cách tổng thể. Từ đó, ta mới có thể đánh giá những điểm cụ thể nào là quan trọng nhất. Sau khi xem xét một hoặc hai điểm trọng yếu, ta mới có thể đánh giá xem chi tiết nào đáng để điều tra kỹ hơn. Hãy đi vào sâu chi tiết và phân tích vấn đề dần dần, nhưng không đi xa hơn mức mình cần. - George Polya, How to solve it.
Những hạn chế trong vấn đề bạn đang giải quyết là gì? Hãy viết chúng ra. Nó có thể là một comment phía trên dòng đầu tiên của một hàm bạn viết, một danh sách các gạch đầu dòng trên đầu tài liệu thiết kế. Điều quan trọng là phải tập trung vào chúng khi cố gắng tìm giải pháp. Gỡ bỏ nhiều ràng buộc khỏi bộ não cũng là một cách loại bỏ một số quả bóng khi tâm trí ta chơi trò “tung hứng” trong khi cố gắng đưa ra một ý tưởng hay. Ngoài ra, hãy cẩn thận nếu có quá nhiều ràng buộc. Nếu xem xét quá nhiều chi tiết cùng một lúc, bạn có thể lạc lối. Chúng có thể khiến bạn mất tập trung vào điểm chính yếu hoặc thậm chí không nhìn thấy điểm chính yếu đó. Bạn có thể vẽ hình minh họa hay một hệ thống ký hiệu phù hợp? Một bước quan trọng khi giải toán là chọn hệ thống ký hiệu. Điều này cần được thực hiện một cách cẩn thận. Làm được ta sẽ tiết kiệm thời gian cho sau này do tránh được những do dự và nhầm lẫn không đáng.
George Polya, How to solve it. Các bản vẽ hay phác thảo sẽ giúp bỏ bớt một số suy nghĩ ra giấy và tạo nhiều khoảng trống hơn cho bộ não tập trung vào những phần khó. Hãy chọn hệ thống ký hiệu giúp bạn hiểu rõ mọi thứ hơn: diagram, flowchart….
Lập kế hoạch
Ta có kế hoạch khi biết, hoặc ít nhất là biết sơ bộ, mình phải thực hiện những phép tính, thuật toán hoặc cấu trúc nào để tìm ra lời giải. Con đường từ hiểu vấn đề đến hình thành một kế hoạch có thể dài và quanh co. Thành tựu chính khi tìm ra giải pháp là hình thành ý tưởng về một kế hoạch. - George Polya, How to solve it
Giải quyết vấn đề trên giấy trước. Việc viết code luôn có vẻ dễ dàng hơn trong đầu và trở nên phức tạp hơn nhiều khi làm nó chạy được. Đừng làm cả hai việc cùng một lúc. Đầu tiên, hãy giải quyết vấn đề. “Chạy thử” một ví dụ trên sổ hoặc trên pseudocode. Sau đó, chạy trên máy. Bạn có biết một vấn đề liên quan? Bạn cần biết ít nhất loại vấn đề hoặc nhóm vấn đề bạn đang giải quyết. Đối với dân phần mềm, hãy thử trả lời những câu hỏi sau: Đây có phải là vấn đề kiến trúc không? Có những mẫu kiến trúc nào thường được sử dụng trong kịch bản này không? Vấn đề này có nhiều hơn ở cấp độ của một giải thuật không? Có giải thuật đã được chứng minh có thể giải quyết loại vấn đề này không? Khi có thể tìm thấy một vấn đề liên quan đến vấn đề của mình và đã được giải quyết trước đó, bạn nên ăn mừng. Đặt tiếp câu hỏi: “Mình có thể sử dụng nó không?” Nhiều vấn đề có thể có một số điểm chung, nhưng những vấn đề có chung các yêu cầu hoặc nền tảng cốt lõi nhất có lẽ sẽ hữu ích nhất. Bạn có thể trình bày lại vấn đề được không? Bạn có thể nhìn nhận vấn đề ở một góc độ khác? Có thể bỏ một phần điều kiện hoặc một số yêu cầu không? Có thể nêu vấn đề này theo một kịch bản cụ thể hơn không? Nếu đang viết các bài test, hãy thử nghĩ đến các ví dụ đơn giản được tạo ra chỉ bởi các điều kiện cụ thể của vấn đề để tìm ra giải pháp cuối cùng, tổng quát hơn. Điều này dẫn chúng ta đến… Nếu không thể giải quyết vấn đề được đề xuất, hãy cố giải quyết một số vấn đề liên quan trước tiên Hoặc một vấn đề liên quan và đơn giản hơn. Giống như viết phần mềm hiệu quả, việc thực hiện giải quyết vấn đề hiệu quả có thể được coi là một quá trình mang tính tịnh tiến. Đừng cố làm mọi thứ cùng một lúc. Xây dựng code cuối cùng hoặc thậm chí là các sơ đồ và ý tưởng thiết kế hệ thống là một quá trình sẽ mang lại lợi ích rất nhiều từ việc thực hiện điều tương tự cho các vấn đề quy mô nhỏ hơn.
Thực hiện kế hoạch
Đề ra phương án, hình thành ý tưởng giải pháp không hề đơn giản. Ta cần rất nhiều yếu tố để thành công: kiến thức tích lũy, thói quen tư duy, sự tập trung và may mắn. Thực hiện kế hoạch lại dễ hơn nhiều; yếu tố chủ chốt là kiên nhẫn. - George Polya, How to solve it.
Phiên bản dành cho lập trình viên của câu trên là “Nghĩ trước, code sau”. Khi đã hiểu các bước của giải thuật hoặc thiết kế hệ thống, việc triển khai sẽ dễ dàng hơn rất nhiều. Polya cho rằng những người giải toán giỏi có khả năng kiểm tra từng bước trong giải pháp sau khi hoàn thành và chất vấn từ đầu đến cuối. Chỉ một dòng suy nghĩ về phân tích, phép toán sai sẽ làm hỏng mọi thứ. Điều này có vẻ không mang đến nguy hiểm tức thì trong lập trình phần mềm, nhưng nó có thể là nguy hiểm chí mạng, vì bug nghiêm trọng sẽ xuất hiện trong thời điểm tồi tệ nhất – và có thể trong trường hợp rất cụ thể. Điều này có nghĩa là cần có phạm vi kiểm thử tốt. Nếu không kiểm thử, bạn sẽ không chất vấn, cứ tự tin coi giải pháp của mình là hoàn hảo. Rất hữu ích khi có tư duy “Nó sẽ có lỗi chứ?” và liên tục đưa ra tất cả các kịch bản thất bại khác nhau trong giải pháp của mình và tư duy này sẽ được thể hiện rõ trong các bài test phần mềm.
Nhìn lại
Ngay cả học sinh khá giỏi, khi đã có được lời giải bài toán và viết xong các lập luận, các bạn liền đóng tập lại. Các bạn đã bỏ lỡ một giai đoạn quan trọng. Khi nhìn lại bài giải, xem xét và nhìn nhận lại kết quả và con đường dẫn đến nó, các bạn có thể củng cố kiến thức và phát triển khả năng giải quyết vấn đề của mình. - George Polya, How to solve it.
Không nhìn lại và đánh giá sự đánh đổi của giải pháp thường xảy ra khi mọi thứ được thực hiện trong vội vàng. Nếu đó là một giải thuật, bạn có thể nói rõ độ phức tạp về thời gian và không gian không? Code có đọc tốt không? Nếu đang đưa ra quyết định sẽ có tác động đến toàn hệ thống, hãy ghi ra những đánh đổi. Dành một giờ để xem xét lại kết quả công việc sẽ giúp bạn tiết kiệm hơn rất nhiều sau này. Bạn có thể nghỉ ngơi rồi quay lại với nó sau. Việc đắm chìm sâu vào vấn đề đang giải quyết có thể sẽ khiến bạn quên mất những chi tiết liên quan. Ngoài ra, giải pháp cho vấn đề cụ thể đó có thể được tổng quát hóa để được sử dụng trong nhiều tình huống. Dùng sự nhạy bén và tư duy cẩn thận để tạo ra những abstraction mới là một cách giải quyết vấn đề đó cho những người khác đối mặt với nó sau này.